# HG changeset patch # User mpm@selenic.com # Date 1116462579 28800 # Node ID 7daef883134f1ef6ea976368a6313aa0e7047917 # Parent 0b0efe409d794cc263ba1b7933763df3eb23a6e5 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 diff --git a/mercurial/hg.py b/mercurial/hg.py --- a/mercurial/hg.py +++ b/mercurial/hg.py @@ -30,42 +30,6 @@ class filelog(revlog): 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 @@ class changelog(revlog): 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 @@ class localrepository: 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 @@ class localrepository: 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 @@ class localrepository: 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 @@ class localrepository: 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 @@ class localrepository: 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 @@ class localrepository: 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 @@ class localrepository: 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] diff --git a/mercurial/revlog.py b/mercurial/revlog.py --- a/mercurial/revlog.py +++ b/mercurial/revlog.py @@ -143,12 +143,6 @@ class revlog: 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 @@ class revlog: 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 @@ class revlog: 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