# # # patch "ChangeLog" # from [87867412708f689af35f8d9c36fc397f6b33a52e] # to [a0f514002071578c34f3ed3b6609a65b90fef85a] # # patch "commands.cc" # from [a10d3cb2745a4e0abf8281668b57204b333e03a6] # to [df7e5f355a8ac6505b5ea92b90036dc727bc14a9] # # patch "merge.cc" # from [6b32ab65201a1b475b64af515bb8edf6481eca46] # to [f129963451cc6c8f1562d84852c6d4e31a211eec] # # patch "merge.hh" # from [d8869e646a690f203ff3c85764929d89399f16cd] # to [b6598ac002b64ca96f130041514d9f6edda74bd8] # # patch "monotone.texi" # from [d62e740d5bf36c317e4ec3fea155132bd7ba702d] # to [b032e26d6e1fdec14a573061fb411780def43795] # # patch "testsuite.at" # from [d67ef3223629641b02551a0a31cbf4644cd60d82] # to [a78067f72148532de9792e0923eccead9c1b8d2c] # ============================================================ --- ChangeLog 87867412708f689af35f8d9c36fc397f6b33a52e +++ ChangeLog a0f514002071578c34f3ed3b6609a65b90fef85a @@ -1,3 +1,17 @@ +2006-02-26 Timothy Brownawell + + Add a new command, merge_into_dir, to allow a project to include an + unrelated project as a subdir. Also add a show_conflicts command that + performs a dry-run merge and prints conflict counts. + * commands.cc: new commands, merge_into_dir and show_conflicts + * commands.cc (propagate): remove duplicated code; this now calls + merge_into_dir with a dir of '' (the empty string). + * monotone.texi: Document the new commands. + * tests/t_{merge_into_dir,show_conflicts}.at: Test the new commands. + * testsuite.at: Use the new tests. + * merge.{cc,hh}: Factor a store_roster_merge_result function out + of interactive_merge_and_store. + 2006-02-26 Nathaniel Smith * roster_merge.cc: Update notes on tests. ============================================================ --- commands.cc a10d3cb2745a4e0abf8281668b57204b333e03a6 +++ commands.cc df7e5f355a8ac6505b5ea92b90036dc727bc14a9 @@ -3157,6 +3157,17 @@ N_("merge from one branch to another asymmetrically"), OPT_DATE % OPT_AUTHOR % OPT_LCA % OPT_MESSAGE % OPT_MSGFILE) { + if (args.size() != 2) + throw usage(name); + vector a = args; + a.push_back(utf8()); + process(app, "merge_into_dir", a); +} + +CMD(merge_into_dir, N_("tree"), N_("SOURCE-BRANCH DEST-BRANCH DIR"), + N_("merge one branch into a subdirectory in another branch"), + OPT_DATE % OPT_AUTHOR % OPT_LCA % OPT_MESSAGE % OPT_MSGFILE) +{ // 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. @@ -3178,10 +3189,14 @@ // 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))") set src_heads, dst_heads; - if (args.size() != 2) + if (args.size() != 3) throw usage(name); get_branch_heads(idx(args, 0)(), app, src_heads); @@ -3220,8 +3235,65 @@ { revision_id merged; transaction_guard guard(app.db); - interactive_merge_and_store(*src_i, *dst_i, merged, app); + { + 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; + std::set left_uncommon_ancestors, right_uncommon_ancestors; + + app.db.get_roster(left_rid, left_roster, left_marking_map); + app.db.get_roster(right_rid, right_roster, right_marking_map); + app.db.get_uncommon_ancestors(left_rid, right_rid, + left_uncommon_ancestors, + right_uncommon_ancestors); + + { + dir_t moved_root = left_roster.root(); + split_path sp, dirname; + path_component basename; + MM(dirname); + if (!idx(args,2)().empty()) + { + file_path_external(idx(args,2)).split(sp); + dirname_basename(sp, dirname, basename); + N(right_roster.has_node(dirname), + F("Path %s not found in destination tree.") % sp); + node_t parent = right_roster.get_node(dirname); + moved_root->parent = parent->self; + moved_root->name = basename; + 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(app, left_rid, right_rid, left_marking_map); + resolve_merge_conflicts (left_rid, right_rid, + left_roster, right_roster, + left_marking_map, right_marking_map, + result, dba, app); + + { + dir_t moved_root = left_roster.root(); + moved_root->parent = 0; + moved_root->name = the_null_component; + } + + // write new files into the db + store_roster_merge_result(left_roster, right_roster, result, + left_rid, right_rid, merged, + app); + } + packet_db_writer dbw(app); cert_revision_in_branch(merged, idx(args, 1)(), app, dbw); @@ -3910,4 +3982,37 @@ cout << dat; } +CMD(show_conflicts, N_("informative"), N_("REV REV"), N_("Show what conflicts would need to be resolved to merge the given revisions."), + OPT_BRANCH_NAME % OPT_DATE % OPT_AUTHOR) +{ + if (args.size() != 2) + throw usage(name); + revision_id l_id, r_id; + complete(app, idx(args,0)(), l_id); + complete(app, idx(args,1)(), r_id); + N(!is_ancestor(l_id, r_id, app), + F("%s in an ancestor of %s; no merge is needed.") % l_id % r_id); + N(!is_ancestor(r_id, l_id, app), + F("%s in an ancestor of %s; no merge is needed.") % r_id % l_id); + roster_t l_roster, r_roster; + marking_map l_marking, r_marking; + app.db.get_roster(l_id, l_roster, l_marking); + app.db.get_roster(r_id, r_roster, r_marking); + std::set l_uncommon_ancestors, r_uncommon_ancestors; + app.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); + + P(F("There are %s node_name_conflicts.") % result.node_name_conflicts.size()); + P(F("There are %s file_content_conflicts.") % result.file_content_conflicts.size()); + P(F("There are %s node_attr_conflicts.") % result.node_attr_conflicts.size()); + P(F("There are %s orphaned_node_conflicts.") % result.orphaned_node_conflicts.size()); + P(F("There are %s rename_target_conflicts.") % result.rename_target_conflicts.size()); + P(F("There are %s directory_loop_conflicts.") % result.directory_loop_conflicts.size()); +} + }; // namespace commands ============================================================ --- merge.cc 6b32ab65201a1b475b64af515bb8edf6481eca46 +++ merge.cc f129963451cc6c8f1562d84852c6d4e31a211eec @@ -139,8 +139,6 @@ right_roster, right_marking_map, right_uncommon_ancestors, result); - roster_t & merged_roster = result.roster; - content_merge_database_adaptor dba(app, left_rid, right_rid, left_marking_map); resolve_merge_conflicts (left_rid, right_rid, left_roster, right_roster, @@ -148,8 +146,22 @@ result, dba, app); // write new files into the db + store_roster_merge_result(left_roster, right_roster, result, + left_rid, right_rid, merged_rid, + app); +} +void +store_roster_merge_result(roster_t const & left_roster, + roster_t const & right_roster, + roster_merge_result & result, + revision_id const & left_rid, + revision_id const & right_rid, + revision_id & merged_rid, + app_state & app) +{ I(result.is_clean()); + roster_t & merged_roster = result.roster; merged_roster.check_sane(); revision_set merged_rev; ============================================================ --- merge.hh d8869e646a690f203ff3c85764929d89399f16cd +++ merge.hh b6598ac002b64ca96f130041514d9f6edda74bd8 @@ -46,4 +46,13 @@ interactive_merge_and_store(revision_id const & left, revision_id const & right, revision_id & merged, app_state & app); +void +store_roster_merge_result(roster_t const & left_roster, + roster_t const & right_roster, + roster_merge_result & result, + revision_id const & left_rid, + revision_id const & right_rid, + revision_id & merged_rid, + app_state & app); + #endif ============================================================ --- monotone.texi d62e740d5bf36c317e4ec3fea155132bd7ba702d +++ monotone.texi b032e26d6e1fdec14a573061fb411780def43795 @@ -3785,6 +3785,25 @@ another branch, again, you can use this command. If the optional @var{ancestor} argument is given, the merge uses that revision as the common ancestor instead of the default ancestor. + address@hidden monotone merge_into_dir @var{sourcebranch} @var{destbranch} @var{dir} +This command takes a unique head from @var{sourcebranch} and merges it +into a unique head of @var{destbranch}, as a directory. The resulting +revision is committed to @var{destbranch}. If either @var{sourcebranch} or address@hidden has multiple heads, @command{merge_into_dir} aborts, doing +nothing. + +The purpose of @command{merge_into_dir} is to permit a project to contain +another project in such a way that @command{propagate} can be used to keep +the contained project up-to-date. It is meant to replace the use of nested +checkouts in many circumstances. + +Note that @command{merge_into_dir} @emph{does not} permit changes made to the +contained project in @var{destbranch} to be propagated back to address@hidden Attempting this would lead to @var{sourcebranch} containing +both projects nested as in @var{destbranch} instead of only the project +originally in @var{sourcebranch}, which is almost certainly not what would be +intended. @end ftable @@ -4467,6 +4486,10 @@ Specifying only the pathname "." will restrict the search for known files to the current subdirectory of the workspace. address@hidden monotone show_conflicts @var{rev} @var{rev} + +This command shows what conflicts would need to be resolved in order to merge +the given revisions. @end ftable ============================================================ --- testsuite.at d67ef3223629641b02551a0a31cbf4644cd60d82 +++ testsuite.at a78067f72148532de9792e0923eccead9c1b8d2c @@ -784,3 +784,5 @@ m4_include(tests/t_pivot_root_revert.at) m4_include(tests/t_pivot_root_update.at) m4_include(tests/t_rosterify_root_suture.at) +m4_include(tests/t_show_conflicts.at) +m4_include(tests/t_merge_into_dir.at)