| Updating/Committing with Multiple Arguments |
| =========================================== |
| |
| Currently, updates and commits work with a single directory argument, |
| as in |
| |
| $ svn update A/D/G |
| |
| or |
| |
| $ svn update // implied "." argument |
| |
| But users would really like to do this: |
| |
| $ svn update A/D/H A/D/G iota |
| |
| When there are multiple arguments, each item's revision number should |
| be bumped as soon as it is updated, so that updates can be interrupted |
| without leaving the working copy in a silly state. Also, the items |
| should be done in the order given, so users on slow links can put |
| higher priority items first. |
| |
| How do we do this? |
| |
| Let TL be an apr_array_header_t representing the target list, in the |
| order the targets appeared on the command line. |
| |
| 1. Convert each path in TL to an absolute path. |
| |
| 2. Remove redundancies from the target list: all targets that are |
| descendants of some other target are removed. |
| |
| |
| 3. Find the longest common prefix ending with "/" for all the |
| targets. Strip that prefix off all targets and save it for |
| later. |
| |
| (Kevin Pilch-Bisson has written code to help with 1, 2 and 3, see |
| libsvn_subr/target.c.) |
| |
| |
| 4. Report the working copy state to the repository, to build a |
| transaction TXN that reflects the state of the working copy _as |
| necessary to update the requested targets_. For example: |
| |
| Suppose the working copy is: |
| |
| ./ (at revision 3) |
| ./iota (at revision 5) |
| ./A/foo (at revision 3) |
| ./A/bar (at revision 7) |
| ./A/baz (at revision 8) |
| ./A/B/ (at revision 10) |
| ./A/C/ (at revision 11) |
| |
| and the user runs this command in "./": |
| |
| $ svn update A/bar A/C |
| |
| Then TXN should look something like this: |
| |
| ./ (at revision 3) |
| ./iota (at revision 3) |
| ./A/foo (at revision 3) |
| ./A/bar (at revision 7) |
| ./A/baz (at revision 3) |
| ./A/B/ (at revision 3) |
| ./A/C/ (at revision 11) |
| |
| because there's no point making TXN reflect portions of the |
| working copy which are not even involved in the update. Thus, |
| the state reporter uses TL to decide what in the working copy to |
| examine, as it builds TXN. Some state will be left unreported, |
| if the update won't affect it anyway. |
| |
| |
| 5. Get an update editor, using the common prefix we saved earlier |
| as the path argument, and pass TL too. |
| |
| /* TODO: interface change to svn_wc_get_update_editor: Take |
| `apr_array_header_t *targets'. */ |
| |
| |
| 6. Drive the update editor: |
| |
| a) Call set_target_revision (edit_baton); |
| |
| b) Call replace_root (), get a root_dir_baton; |
| |
| c) For each target T in TL: |
| |
| if (T is a directory) |
| { |
| call replace_directory()'s to get down to bottom of T; |
| call svn_fs_dir_delta (roots, paths, dir_baton); |
| |
| /* TODO: interface change to svn_fs_dir_delta. |
| Instead of driving the whole edit, it should assume |
| that the edit has been started, and take the editor |
| and a directory baton as arguments. It takes over |
| driving until close_directory() is called on that |
| baton, then returns control to its caller. So it |
| promises never to call set_target_revision() or |
| replace_root(). */ |
| } |
| else if (T is a file) |
| { |
| call replace_directory()'s to get down to T's parent; |
| somehow (involving svn_fs_file_delta?) drive the |
| editor to make the appropriate changes to the file; |
| |
| /* TODO: interface change to svn_fs_file_delta, or |
| rather: current svn_fs_file_delta gets named |
| something new, and the old name is occupied by a |
| companion function to svn_fs_dir_delta: it will |
| take an editor and a file baton and do the |
| appropriate stuff. */ |
| } |
| |
| d) Call close_edit(edit_baton); |
| |
| /* TODO: new interface in svn_fs.h: we need a function to |
| drive an editor, temporarily handing off control to |
| svn_fs_dir_delta() as necessary, as described in |
| 5(c) and 5(d). */ |
| |
| |
| That's it. Remember, the update editor knows the target list too, so |
| it uses the following rules for close_directory, close_file, and |
| close_edit: |
| |
| |
| close_foo (FOO) /* foo is file or directory */ |
| { |
| if (FOO is a member of TL, or a descendant of a member of TL) |
| { |
| set FOO's revision number to the new revision; |
| } |
| if (FOO is a member of TL) |
| { |
| remove FOO from TL |
| } |
| |
| /* Of course, when foo is a directory, the above code doesn't run |
| * when close_directory(foo_baton) is called, but rather when |
| * foo_baton's reference count reaches zero -- that's when |
| * directory closure *really* happens in the update editor. |
| */ |
| } |
| |
| |
| close_edit () |
| { |
| foreach (member M in TL) |
| { |
| if (M is a file) |
| { |
| set to new revision; |
| } |
| else if (M is a directory) |
| { |
| recurse into M, setting everything underneath to new |
| the new revision, finally set M to the revision when |
| come out. |
| } |
| } |
| } |
| |
| /* TODO: make the editor behave as described above. */ |