# HG changeset patch # User mpm@selenic.com # Date 1117040094 28800 # Node ID 083c38bdfa6468113b31a7def64e89b00fbd6551 # Parent 1f6c61a6058644f4b51e84f81b026cb7a05fc958# Parent 1d5f799ebe1e5e7defd09b8a373caff009d058a4 Merge from hgweb diff --git a/hg b/hg --- a/hg +++ b/hg @@ -23,23 +23,22 @@ def help(): print """\ commands: - init create a new repository in this directory + add [files...] add the given files in the next commit + addremove add all new files, delete all missing files + annotate [files...] show changeset number per file line branch create a branch of in this directory - merge merge changes from into local repository checkout [changeset] checkout the latest or given changeset - status show new, missing, and changed files in working dir - add [files...] add the given files in the next commit - remove [files...] remove the given files in the next commit - addremove add all new files, delete all missing files commit commit all changes to the repository - history show changeset history - log show revision history of a single file + diff [files...] diff working directory (or selected files) dump [rev] dump the latest or given revision of a file dumpmanifest [rev] dump the latest or given revision of the manifest - diff [files...] diff working directory (or selected files) + history show changeset history + init create a new repository in this directory + log show revision history of a single file + merge merge changes from into local repository + remove [files...] remove the given files in the next commit + status show new, missing, and changed files in working dir tags show current changeset tags - annotate [files...] show changeset number per file line - blame [files...] show commit user per file line """ def filterfiles(list, files): @@ -215,47 +214,57 @@ elif cmd == "diff": diff(args, *revs) elif cmd == "annotate": + bcache = {} + + def getnode(rev): + return hg.short(repo.changelog.node(rev)) + + def getname(rev): + try: + return bcache[rev] + except KeyError: + cl = repo.changelog.read(repo.changelog.node(rev)) + name = cl[1] + f = name.find('@') + if f >= 0: + name = name[:f] + bcache[rev] = name + return name + aoptions = {} - opts = [('r', 'revision', '', 'revision')] + opts = [('r', 'revision', '', 'revision'), + ('u', 'user', None, 'show user'), + ('n', 'number', None, 'show revision number'), + ('c', 'changeset', None, 'show changeset')] + args = fancyopts.fancyopts(args, opts, aoptions, - 'hg annotate [-r id] [files]') + 'hg annotate [-u] [-c] [-n] [-r id] [files]') + + opmap = [['user', getname], ['number', str], ['changeset', getnode]] + if not aoptions['user'] and not aoptions['changeset']: + aoptions['number'] = 1 if args: if relpath: args = [ os.path.join(relpath, x) for x in args ] - node = repo.current if aoptions['revision']: node = repo.changelog.lookup(aoptions['revision']) change = repo.changelog.read(node) mmap = repo.manifest.read(change[0]) + maxuserlen = 0 + maxchangelen = 0 for f in args: - for n, l in repo.file(f).annotate(mmap[f]): - sys.stdout.write("% 6s:%s"%(n, l)) + lines = repo.file(f).annotate(mmap[f]) + pieces = [] -elif cmd == "blame": - aoptions = {} - opts = [('r', 'revision', '', 'revision')] - args = fancyopts.fancyopts(args, opts, aoptions, - 'hg blame [-r id] [files]') - if args: - bcache = {} - node = repo.current - if aoptions['revision']: - node = repo.changelog.lookup(aoptions['revision']) - change = repo.changelog.read(node) - mmap = repo.manifest.read(change[0]) - for f in args: - for n, l in repo.file(f).annotate(mmap[f]): - try: - name = bcache[n] - except KeyError: - cl = repo.changelog.read(repo.changelog.node(n)) - name = cl[1] - f = name.find('@') - if f >= 0: - name = name[:f] - bcache[n] = name - sys.stdout.write("% 10s:%s"%(name, l)) + for o, f in opmap: + if aoptions[o]: + l = [ f(n) for n,t in lines ] + m = max(map(len, l)) + pieces.append([ "%*s" % (m, x) for x in l]) + + for p,l in zip(zip(*pieces), lines): + sys.stdout.write(" ".join(p) + ": " + l[1]) elif cmd == "export": node = repo.lookup(args[0]) @@ -431,7 +440,7 @@ elif cmd == "verify": errors += 1 try: changes = repo.changelog.read(n) - except Error, inst: + except Exception, inst: ui.warn("unpacking changeset %s: %s\n" % (short(n), inst)) errors += 1 @@ -508,8 +517,8 @@ elif cmd == "verify": # verify contents try: t = fl.read(n) - except Error, inst: - ui.warn("unpacking file %s %s: %s\n" % (f, short(n), inst)) + except Exception, inst: + ui.warn("unpacking file %s %s: %s\n" % (f, hg.short(n), inst)) errors += 1 # verify parents diff --git a/mercurial/hg.py b/mercurial/hg.py --- a/mercurial/hg.py +++ b/mercurial/hg.py @@ -298,8 +298,6 @@ class localrepository: self.join("undo")) def commit(self, parent, update = None, text = ""): - tr = self.transaction() - try: remove = [ l[:-1] for l in self.opener("to-remove") ] os.unlink(self.join("to-remove")) @@ -310,6 +308,12 @@ class localrepository: if update == None: update = self.diffdir(self.root, parent)[0] + if not update: + self.ui.status("nothing changed\n") + return + + tr = self.transaction() + # check in files new = {} linkrev = self.changelog.count() @@ -509,6 +513,8 @@ class localrepository: unknown = [tip] search = [] fetch = [] + seen = {} + seenbranch = {} if tip[0] in m: self.ui.note("nothing to do!\n") @@ -516,10 +522,18 @@ class localrepository: while unknown: n = unknown.pop(0) + seen[n[0]] = 1 + + self.ui.debug("examining %s:%s\n" % (short(n[0]), short(n[1]))) if n == nullid: break + if n in seenbranch: + self.ui.debug("branch already found\n") + continue if n[1] and n[1] in m: # do we know the base? - self.ui.debug("found incomplete branch %s\n" % short(n[1])) + self.ui.debug("found incomplete branch %s:%s\n" + % (short(n[0]), short(n[1]))) search.append(n) # schedule branch range for scanning + seenbranch[n] = 1 else: if n[2] in m and n[3] in m: if n[1] not in fetch: @@ -527,9 +541,19 @@ class localrepository: short(n[1])) fetch.append(n[1]) # earliest unknown continue - for b in remote.branches([n[2], n[3]]): - if b[0] not in m: - unknown.append(b) + + r = [] + for a in n[2:4]: + if a not in seen: r.append(a) + + if r: + self.ui.debug("requesting %s\n" % + " ".join(map(short, r))) + for b in remote.branches(r): + self.ui.debug("received %s:%s\n" % + (short(b[0]), short(b[1]))) + if b[0] not in m and b[0] not in seen: + unknown.append(b) while search: n = search.pop(0) @@ -612,6 +636,7 @@ class localrepository: tr = self.transaction() simple = True + need = {} self.ui.status("adding changesets\n") # pull off the changeset group @@ -634,9 +659,62 @@ class localrepository: simple = False resolverev = self.changelog.count() + # resolve the manifest to determine which files + # we care about merging + self.ui.status("resolving manifests\n") + ma = self.manifest.ancestor(mm, mo) + omap = self.manifest.read(mo) # other + amap = self.manifest.read(ma) # ancestor + mmap = self.manifest.read(mm) # mine + nmap = {} + + self.ui.debug(" ancestor %s local %s remote %s\n" % + (short(ma), short(mm), short(mo))) + + for f, mid in mmap.iteritems(): + if f in omap: + if mid != omap[f]: + self.ui.debug(" %s versions differ, do resolve\n" % f) + need[f] = mid # use merged version or local version + else: + nmap[f] = mid # keep ours + del omap[f] + elif f in amap: + if mid != amap[f]: + r = self.ui.prompt( + (" local changed %s which remote deleted\n" % f) + + "(k)eep or (d)elete?", "[kd]", "k") + if r == "k": nmap[f] = mid + else: + self.ui.debug("other deleted %s\n" % f) + pass # other deleted it + else: + self.ui.debug("local created %s\n" %f) + nmap[f] = mid # we created it + + del mmap + + for f, oid in omap.iteritems(): + if f in amap: + if oid != amap[f]: + r = self.ui.prompt( + ("remote changed %s which local deleted\n" % f) + + "(k)eep or (d)elete?", "[kd]", "k") + if r == "k": nmap[f] = oid + else: + pass # probably safe + else: + self.ui.debug("remote created %s, do resolve\n" % f) + need[f] = oid + + del omap + del amap + + new = need.keys() + new.sort() + # process the files self.ui.status("adding files\n") - new = {} while 1: f = getchunk(4) if not f: break @@ -645,11 +723,17 @@ class localrepository: fl = self.file(f) o = fl.tip() n = fl.addgroup(fg, lambda x: self.changelog.rev(x), tr) - if not simple: - 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) + if f in need: + del need[f] + # manifest resolve determined we need to merge the tips + nmap[f] = self.merge3(fl, f, o, n, tr, resolverev) + + if need: + # we need to do trivial merges on local files + for f in new: + if f not in need: continue + fl = self.file(f) + nmap[f] = self.merge3(fl, f, need[f], fl.tip(), tr, resolverev) # For simple merges, we don't need to resolve manifests or changesets if simple: @@ -657,64 +741,11 @@ class localrepository: tr.close() return - # resolve the manifest to point to all the merged files - self.ui.status("resolving manifests\n") - ma = self.manifest.ancestor(mm, mo) - omap = self.manifest.read(mo) # other - amap = self.manifest.read(ma) # ancestor - mmap = self.manifest.read(mm) # mine - self.ui.debug("ancestor %s local %s remote %s\n" % - (short(ma), short(mm), short(mo))) - nmap = {} - - for f, mid in mmap.iteritems(): - if f in omap: - if mid != omap[f]: - self.ui.debug("%s versions differ\n" % f) - 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] - elif f in amap: - if mid != amap[f]: - r = self.ui.prompt( - ("local changed %s which remote deleted\n" % f) + - "(k)eep or (d)elete?", "[kd]", "k") - if r == "k": nmap[f] = mid - else: - self.ui.debug("other deleted %s\n" % f) - pass # other deleted it - else: - self.ui.debug("local created %s\n" %f) - nmap[f] = mid # we created it - - del mmap - - for f, oid in omap.iteritems(): - if f in amap: - if oid != amap[f]: - r = self.ui.prompt( - ("remote changed %s which local deleted\n" % f) + - "(k)eep or (d)elete?", "[kd]", "k") - if r == "k": nmap[f] = oid - else: - pass # probably safe - else: - self.ui.debug("remote created %s\n" % f) - 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 = "\nHG: merge resolve\n" + \ @@ -749,7 +780,7 @@ class localrepository: cmd = os.environ["HGMERGE"] self.ui.debug("invoking merge with %s\n" % cmd) - r = os.system("%s %s %s %s" % (cmd, a, b, c)) + r = os.system("%s %s %s %s %s" % (cmd, a, b, c, fn)) if r: raise "Merge failed!" @@ -776,7 +807,7 @@ class remoterepository: def branches(self, nodes): n = " ".join(map(hex, nodes)) d = self.do_cmd("branches", nodes=n).read() - br = [ map(bin, b.split(" ")) for b in d.splitlines() ] + br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ] return br def between(self, pairs): diff --git a/mercurial/hgweb.py b/mercurial/hgweb.py diff --git a/mercurial/revlog.py b/mercurial/revlog.py --- a/mercurial/revlog.py +++ b/mercurial/revlog.py @@ -8,7 +8,7 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -import zlib, struct, sha, os, tempfile, binascii +import zlib, struct, sha, os, tempfile, binascii, heapq from mercurial import mdiff def hex(node): return binascii.hexlify(node) @@ -276,38 +276,42 @@ class revlog: return node def ancestor(self, a, b): - def expand(list, map): - a = [] - while list: - n = list.pop(0) - map[n] = 1 - yield n - for p in self.parents(n): - if p != nullid and p not in map: - list.append(p) - yield nullid + # calculate the distance of every node from root + dist = {nullid: 0} + for i in xrange(self.count()): + n = self.node(i) + p1, p2 = self.parents(n) + dist[n] = max(dist[p1], dist[p2]) + 1 + + # traverse ancestors in order of decreasing distance from root + def ancestors(node): + # we store negative distances because heap returns smallest member + h = [(-dist[node], node)] + seen = {} + earliest = self.count() + while h: + d, n = heapq.heappop(h) + r = self.rev(n) + if n not in seen: + seen[n] = 1 + yield (-d, n) + for p in self.parents(n): + heapq.heappush(h, (-dist[p], p)) - amap = {} - bmap = {} - ag = expand([a], amap) - bg = expand([b], bmap) - adone = bdone = 0 + x = ancestors(a) + y = ancestors(b) + lx = x.next() + ly = y.next() - while not adone or not bdone: - if not adone: - an = ag.next() - if an == nullid: - adone = 1 - elif an in bmap: - return an - if not bdone: - bn = bg.next() - if bn == nullid: - bdone = 1 - elif bn in amap: - return bn - - return nullid + # increment each ancestor list until it is closer to root than + # the other, or they match + while 1: + if lx == ly: + return lx[1] + elif lx < ly: + ly = y.next() + elif lx > ly: + lx = x.next() def group(self, linkmap): # given a list of changeset revs, return a set of deltas and diff --git a/setup.py b/setup.py diff --git a/templates/changelogentry.tmpl b/templates/changelogentry.tmpl diff --git a/templates/changeset.tmpl b/templates/changeset.tmpl diff --git a/templates/fileannotate.tmpl b/templates/fileannotate.tmpl diff --git a/templates/filediff.tmpl b/templates/filediff.tmpl diff --git a/templates/filelog.tmpl b/templates/filelog.tmpl diff --git a/templates/filelogentry.tmpl b/templates/filelogentry.tmpl diff --git a/templates/filerevision.tmpl b/templates/filerevision.tmpl diff --git a/templates/header.tmpl b/templates/header.tmpl diff --git a/templates/manifest.tmpl b/templates/manifest.tmpl diff --git a/templates/map b/templates/map