From patchwork Mon Apr 7 00:19:02 2014 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit Subject: [6, of, 6, bid, merge, v2, RFC] merge: take bids for merge actions for different ancestors and pick the best From: Mads Kiilerich X-Patchwork-Id: 4240 Message-Id: <29b10f9b64e6efc8830f.1396829942@localhost.localdomain> To: mercurial-devel@selenic.com Date: Mon, 07 Apr 2014 02:19:02 +0200 # HG changeset patch # User Mads Kiilerich # Date 1393728157 -3600 # Sun Mar 02 03:42:37 2014 +0100 # Node ID 29b10f9b64e6efc8830fd68b2bf3f32d1f64e472 # Parent b8eaf05d1ce2dcacbfcf0168053568cd539f561c [RFC] merge: take bids for merge actions for different ancestors and pick the best The basic idea is to do the merge planning with all the available ancestors, consider the resulting actions as "bids" and make an "auction" and automatically pick the most favourable action for each file. This just implements the basic functionality and do just consider "keep" and "get" actions. The heuristics for picking the best action can be tweaked later on. The code path for merging with a single ancestor is not changed. Only the special case that can benefit from it will collect bids and make an auction. diff --git a/mercurial/merge.py b/mercurial/merge.py --- a/mercurial/merge.py +++ b/mercurial/merge.py @@ -723,15 +723,64 @@ def calculateupdates(repo, wctx, mctx, a acceptremote, followcopies): "Calculate the actions needed to merge mctx into wctx using ancestors" - ancestor = ancestors[0] + # Call for bids + fbids = {} # mapping filename to list af action bids + for ancestor in ancestors: + if len(ancestors) > 1: + repo.ui.note(_('calculating bids for ancestor %s\n') % ancestor) + actions = manifestmerge(repo, wctx, mctx, ancestor, branchmerge, force, + partial, acceptremote, followcopies) + if len(ancestors) > 1: + for a in sorted(actions): + repo.ui.debug(' %s: %s\n' % (a[0], a[1])) + f = a[0] + if f in fbids: + fbids[f].append(a) + else: + fbids[f] = [a] + + # Pick the best bid for each file if len(ancestors) > 1: - ancestor = wctx.ancestor(mctx) - assert ancestor in ancestors - - actions = manifestmerge(repo, wctx, mctx, - ancestor, - branchmerge, force, - partial, acceptremote, followcopies) + repo.ui.note(_('merging merge bids\n')) + actions = [] + for f, bidsl in sorted(fbids.items()): + # Consensus? + a0 = bidsl[0] + if util.all(a == a0 for a in bidsl[1:]): # len(bidsl) is > 1 + repo.ui.note(" %s: consensus for %s\n" % (f, a0[1])) + actions.append(a0) + continue + # Group bids by kind of action + bids = {} + for a in bidsl: + m = a[1] + if m in bids: + bids[m].append(a) + else: + bids[m] = [a] + # If keep is an option, just do it. + if "k" in bids: + repo.ui.note(" %s: picking 'keep' action\n" % f) + actions.append(bids["k"][0]) + continue + # If all gets agree [how could they not?], just do it. + if "g" in bids: + ga0 = bids["g"][0] + if util.all(a == ga0 for a in bids["g"][1:]): + repo.ui.note(" %s: picking 'get' action\n" % f) + actions.append(ga0) + continue + # TODO: Consider other simple actions such as mode changes + # Handle inefficient democrazy. + repo.ui.note(_(' %s: multiple merge bids:\n') % (f, m)) + for a in bidsl: + repo.ui.note(' %s: %s\n' % (f, a[1])) + # Pick random action. TODO: Instead, prompt user when resolving + a0 = bidsl[0] + repo.ui.warn(_(' %s: ambiguous merge - picked %s action)\n') % + (f, a0[1])) + actions.append(a0) + continue # Filter out prompts. newactions, prompts = [], [] diff --git a/tests/test-merge-criss-cross.t b/tests/test-merge-criss-cross.t --- a/tests/test-merge-criss-cross.t +++ b/tests/test-merge-criss-cross.t @@ -72,22 +72,29 @@ Criss cross merging $ hg merge -v --debug --tool internal:dump 5 + calculating bids for ancestor 0f6b37dbe527 searching for copies back to rev 3 resolving manifests branchmerge: True, force: False, partial: False ancestor: 0f6b37dbe527, local: 3b08d01b0ab5+, remote: adfe50279922 + f1: g + f2: m + calculating bids for ancestor 40663881a6dd + searching for copies back to rev 3 + resolving manifests + branchmerge: True, force: False, partial: False + ancestor: 40663881a6dd, local: 3b08d01b0ab5+, remote: adfe50279922 + f1: m + f2: k + merging merge bids + f1: picking 'get' action + f2: picking 'keep' action f1: remote is newer -> g - f2: versions differ -> m - preserving f2 for resolve of f2 + f2: keep -> k getting f1 - updating: f1 1/2 files (50.00%) - updating: f2 2/2 files (100.00%) - picked tool 'internal:dump' for f2 (binary False symlink False) - merging f2 - my f2@3b08d01b0ab5+ other f2@adfe50279922 ancestor f2@40494bf2444c - 1 files updated, 0 files merged, 0 files removed, 1 files unresolved - use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon - [1] + updating: f1 1/1 files (100.00%) + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) $ head * ==> f1 <== @@ -95,17 +102,88 @@ Criss cross merging ==> f2 <== 6 second change + +The other way around: + + $ hg up -C -r5 + 2 files updated, 0 files merged, 0 files removed, 0 files unresolved + $ hg merge -v --debug + calculating bids for ancestor 0f6b37dbe527 + searching for copies back to rev 3 + resolving manifests + branchmerge: True, force: False, partial: False + ancestor: 0f6b37dbe527, local: adfe50279922+, remote: 3b08d01b0ab5 + f1: k + f2: m + calculating bids for ancestor 40663881a6dd + searching for copies back to rev 3 + resolving manifests + branchmerge: True, force: False, partial: False + ancestor: 40663881a6dd, local: adfe50279922+, remote: 3b08d01b0ab5 + f1: m + f2: g + merging merge bids + f1: picking 'keep' action + f2: picking 'get' action + f1: keep -> k + f2: remote is newer -> g + getting f2 + updating: f2 1/1 files (100.00%) + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + + $ head * + ==> f1 <== + 5 second change - ==> f2.base <== - 0 base - - ==> f2.local <== + ==> f2 <== 6 second change - - ==> f2.orig <== - 6 second change - - ==> f2.other <== - 2 first change + +Verify how the output looks and and how verbose it is: + + $ hg up -qC + $ hg merge + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + + $ hg up -qC + $ hg merge -v + calculating bids for ancestor 0f6b37dbe527 + resolving manifests + calculating bids for ancestor 40663881a6dd + resolving manifests + merging merge bids + f1: picking 'get' action + f2: picking 'keep' action + getting f1 + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + + $ hg up -qC + $ hg merge -v --debug + calculating bids for ancestor 0f6b37dbe527 + searching for copies back to rev 3 + resolving manifests + branchmerge: True, force: False, partial: False + ancestor: 0f6b37dbe527, local: 3b08d01b0ab5+, remote: adfe50279922 + f1: g + f2: m + calculating bids for ancestor 40663881a6dd + searching for copies back to rev 3 + resolving manifests + branchmerge: True, force: False, partial: False + ancestor: 40663881a6dd, local: 3b08d01b0ab5+, remote: adfe50279922 + f1: m + f2: k + merging merge bids + f1: picking 'get' action + f2: picking 'keep' action + f1: remote is newer -> g + f2: keep -> k + getting f1 + updating: f1 1/1 files (100.00%) + 1 files updated, 0 files merged, 0 files removed, 0 files unresolved + (branch merge, don't forget to commit) + $ cd ..