changeset 94:7daef883134f

Refactor merge code Delete old code Fix calculation of newer nodes on server Fix branch recursion on client Fix manifest merge problems Add more debugging and note messages to merge
author mpm@selenic.com
date Wed, 18 May 2005 16:29:39 -0800
parents 0b0efe409d79
children 589f507bb259
files mercurial/hg.py mercurial/revlog.py
diffstat 2 files changed, 37 insertions(+), 210 deletions(-) [+]
line wrap: on
line diff
--- a/mercurial/hg.py	Wed May 18 15:24:18 2005 -0800
+++ b/mercurial/hg.py	Wed May 18 16:29:39 2005 -0800
@@ -30,42 +30,6 @@
     def add(self, text, transaction, link, p1=None, p2=None):
         return self.addrevision(text, transaction, link, p1, p2)
 
-    def resolvedag(self, old, new, transaction, link):
-        """resolve unmerged heads in our DAG"""
-        if old == new: return None
-        a = self.ancestor(old, new)
-        if old == a: return None
-        return self.merge3(old, new, a, transaction, link)
-
-    def merge3(self, my, other, base, transaction, link):
-        """perform a 3-way merge and append the result"""
-        def temp(prefix, node):
-            (fd, name) = tempfile.mkstemp(prefix)
-            f = os.fdopen(fd, "w")
-            f.write(self.revision(node))
-            f.close()
-            return name
-
-        a = temp("local", my)
-        b = temp("remote", other)
-        c = temp("parent", base)
-
-        cmd = os.environ["HGMERGE"]
-        r = os.system("%s %s %s %s" % (cmd, a, b, c))
-        if r:
-            raise "Merge failed, implement rollback!"
-
-        t = open(a).read()
-        os.unlink(a)
-        os.unlink(b)
-        os.unlink(c)
-        return self.addrevision(t, transaction, link, my, other)
-
-    def merge(self, other, transaction, linkseq, link):
-        """perform a merge and resolve resulting heads"""
-        (o, n) = self.mergedag(other, transaction, linkseq)
-        return self.resolvedag(o, n, transaction, link)
-
     def annotate(self, node):
         revs = []
         while node != nullid:
@@ -160,9 +124,6 @@
         text = "\n".join(l)
         return self.addrevision(text, transaction, self.count(), p1, p2)
 
-    def merge3(self, my, other, base):
-        pass
-
 class dircache:
     def __init__(self, opener, ui):
         self.opener = opener
@@ -335,122 +296,6 @@
     def transaction(self):
         return transaction(self.opener, self.join("journal"))
 
