# # # patch "cmd_merging.cc" # from [c00d7dd2a1dd4dda7da812ae6216b6ee391238b9] # to [b9203c6c2626d9a22bcc69c5c726a82131824aa3] # # patch "cmd_netsync.cc" # from [34174bb7ce83722d7a9859b7065c40243ca0ba75] # to [e7134210a0f4aa995a911a9d9a114897200493f1] # # patch "cmd_ws_commit.cc" # from [77b18e38145f345b0eb65b73cc05619464a8fc59] # to [b9f6cc0fb67f14fd01a04709f9631f4c03069f6d] # # patch "monotone.texi" # from [8542311382a69a2d94963663f5336ad83f842ea2] # to [6418fdbd3f1062756cbfa805de0318b7183bfa19] # # patch "options_list.hh" # from [cd0ff5a0d051b4644c02a5f81ed262a512c65360] # to [70ce6c3a2c470461626c336383e813adb06c1041] # # patch "work.cc" # from [04462308709ee929fb10ab635240551c3e1e74ff] # to [8158573a9380c17be7f626ac6919316d922a3b2e] # # patch "work.hh" # from [cd117c8295b2bdf48db35e92aaf29842c27a1b7f] # to [ec26896a29463101ca0a0b47710d21fc123f1777] # ============================================================ --- cmd_merging.cc c00d7dd2a1dd4dda7da812ae6216b6ee391238b9 +++ cmd_merging.cc b9203c6c2626d9a22bcc69c5c726a82131824aa3 @@ -143,7 +143,8 @@ CMD(update, "update", "", CMD_REF(worksp "different revision, preserving uncommitted changes as it does so. " "If a revision is given, update the workspace to that revision. " "If not, update the workspace to the head of the branch."), - options::opts::branch | options::opts::revision) + options::opts::branch | options::opts::revision | + options::opts::move_conflicting_paths) { if (args.size() > 0) throw usage(execid); @@ -291,7 +292,8 @@ CMD(update, "update", "", CMD_REF(worksp // Now finally modify the workspace cset update; make_cset(*working_roster, merged_roster, update); - work.perform_content_update(db, update, wca); + work.perform_content_update(db, update, wca, true, + app.opts.move_conflicting_paths); revision_t remaining; make_revision_for_workspace(chosen_rid, chosen_roster, @@ -681,7 +683,7 @@ CMD(merge_into_workspace, "merge_into_wo "pending changes in the current workspace. Both OTHER-REVISION and " "the workspace's base revision will be recorded as parents on commit. " "The workspace's selected branch is not changed."), - options::opts::none) + options::opts::move_conflicting_paths) { revision_id left_id, right_id; cached_roster left, right; @@ -772,7 +774,8 @@ CMD(merge_into_workspace, "merge_into_wo make_cset(*left.first, merge_result.roster, update); // small race condition here... - work.perform_content_update(db, update, wca); + work.perform_content_update(db, update, wca, true, + app.opts.move_conflicting_paths); work.put_work_rev(merged_rev); work.update_any_attrs(db); work.maybe_update_inodeprints(db); @@ -1005,7 +1008,8 @@ CMD(pluck, "pluck", "", CMD_REF(workspac "compared to its parent.\n" "If two revisions are given, applies the changes made to get from the " "first revision to the second."), - options::opts::revision | options::opts::depth | options::opts::exclude) + options::opts::revision | options::opts::depth | options::opts::exclude | + options::opts::move_conflicting_paths) { database db(app); workspace work(app); @@ -1147,7 +1151,8 @@ CMD(pluck, "pluck", "", CMD_REF(workspac MM(update); make_cset(*working_roster, merged_roster, update); E(!update.empty(), F("no changes were applied")); - work.perform_content_update(db, update, wca); + work.perform_content_update(db, update, wca, true, + app.opts.move_conflicting_paths); P(F("applied changes to workspace")); ============================================================ --- cmd_netsync.cc 34174bb7ce83722d7a9859b7065c40243ca0ba75 +++ cmd_netsync.cc e7134210a0f4aa995a911a9d9a114897200493f1 @@ -439,7 +439,10 @@ CMD(clone, "clone", "", CMD_REF(network) content_merge_checkout_adaptor wca(db); - work.perform_content_update(db, checkout, wca, false); + // FIXME: in contrast to checkout, clone must be given an non-existant + // directory, thus it makes no sense to allow moving conflicting files in + // it away - since there just should not be any existing ones. + work.perform_content_update(db, checkout, wca, false, false); work.update_any_attrs(db); work.maybe_update_inodeprints(db); ============================================================ --- cmd_ws_commit.cc 77b18e38145f345b0eb65b73cc05619464a8fc59 +++ cmd_ws_commit.cc b9f6cc0fb67f14fd01a04709f9631f4c03069f6d @@ -552,7 +552,7 @@ CMD(pivot_root, "pivot_root", "", CMD_RE "that is currently the root " "directory will have name PUT_OLD.\n" "Use of --bookkeep-only is NOT recommended."), - options::opts::bookkeep_only) + options::opts::bookkeep_only | options::opts::move_conflicting_paths) { if (args.size() != 2) throw usage(execid); @@ -562,7 +562,8 @@ CMD(pivot_root, "pivot_root", "", CMD_RE file_path new_root = file_path_external(idx(args, 0)); file_path put_old = file_path_external(idx(args, 1)); work.perform_pivot_root(db, new_root, put_old, - app.opts.bookkeep_only); + app.opts.bookkeep_only, + app.opts.move_conflicting_paths); } CMD(status, "status", "", CMD_REF(informative), N_("[PATH]..."), @@ -600,7 +601,8 @@ CMD(checkout, "checkout", "co", CMD_REF( N_("If a revision is given, that's the one that will be checked out. " "Otherwise, it will be the head of the branch (given or implicit). " "If no directory is given, the branch name will be used as directory."), - options::opts::branch | options::opts::revision) + options::opts::branch | options::opts::revision | + options::opts::move_conflicting_paths) { revision_id revid; system_path dir; @@ -695,7 +697,8 @@ CMD(checkout, "checkout", "co", CMD_REF( content_merge_checkout_adaptor wca(db); - work.perform_content_update(db, checkout, wca, false); + work.perform_content_update(db, checkout, wca, false, + app.opts.move_conflicting_paths); work.update_any_attrs(db); work.maybe_update_inodeprints(db); ============================================================ --- monotone.texi 8542311382a69a2d94963663f5336ad83f842ea2 +++ monotone.texi 6418fdbd3f1062756cbfa805de0318b7183bfa19 @@ -3323,14 +3323,19 @@ @section Workspace Collisions These examples describe collisions on @command{update}; the same kinds of things can happen with other commands that can bring changes into -your workspace, such as @command{checkout} or @command{pluck} too. +your workspace, such as @command{checkout}, @command{pivot_root} or address@hidden too. Monotone is careful to avoid hitting such collisions. Before changing the workspace, it will try and detect the possibility of collisions, and the command will fail, warning you about the names that collide. The file content in the database is safe and can be recovered at any time, so monotone is conservative and will refuse to destroy the information -in your workspace contents. +in your workspace contents. Furthermore, all workspace-changing commands have +an option @option{--move-conflicting-paths}, which moves unversioned, but +conflicting files and directories from the workspace into a new subdirectory +under _MTN/conflicts. This is useful if you want to ensure that an update +always succeeds and you just want to move blocking paths out of the way. However, monotone cannot detect all kinds of failures and collisions in your workspace. For example: @@ -4454,10 +4459,10 @@ @section Tree With an explicit @option{--revision} argument, the command outputs contents of @var{path} at that revision. address@hidden mtn checkout address@hidden @var{directory} address@hidden mtn co address@hidden @var{directory} address@hidden mtn address@hidden checkout @var{directory} address@hidden mtn address@hidden co @var{directory} address@hidden mtn checkout [--move-conflicting-paths] address@hidden @var{directory} address@hidden mtn co [--move-conflicting-paths] address@hidden @var{directory} address@hidden mtn [--move-conflicting-paths] address@hidden checkout @var{directory} address@hidden mtn [--move-conflicting-paths] address@hidden co @var{directory} These commands copy a revision @var{id} out of your database, recording the chosen revision (the @dfn{base revision}) in the file @@ -4825,8 +4830,8 @@ @section Workspace deleted from the workspace. Only missing files matching the given file or directory arguments are reverted. address@hidden mtn update address@hidden mtn update address@hidden address@hidden mtn update [--move-conflicting-paths] address@hidden mtn update [--move-conflicting-paths] address@hidden Without a @option{--revision} argument, this command incorporates ``recent'' changes found in your database into your workspace. It does this by performing 3 separate stages. If any of these stages @@ -4868,8 +4873,8 @@ @section Workspace When running @command{update}, it is sometimes possible for @ref{Workspace Collisions} to occur. address@hidden mtn pluck address@hidden address@hidden mtn pluck address@hidden address@hidden address@hidden mtn pluck [--move-conflicting-paths] address@hidden address@hidden mtn pluck [--move-conflicting-paths] address@hidden address@hidden This command takes changes made at any point in history, and attempts to edit your current workspace to include those changes. The end result is @@ -4912,7 +4917,7 @@ @section Workspace in inodeprints mode, and that the inodeprints cache is accurate and up to date. address@hidden mtn pivot_root [--bookkeep-only] pivot_root @var{new_root} @var{put_old} address@hidden mtn pivot_root [--bookkeep-only] [--move-conflicting-paths] pivot_root @var{new_root} @var{put_old} Most users will never need this command. It is primarily useful in certain tricky cases where one wishes to combine several projects into one, or split one project into several. See also @command{merge_into_dir}. ============================================================ --- options_list.hh cd0ff5a0d051b4644c02a5f81ed262a512c65360 +++ options_list.hh 70ce6c3a2c470461626c336383e813adb06c1041 @@ -283,6 +283,15 @@ OPT(bookkeep_only, "bookkeep-only", bool } #endif +OPT(move_conflicting_paths, "move-conflicting-paths", bool, false, + gettext_noop("move conflicting, unversioned paths into _MTN/conflicts " + "before proceeding with any workspace change")) +#ifdef option_bodies +{ + move_conflicting_paths = true; +} +#endif + GOPT(ssh_sign, "ssh-sign", std::string, "yes", gettext_noop("controls use of ssh-agent. valid arguments are: " "'yes' to use ssh-agent to make signatures if possible, " ============================================================ --- work.cc 04462308709ee929fb10ab635240551c3e1e74ff +++ work.cc 8158573a9380c17be7f626ac6919316d922a3b2e @@ -930,11 +930,12 @@ struct simulated_working_tree : public e node_id_source & nis; set blocked_paths; - set leftover_paths; + set conflicting_paths; + int conflicts; map nid_map; simulated_working_tree(roster_t & r, temp_node_id_source & n) - : workspace(r), nis(n) {} + : workspace(r), nis(n), conflicts(0) {} virtual node_id detach_node(file_path const & src); virtual void drop_detached_node(node_id nid); @@ -954,6 +955,9 @@ struct simulated_working_tree : public e virtual void commit(); + virtual bool has_conflicting_paths() const { return conflicting_paths.size() > 0; } + virtual set get_conflicting_paths() const { return conflicting_paths; } + virtual ~simulated_working_tree(); }; @@ -1173,10 +1177,11 @@ simulated_working_tree::drop_detached_no { map::const_iterator i = nid_map.find(nid); I(i != nid_map.end()); - L(FL("directory '%s' should be dropped, but is not empty") % i->second); + W(F("cannot drop non-empty directory '%s'") % i->second); + conflicts++; for (dir_map::const_iterator j = dir->children.begin(); j != dir->children.end(); ++j) - leftover_paths.insert(i->second / j->first); + conflicting_paths.insert(i->second / j->first); } } } @@ -1205,9 +1210,10 @@ simulated_working_tree::attach_node(node if (workspace.has_node(dst)) { - L(FL("attach node %d blocked by unversioned path '%s'") % nid % dst); + W(F("attach node %d blocked by unversioned path '%s'") % nid % dst); blocked_paths.insert(dst); - leftover_paths.insert(dst); + conflicting_paths.insert(dst); + conflicts++; } else if (dst.empty()) { @@ -1223,7 +1229,7 @@ simulated_working_tree::attach_node(node workspace.attach_node(nid, dst); else { - L(FL("attach node %d blocked by blocked parent '%s'") + W(F("attach node %d blocked by blocked parent '%s'") % nid % parent); blocked_paths.insert(dst); } @@ -1255,49 +1261,53 @@ simulated_working_tree::commit() void simulated_working_tree::commit() { - // if we have found paths during the test-run which will conflict with newly - // attached or to-be-dropped nodes, move these paths out of the way into - // _MTN/leftover while keeping the path to these paths intact in case the - // user wants them back - if (leftover_paths.size() > 0) - { - string now = date_t::now().as_iso_8601_extended(); - bookkeeping_path leftover_path = bookkeeping_root / "leftover" / now.data(); - require_path_is_nonexistent(leftover_path, - F("cannot move left-over paths - " - "base path %s already exists") % leftover_path); + if (conflicts > 0) + { + W(F("%d workspace conflicts") % conflicts); + } +} - mkdir_p(leftover_path); +simulated_working_tree::~simulated_working_tree() +{ +} - for (set::const_iterator i = leftover_paths.begin(); - i != leftover_paths.end(); ++i) - { - L(FL("processing %s") % *i); - file_path basedir = (*i).dirname(); - if (!basedir.empty()) - mkdir_p(leftover_path / basedir); +}; // anonymous namespace - bookkeeping_path new_path = leftover_path / *i; - if (directory_exists(*i)) - move_dir(*i, new_path); - else if (file_exists(*i)) - move_file(*i, new_path); - else - I(false); +static void +move_conflicting_paths_into_bookkeeping(set const & leftover_paths) +{ + I(leftover_paths.size() > 0); - P(F("moved left-over path %s to %s") % *i % new_path); - } - } -} + string now = date_t::now().as_iso_8601_extended(); + bookkeeping_path leftover_path = bookkeeping_root / "conflicts" / now.data(); + require_path_is_nonexistent(leftover_path, + F("cannot move conflicting paths - " + "base path %s already exists") % leftover_path); -simulated_working_tree::~simulated_working_tree() -{ -} + mkdir_p(leftover_path); + for (set::const_iterator i = leftover_paths.begin(); + i != leftover_paths.end(); ++i) + { + L(FL("processing %s") % *i); -}; // anonymous namespace + file_path basedir = (*i).dirname(); + if (!basedir.empty()) + mkdir_p(leftover_path / basedir); + bookkeeping_path new_path = leftover_path / *i; + if (directory_exists(*i)) + move_dir(*i, new_path); + else if (file_exists(*i)) + move_file(*i, new_path); + else + I(false); + + P(F("moved conflicting path %s to %s") % *i % new_path); + } +} + static void add_parent_dirs(database & db, node_id_source & nis, workspace & work, file_path const & dst, roster_t & ros) @@ -1736,7 +1746,8 @@ workspace::perform_pivot_root(database & workspace::perform_pivot_root(database & db, file_path const & new_root, file_path const & put_old, - bool bookkeep_only) + bool bookkeep_only, + bool move_conflicting_paths) { temp_node_id_source nis; roster_t new_roster; @@ -1790,7 +1801,7 @@ workspace::perform_pivot_root(database & if (!bookkeep_only) { content_merge_empty_adaptor cmea; - perform_content_update(db, cs, cmea); + perform_content_update(db, cs, cmea, true, move_conflicting_paths); } update_any_attrs(db); } @@ -1799,7 +1810,8 @@ workspace::perform_content_update(databa workspace::perform_content_update(database & db, cset const & update, content_merge_adaptor const & ca, - bool const messages) + bool const messages, + bool const move_conflicting_paths) { roster_t roster; temp_node_id_source nis; @@ -1821,6 +1833,18 @@ workspace::perform_content_update(databa simulated_working_tree swt(roster, nis); update.apply_to(swt); + // if we have found paths during the test-run which will conflict with newly + // attached or to-be-dropped nodes, move these paths out of the way into + // _MTN/leftover while keeping the path to these paths intact in case the + // user wants them back + if (swt.has_conflicting_paths()) + { + E(move_conflicting_paths, + F("re-run this command with --move-conflicting-paths to move " + "conflicting paths out of the way.")); + move_conflicting_paths_into_bookkeeping(swt.get_conflicting_paths()); + } + mkdir_p(detached); editable_working_tree ewt(*this, ca, messages); ============================================================ --- work.hh cd117c8295b2bdf48db35e92aaf29842c27a1b7f +++ work.hh ec26896a29463101ca0a0b47710d21fc123f1777 @@ -131,7 +131,7 @@ public: void perform_deletions(database & db, std::set const & targets, - bool recursive, + bool recursive, bool bookkeep_only); void perform_rename(database & db, @@ -142,12 +142,14 @@ public: void perform_pivot_root(database & db, file_path const & new_root, file_path const & put_old, - bool bookkeep_only); + bool bookkeep_only, + bool move_conflicting_paths); void perform_content_update(database & db, cset const & cs, content_merge_adaptor const & ca, - bool messages = true); + bool const messages = true, + bool const move_conflicting_paths = false); void update_any_attrs(database & db); void init_attributes(file_path const & path, editable_roster_base & er); @@ -255,4 +257,4 @@ public: // vim: et:sw=2:sts=2:ts=2:cino=>2s,{s,\:s,+s,t0,g0,^-2,e-2,n-2,p2s,(0,=s: #endif // __WORK_HH__ - +