Below is the file 'cmd_merging.cc' from this revision. You can also download the file.
// Copyright (C) 2008 Stephen Leake <stephen_leake@stephe-leake.org> // Copyright (C) 2002 Graydon Hoare <graydon@pobox.com> // // This program is made available under the GNU GPL version 2.0 or // greater. See the accompanying file COPYING for details. // // This program is distributed WITHOUT ANY WARRANTY; without even the // implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR // PURPOSE. #include "base.hh" #include <cstring> #include <iostream> #include <iomanip> #include "basic_io.hh" #include "cmd.hh" #include "diff_patch.hh" #include "merge.hh" #include "restrictions.hh" #include "revision.hh" #include "roster_merge.hh" #include "transforms.hh" #include "update.hh" #include "work.hh" #include "safe_map.hh" #include "ui.hh" #include "app_state.hh" #include "project.hh" #include "simplestring_xform.hh" #include "keys.hh" #include "key_store.hh" #include "database.hh" using std::cout; using std::make_pair; using std::map; using std::set; using std::string; using std::vector; using std::strlen; using boost::shared_ptr; static void three_way_merge(revision_id const & ancestor_rid, roster_t const & ancestor_roster, revision_id const & left_rid, roster_t const & left_roster, revision_id const & right_rid, roster_t const & right_roster, roster_merge_result & result, marking_map & left_markings, marking_map & right_markings) { MM(ancestor_roster); MM(left_roster); MM(right_roster); MM(ancestor_rid); MM(left_rid); MM(right_rid); // Mark up the ANCESTOR marking_map ancestor_markings; MM(ancestor_markings); mark_roster_with_no_parents(ancestor_rid, ancestor_roster, ancestor_markings); // Mark up the LEFT roster left_markings.clear(); MM(left_markings); mark_roster_with_one_parent(ancestor_roster, ancestor_markings, left_rid, left_roster, left_markings); // Mark up the RIGHT roster right_markings.clear(); MM(right_markings); mark_roster_with_one_parent(ancestor_roster, ancestor_markings, right_rid, right_roster, right_markings); // Make the synthetic graph, by creating uncommon ancestor sets std::set<revision_id> left_uncommon_ancestors, right_uncommon_ancestors; safe_insert(left_uncommon_ancestors, left_rid); safe_insert(right_uncommon_ancestors, right_rid); P(F("[left] %s") % left_rid); P(F("[right] %s") % right_rid); // And do the merge roster_merge(left_roster, left_markings, left_uncommon_ancestors, right_roster, right_markings, right_uncommon_ancestors, result); } static bool pick_branch_for_update(options & opts, database & db, revision_id chosen_rid) { bool switched_branch = false; // figure out which branches the target is in vector< revision<cert> > certs; db.get_revision_certs(chosen_rid, branch_cert_name, certs); erase_bogus_certs(db, certs); set< branch_name > branches; for (vector< revision<cert> >::const_iterator i = certs.begin(); i != certs.end(); i++) branches.insert(branch_name(i->inner().value())); if (branches.find(opts.branchname) != branches.end()) { L(FL("using existing branch %s") % opts.branchname()); } else { P(F("target revision is not in current branch")); if (branches.size() > 1) { // multiple non-matching branchnames string branch_list; for (set<branch_name>::const_iterator i = branches.begin(); i != branches.end(); i++) branch_list += "\n " + (*i)(); N(false, F("target revision is in multiple branches:%s\n\n" "try again with explicit --branch") % branch_list); } else if (branches.size() == 1) { // one non-matching, inform and update opts.branchname = *(branches.begin()); switched_branch = true; } else { I(branches.size() == 0); W(F("target revision not in any branch\n" "next commit will use branch %s") % opts.branchname); } } return switched_branch; } CMD(update, "update", "", CMD_REF(workspace), "", N_("Updates the workspace"), N_("This command modifies your workspace to be based off of a " "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) { if (args.size() > 0) throw usage(execid); if (app.opts.revision_selectors.size() > 1) throw usage(execid); database db(app); workspace work(app); project_t project(db); // Figure out where we are parent_map parents; work.get_parent_rosters(db, parents); N(parents.size() == 1, F("this command can only be used in a single-parent workspace")); revision_id old_rid = parent_id(parents.begin()); N(!null_id(old_rid), F("this workspace is a new project; cannot update")); // Figure out where we're going N(!app.opts.branchname().empty(), F("cannot determine branch for update")); revision_id chosen_rid; if (app.opts.revision_selectors.size() == 0) { P(F("updating along branch '%s'") % app.opts.branchname); set<revision_id> candidates; pick_update_candidates(app.lua, project, candidates, old_rid, app.opts.branchname, app.opts.ignore_suspend_certs); N(!candidates.empty(), F("your request matches no descendents of the current revision\n" "in fact, it doesn't even match the current revision\n" "maybe you want something like --revision=h:%s") % app.opts.branchname); if (candidates.size() != 1) { P(F("multiple update candidates:")); for (set<revision_id>::const_iterator i = candidates.begin(); i != candidates.end(); ++i) P(i18n_format(" %s") % describe_revision(project, *i)); P(F("choose one with '%s update -r<id>'") % ui.prog_name); E(false, F("multiple update candidates remain after selection")); } chosen_rid = *(candidates.begin()); } else { complete(app.opts, app.lua, project, app.opts.revision_selectors[0](), chosen_rid); } I(!null_id(chosen_rid)); // do this notification before checking to see if we can bail out early, // because when you are at one of several heads, and you hit update, you // want to know that merging would let you update further. notify_if_multiple_heads(project, app.opts.branchname, app.opts.ignore_suspend_certs); if (old_rid == chosen_rid) { P(F("already up to date at %s") % old_rid); // do still switch the workspace branch, in case they have used // update to switch branches. work.set_ws_options(app.opts, true); return; } P(F("selected update target %s") % chosen_rid); // Fiddle around with branches, in an attempt to guess what the user // wants. bool switched_branch = pick_branch_for_update(app.opts, db, chosen_rid); if (switched_branch) P(F("switching to branch %s") % app.opts.branchname()); // Okay, we have a target, we have a branch, let's do this merge! // We have: // // old --> working // | | // V V // chosen --> merged // // - old is the revision specified in _MTN/revision // - working is based on old and includes the workspace's changes // - chosen is the revision we're updating to and will end up in _MTN/revision // - merged is the merge of working and chosen, that will become the new // workspace // // we apply the working to merged cset to the workspace // and write the cset from chosen to merged changeset in _MTN/work temp_node_id_source nis; // Get the OLD and WORKING rosters roster_t_cp old_roster = parent_cached_roster(parents.begin()).first; MM(*old_roster); shared_ptr<roster_t> working_roster = shared_ptr<roster_t>(new roster_t()); MM(*working_roster); work.get_current_roster_shape(db, nis, *working_roster); work.update_current_roster_from_filesystem(*working_roster); revision_t working_rev; revision_id working_rid; make_revision_for_workspace(parents, *working_roster, working_rev); calculate_ident(working_rev, working_rid); // Get the CHOSEN roster roster_t chosen_roster; MM(chosen_roster); db.get_roster(chosen_rid, chosen_roster); // And finally do the merge roster_merge_result result; marking_map left_markings, right_markings; three_way_merge(old_rid, *old_roster, working_rid, *working_roster, chosen_rid, chosen_roster, result, left_markings, right_markings); roster_t & merged_roster = result.roster; map<file_id, file_path> paths; get_content_paths(*working_roster, paths); content_merge_workspace_adaptor wca(db, old_rid, old_roster, left_markings, right_markings, paths); wca.cache_roster(working_rid, working_roster); resolve_merge_conflicts(app.lua, *working_roster, chosen_roster, result, wca); // Make sure it worked... I(result.is_clean()); merged_roster.check_sane(true); // Now finally modify the workspace cset update; make_cset(*working_roster, merged_roster, update); work.perform_content_update(db, update, wca); revision_t remaining; make_revision_for_workspace(chosen_rid, chosen_roster, merged_roster, remaining); // small race condition here... work.put_work_rev(remaining); work.update_any_attrs(db); work.maybe_update_inodeprints(db); work.set_ws_options(app.opts, true); if (switched_branch) P(F("switched branch; next commit will use branch %s") % app.opts.branchname()); P(F("updated to base revision %s") % chosen_rid); } // Subroutine of CMD(merge) and CMD(explicit_merge). Merge LEFT with RIGHT, // placing results onto BRANCH. Note that interactive_merge_and_store may // bomb out, and therefore so may this. static void merge_two(options & opts, lua_hooks & lua, project_t & project, key_store & keys, revision_id const & left, revision_id const & right, branch_name const & branch, string const & caller, std::ostream & output, bool automate) { // The following mess constructs a neatly formatted log message that looks // like this: // CALLER of 'LEFT' // and 'RIGHT' // to branch 'BRANCH' // where the last line is left out if we're merging onto the current branch. // We use a stringstream because boost::format does not support %-*s. using std::ostringstream; using std::setw; using std::max; ostringstream log; size_t fieldwidth = max(caller.size() + strlen(" of '"), strlen("and '")); if (branch != opts.branchname) fieldwidth = max(fieldwidth, strlen("to branch '")); log << setw(fieldwidth - strlen(" of '")) << caller << " of '" << left << "'\n" << setw(fieldwidth) << "and '" << right << "'\n"; if (branch != opts.branchname) log << setw(fieldwidth) << "to branch '" << branch << "'\n"; // Now it's time for the real work. if (automate) { output << left << " " << right << " "; } else { P(F("[left] %s") % left); P(F("[right] %s") % right); } revision_id merged; transaction_guard guard(project.db); interactive_merge_and_store(lua, project.db, left, right, merged); project.put_standard_certs_from_options(opts, lua, keys, merged, branch, utf8(log.str())); guard.commit(); if (automate) output << merged << "\n"; else P(F("[merged] %s") % merged); } typedef std::pair<revision_id, revision_id> revpair; typedef set<revision_id>::const_iterator rid_set_iter; // Subroutine of 'merge' and 'automate show_conflicts'; find first pair of // heads to merge. static revpair find_heads_to_merge(database & db, set<revision_id> const heads) { I(heads.size() > 2); map<revision_id, revpair> heads_for_ancestor; set<revision_id> ancestors; // For every pair of heads, determine their merge ancestor, and // remember the ancestor->head mapping. for (rid_set_iter i = heads.begin(); i != heads.end(); ++i) for (rid_set_iter j = i; j != heads.end(); ++j) { // It is not possible to initialize j to i+1 (set iterators // expose neither operator+ nor a nondestructive next() method) if (j == i) continue; revision_id ancestor; find_common_ancestor_for_merge(db, *i, *j, ancestor); // More than one pair might have the same ancestor (e.g. if we // have three heads all with the same parent); as this table // will be recalculated on every pass, we just take the first // one we find. if (ancestors.insert(ancestor).second) safe_insert(heads_for_ancestor, std::make_pair(ancestor, revpair(*i, *j))); } // Erasing ancestors from ANCESTORS will now produce a set of merge // ancestors each of which is not itself an ancestor of any other // merge ancestor. erase_ancestors(db, ancestors); I(ancestors.size() > 0); // Take the first ancestor from the above set. return heads_for_ancestor[*ancestors.begin()]; } // should merge support --message, --message-file? It seems somewhat weird, // since a single 'merge' command may perform arbitrarily many actual merges. // (Possibility: append the --message/--message-file text to the synthetic // log message constructed in merge_two().) CMD(merge, "merge", "", CMD_REF(tree), "", N_("Merges unmerged heads of a branch"), "", options::opts::branch | options::opts::date | options::opts::author) { database db(app); key_store keys(app); project_t project(db); if (args.size() != 0) throw usage(execid); N(app.opts.branchname() != "", F("please specify a branch, with --branch=BRANCH")); set<revision_id> heads; project.get_branch_heads(app.opts.branchname, heads, app.opts.ignore_suspend_certs); N(heads.size() != 0, F("branch '%s' is empty") % app.opts.branchname); if (heads.size() == 1) { P(F("branch '%s' is already merged") % app.opts.branchname); return; } P(FP("%d head on branch '%s'", "%d heads on branch '%s'", heads.size()) % heads.size() % app.opts.branchname); // avoid failure after lots of work cache_user_key(app.opts, app.lua, db, keys); size_t pass = 1, todo = heads.size() - 1; // If there are more than two heads to be merged, on each iteration we // merge a pair whose least common ancestor is not an ancestor of any // other pair's least common ancestor. For example, if the history graph // looks like this: // // X // / \. (periods to prevent multi-line // Y C comment warnings) // / \. // A B // // A and B will be merged first, and then the result will be merged with C. while (heads.size() > 2) { P(F("merge %d / %d:") % pass % todo); P(F("calculating best pair of heads to merge next")); revpair p = find_heads_to_merge(db, heads); merge_two(app.opts, app.lua, project, keys, p.first, p.second, app.opts.branchname, string("merge"), std::cout, false); project.get_branch_heads(app.opts.branchname, heads, app.opts.ignore_suspend_certs); pass++; } // Last one. I(pass == todo); if (todo > 1) P(F("merge %d / %d:") % pass % todo); rid_set_iter i = heads.begin(); revision_id left = *i++; revision_id right = *i++; I(i == heads.end()); merge_two(app.opts, app.lua, project, keys, left, right, app.opts.branchname, string("merge"), std::cout, false); P(F("note: your workspaces have not been updated")); } CMD(propagate, "propagate", "", CMD_REF(tree), N_("SOURCE-BRANCH DEST-BRANCH"), N_("Merges from one branch to another asymmetrically"), "", options::opts::date | options::opts::author | options::opts::message | options::opts::msgfile) { if (args.size() != 2) throw usage(execid); args_vector a = args; a.push_back(arg_type()); process(app, make_command_id("tree merge_into_dir"), a); } // This is a special merge operator, but very useful for people // maintaining "slightly disparate but related" trees. It does a one-way // merge; less powerful than putting things in the same branch and also // more flexible. // // 1. Check to see if src and dst branches are merged, if not abort, if so // call heads N1 and N2 respectively. // // 2. (FIXME: not yet present) Run the hook propagate ("src-branch", // "dst-branch", N1, N2) which gives the user a chance to massage N1 into // a state which is likely to "merge nicely" with N2, eg. edit pathnames, // omit optional files of no interest. // // 3. Do a normal 2 or 3-way merge on N1 and N2, depending on the // existence of common ancestors. // // 4. Save the results as the delta (N2,M), the ancestry edges (N1,M) // and (N2,M), and the cert (N2,dst). // // There are also special cases we have to check for where no merge is // actually necessary, because there hasn't been any divergence since the // last time propagate was run. // // If dir is not the empty string, rename the root of N1 to have the name // 'dir' in the merged tree. (ie, it has name "basename(dir)", and its // parent node is "N2.get_node(dirname(dir))") CMD(merge_into_dir, "merge_into_dir", "", CMD_REF(tree), N_("SOURCE-BRANCH DEST-BRANCH DIR"), N_("Merges one branch into a subdirectory in another branch"), "", options::opts::date | options::opts::author | options::opts::message | options::opts::msgfile) { database db(app); key_store keys(app); project_t project(db); set<revision_id> src_heads, dst_heads; if (args.size() != 3) throw usage(execid); project.get_branch_heads(branch_name(idx(args, 0)()), src_heads, app.opts.ignore_suspend_certs); project.get_branch_heads(branch_name(idx(args, 1)()), dst_heads, app.opts.ignore_suspend_certs); N(src_heads.size() != 0, F("branch '%s' is empty") % idx(args, 0)()); N(src_heads.size() == 1, F("branch '%s' is not merged") % idx(args, 0)()); N(dst_heads.size() != 0, F("branch '%s' is empty") % idx(args, 1)()); N(dst_heads.size() == 1, F("branch '%s' is not merged") % idx(args, 1)()); set<revision_id>::const_iterator src_i = src_heads.begin(); set<revision_id>::const_iterator dst_i = dst_heads.begin(); if (*src_i == *dst_i || is_ancestor(db, *src_i, *dst_i)) { P(F("branch '%s' is up-to-date with respect to branch '%s'") % idx(args, 1)() % idx(args, 0)()); P(F("no action taken")); return; } cache_user_key(app.opts, app.lua, db, keys); P(F("propagating %s -> %s") % idx(args,0) % idx(args,1)); P(F("[left] %s") % *src_i); P(F("[right] %s") % *dst_i); // check for special cases if (is_ancestor(db, *dst_i, *src_i)) { P(F("no merge necessary; putting %s in branch '%s'") % *src_i % idx(args, 1)()); transaction_guard guard(db); project.put_revision_in_branch(keys, *src_i, branch_name(idx(args, 1)())); guard.commit(); } else { revision_id merged; transaction_guard guard(db); { revision_id const & left_rid(*src_i), & right_rid(*dst_i); roster_t left_roster, right_roster; MM(left_roster); MM(right_roster); marking_map left_marking_map, right_marking_map; set<revision_id> left_uncommon_ancestors, right_uncommon_ancestors; db.get_roster(left_rid, left_roster, left_marking_map); db.get_roster(right_rid, right_roster, right_marking_map); db.get_uncommon_ancestors(left_rid, right_rid, left_uncommon_ancestors, right_uncommon_ancestors); if (!idx(args,2)().empty()) { dir_t moved_root = left_roster.root(); file_path pth = file_path_external(idx(args, 2)); file_path dir; path_component base; MM(dir); pth.dirname_basename(dir, base); N(right_roster.has_node(dir), F("Path %s not found in destination tree.") % pth); node_t parent = right_roster.get_node(dir); moved_root->parent = parent->self; moved_root->name = base; marking_map::iterator i = left_marking_map.find(moved_root->self); I(i != left_marking_map.end()); i->second.parent_name.clear(); i->second.parent_name.insert(left_rid); } roster_merge_result result; roster_merge(left_roster, left_marking_map, left_uncommon_ancestors, right_roster, right_marking_map, right_uncommon_ancestors, result); content_merge_database_adaptor dba(db, left_rid, right_rid, left_marking_map, right_marking_map); resolve_merge_conflicts(app.lua, left_roster, right_roster, result, dba); { dir_t moved_root = left_roster.root(); moved_root->parent = the_null_node; moved_root->name = path_component(); } // Write new files into the db. store_roster_merge_result(db, left_roster, right_roster, result, left_rid, right_rid, merged); } bool log_message_given; utf8 log_message; process_commit_message_args(app.opts, log_message_given, log_message); if (!log_message_given) log_message = utf8((FL("propagate from branch '%s' (head %s)\n" " to branch '%s' (head %s)\n") % idx(args, 0) % *src_i % idx(args, 1) % *dst_i).str()); project.put_standard_certs_from_options(app.opts, app.lua, keys, merged, branch_name(idx(args, 1)()), log_message); guard.commit(); P(F("[merged] %s") % merged); } } CMD(merge_into_workspace, "merge_into_workspace", "", CMD_REF(tree), N_("OTHER-REVISION"), N_("Merges a revision into the current workspace's base revision"), N_("Merges OTHER-REVISION into the current workspace's base revision, " "and update the current workspace with the result. There can be no " "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) { revision_id left_id, right_id; cached_roster left, right; shared_ptr<roster_t> working_roster = shared_ptr<roster_t>(new roster_t()); if (args.size() != 1) throw usage(execid); database db(app); workspace work(app); project_t project(db); // Get the current state of the workspace. // This command cannot be applied to a workspace with more than one parent // (revs can have no more than two parents). revision_id working_rid; { parent_map parents; work.get_parent_rosters(db, parents); N(parents.size() == 1, F("this command can only be used in a single-parent workspace")); temp_node_id_source nis; work.get_current_roster_shape(db, nis, *working_roster); work.update_current_roster_from_filesystem(*working_roster); N(parent_roster(parents.begin()) == *working_roster, F("'%s' can only be used in a workspace with no pending changes") % join_words(execid)()); left_id = parent_id(parents.begin()); left = parent_cached_roster(parents.begin()); revision_t working_rev; make_revision_for_workspace(parents, *working_roster, working_rev); calculate_ident(working_rev, working_rid); } complete(app.opts, app.lua, project, idx(args, 0)(), right_id); db.get_roster(right_id, right); N(!(left_id == right_id), F("workspace is already at revision %s") % left_id); P(F("[left] %s") % left_id); P(F("[right] %s") % right_id); set<revision_id> left_uncommon_ancestors, right_uncommon_ancestors; db.get_uncommon_ancestors(left_id, right_id, left_uncommon_ancestors, right_uncommon_ancestors); roster_merge_result merge_result; MM(merge_result); roster_merge(*left.first, *left.second, left_uncommon_ancestors, *right.first, *right.second, right_uncommon_ancestors, merge_result); revision_id lca_id; cached_roster lca; find_common_ancestor_for_merge(db, left_id, right_id, lca_id); db.get_roster(lca_id, lca); map<file_id, file_path> paths; get_content_paths(*working_roster, paths); content_merge_workspace_adaptor wca(db, lca_id, lca.first, *left.second, *right.second, paths); wca.cache_roster(working_rid, working_roster); resolve_merge_conflicts(app.lua, *left.first, *right.first, merge_result, wca); // Make sure it worked... I(merge_result.is_clean()); merge_result.roster.check_sane(true); // Construct the workspace revision. parent_map parents; safe_insert(parents, std::make_pair(left_id, left)); safe_insert(parents, std::make_pair(right_id, right)); revision_t merged_rev; make_revision_for_workspace(parents, merge_result.roster, merged_rev); // Note: the csets in merged_rev are _not_ suitable for submission to // perform_content_update, because content changes have been dropped. cset update; make_cset(*left.first, merge_result.roster, update); // small race condition here... work.perform_content_update(db, update, wca); work.put_work_rev(merged_rev); work.update_any_attrs(db); work.maybe_update_inodeprints(db); P(F("updated to result of merge\n" " [left] %s\n" "[right] %s\n") % left_id % right_id); } CMD(explicit_merge, "explicit_merge", "", CMD_REF(tree), N_("LEFT-REVISION RIGHT-REVISION DEST-BRANCH"), N_("Merges two explicitly given revisions"), N_("The results of the merge are placed on the branch specified by " "DEST-BRANCH."), options::opts::date | options::opts::author) { database db(app); key_store keys(app); project_t project(db); revision_id left, right; branch_name branch; if (args.size() != 3) throw usage(execid); complete(app.opts, app.lua, project, idx(args, 0)(), left); complete(app.opts, app.lua, project, idx(args, 1)(), right); branch = branch_name(idx(args, 2)()); N(!(left == right), F("%s and %s are the same revision, aborting") % left % right); N(!is_ancestor(db, left, right), F("%s is already an ancestor of %s") % left % right); N(!is_ancestor(db, right, left), F("%s is already an ancestor of %s") % right % left); // avoid failure after lots of work cache_user_key(app.opts, app.lua, db, keys); merge_two(app.opts, app.lua, project, keys, left, right, branch, string("explicit merge"), std::cout, false); } namespace { namespace syms { symbol const ancestor("ancestor"); symbol const left("left"); symbol const right("right"); } } static void show_conflicts_core (database & db, revision_id const & l_id, revision_id const & r_id, bool const basic_io, std::ostream & output) { N(!is_ancestor(db, l_id, r_id), F("%s is an ancestor of %s; no merge is needed.") % l_id % r_id); N(!is_ancestor(db, r_id, l_id), F("%s is an ancestor of %s; no merge is needed.") % r_id % l_id); shared_ptr<roster_t> l_roster = shared_ptr<roster_t>(new roster_t()); shared_ptr<roster_t> r_roster = shared_ptr<roster_t>(new roster_t()); marking_map l_marking, r_marking; db.get_roster(l_id, *l_roster, l_marking); db.get_roster(r_id, *r_roster, r_marking); set<revision_id> l_uncommon_ancestors, r_uncommon_ancestors; db.get_uncommon_ancestors(l_id, r_id, l_uncommon_ancestors, r_uncommon_ancestors); roster_merge_result result; roster_merge(*l_roster, l_marking, l_uncommon_ancestors, *r_roster, r_marking, r_uncommon_ancestors, result); // note that left and right are in the order specified on the command line // they are not in lexical order as they are with other merge commands so // they may appear swapped here. The user may have done that deliberately, // especially via automate, so we don't sort them here. basic_io::stanza st; if (basic_io) { st.push_binary_pair(syms::left, l_id.inner()); st.push_binary_pair(syms::right, r_id.inner()); } else { P(F("[left] %s") % l_id); P(F("[right] %s") % r_id); } if (result.is_clean()) { if (basic_io) { basic_io::printer pr; pr.print_stanza(st); output.write(pr.buf.data(), pr.buf.size()); } else P(F("no conflicts detected")); } else { content_merge_database_adaptor adaptor(db, l_id, r_id, l_marking, r_marking); { basic_io::printer pr; st.push_binary_pair(syms::ancestor, adaptor.lca.inner()); pr.print_stanza(st); output.write(pr.buf.data(), pr.buf.size()); } // The basic_io routines in roster_merge.cc access these rosters via // the adaptor. adaptor.cache_roster (l_id, l_roster); adaptor.cache_roster (r_id, r_roster); result.report_missing_root_conflicts(*l_roster, *r_roster, adaptor, basic_io, output); result.report_invalid_name_conflicts(*l_roster, *r_roster, adaptor, basic_io, output); result.report_directory_loop_conflicts(*l_roster, *r_roster, adaptor, basic_io, output); result.report_orphaned_node_conflicts(*l_roster, *r_roster, adaptor, basic_io, output); result.report_multiple_name_conflicts(*l_roster, *r_roster, adaptor, basic_io, output); result.report_duplicate_name_conflicts(*l_roster, *r_roster, adaptor, basic_io, output); result.report_attribute_conflicts(*l_roster, *r_roster, adaptor, basic_io, output); result.report_file_content_conflicts(*l_roster, *r_roster, adaptor, basic_io, output); } } CMD(show_conflicts, "show_conflicts", "", CMD_REF(informative), N_("REV REV"), N_("Shows what conflicts need resolution between two revisions"), N_("The conflicts are calculated based on the two revisions given in " "the REV parameters."), options::opts::none) { database db(app); project_t project(db); if (args.size() != 2) throw usage(execid); revision_id l_id, r_id; complete(app.opts, app.lua, project, idx(args,0)(), l_id); complete(app.opts, app.lua, project, idx(args,1)(), r_id); show_conflicts_core(db, l_id, r_id, false, std::cout); } // Name: show_conflicts // Arguments: // Two revision ids (optional, determined from the workspace if not given; there must be exactly two heads) // Added in: 7.1 // Purpose: Prints the conflicts between two revisions, to aid in merging them. // // Output format: see monotone.texi // // Error conditions: // // If the revision IDs are unknown or invalid prints an error message to // stderr and exits with status 1. // // If revision ids are not given, and the current workspace does not have // two heads, prints an error message to stderr and exits with status 1. // CMD_AUTOMATE(show_conflicts, N_("[LEFT_REVID RIGHT_REVID]"), N_("Shows the conflicts between two revisions."), N_("If no arguments are given, LEFT_REVID and RIGHT_REVID default to the " "first two heads that would be chosen by the 'merge' command."), options::opts::branch) { database db(app); project_t project(db); revision_id l_id, r_id; if (args.size() == 0) { // get ids from heads N(app.opts.branchname() != "", F("please specify a branch, with --branch=BRANCH")); set<revision_id> heads; project.get_branch_heads(app.opts.branchname, heads, app.opts.ignore_suspend_certs); N(heads.size() >= 2, F("branch '%s' has %d heads; must be at least 2 for show_conflicts") % app.opts.branchname % heads.size()); if (heads.size() == 2) { set<revision_id>::const_iterator i = heads.begin(); l_id = *i; ++i; r_id = *i; } else { revpair p = find_heads_to_merge (db, heads); l_id = p.first; r_id = p.second; } } else if (args.size() == 2) { // get ids from args complete(app.opts, app.lua, project, idx(args,0)(), l_id); complete(app.opts, app.lua, project, idx(args,1)(), r_id); } else N(false, F("wrong argument count")); show_conflicts_core(db, l_id, r_id, true, output); } CMD(pluck, "pluck", "", CMD_REF(workspace), N_("[-r FROM] -r TO [PATH...]"), N_("Applies changes made at arbitrary places in history"), N_("This command takes changes made at any point in history, and " "edits your current workspace to include those changes. The end result " "is identical to 'mtn diff -r FROM -r TO | patch -p0', except that " "this command uses monotone's merger, and thus intelligently handles " "renames, conflicts, and so on.\n" "If one revision is given, applies the changes made in that revision " "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) { database db(app); workspace work(app); project_t project(db); // Work out our arguments revision_id from_rid, to_rid; if (app.opts.revision_selectors.size() == 1) { complete(app.opts, app.lua, project, idx(app.opts.revision_selectors, 0)(), to_rid); std::set<revision_id> parents; db.get_revision_parents(to_rid, parents); N(parents.size() == 1, F("revision %s is a merge\n" "to apply the changes relative to one of its parents, use:\n" " %s pluck -r PARENT -r %s") % to_rid % ui.prog_name % to_rid); from_rid = *parents.begin(); } else if (app.opts.revision_selectors.size() == 2) { complete(app.opts, app.lua, project, idx(app.opts.revision_selectors, 0)(), from_rid); complete(app.opts, app.lua, project, idx(app.opts.revision_selectors, 1)(), to_rid); } else throw usage(execid); N(!(from_rid == to_rid), F("no changes to apply")); // notionally, we have the situation // // from --> working // | | // V V // to --> merged // // - from is the revision we start plucking from // - to is the revision we stop plucking at // - working is the current contents of the workspace // - merged is the result of the plucking, and achieved by running a // merge in the fictional graph seen above // // To perform the merge, we use the real from roster, and the real working // roster, but synthesize a temporary 'to' roster. This ensures that the // 'from', 'working' and 'base' rosters all use the same nid namespace, // while any additions that happened between 'from' and 'to' should be // considered as new nodes, even if the file that was added is in fact in // 'working' already -- so 'to' needs its own namespace. (Among other // things, it is impossible with our merge formalism to have the above // graph with a node that exists in 'to' and 'working', but not 'from'.) // // finally, we take the cset from working -> merged, and apply that to the // workspace // and take the cset from the workspace's base, and write that to _MTN/work // The node id source we'll use for the 'working' and 'to' rosters. temp_node_id_source nis; // Get the FROM roster shared_ptr<roster_t> from_roster = shared_ptr<roster_t>(new roster_t()); MM(*from_roster); db.get_roster(from_rid, *from_roster); // Get the WORKING roster shared_ptr<roster_t> working_roster = shared_ptr<roster_t>(new roster_t()); MM(*working_roster); work.get_current_roster_shape(db, nis, *working_roster); work.update_current_roster_from_filesystem(*working_roster); // Get the FROM->TO cset... cset from_to_to; MM(from_to_to); cset from_to_to_excluded; MM(from_to_to_excluded); { roster_t to_true_roster; db.get_roster(to_rid, to_true_roster); node_restriction mask(work, args_to_paths(args), args_to_paths(app.opts.exclude_patterns), app.opts.depth, *from_roster, to_true_roster); roster_t restricted_roster; make_restricted_roster(*from_roster, to_true_roster, restricted_roster, mask); make_cset(*from_roster, restricted_roster, from_to_to); make_cset(restricted_roster, to_true_roster, from_to_to_excluded); } N(!from_to_to.empty(), F("no changes to be applied")); // ...and use it to create the TO roster shared_ptr<roster_t> to_roster = shared_ptr<roster_t>(new roster_t()); MM(*to_roster); { *to_roster = *from_roster; editable_roster_base editable_to_roster(*to_roster, nis); from_to_to.apply_to(editable_to_roster); } parent_map parents; work.get_parent_rosters(db, parents); revision_t working_rev; revision_id working_rid; make_revision_for_workspace(parents, *working_roster, working_rev); calculate_ident(working_rev, working_rid); // Now do the merge roster_merge_result result; marking_map left_markings, right_markings; three_way_merge(from_rid