merge.
authorVadim Gelfer <vadim.gelfer@gmail.com>
Wed, 23 Aug 2006 08:42:21 -0700
changeset 3011 6e49bb42620b
parent 3010 06696f9c30c0 (current diff)
parent 2978 962b9c7df641 (diff)
child 3012 abcd6ae3cf5a
merge.
--- a/mercurial/merge.py	Tue Aug 22 17:01:24 2006 -0700
+++ b/mercurial/merge.py	Wed Aug 23 08:42:21 2006 -0700
@@ -70,13 +70,16 @@
     p1, p2 = pl[0], node
     pa = repo.changelog.ancestor(p1, p2)
 
+    # are we going backwards?
+    backwards = (pa == p2)
+
     # is there a linear path from p1 to p2?
     linear_path = (pa == p1 or pa == p2)
     if branchmerge and linear_path:
         raise util.Abort(_("there is nothing to merge, just use "
                            "'hg update' or look at 'hg heads'"))
 
-    if not overwrite and not linear_path and not branchmerge:
+    if not linear_path and not (overwrite or branchmerge):
         raise util.Abort(_("update spans branches, use 'hg merge' "
                            "or 'hg update -C' to lose changes"))
 
@@ -88,7 +91,7 @@
     m1n = repo.changelog.read(p1)[0]
     m2n = repo.changelog.read(p2)[0]
     man = repo.manifest.ancestor(m1n, m2n)
-    m1 = repo.manifest.read(m1n)
+    m1 = repo.manifest.read(m1n).copy()
     m2 = repo.manifest.read(m2n).copy()
     ma = repo.manifest.read(man)
 
@@ -107,27 +110,18 @@
     repo.ui.debug(_(" ancestor %s local %s remote %s\n") %
                   (short(man), short(m1n), short(m2n)))
 
-    merge = {}
-    get = {}
-    remove = []
+    action = {}
     forget = []
 
-    # construct a working dir manifest
-    mw = m1.copy()
+    # update m1 from working dir
     umap = dict.fromkeys(unknown)
 
     for f in added + modified + unknown:
-        mw[f] = ""
-        # is the wfile new and matches m2?
-        if (f not in m1 and f in m2 and
-            not repo.file(f).cmp(m2[f], repo.wread(f))):
-            mw[f] = m2[f]
-
-        mw.set(f, util.is_exec(repo.wjoin(f), mw.execf(f)))
+        m1[f] = m1.get(f, nullid) + "+"
+        m1.set(f, util.is_exec(repo.wjoin(f), m1.execf(f)))
 
     for f in deleted + removed:
-        if f in mw:
-            del mw[f]
+        del m1[f]
 
         # If we're jumping between revisions (as opposed to merging),
         # and if neither the working directory nor the target rev has
@@ -137,39 +131,44 @@
         if linear_path and f not in m2:
             forget.append(f)
 
+    if partial:
+        for f in m1.keys():
+            if not partial(f): del m1[f]
+        for f in m2.keys():
+            if not partial(f): del m2[f]
+
     # Compare manifests
-    for f, n in mw.iteritems():
-        if partial and not partial(f):
-            continue
+    for f, n in m1.iteritems():
         if f in m2:
-            s = 0
+            queued = 0
 
             # are files different?
             if n != m2[f]:
                 a = ma.get(f, nullid)
                 # are both different from the ancestor?
-                if n != a and m2[f] != a:
+                if not overwrite and n != a and m2[f] != a:
                     repo.ui.debug(_(" %s versions differ, resolve\n") % f)
-                    merge[f] = (fmerge(f, mw, m2, ma), m1.get(f, nullid), m2[f])
-                    s = 1
+                    action[f] = (fmerge(f, m1, m2, ma), n[:20], m2[f])
+                    queued = 1
                 # are we clobbering?
                 # is remote's version newer?
-                # or are we going back in time?
-                elif overwrite or m2[f] != a or (p2 == pa and mw[f] == m1[f]):
+                # or are we going back in time and clean?
+                elif overwrite or m2[f] != a or (backwards and not n[20:]):
                     repo.ui.debug(_(" remote %s is newer, get\n") % f)
-                    get[f] = (m2.execf(f), m2[f])
-                    s = 1
+                    action[f] = (m2.execf(f), m2[f], None)
+                    queued = 1
             elif f in umap or f in added:
                 # this unknown file is the same as the checkout
                 # we need to reset the dirstate if the file was added
-                get[f] = (m2.execf(f), m2[f])
+                action[f] = (m2.execf(f), m2[f], None)
 
-            if not s and mw.execf(f) != m2.execf(f):
+            # do we still need to look at mode bits?
+            if not queued and m1.execf(f) != m2.execf(f):
                 if overwrite:
                     repo.ui.debug(_(" updating permissions for %s\n") % f)
                     util.set_exec(repo.wjoin(f), m2.execf(f))
                 else:
