# # # delete "tests/automate_show_conflicts_options/expected-1.stdout" # # delete "tests/automate_show_conflicts_options/expected-2.stdout" # # rename "tests/automate_show_conflicts_options" # to "tests/automate_show_conflicts_defaults" # # patch "cmd_merging.cc" # from [12f8cc6f507023f1ad3d8e38c75a8eea7d1c9405] # to [7799e4b0ea0bdcf546fd4e25b269bb8757288633] # # patch "monotone.texi" # from [2761e46dbfa42bf2386f7e166dedb22759d4e25b] # to [818ad1e8a4851b967a44ed7059021a5d8660d129] # # patch "tests/automate_show_conflicts_defaults/__driver__.lua" # from [ec85dcf4237ebbfac06f4b9fdd2c6ae61e8f9775] # to [7df4a89ca990f60c35d01b30a6e1845b787e97e1] # # patch "tests/common/test_utils_inventory.lua" # from [d0343ce781bb1284387d194b7a3e50d2b7a52c0d] # to [1c4722f9a1a0ee2f1d2e7ef715fdf22c13c77837] # ============================================================ --- cmd_merging.cc 12f8cc6f507023f1ad3d8e38c75a8eea7d1c9405 +++ cmd_merging.cc 7799e4b0ea0bdcf546fd4e25b269bb8757288633 @@ -367,6 +367,49 @@ merge_two(options & opts, lua_hooks & lu P(F("[merged] %s") % merged); } +typedef std::pair revpair; +typedef set::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 const heads) +{ + I(heads.size() > 2); + map heads_for_ancestor; + set 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 @@ -380,9 +423,6 @@ CMD(merge, "merge", "", CMD_REF(tree), " key_store keys(app); project_t project(db); - typedef std::pair revpair; - typedef set::const_iterator rid_set_iter; - if (args.size() != 0) throw usage(execid); @@ -406,8 +446,6 @@ CMD(merge, "merge", "", CMD_REF(tree), " // avoid failure after lots of work cache_user_key(app.opts, app.lua, db, keys); - map heads_for_ancestor; - set ancestors; size_t pass = 1, todo = heads.size() - 1; // If there are more than two heads to be merged, on each iteration we @@ -427,43 +465,12 @@ CMD(merge, "merge", "", CMD_REF(tree), " P(F("merge %d / %d:") % pass % todo); P(F("calculating best pair of heads to merge next")); - // 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; + revpair p = find_heads_to_merge(db, heads); - 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 and merge its - // corresponding pair of heads. - revpair p = heads_for_ancestor[*ancestors.begin()]; - merge_two(app.opts, app.lua, project, keys, p.first, p.second, app.opts.branchname, string("merge"), std::cout, false); - ancestors.clear(); - heads_for_ancestor.clear(); project.get_branch_heads(app.opts.branchname, heads, app.opts.ignore_suspend_certs); pass++; @@ -948,11 +955,19 @@ CMD_AUTOMATE(show_conflicts, N_("[LEFT_R N(heads.size() >= 2, F("branch '%s' has %d heads; must be at least 2 for show_conflicts") % app.opts.branchname % heads.size()); - // FIXME: factor out head choosing algorithm from merge above - set::const_iterator i = heads.begin(); - l_id = *i; - ++i; - r_id = *i; + if (heads.size() == 2) + { + set::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) { ============================================================ --- monotone.texi 2761e46dbfa42bf2386f7e166dedb22759d4e25b +++ monotone.texi 818ad1e8a4851b967a44ed7059021a5d8660d129 @@ -8528,16 +8528,15 @@ @section Automation @end table address@hidden mtn automate show_conflicts @var{[left_rev right_rev]} address@hidden mtn automate show_conflicts address@hidden right_rev}] @table @strong @item Arguments: Optional left and right revision ids. -If no revs are given, @var{left_rev} and @var{right_rev} default to -the first two heads that would be chosen by the @command{merge} -command. +If no revs are given, they default to the first two heads that would +be chosen by the @command{merge} command. @item Added in: @@ -8547,14 +8546,14 @@ @section Automation Show all conflicts between two revisions. -This is intented to be used when an attempted merge has failed due to -conflicts; an external tool can guide the user thru resolving each -conflict in turn, then redo the original command. +This is intended to be used before a @command{merge}; an external tool +can guide the user thru resolving each conflict in turn, then do the +merge. For more information on conflicts, @ref{Merge Conflicts}. -Note that this does not show conflicts due to update commands, since -in that case one revision is the workspace. +Note that this cannot be used to show conflicts that would occur in an address@hidden, since in that case one revision is the workspace. @item Sample output: @@ -8765,19 +8764,16 @@ @section Automation right_name "" @end verbatim address@hidden group address@hidden smallexample - @item Output format: -First the revision ids of the left and right revisions are printed in -one basic_io stanza. +First the revision ids of the left and right revisions, and their +common ancestor, are printed in one basic_io stanza. Then each conflict is listed in a basic_io stanza. Stanzas are separated by blank lines. Each conflict stanza starts with a @code{conflict} line, and contains -up to FIXME: lines. The order of the lines is not important, and may +up to eleven lines. The order of the lines is not important, and may change in future revisions, except that the first line will always be @code{conflict}. @@ -8787,12 +8783,9 @@ @section Automation @item Error conditions: -If the revision IDs are gvien, but either is unknown or +If the revision IDs are given, but either is 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. - @end table @end ftable ============================================================ --- tests/automate_show_conflicts_defaults/__driver__.lua ec85dcf4237ebbfac06f4b9fdd2c6ae61e8f9775 +++ tests/automate_show_conflicts_defaults/__driver__.lua 7df4a89ca990f60c35d01b30a6e1845b787e97e1 @@ -1,44 +1,57 @@ --- Test 'automate show_conflicts' options and arguments +-- Test 'automate show_conflicts' argument defaults -- --- options and arguments determine between which revisions conflicts are shown --- -- See automate_show_conflicts for all conflict cases mtn_setup() +include ("common/test_utils_inventory.lua") check(get("expected-1.stdout")) check(get("expected-2.stdout")) --- Get a non-empty base revision, then create conflicts +-- Get a non-empty base revision, then create three heads addfile("randomfile", "blah blah blah") -commit() -base = base_revision() +commit("testbranch", "base_1") +base_1 = base_revision() -- Abe adds conflict files addfile("checkout.sh", "checkout.sh abe 1") addfile("thermostat.c", "thermostat westinghouse") -commit() +commit("testbranch", "abe_1") abe_1 = base_revision() -revert_to(base) +revert_to(base_1) +-- Beth adds non-conflict files +addfile("user_guide.text", "really cool stuff") +commit("testbranch", "base_2") +base_2 = base_revision() + -- Beth adds conflict files addfile("checkout.sh", "checkout.sh beth 1") -addfile("thermostat.c", "thermostat honeywell") -commit() +addfile("thermostat.c", "thermostat honeywell beth") +commit("testbranch", "beth_1") beth_1 = base_revision() --- No options or args; must be two heads (same as typical merge case) -check(mtn("automate", "show_conflicts"), 0, true, false) -canonicalize("stdout") -check(readfile("expected-1.stdout") == readfile("stdout")) +revert_to(base_2) --- Now specify revisions, in an order that reverses left/right from --- the previous, to show the arguments are used. +-- Chuck adds conflict files +addfile("checkout.sh", "checkout.sh beth 1") +addfile("thermostat.c", "thermostat honeywell chuck") +commit("testbranch", "chuck_1") +chuck_1 = base_revision() -check(mtn("automate", "show_conflicts", beth_1, abe_1), 0, true, false) -canonicalize("stdout") -check(readfile("expected-2.stdout") == readfile("stdout")) +-- Check that 'automate show_conflicts' picks the same heads to +-- compare first as 'merge' does +check(mtn("merge"), 1, false, false) +-- mtn: [left] 19ab79c40805c9dc5e25d0b6fa5134291e0b42d9 = beth_1 +-- mtn: [right] d8a8bc9623c1ff9c0a5c082e40f0ff8ec6b43e72 = chuck_1 +check(mtn("automate", "show_conflicts"), 0, true, false) +parsed = parse_basic_io(readfile("stdout")) + +check_basic_io_line (1, parsed[1], "left", beth_1) +check_basic_io_line (2, parsed[2], "right", chuck_1) +check_basic_io_line (3, parsed[3], "ancestor", base_2) + -- end of file ============================================================ --- tests/common/test_utils_inventory.lua d0343ce781bb1284387d194b7a3e50d2b7a52c0d +++ tests/common/test_utils_inventory.lua 1c4722f9a1a0ee2f1d2e7ef715fdf22c13c77837 @@ -49,12 +49,13 @@ function xfail_inventory (parsed, parsed function xfail_inventory (parsed, parsed_index, stanza) return check_inventory(parsed, parsed_index, stanza, true) -end -- check_inventory +end function check_inventory (parsed, parsed_index, stanza, xfail) -- 'stanza' is a table for one stanza -- 'parsed_index' gives the first index for this stanza in 'parsed' -- (which should be the output of parse_basic_io). +-- Compare 'stanza' to 'parsed'; fail if different. -- Returns parsed_index incremented to the next index to check. -- we assume that any test failure is not an expected failure if not