--- a/hg
+++ b/hg
@@ -66,9 +66,8 @@ def diff(files = None, node1 = None, nod
else:
date2 = time.asctime()
if not node1:
- node1 = repo.current
- (c, a, d) = repo.diffdir(repo.root, node1)
- a = [] # ignore unknown files in repo, by popular request
+ node1 = repo.dirstate.parents()[0]
+ (c, a, d, u) = repo.diffdir(repo.root, node1)
def read(f): return file(os.path.join(repo.root, f)).read()
change = repo.changelog.read(node1)
@@ -79,9 +78,7 @@ def diff(files = None, node1 = None, nod
c, a, d = map(lambda x: filterfiles(x, files), (c, a, d))
for f in c:
- to = ""
- if mmap.has_key(f):
- to = repo.file(f).read(mmap[f])
+ to = repo.file(f).read(mmap[f])
tn = read(f)
sys.stdout.write(mdiff.unidiff(to, date1, tn, date2, f))
for f in a:
@@ -129,24 +126,21 @@ relpath = None
if os.getcwd() != repo.root:
relpath = os.getcwd()[len(repo.root) + 1: ]
-if cmd == "checkout" or cmd == "co":
- node = repo.changelog.tip()
- if args:
- node = repo.lookup(args[0])
- repo.checkout(node)
-
elif cmd == "add":
repo.add(args)
+elif cmd == "forget":
+ repo.forget(args)
+
elif cmd == "remove" or cmd == "rm" or cmd == "del" or cmd == "delete":
repo.remove(args)
elif cmd == "commit" or cmd == "checkin" or cmd == "ci":
if 1:
if len(args) > 0:
- repo.commit(repo.current, args)
+ repo.commit(args)
else:
- repo.commit(repo.current)
+ repo.commit()
elif cmd == "rawcommit":
"raw commit interface"
rc = {}
@@ -209,7 +203,7 @@ elif cmd == "import" or cmd == "patch":
if files:
if os.system("patch -p%d < %s %s" % (strip, pf, quiet)):
raise "patch failed!"
- repo.commit(repo.current, files, text)
+ repo.commit(files, text)
elif cmd == "diff":
revs = []
@@ -256,7 +250,7 @@ elif cmd == "debugaddchangegroup":
repo.addchangegroup(data)
elif cmd == "addremove":
- (c, a, d) = repo.diffdir(repo.root, repo.current)
+ (c, a, d, u) = repo.diffdir(repo.root)
repo.add(a)
repo.remove(d)
@@ -361,11 +355,6 @@ elif cmd == "debugindexdot":
print "}"
elif cmd == "merge":
- (c, a, d) = repo.diffdir(repo.root, repo.current)
- if c:
- ui.warn("aborting (outstanding changes in working directory)\n")
- sys.exit(1)
-
if args:
paths = {}
try:
@@ -422,7 +411,6 @@ elif cmd == "verify":
manifestchangeset[changes[0]] = n
for f in changes[3]:
- revisions += 1
filelinkrevs.setdefault(f, []).append(i)
ui.status("checking manifests\n")
@@ -473,6 +461,7 @@ elif cmd == "verify":
fl = repo.file(f)
nodes = { hg.nullid: 1 }
for i in range(fl.count()):
+ revisions += 1
n = fl.node(i)
if n not in filenodes[f]:
@@ -510,10 +499,6 @@ elif cmd == "verify":
nodes[n] = 1
# cross-check
- for flr in filelinkrevs[f]:
- ui.warn("changeset rev %d not in %s\n" % (flr, f))
- errors += 1
-
for node in filenodes[f]:
ui.warn("node %s in manifests not in %s\n" % (hg.hex(n), f))
errors += 1
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -1,4 +1,4 @@
-import os, re, traceback, sys, signal
+import os, re, traceback, sys, signal, time
from mercurial import fancyopts, ui, hg
class UnknownCommand(Exception): pass
@@ -68,8 +68,13 @@ def branch(ui, path):
# this should eventually support remote repos
os.system("cp -al %s/.hg .hg" % path)
-def checkout(u, repo, changeset=None):
+def checkout(ui, repo, changeset=None):
'''checkout a given changeset or the current tip'''
+ (c, a, d, u) = repo.diffdir(repo.root)
+ if c or a or d:
+ ui.warn("aborting (outstanding changes in working directory)\n")
+ sys.exit(1)
+
node = repo.changelog.tip()
if changeset:
node = repo.lookup(changeset)
@@ -97,7 +102,7 @@ def annotate(u, repo, *args, **ops):
ops['number'] = 1
args = relpath(repo, args)
- node = repo.current
+ node = repo.dirstate.parents()[0]
if ops['revision']:
node = repo.changelog.lookup(ops['revision'])
change = repo.changelog.read(node)
@@ -117,6 +122,45 @@ def annotate(u, repo, *args, **ops):
for p,l in zip(zip(*pieces), lines):
u.write(" ".join(p) + ": " + l[1])
+def heads(ui, repo):
+ '''show current repository heads'''
+ for n in repo.changelog.heads():
+ i = repo.changelog.rev(n)
+ changes = repo.changelog.read(n)
+ (p1, p2) = repo.changelog.parents(n)
+ (h, h1, h2) = map(hg.hex, (n, p1, p2))
+ (i1, i2) = map(repo.changelog.rev, (p1, p2))
+ print "rev: %4d:%s" % (i, h)
+ print "parents: %4d:%s" % (i1, h1)
+ if i2: print " %4d:%s" % (i2, h2)
+ print "manifest: %4d:%s" % (repo.manifest.rev(changes[0]),
+ hg.hex(changes[0]))
+ print "user:", changes[1]
+ print "date:", time.asctime(
+ time.localtime(float(changes[2].split(' ')[0])))
+ if ui.verbose: print "files:", " ".join(changes[3])
+ print "description:"
+ print changes[4]
+
+def parents(ui, repo, node = None):
+ '''show the parents of the current working dir'''
+ if node:
+ p = repo.changelog.parents(repo.lookup(hg.bin(node)))
+ else:
+ p = repo.dirstate.parents()
+
+ for n in p:
+ if n != hg.nullid:
+ ui.write("%d:%s\n" % (repo.changelog.rev(n), hg.hex(n)))
+
+def resolve(ui, repo, node = None):
+ '''merge a given node or the current tip into the working dir'''
+ if not node:
+ node = repo.changelog.tip()
+ else:
+ node = repo.lookup(node)
+ repo.resolve(node)
+
def status(ui, repo):
'''show changed files in the working directory
@@ -124,12 +168,13 @@ C = changed
A = added
R = removed
? = not tracked'''
- (c, a, d) = repo.diffdir(repo.root, repo.current)
- (c, a, d) = map(lambda x: relfilter(repo, x), (c, a, d))
+ (c, a, d, u) = repo.diffdir(repo.root)
+ (c, a, d, u) = map(lambda x: relfilter(repo, x), (c, a, d, u))
for f in c: print "C", f
- for f in a: print "?", f
+ for f in a: print "A", f
for f in d: print "R", f
+ for f in u: print "?", f
def undo(ui, repo):
repo.undo()
@@ -137,6 +182,7 @@ def undo(ui, repo):
table = {
"init": (init, [], 'hg init'),
"branch|clone": (branch, [], 'hg branch [path]'),
+ "heads": (heads, [], 'hg heads'),
"help": (help, [], 'hg help [command]'),
"checkout|co": (checkout, [], 'hg checkout [changeset]'),
"ann|annotate": (annotate,
@@ -145,6 +191,8 @@ table = {
('n', 'number', None, 'show revision number'),
('c', 'changeset', None, 'show changeset')],
'hg annotate [-u] [-c] [-n] [-r id] [files]'),
+ "parents": (parents, [], 'hg parents [node]'),
+ "resolve": (resolve, [], 'hg resolve [node]'),
"status": (status, [], 'hg status'),
"undo": (undo, [], 'hg undo'),
}
--- a/mercurial/hg.py
+++ b/mercurial/hg.py
@@ -5,7 +5,7 @@
# This software may be used and distributed according to the terms
# of the GNU General Public License, incorporated herein by reference.
-import sys, struct, sha, socket, os, time, re, urllib2
+import sys, struct, sha, socket, os, time, re, urllib2, tempfile
import urllib
from mercurial import byterange, lock
from mercurial.transaction import *
@@ -149,57 +149,87 @@ class changelog(revlog):
text = "\n".join(l)
return self.addrevision(text, transaction, self.count(), p1, p2)
-class dircache:
+class dirstate:
def __init__(self, opener, ui):
self.opener = opener
self.dirty = 0
self.ui = ui
self.map = None
+ self.pl = None
+
def __del__(self):
- if self.dirty: self.write()
+ if self.dirty:
+ self.write()
+
def __getitem__(self, key):
try:
return self.map[key]
except TypeError:
self.read()
return self[key]
-
+
+ def __contains__(self, key):
+ if not self.map: self.read()
+ return key in self.map
+
+ def parents(self):
+ if not self.pl:
+ self.read()
+ return self.pl
+
+ def setparents(self, p1, p2 = nullid):
+ self.dirty = 1
+ self.pl = p1, p2
+
+ def state(self, key):
+ try:
+ return self[key][0]
+ except KeyError:
+ return "?"
+
def read(self):
if self.map is not None: return self.map
self.map = {}
+ self.pl = [nullid, nullid]
try:
- st = self.opener("dircache").read()
+ st = self.opener("dirstate").read()
except: return
- pos = 0
+ self.pl = [st[:20], st[20: 40]]
+
+ pos = 40
while pos < len(st):
- e = struct.unpack(">llll", st[pos:pos+16])
- l = e[3]
- pos += 16
+ e = struct.unpack(">cllll", st[pos:pos+17])
+ l = e[4]
+ pos += 17
f = st[pos:pos + l]
- self.map[f] = e[:3]
+ self.map[f] = e[:4]
pos += l
- def update(self, files):
+ def update(self, files, state):
+ ''' current states:
+ n normal
+ m needs merging
+ i invalid
+ r marked for removal
+ a marked for addition'''
+
if not files: return
self.read()
self.dirty = 1
for f in files:
- try:
- s = os.stat(f)
- self.map[f] = (s.st_mode, s.st_size, s.st_mtime)
- except IOError:
- self.remove(f)
+ if state == "r":
+ self.map[f] = ('r', 0, 0, 0)
+ else:
+ try:
+ s = os.stat(f)
+ self.map[f] = (state, s.st_mode, s.st_size, s.st_mtime)
+ except OSError:
+ if state != "i": raise
+ self.map[f] = ('r', 0, 0, 0)
- def taint(self, files):
- if not files: return
- self.read()
- self.dirty = 1
- for f in files:
- self.map[f] = (0, -1, 0)
-
- def remove(self, files):
+ def forget(self, files):
if not files: return
self.read()
self.dirty = 1
@@ -207,7 +237,7 @@ class dircache:
try:
del self.map[f]
except KeyError:
- self.ui.warn("Not in dircache: %s\n" % f)
+ self.ui.warn("not in dirstate: %s!\n" % f)
pass
def clear(self):
@@ -215,9 +245,10 @@ class dircache:
self.dirty = 1
def write(self):
- st = self.opener("dircache", "w")
+ st = self.opener("dirstate", "w")
+ st.write("".join(self.pl))
for f, e in self.map.items():
- e = struct.pack(">llll", e[0], e[1], e[2], len(f))
+ e = struct.pack(">cllll", e[0], e[1], e[2], e[3], len(f))
st.write(e + f)
self.dirty = 0
@@ -280,16 +311,8 @@ class localrepository:
self.tags = None
if not self.remote:
- self.dircache = dircache(self.opener, ui)
- try:
- self.current = bin(self.opener("current").read())
- except IOError:
- self.current = None
+ self.dirstate = dirstate(self.opener, ui)
- def setcurrent(self, node):
- self.current = node
- self.opener("current", "w").write(hex(node))
-
def ignore(self, f):
if self.ignorelist is None:
self.ignorelist = []
@@ -330,7 +353,7 @@ class localrepository:
self.join("undo"))
def recover(self):
- self.lock()
+ lock = self.lock()
if os.path.exists(self.join("recover")):
self.ui.status("attempting to rollback interrupted transaction\n")
return rollback(self.opener, self.join("recover"))
@@ -338,23 +361,20 @@ class localrepository:
self.ui.warn("no interrupted transaction available\n")
def undo(self):
- self.lock()
+ lock = self.lock()
if os.path.exists(self.join("undo")):
+ f = self.changelog.read(self.changelog.tip())[3]
self.ui.status("attempting to rollback last transaction\n")
rollback(self.opener, self.join("undo"))
self.manifest = manifest(self.opener)
self.changelog = changelog(self.opener)
- self.ui.status("discarding dircache\n")
+ self.ui.status("discarding dirstate\n")
node = self.changelog.tip()
- mf = self.changelog.read(node)[0]
- mm = self.manifest.read(mf)
- f = mm.keys()
f.sort()
- self.setcurrent(node)
- self.dircache.clear()
- self.dircache.taint(f)
+ self.dirstate.setparents(node)
+ self.dirstate.update(f, 'i')
else:
self.ui.warn("no undo information available\n")
@@ -369,7 +389,8 @@ class localrepository:
raise inst
def rawcommit(self, files, text, user, date, p1=None, p2=None):
- p1 = p1 or self.current or nullid
+ p1 = p1 or self.dirstate.parents()[0] or nullid
+ p2 = p2 or self.dirstate.parents()[1] or nullid
pchange = self.changelog.read(p1)
pmmap = self.manifest.read(pchange[0])
tr = self.transaction()
@@ -382,70 +403,83 @@ class localrepository:
self.ui.warn("Read file %s error, skipped\n" % f)
continue
r = self.file(f)
+ # FIXME - need to find both parents properly
prev = pmmap.get(f, nullid)
mmap[f] = r.add(t, tr, linkrev, prev)
mnode = self.manifest.add(mmap, tr, linkrev, pchange[0])
n = self.changelog.add(mnode, files, text, tr, p1, p2, user ,date, )
tr.close()
- self.setcurrent(n)
- self.dircache.clear()
- self.dircache.update(mmap)
+ self.dirstate.setparents(p1, p2)
+ self.dirstate.clear()
+ self.dirstate.update(mmap.keys(), "n")
- def commit(self, parent, update = None, text = ""):
- self.lock()
- try:
- remove = [ l[:-1] for l in self.opener("to-remove") ]
- os.unlink(self.join("to-remove"))
+ def commit(self, files = None, text = ""):
+ commit = []
+ remove = []
+ if files:
+ for f in files:
+ s = self.dirstate.state(f)
+ if s in 'cai':
+ commit.append(f)
+ elif s == 'r':
+ remove.append(f)
+ else:
+ self.warn("%s not tracked!\n")
+ else:
+ (c, a, d, u) = self.diffdir(self.root)
+ commit = c + a
+ remove = d
- except IOError:
- remove = []
-
- if update == None:
- update = self.diffdir(self.root, parent)[0]
-
- if not update:
+ if not commit and not remove:
self.ui.status("nothing changed\n")
return
+ p1, p2 = self.dirstate.parents()
+ c1 = self.changelog.read(p1)
+ c2 = self.changelog.read(p2)
+ m1 = self.manifest.read(c1[0])
+ m2 = self.manifest.read(c2[0])
+ lock = self.lock()
tr = self.transaction()
# check in files
new = {}
linkrev = self.changelog.count()
- update.sort()
- for f in update:
+ commit.sort()
+ for f in commit:
self.ui.note(f + "\n")
try:
t = file(f).read()
except IOError:
- remove.append(f)
- continue
+ self.warn("trouble committing %s!\n" % f)
+ raise
+
r = self.file(f)
- new[f] = r.add(t, tr, linkrev)
+ fp1 = m1.get(f, nullid)
+ fp2 = m2.get(f, nullid)
+ new[f] = r.add(t, tr, linkrev, fp1, fp2)
# update manifest
- mmap = self.manifest.read(self.manifest.tip())
- mmap.update(new)
- for f in remove:
- del mmap[f]
- mnode = self.manifest.add(mmap, tr, linkrev)
+ m1.update(new)
+ for f in remove: del m1[f]
+ mn = self.manifest.add(m1, tr, linkrev, c1[0], c2[0])
# add changeset
new = new.keys()
new.sort()
- edittext = text + "\n" + "HG: manifest hash %s\n" % hex(mnode)
+ edittext = text + "\n" + "HG: manifest hash %s\n" % hex(mn)
edittext += "".join(["HG: changed %s\n" % f for f in new])
edittext += "".join(["HG: removed %s\n" % f for f in remove])
edittext = self.ui.edit(edittext)
- n = self.changelog.add(mnode, new, edittext, tr)
+ n = self.changelog.add(mn, new, edittext, tr, p1, p2)
tr.close()
- self.setcurrent(n)
- self.dircache.update(new)
- self.dircache.remove(remove)
+ self.dirstate.setparents(n)
+ self.dirstate.update(new, "n")
+ self.dirstate.forget(remove)
def checkout(self, node):
# checkout is really dumb at the moment
@@ -464,23 +498,25 @@ class localrepository:
os.makedirs(os.path.dirname(f))
file(f, "w").write(t)
- self.setcurrent(node)
- self.dircache.clear()
- self.dircache.update([f for f,n in l])
+ self.dirstate.setparents(node)
+ self.dirstate.clear()
+ self.dirstate.update([f for f,n in l], "n")
- def diffdir(self, path, changeset):
+ def diffdir(self, path, changeset = None):
changed = []
+ added = []
+ unknown = []
mf = {}
- added = []
if changeset:
change = self.changelog.read(changeset)
mf = self.manifest.read(change[0])
-
- if changeset == self.current:
- dc = self.dircache.copy()
+ dc = dict.fromkeys(mf)
else:
- dc = dict.fromkeys(mf)
+ changeset = self.dirstate.parents()[0]
+ change = self.changelog.read(changeset)
+ mf = self.manifest.read(change[0])
+ dc = self.dirstate.copy()
def fcmp(fn):
t1 = file(os.path.join(self.root, fn)).read()
@@ -498,22 +534,33 @@ class localrepository:
if fn in dc:
c = dc[fn]
del dc[fn]
- if not c or c[1] < 0:
+ if not c:
if fcmp(fn):
changed.append(fn)
- elif c[1] != s.st_size:
+ elif c[0] == 'i':
+ if fn not in mf:
+ added.append(fn)
+ elif fcmp(fn):
+ changed.append(fn)
+ elif c[0] == 'm':
changed.append(fn)
- elif c[0] != s.st_mode or c[2] != s.st_mtime:
+ elif c[0] == 'a':
+ added.append(fn)
+ elif c[0] == 'r':
+ unknown.append(fn)
+ elif c[2] != s.st_size:
+ changed.append(fn)
+ elif c[1] != s.st_mode or c[3] != s.st_mtime:
if fcmp(fn):
changed.append(fn)
else:
if self.ignore(fn): continue
- added.append(fn)
+ unknown.append(fn)
deleted = dc.keys()
deleted.sort()
- return (changed, added, deleted)
+ return (changed, added, deleted, unknown)
def diffrevs(self, node1, node2):
changed, added = [], []
@@ -537,12 +584,34 @@ class localrepository:
return (changed, added, deleted)
def add(self, list):
- self.dircache.taint(list)
+ for f in list:
+ p = os.path.join(self.root, f)
+ if not os.path.isfile(p):
+ self.ui.warn("%s does not exist!\n" % f)
+ elif self.dirstate.state(f) == 'n':
+ self.ui.warn("%s already tracked!\n" % f)
+ else:
+ self.dirstate.update([f], "a")
+
+ def forget(self, list):
+ for f in list:
+ if self.dirstate.state(f) not in 'ai':
+ self.ui.warn("%s not added!\n" % f)
+ else:
+ self.dirstate.forget([f])
def remove(self, list):
- dl = self.opener("to-remove", "a")
for f in list:
- dl.write(f + "\n")
+ p = os.path.join(self.root, f)
+ if os.path.isfile(p):
+ self.ui.warn("%s still exists!\n" % f)
+ elif f not in self.dirstate:
+ self.ui.warn("%s not tracked!\n" % f)
+ else:
+ self.dirstate.update([f], "r")
+
+ def heads(self):
+ return self.changelog.heads()
def branches(self, nodes):
if not nodes: nodes = [self.changelog.tip()]
@@ -609,22 +678,24 @@ class localrepository:
seen = {}
seenbranch = {}
- self.ui.status("searching for changes\n")
- tip = remote.branches([])[0]
- self.ui.debug("remote tip branch is %s:%s\n" %
- (short(tip[0]), short(tip[1])))
-
# if we have an empty repo, fetch everything
if self.changelog.tip() == nullid:
+ self.ui.status("requesting all changes\n")
return remote.changegroup([nullid])
# otherwise, assume we're closer to the tip than the root
- unknown = [tip]
+ self.ui.status("searching for changes\n")
+ heads = remote.heads()
+ unknown = []
+ for h in heads:
+ if h not in m:
+ unknown.append(h)
- if tip[0] in m:
+ if not unknown:
self.ui.status("nothing to do!\n")
return None
-
+
+ unknown = remote.branches(unknown)
while unknown:
n = unknown.pop(0)
seen[n[0]] = 1
@@ -716,9 +787,7 @@ class localrepository:
yield y
def addchangegroup(self, generator):
- changesets = files = revisions = 0
- self.lock()
class genread:
def __init__(self, generator):
self.g = generator
@@ -732,9 +801,6 @@ class localrepository:
d, self.buf = self.buf[:l], self.buf[l:]
return d
- if not generator: return
- source = genread(generator)
-
def getchunk():
d = source.read(4)
if not d: return ""
@@ -748,141 +814,154 @@ class localrepository:
if not c: break
yield c
- tr = self.transaction()
- simple = True
- need = {}
-
- self.ui.status("adding changesets\n")
- # pull off the changeset group
- def report(x):
+ def csmap(x):
self.ui.debug("add changeset %s\n" % short(x))
return self.changelog.count()
+ def revmap(x):
+ return self.changelog.rev(x)
+
+ if not generator: return
+ changesets = files = revisions = 0
+
+ source = genread(generator)
+ lock = self.lock()
+ tr = self.transaction()
+
+ # pull off the changeset group
+ self.ui.status("adding changesets\n")
co = self.changelog.tip()
- cn = self.changelog.addgroup(getgroup(), report, tr)
-
+ cn = self.changelog.addgroup(getgroup(), csmap, tr, 1) # unique
changesets = self.changelog.rev(cn) - self.changelog.rev(co)
+ # pull off the manifest group
self.ui.status("adding manifests\n")
- # pull off the manifest group
mm = self.manifest.tip()
- mo = self.manifest.addgroup(getgroup(),
- lambda x: self.changelog.rev(x), tr)
-
- # do we need a resolve?
- if self.changelog.ancestor(co, cn) != co:
- 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()
+ mo = self.manifest.addgroup(getgroup(), revmap, tr)
# process the files
- self.ui.status("adding files\n")
+ self.ui.status("adding file revisions\n")
while 1:
f = getchunk()
if not f: break
self.ui.debug("adding %s revisions\n" % f)
fl = self.file(f)
o = fl.tip()
- n = fl.addgroup(getgroup(), lambda x: self.changelog.rev(x), tr)
+ n = fl.addgroup(getgroup(), revmap, tr)
revisions += fl.rev(n) - fl.rev(o)
files += 1
- 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)
+
+ self.ui.status(("modified %d files, added %d changesets" +
+ " and %d new revisions\n")
+ % (files, changesets, revisions))
- 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)
- revisions += 1
+ tr.close()
+ return
- # For simple merges, we don't need to resolve manifests or changesets
- if simple:
- self.ui.debug("simple merge, skipping resolve\n")
- self.ui.status(("modified %d files, added %d changesets" +
- " and %d new revisions\n")
- % (files, changesets, revisions))
- tr.close()
+ def resolve(self, node):
+ pl = self.dirstate.parents()
+ if pl[1] != nullid:
+ self.ui.warn("last merge not committed")
return
- node = self.manifest.add(nmap, tr, resolverev, mm, mo)
- revisions += 1
+ p1, p2 = pl[0], node
+ m1n = self.changelog.read(p1)[0]
+ m2n = self.changelog.read(p2)[0]
+ man = self.manifest.ancestor(m1n, m2n)
+ m1 = self.manifest.read(m1n)
+ m2 = self.manifest.read(m2n)
+ ma = self.manifest.read(man)
+
+ (c, a, d, u) = self.diffdir(self.root)
+
+ # resolve the manifest to determine which files
+ # we care about merging
+ self.ui.status("resolving manifests\n")
+ self.ui.debug(" ancestor %s local %s remote %s\n" %
+ (short(man), short(m1n), short(m2n)))
+
+ merge = {}
+ get = {}
+ remove = []
- # 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")
- if co == cn: cn = -1
+ # construct a working dir manifest
+ mw = m1.copy()
+ for f in a + c:
+ mw[f] = nullid
+ for f in d:
+ del mw[f]
+
+ for f, n in mw.iteritems():
+ if f in m2:
+ if n != m2[f]:
+ self.ui.debug(" %s versions differ, do resolve\n" % f)
+ merge[f] = (m1.get(f, nullid), m2[f])
+ del m2[f]
+ elif f in ma:
+ if n != ma[f]:
+ r = self.ui.prompt(
+ (" local changed %s which remote deleted\n" % f) +
+ "(k)eep or (d)elete?", "[kd]", "k")
+ if r == "d":
+ remove.append(f)
+ else:
+ self.ui.debug("other deleted %s\n" % f)
+ pass # other deleted it
+ else:
+ self.ui.debug("local created %s\n" %f)
- edittext = "\nHG: merge resolve\n" + \
- "HG: manifest hash %s\n" % hex(node) + \
- "".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)
- revisions += 1
+ for f, n in m2.iteritems():
+ if f in ma:
+ if n != ma[f]:
+ r = self.ui.prompt(
+ ("remote changed %s which local deleted\n" % f) +
+ "(k)eep or (d)elete?", "[kd]", "k")
+ if r == "d": remove.append(f)
+ else:
+ pass # probably safe
+ else:
+ self.ui.debug("remote created %s, do resolve\n" % f)
+ get[f] = n
+
+ del mw, m1, m2, ma
+
+ self.dirstate.setparents(p1, p2)
- self.ui.status("added %d changesets, %d files, and %d new revisions\n"
- % (changesets, files, revisions))
+ # get the files we don't need to change
+ files = get.keys()
+ files.sort()
+ for f in files:
+ if f[0] == "/": continue
+ self.ui.note(f, "\n")
+ t = self.file(f).revision(get[f])
+ try:
+ file(f, "w").write(t)
+ except IOError:
+ os.makedirs(os.path.dirname(f))
+ file(f, "w").write(t)
+
+ # we have to remember what files we needed to get/change
+ # because any file that's different from either one of its
+ # parents must be in the changeset
+ self.dirstate.update(files, 'm')
- tr.close()
+ # merge the tricky bits
+ files = merge.keys()
+ files.sort()
+ for f in files:
+ m, o = merge[f]
+ self.merge3(f, m, o)
- def merge3(self, fl, fn, my, other, transaction, link):
- """perform a 3-way merge and append the result"""
+ # same here
+ self.dirstate.update(files, 'm')
+
+ for f in remove:
+ self.ui.note("removing %s\n" % f)
+ #os.unlink(f)
+ self.dirstate.update(remove, 'r')
+
+ def merge3(self, fn, my, other):
+ """perform a 3-way merge in the working directory"""
def temp(prefix, node):
pre = "%s~%s." % (os.path.basename(fn), prefix)
@@ -892,30 +971,23 @@ class localrepository:
f.close()
return name
+ fl = self.file(fn)
base = fl.ancestor(my, other)
- self.ui.note("resolving %s\n" % fn)
- self.ui.debug("local %s remote %s ancestor %s\n" %
- (short(my), short(other), short(base)))
-
- if my == base:
- text = fl.revision(other)
- else:
- a = temp("local", my)
- b = temp("remote", other)
- c = temp("parent", base)
+ a = fn
+ b = temp("other", other)
+ c = temp("base", base)
- cmd = os.environ["HGMERGE"]
- self.ui.debug("invoking merge with %s\n" % cmd)
- r = os.system("%s %s %s %s %s" % (cmd, a, b, c, fn))
- if r:
- raise "Merge failed!"
+ self.ui.note("resolving %s\n" % fn)
+ self.ui.debug("file %s: other %s ancestor %s\n" %
+ (fn, short(other), short(base)))
- text = open(a).read()
- os.unlink(a)
- os.unlink(b)
- os.unlink(c)
-
- return fl.add(text, transaction, link, my, other)
+ cmd = os.environ["HGMERGE"]
+ r = os.system("%s %s %s %s %s" % (cmd, a, b, c, fn))
+ if r:
+ self.ui.warn("merging %s failed!\n" % f)
+
+ os.unlink(b)
+ os.unlink(c)
class remoterepository:
def __init__(self, ui, path):
@@ -930,6 +1002,14 @@ class remoterepository:
cu = "%s?%s" % (self.url, qs)
return urllib.urlopen(cu)
+ def heads(self):
+ d = self.do_cmd("heads").read()
+ try:
+ return map(bin, d[:-1].split(" "))
+ except:
+ self.ui.warn("unexpected response:\n" + d[:400] + "\n...\n")
+ raise
+
def branches(self, nodes):
n = " ".join(map(hex, nodes))
d = self.do_cmd("branches", nodes=n).read()