-                    if fmerge(f, mw, m2, ma) != mw.execf(f):
+                    if fmerge(f, m1, m2, ma) != m1.execf(f):
                         repo.ui.debug(_(" updating permissions for %s\n")
                                       % f)
                         util.set_exec(repo.wjoin(f), mode)
@@ -177,61 +176,54 @@
         elif f in ma:
             if n != ma[f]:
                 r = _("d")
-                if not overwrite and (linear_path or branchmerge):
+                if not overwrite:
                     r = repo.ui.prompt(
                         (_(" local changed %s which remote deleted\n") % f) +
                          _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
                 if r == _("d"):
-                    remove.append(f)
+                    action[f] = (None, None, None)
             else:
                 repo.ui.debug(_("other deleted %s\n") % f)
-                remove.append(f) # other deleted it
+                action[f] = (None, None, None)
         else:
             # file is created on branch or in working directory
             if overwrite and f not in umap:
                 repo.ui.debug(_("remote deleted %s, clobbering\n") % f)
-                remove.append(f)
-            elif n == m1.get(f, nullid): # same as parent
-                if p2 == pa: # going backwards?
+                action[f] = (None, None, None)
+            elif not n[20:]: # same as parent
+                if backwards:
                     repo.ui.debug(_("remote deleted %s\n") % f)
-                    remove.append(f)
+                    action[f] = (None, None, None)
                 else:
                     repo.ui.debug(_("local modified %s, keeping\n") % f)
             else:
                 repo.ui.debug(_("working dir created %s, keeping\n") % f)
 
     for f, n in m2.iteritems():
-        if partial and not partial(f):
-            continue
         if f[0] == "/":
             continue
         if f in ma and n != ma[f]:
             r = _("k")
-            if not overwrite and (linear_path or branchmerge):
+            if not overwrite:
                 r = repo.ui.prompt(
                     (_("remote changed %s which local deleted\n") % f) +
                      _("(k)eep or (d)elete?"), _("[kd]"), _("k"))
             if r == _("k"):
-                get[f] = (m2.execf(f), n)
+                action[f] = (m2.execf(f), n, None)
         elif f not in ma:
             repo.ui.debug(_("remote created %s\n") % f)
-            get[f] = (m2.execf(f), n)
+            action[f] = (m2.execf(f), n, None)
         else:
-            if overwrite or p2 == pa: # going backwards?
+            if overwrite or backwards:
                 repo.ui.debug(_("local deleted %s, recreating\n") % f)
-                get[f] = (m2.execf(f), n)
+                action[f] = (m2.execf(f), n, None)
             else:
                 repo.ui.debug(_("local deleted %s\n") % f)
 
-    del mw, m1, m2, ma
+    del m1, m2, ma
 
     ### apply phase
 
-    if overwrite:
-        for f in merge:
-            get[f] = merge[f][:2]
-        merge = {}
-
     if linear_path or overwrite:
         # we don't need to do any magic, just jump to the new rev
         p1, p2 = p2, nullid
@@ -243,79 +235,75 @@
 
     repo.hook('preupdate', throw=True, parent1=xp1, parent2=xxp2)
 
-    # get the files we don't need to change
-    files = get.keys()
-    files.sort()
-    for f in files:
-        flag, node = get[f]
-        if f[0] == "/":
-            continue
-        repo.ui.note(_("getting %s\n") % f)
-        t = repo.file(f).read(node)
-        repo.wwrite(f, t)
-        util.set_exec(repo.wjoin(f), flag)
-
-    # merge the tricky bits
+    # update files
     unresolved = []
-    files = merge.keys()
+    updated, merged, removed = 0, 0, 0
+    files = action.keys()
     files.sort()
     for f in files:
-        repo.ui.status(_("merging %s\n") % f)
-        flag, my, other = merge[f]
-        ret = merge3(repo, f, my, other, xp1, xp2)
-        if ret:
-            unresolved.append(f)
-        util.set_exec(repo.wjoin(f), flag)
-
-    remove.sort()
-    for f in remove:
-        repo.ui.note(_("removing %s\n") % f)
-        util.audit_path(f)
-        try:
-            util.unlink(repo.wjoin(f))
-        except OSError, inst:
-            if inst.errno != errno.ENOENT:
-                repo.ui.warn(_("update failed to remove %s: %s!\n") %
-                             (f, inst.strerror))
+        flag, my, other = action[f]
+        if f[0] == "/":
+            continue
+        if not my:
+            repo.ui.note(_("removing %s\n") % f)
+            util.audit_path(f)
+            try:
+                util.unlink(repo.wjoin(f))
+            except OSError, inst:
+                if inst.errno != errno.ENOENT:
+                    repo.ui.warn(_("update failed to remove %s: %s!\n") %
+                                 (f, inst.strerror))
+            removed +=1
+        elif other:
+            repo.ui.status(_("merging %s\n") % f)
+            if merge3(repo, f, my, other, xp1, xp2):
+                unresolved.append(f)
+            util.set_exec(repo.wjoin(f), flag)
+            merged += 1
+        else:
+            repo.ui.note(_("getting %s\n") % f)
+            t = repo.file(f).read(my)
+            repo.wwrite(f, t)
+            util.set_exec(repo.wjoin(f), flag)
+            updated += 1
 
     # update dirstate
     if not partial:
         repo.dirstate.setparents(p1, p2)
         repo.dirstate.forget(forget)
-        if branchmerge:
-            repo.dirstate.update(remove, 'r')
-        else:
-            repo.dirstate.forget(remove)
-
-        files = get.keys()
-        files.sort()
-        for f in files:
-            if branchmerge:
-                repo.dirstate.update([f], 'n', st_mtime=-1)
-            else:
-                repo.dirstate.update([f], 'n')
-
-        files = merge.keys()
+        files = action.keys()
         files.sort()
         for f in files:
-            if branchmerge:
-                # We've done a branch merge, mark this file as merged
-                # so that we properly record the merger later
-                repo.dirstate.update([f], 'm')
+            flag, my, other = action[f]
+            if not my:
+                if branchmerge:
+                    repo.dirstate.update([f], 'r')
+                else:
+                    repo.dirstate.forget([f])
+            elif not other:
+                if branchmerge:
+                    repo.dirstate.update([f], 'n', st_mtime=-1)
+                else:
+                    repo.dirstate.update([f], 'n')
             else:
-                # We've update-merged a locally modified file, so
-                # we set the dirstate to emulate a normal checkout
-                # of that file some time in the past. Thus our
-                # merge will appear as a normal local file
-                # modification.
-                fl = repo.file(f)
-                f_len = fl.size(fl.rev(other))
-                repo.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
+                if branchmerge:
+                    # We've done a branch merge, mark this file as merged
+                    # so that we properly record the merger later
+                    repo.dirstate.update([f], 'm')
+                else:
+                    # We've update-merged a locally modified file, so
+                    # we set the dirstate to emulate a normal checkout
+                    # of that file some time in the past. Thus our
+                    # merge will appear as a normal local file
+                    # modification.
+                    fl = repo.file(f)
+                    f_len = fl.size(fl.rev(other))
+                    repo.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
 
     if show_stats:
-        stats = ((len(get), _("updated")),
-                 (len(merge) - len(unresolved), _("merged")),
-                 (len(remove), _("removed")),
+        stats = ((updated, _("updated")),
+                 (merged - len(unresolved), _("merged")),
+                 (removed, _("removed")),
                  (len(unresolved), _("unresolved")))
         note = ", ".join([_("%d files %s") % s for s in stats])
         repo.ui.status("%s\n" % note)
--- a/tests/test-merge1.out	Tue Aug 22 17:01:24 2006 -0700
+++ b/tests/test-merge1.out	Wed Aug 23 08:42:21 2006 -0700
@@ -1,6 +1,8 @@
 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
 %% no merges expected
-1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+merging for b
+merging b
+0 files updated, 1 files merged, 0 files removed, 0 files unresolved
 (branch merge, don't forget to commit)
 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
 %% merge should fail
--- a/tests/test-up-local-change.out	Tue Aug 22 17:01:24 2006 -0700
+++ b/tests/test-up-local-change.out	Wed Aug 23 08:42:21 2006 -0700
@@ -19,10 +19,10 @@
  ancestor a0c8bcbbb45c local a0c8bcbbb45c remote 1165e8bd193e
  a versions differ, resolve
 remote created b
-getting b
 merging a
 resolving a
 file a: my b789fdd96dc2 other d730145abbf9 ancestor b789fdd96dc2
+getting b
 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
 changeset:   1:802f095af299
 tag:         tip
@@ -53,10 +53,10 @@
  ancestor a0c8bcbbb45c local a0c8bcbbb45c remote 1165e8bd193e
  a versions differ, resolve
 remote created b
-getting b
 merging a
 resolving a
 file a: my b789fdd96dc2 other d730145abbf9 ancestor b789fdd96dc2
+getting b
 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
 changeset:   1:802f095af299
 tag:         tip
@@ -137,4 +137,5 @@
 adding manifests
 adding file changes
 added 1 changesets with 1 changes to 1 files
-1 files updated, 0 files merged, 0 files removed, 0 files unresolved
+merging a
+0 files updated, 1 files merged, 0 files removed, 0 files unresolved