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. */