-    def merge(self, other):
-        tr = self.transaction()
-        changed = {}
-        new = {}
-        seqrev = self.changelog.count()
-        # some magic to allow fiddling in nested scope
-        nextrev = [seqrev]
-
-        # helpers for back-linking file revisions to local changeset
-        # revisions so we can immediately get to changeset from annotate
-        def accumulate(text):
-            # track which files are added in which changeset and the
-            # corresponding _local_ changeset revision
-            files = self.changelog.extract(text)[3]
-            for f in files:
-                changed.setdefault(f, []).append(nextrev[0])
-            nextrev[0] += 1
-
-        def seq(start):
-            while 1:
-                yield start
-                start += 1
-
-        def lseq(l):
-            for r in l:
-                yield r
-
-        # begin the import/merge of changesets
-        self.ui.status("merging new changesets\n")
-        (co, cn) = self.changelog.mergedag(other.changelog, tr,
-                                           seq(seqrev), accumulate)
-        resolverev = self.changelog.count()
-
-        # is there anything to do?
-        if co == cn:
-            tr.close()
-            return
-        
-        # do we need to resolve?
-        simple = (co == self.changelog.ancestor(co, cn))
-
-        # merge all files changed by the changesets,
-        # keeping track of the new tips
-        changelist = changed.keys()
-        changelist.sort()
-        for f in changelist:
-            sys.stdout.write(".")
-            sys.stdout.flush()
-            r = self.file(f)
-            node = r.merge(other.file(f), tr, lseq(changed[f]), resolverev)
-            if node:
-                new[f] = node
-        sys.stdout.write("\n")
-
-        # begin the merge of the manifest
-        self.ui.status("merging manifests\n")
-        (mm, mo) = self.manifest.mergedag(other.manifest, tr, seq(seqrev))
-
-        # For simple merges, we don't need to resolve manifests or changesets
-        if simple:
-            tr.close()
-            return
-
-        ma = self.manifest.ancestor(mm, mo)
-
-        # resolve the manifest to point to all the merged files
-        self.ui.status("resolving manifests\n")
-        omap = self.manifest.read(mo) # other
-        amap = self.manifest.read(ma) # ancestor
-        mmap = self.manifest.read(mm) # mine
-        nmap = {}
-
-        for f, mid in mmap.iteritems():
-            if f in omap:
-                if mid != omap[f]: 
-                    nmap[f] = new.get(f, mid) # use merged version
-                else:
-                    nmap[f] = new.get(f, mid) # they're the same
-                del omap[f]
-            elif f in amap:
-                if mid != amap[f]: 
-                    pass # we should prompt here
-                else:
-                    pass # other deleted it
-            else:
-                nmap[f] = new.get(f, mid) # we created it
-                
-        del mmap
-
-        for f, oid in omap.iteritems():
-            if f in amap:
-                if oid != amap[f]:
-                    pass # this is the nasty case, we should prompt
-                else:
-                    pass # probably safe
-            else:
-                nmap[f] = new.get(f, oid) # remote created it
-
-        del omap
-        del amap
-
-        node = self.manifest.add(nmap, tr, resolverev, mm, mo)
-
-        # Now all files and manifests are merged, we add the changed files
-        # and manifest id to the changelog
-        self.ui.status("committing merge changeset\n")
-        new = new.keys()
-        new.sort()
-        if co == cn: cn = -1
-
-        edittext = "\n"+"".join(["HG: changed %s\n" % f for f in new])
-        edittext = self.ui.edit(edittext)
-        n = self.changelog.add(node, new, edittext, tr, co, cn)
-
-        tr.close()
-
     def commit(self, parent, update = None, text = ""):
         tr = self.transaction()
         
@@ -640,19 +485,26 @@
     def newer(self, nodes):
         m = {}
         nl = []
+        pm = {}
         cl = self.changelog
         t = l = cl.count()
+
+        # find the lowest numbered node
         for n in nodes:
             l = min(l, cl.rev(n))
-            for p in cl.parents(n):
-                m[p] = 1
+            m[n] = 1
 
         for i in xrange(l, t):
             n = cl.node(i)
+            if n in m: # explicitly listed
+                pm[n] = 1
+                nl.append(n)
+                continue
             for p in cl.parents(n):
-                if p in m and n not in m:
-                    m[n] = 1
+                if p in pm: # parent listed
+                    pm[n] = 1
                     nl.append(n)
+                    break
 
         return nl
 
@@ -676,13 +528,14 @@
                 self.ui.debug("found incomplete branch %s\n" % short(n[1]))
                 search.append(n) # schedule branch range for scanning
             else:
+                if n[2] in m and n[3] in m:
+                    if n[1] not in fetch:
+                        self.ui.debug("found new changeset %s\n" %
+                                      short(n[1]))
+                        fetch.append(n[1]) # earliest unknown
+                        continue
                 for b in remote.branches([n[2], n[3]]):
-                    if b[0] in m:
-                        if n[1] not in fetch:
-                            self.ui.debug("found new changeset %s\n" %
-                                          short(n[1]))
-                            fetch.append(n[1]) # earliest unknown
-                    else:
+                    if b[0] not in m:
                         unknown.append(b)
   
         while search:
@@ -707,7 +560,7 @@
             if f in m:
                 raise "already have", short(f[:4])
 
