# # # add_file "tests/t_rename_destdir.at" # content [8e16b441f52d44260f34fa9edd99ca92c3b5863a] # # patch "ChangeLog" # from [4ce17cf890b330654a50dc12c047f67328ca01ed] # to [f5febf6b21c9aeb3fc2bd4cd81fc6b7854ef03be] # # patch "commands.cc" # from [8743a12d037788901f07148fad730dfd899f125c] # to [4d20869a08800d21d6ad8df345d34d5ffbeb6ff9] # # patch "monotone.texi" # from [cbc87b47e45384cb320b2b59c4ebdf0ae82f2867] # to [d3d60b1a250d8b10dfba56c5c28d9198400c7e3b] # # patch "tests/t_no_rename_overwrite.at" # from [1fe86d8eeda1180521602e9b66628aadad9b75ac] # to [1f1e1481d40d08fbd33eae39850f79fd4804b4a7] # # patch "testsuite.at" # from [47092721ad423cb1e208539f382826ec25574d5b] # to [a9c0964810370a40023bba920ab6b811b547fb0b] # # patch "vocab.cc" # from [d8da5a3ac67bb8c2b29a5cb5f3e3704e1375d954] # to [084a7a4538c3e0b062b901d0ba891fd6b66cfdfe] # # patch "work.cc" # from [5f27cea289f3b61f7f645643bf7d6b158ca031ce] # to [f0b8375e1b0bfbaf32770b41ece3015e2a34cb66] # # patch "work.hh" # from [abc46f82b48bd3a9c31beb58446c3ef484880088] # to [8c9dc3dba09d6c20d9afb4c9f7c1e11e28e489bb] # ============================================================ --- tests/t_rename_destdir.at 8e16b441f52d44260f34fa9edd99ca92c3b5863a +++ tests/t_rename_destdir.at 8e16b441f52d44260f34fa9edd99ca92c3b5863a @@ -0,0 +1,94 @@ +# -*- Autoconf -*- + +AT_SETUP([rename files into a directory]) + +MONOTONE_SETUP + +ADD_FILE(gnat, [gnat +]) +ADD_FILE(mosquito, [mosquito +]) +ADD_FILE(termite, [termite +]) +ADD_FILE(ant, [ant +]) + +# will force foo/, bar/ and foo/gnat/ to be created +AT_CHECK(mkdir foo bar foo/gnat) +ADD_FILE(foo/dummy, [... ... ... +]) +ADD_FILE(bar/dummy, [a b c +]) +ADD_FILE(foo/gnat/dummmy, [la la la +]) + +COMMIT(testbranch) + +# checkout in a clean dir and cd there +m4_define([REN_CHECKOUT], [ +AT_CHECK(rm -fr $_ROOT_DIR/checkout, [], [ignore], [ignore]) +AT_CHECK(MONOTONE checkout -b testbranch $_ROOT_DIR/checkout, [0], [ignore], [ignore]) +]) + +REN_CHECKOUT + +# basics +AT_CHECK(cd checkout && MONOTONE rename ant foo, [0], [ignore], [ignore]) +AT_CHECK(cd checkout && MONOTONE rename mosquito termite foo, [0], [ignore], [ignore]) + +REN_CHECKOUT + +# with --execute +AT_CHECK(cd checkout && MONOTONE --execute rename ant foo, [0], [ignore], [ignore]) +AT_CHECK(cd checkout && MONOTONE --execute rename mosquito termite foo, [0], [ignore], [ignore]) +AT_CHECK(cd checkout && test -e foo/ant -a -e foo/mosquito -a -e foo/termite) +AT_CHECK(cd checkout && test ! -e ant -a ! -e mosquito -a ! -e termite) + +# to root +AT_CHECK(cd checkout && MONOTONE --execute rename foo/ant ., [0], [ignore], [ignore]) +AT_CHECK(cd checkout && MONOTONE rename foo/termite ., [0], [ignore], [ignore]) +AT_CHECK(cd checkout && test -e ant -a -e foo/termite -a ! -e foo/ant -a ! -e termite) + +REN_CHECKOUT + +# conflicts +AT_CHECK(cd checkout && MONOTONE rename gnat foo, [1], [ignore], [ignore]) +AT_CHECK(cd checkout && MONOTONE rename gnat termite foo, [1], [ignore], [ignore]) +AT_CHECK(cd checkout && MONOTONE rename termite foo, [0], [ignore], [ignore]) + +AT_CHECK(cd checkout && MONOTONE rename mosquito foo/ant, [0], [ignore], [ignore]) +AT_CHECK(cd checkout && MONOTONE rename ant foo, [1], [ignore], [ignore]) + +REN_CHECKOUT + +AT_CHECK(cd checkout && MONOTONE --execute rename gnat foo, [1], [ignore], [ignore]) +AT_CHECK(cd checkout && MONOTONE --execute rename gnat termite foo, [1], [ignore], [ignore]) +AT_CHECK(cd checkout && MONOTONE --execute rename termite foo, [0], [ignore], [ignore]) +AT_CHECK(cd checkout && test -e foo/termite -a ! -e termite -a -e gnat -a -d foo/gnat -a ! -e foo/gnat/gnat) + +REN_CHECKOUT + +## todo: +# issues with missing files, should usually be allowed? +# rename to self +# rename root node + +# rename to non-existing dir path: "foo->blweorih/o4thoihs" (this isn't a destdir case, but needs testing somewhere). + +# file0->bar, file0 doesn't exist + +# file0->bar, file0 exists, -e + +# file0->bar, file0 doesn't exist, -e + +# check that nothing happens if any would fail +# file0->bar file1->bar, file0 exists, file1 doesn't + +# file0->bar file1->bar, file0 exists, file1 doesn't, -e + +# file0->bar, bar/file0 exists in working, file0 doesn't -e + +# file0->bar + + +AT_CLEANUP ============================================================ --- ChangeLog 4ce17cf890b330654a50dc12c047f67328ca01ed +++ ChangeLog f5febf6b21c9aeb3fc2bd4cd81fc6b7854ef03be @@ -1,5 +1,14 @@ 2006-01-19 Matt Johnston + * work.{cc,hh}, commands.cc: add "rename src1 [src2 ...] dst/" + syntax. + * monotone.texi: update + * testsuite.at, tests/t_rename_destdir: new test (is incomplete). + * tests/t_no_rename_overwrite.at: syntax should now succeed. + * vocab.cc: add hexenc dump() instantiation. + +2006-01-19 Matt Johnston + * HACKING: escape the colon in the cino vim modeline option. 2006-01-19 Nathaniel Smith ============================================================ --- commands.cc 8743a12d037788901f07148fad730dfd899f125c +++ commands.cc 4d20869a08800d21d6ad8df345d34d5ffbeb6ff9 @@ -1217,19 +1217,26 @@ ALIAS(rm, drop); -CMD(rename, N_("working copy"), N_("SRC DST"), +CMD(rename, N_("working copy"), + N_("SRC DEST\n" + "SRC1 [SRC2 [...]] DEST_DIR"), N_("rename entries in the working copy"), OPT_EXECUTE) { - if (args.size() != 2) + if (args.size() < 2) throw usage(name); app.require_working_copy(); - file_path src_path = file_path_external(idx(args, 0)); - file_path dst_path = file_path_external(idx(args, 1)); + file_path dst_path = file_path_external(args.back()); - perform_rename(src_path, dst_path, app); + set src_paths; + for (size_t i = 0; i < args.size()-1; i++) + { + file_path s = file_path_external(idx(args, i)); + src_paths.insert(s); + } + perform_rename(src_paths, dst_path, app); } ALIAS(mv, rename) ============================================================ --- monotone.texi cbc87b47e45384cb320b2b59c4ebdf0ae82f2867 +++ monotone.texi d3d60b1a250d8b10dfba56c5c28d9198400c7e3b @@ -3848,9 +3848,11 @@ you should run @command{drop}, and then perform the actual delete using whatever mechanism you normally use to delete files. address@hidden monotone rename @var{src} @var{dst} address@hidden monotone [--execute] rename @var{src} @var{dst} address@hidden monotone [--execute] rename @var{src1} @var{...} @var{dst/} This command places ``rename'' entries for the paths specified in address@hidden and @var{dst} in the working copy's ``work list''. The work address@hidden and @var{dst} in the working copy's ``work list''. The second form +renames a number of source paths to the given destination. The work list of your working copy is located at @file{MT/work}, and is a list of explicit pathname changes you wish to commit at some future time, such as addition, removal, or renaming of files. This command also moves any @@ -3864,10 +3866,9 @@ will have any renamed entries in its manifest adjusted to their new names. -Currently this command does @emph{not} actually rename the file address@hidden in your filesystem; after you run this command, you should do -the actual rename, using whatever mechanism you normally use to rename -files. +The option @option{--execute} will make ``rename'' perform the actual +rename operations in the filesystem. It will ignore missing source files +and will not overwrite existing destination files. @item monotone commit @itemx monotone commit address@hidden ============================================================ --- tests/t_no_rename_overwrite.at 1fe86d8eeda1180521602e9b66628aadad9b75ac +++ tests/t_no_rename_overwrite.at 1f1e1481d40d08fbd33eae39850f79fd4804b4a7 @@ -18,16 +18,12 @@ AT_CHECK(MONOTONE rename unknown_file other_file, [1], [ignore], [ignore]) AT_CHECK(MONOTONE rename rename_file target_file, [1], [ignore], [ignore]) -AT_CHECK(MONOTONE rename rename_file target_dir, [1], [ignore], [ignore]) AT_CHECK(MONOTONE rename rename_dir target_file, [1], [ignore], [ignore]) -AT_CHECK(MONOTONE rename rename_dir target_dir, [1], [ignore], [ignore]) COMMIT(testbranch) AT_CHECK(MONOTONE rename unknown_file other_file, [1], [ignore], [ignore]) AT_CHECK(MONOTONE rename rename_file target_file, [1], [ignore], [ignore]) -AT_CHECK(MONOTONE rename rename_file target_dir, [1], [ignore], [ignore]) AT_CHECK(MONOTONE rename rename_dir target_file, [1], [ignore], [ignore]) -AT_CHECK(MONOTONE rename rename_dir target_dir, [1], [ignore], [ignore]) AT_CLEANUP ============================================================ --- testsuite.at 47092721ad423cb1e208539f382826ec25574d5b +++ testsuite.at a9c0964810370a40023bba920ab6b811b547fb0b @@ -757,3 +757,4 @@ m4_include(tests/t_revert_ignored.at) m4_include(tests/t_no_persist_phrase.at) m4_include(tests/t_check_db_format.at) +m4_include(tests/t_rename_destdir.at) ============================================================ --- vocab.cc d8da5a3ac67bb8c2b29a5cb5f3e3704e1375d954 +++ vocab.cc 084a7a4538c3e0b062b901d0ba891fd6b66cfdfe @@ -298,6 +298,9 @@ template void dump(file_id const & r, std::string &); +template +void dump(hexenc const & r, std::string &); + // the rest is unit tests #ifdef BUILD_UNIT_TESTS ============================================================ --- work.cc 5f27cea289f3b61f7f645643bf7d6b158ca031ce +++ work.cc f0b8375e1b0bfbaf32770b41ece3015e2a34cb66 @@ -237,40 +237,112 @@ build.visit_dir(dirname); } -void -perform_rename(file_path const & src_path, +void +perform_rename(set const & src_paths, file_path const & dst_path, app_state & app) { temp_node_id_source nis; roster_t base_roster, new_roster; - split_path src, dst; + split_path dst; + set srcs; + set< pair > renames; + I(!src_paths.empty()); + get_base_and_current_roster_shape(base_roster, new_roster, nis, app); - src_path.split(src); dst_path.split(dst); - N(new_roster.has_node(src), - F("%s does not exist in current revision\n") % src_path); + if (src_paths.size() == 1 && !new_roster.has_node(dst)) + { + // "rename SRC DST" case + split_path s; + src_paths.begin()->split(s); + renames.insert( make_pair(s, dst) ); + add_parent_dirs(dst, new_roster, nis, app); + } + else + { + // "rename SRC1 SRC2 DST" case + N(new_roster.has_node(dst), + F("destination dir %s/ does not exist in current revision") % dst_path); - N(!new_roster.has_node(dst), - F("%s already exists in current revision\n") % dst_path); + N(is_dir_t(new_roster.get_node(dst)), + F("destination %s is an existing file in current revision") % dst_path); - add_parent_dirs(dst, new_roster, nis, app); + for (set::const_iterator i = src_paths.begin(); + i != src_paths.end(); i++) + { + split_path s; + i->split(s); + // TODO "rename . foo/" might be valid? Or should it already have been + // normalised..., in which case it might be an I(). + N(!s.empty(), + F("empty path %s is not allowed") % *i); - P(F("adding %s -> %s to working copy rename set\n") % src_path % dst_path); + path_component src_basename = s.back(); + split_path d(dst); + d.push_back(src_basename); + renames.insert( make_pair(s, d) ); + } + } - node_id nid = new_roster.detach_node(src); - new_roster.attach_node(nid, dst); + // one iteration to check for existing/missing files + for (set< pair >::const_iterator i = renames.begin(); + i != renames.end(); i++) + { + N(new_roster.has_node(i->first), + F("%s does not exist in current revision") % file_path(i->first)); - // this should fail if src doesn't exist or dst does - if (app.execute && (path_exists(src_path) || !path_exists(dst_path))) - move_path(src_path, dst_path); + N(!new_roster.has_node(i->second), + F("destination %s already exists in current revision") % file_path(i->second)); + } + // do the attach/detaching + for (set< pair >::const_iterator i = renames.begin(); + i != renames.end(); i++) + { + node_id nid = new_roster.detach_node(i->first); + new_roster.attach_node(nid, i->second); + P(F("adding %s -> %s to working copy rename set") + % file_path(i->first) + % file_path(i->second)); + } + cset new_work; make_cset(base_roster, new_roster, new_work); put_work_cset(new_work); + + if (app.execute) + { + for (set< pair >::const_iterator i = renames.begin(); + i != renames.end(); i++) + { + file_path s(i->first); + file_path d(i->second); + // silently skip files where src doesn't exist or dst does + bool have_src = path_exists(s); + bool have_dst = path_exists(d); + if (have_src && !have_dst) + { + move_path(s, d); + } + else if (!have_src && !have_dst) + { + W(F("%s doesn't exist in working copy, skipping") % s); + } + else if (have_src && have_dst) + { + W(F("destination %s already exists in working copy, skipping") % d); + } + else + { + L(FL("skipping move_path %s->%s silently, src doesn't exist, dst does") + % s % d); + } + } + } update_any_attrs(app); } ============================================================ --- work.hh abc46f82b48bd3a9c31beb58446c3ef484880088 +++ work.hh 8c9dc3dba09d6c20d9afb4c9f7c1e11e28e489bb @@ -66,8 +66,8 @@ perform_deletions(path_set const & targets, app_state & app); void -perform_rename(file_path const & src_path, - file_path const & dst_path, +perform_rename(std::set const & src_paths, + file_path const & dst_dir, app_state & app); // the "work" file contains the current cset representing uncommitted