--- 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 <path> create a branch of <path> in this directory
- merge <path> merge changes from <path> 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 <file> show revision history of a single file
+ diff [files...] diff working directory (or selected files)
dump <file> [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 <file> show revision history of a single file
+ merge <path> merge changes from <path> 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
--- 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):
--- 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