-        self.ui.note("merging new changesets starting at " +
+        self.ui.note("adding new changesets starting at " +
                      " ".join([short(f) for f in fetch]) + "\n")
 
         return remote.changegroup(fetch)
@@ -767,13 +620,17 @@
         tr = self.transaction()
         simple = True
 
-        self.ui.status("merging changesets\n")
+        self.ui.status("adding changesets\n")
         # pull off the changeset group
+        def report(x):
+            self.ui.debug("add changeset %s\n" % short(x))
+            return self.changelog.count()
+            
         csg = getchunk()
         co = self.changelog.tip()
-        cn = self.changelog.addgroup(csg, lambda x: self.changelog.count(), tr)
+        cn = self.changelog.addgroup(csg, report, tr)
 
-        self.ui.status("merging manifests\n")
+        self.ui.status("adding manifests\n")
         # pull off the manifest group
         mfg = getchunk()
         mm = self.manifest.tip()
@@ -785,21 +642,21 @@
             resolverev = self.changelog.count()
 
         # process the files
-        self.ui.status("merging files\n")
+        self.ui.status("adding files\n")
         new = {}
         while 1:
             f = getchunk(4)
             if not f: break
             fg = getchunk()
-
+            self.ui.debug("adding %s revisions\n" % f)
             fl = self.file(f)
             o = fl.tip()
             n = fl.addgroup(fg, lambda x: self.changelog.rev(x), tr)
             if not simple:
-                nn = fl.resolvedag(o, n, tr, resolverev)
-                if nn:
-                    self.ui.note("merged %s\n", f)
-                    new[f] = nn
+                if o == n: continue
+                # this file has changed between branches, so it must be
+                # represented in the merge changeset
+                new[f] = self.merge3(fl, f, o, n, tr, resolverev)
 
         # For simple merges, we don't need to resolve manifests or changesets
         if simple:
@@ -821,8 +678,9 @@
             if f in omap:
                 if mid != omap[f]:
                     self.ui.debug("%s versions differ\n" % f)
-                    if f in new: self.ui.note("%s updated in resolve\n" % f)
-                    nmap[f] = new.get(f, mid) # use merged version
+                    if f in new: self.ui.debug("%s updated in resolve\n" % f)
+                    # use merged version or local version
+                    nmap[f] = new.get(f, mid)
                 else:
                     nmap[f] = mid # keep ours
                 del omap[f]
--- a/mercurial/revlog.py	Wed May 18 15:24:18 2005 -0800
+++ b/mercurial/revlog.py	Wed May 18 16:29:39 2005 -0800
@@ -143,12 +143,6 @@
                 
         return None
 
-    def revisions(self, list):
-        # this can be optimized to do spans, etc
-        # be stupid for now
-        for node in list:
-            yield self.revision(node)
-
     def diff(self, a, b):
         return mdiff.textdiff(a, b)
 
@@ -272,34 +266,9 @@
 
         return nullid
 
-    def mergedag(self, other, transaction, linkseq, accumulate = None):
-        """combine the nodes from other's DAG into ours"""
-        old = self.tip()
-        i = self.count()
-        l = []
-
-        # merge the other revision log into our DAG
-        for r in range(other.count()):
-            id = other.node(r)
-            if id not in self.nodemap:
-                (xn, yn) = other.parents(id)
-                l.append((id, xn, yn))
-                self.nodemap[id] = i
-                i += 1
-
-        # merge node date for new nodes
-        r = other.revisions([e[0] for e in l])
-        for e in l:
-            t = r.next()
-            if accumulate: accumulate(t)
-            self.addrevision(t, transaction, linkseq.next(), e[1], e[2])
-
-        # return the unmerged heads for later resolving
-        return (old, self.tip())
-
     def group(self, linkmap):
         # given a list of changeset revs, return a set of deltas and
-        # metadata corresponding to nodes the first delta is
+        # metadata corresponding to nodes. the first delta is
         # parent(nodes[0]) -> nodes[0] the receiver is guaranteed to
         # have this parent as it has all history before these
         # changesets. parent is parent[0]
@@ -440,9 +409,9 @@
         while pos < len(data):
             l, node, p1, p2, cs = struct.unpack(">l20s20s20s20s",
                                                 data[pos:pos+84])
+            link = linkmapper(cs)
             if node in self.nodemap:
                 raise "already have %s" % hex(node[:4])
-            link = linkmapper(cs)
             delta = data[pos + 84:pos + l]
             pos += l