# HG changeset patch # User mpm@selenic.com # Date 1117834357 28800 # Node ID 4f802588cdfbe71a17a754bd5c477b2c2e8239c5 # Parent fc4a6e5b58121380fe3afcd3b0d9e2ced0265855# Parent 85f0420fd8485ceb1cad9b29a64d4fb54552b96e Merge from tah diff -r 85f0420fd848 -r 4f802588cdfb hg --- a/hg Thu Jun 02 09:23:44 2005 +0100 +++ b/hg Fri Jun 03 13:32:37 2005 -0800 @@ -66,9 +66,8 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 diff -r 85f0420fd848 -r 4f802588cdfb mercurial/commands.py --- a/mercurial/commands.py Thu Jun 02 09:23:44 2005 +0100 +++ b/mercurial/commands.py Fri Jun 03 13:32:37 2005 -0800 @@ -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 @@ # 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ ('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'), } diff -r 85f0420fd848 -r 4f802588cdfb mercurial/hg.py --- a/mercurial/hg.py Thu Jun 02 09:23:44 2005 +0100 +++ b/mercurial/hg.py Fri Jun 03 13:32:37 2005 -0800 @@ -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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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 @@ 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() diff -r 85f0420fd848 -r 4f802588cdfb mercurial/hgweb.py --- a/mercurial/hgweb.py Thu Jun 02 09:23:44 2005 +0100 +++ b/mercurial/hgweb.py Fri Jun 03 13:32:37 2005 -0800 @@ -608,6 +608,11 @@ elif args['cmd'][0] == 'filelog': write(self.filelog(args['file'][0], args['filenode'][0])) + elif args['cmd'][0] == 'heads': + httphdr("text/plain") + h = self.repo.heads() + sys.stdout.write(" ".join(map(hex, h)) + "\n") + elif args['cmd'][0] == 'branches': httphdr("text/plain") nodes = [] diff -r 85f0420fd848 -r 4f802588cdfb mercurial/revlog.py --- a/mercurial/revlog.py Thu Jun 02 09:23:44 2005 +0100 +++ b/mercurial/revlog.py Fri Jun 03 13:32:37 2005 -0800 @@ -156,6 +156,17 @@ def end(self, rev): return self.start(rev) + self.length(rev) def base(self, rev): return self.index[rev][2] + def heads(self): + p = {} + h = [] + for r in range(self.count() - 1, 0, -1): + n = self.node(r) + if n not in p: + h.append(n) + for pn in self.parents(n): + p[pn] = 1 + return h + def lookup(self, id): try: rev = int(id) @@ -422,7 +433,7 @@ yield struct.pack(">l", 0) - def addgroup(self, revs, linkmapper, transaction): + def addgroup(self, revs, linkmapper, transaction, unique = 0): # given a set of deltas, add them to the revision log. the # first delta is against its parent, which should be in our # log, the rest are against the previous delta. @@ -452,7 +463,10 @@ node, p1, p2, cs = struct.unpack("20s20s20s20s", chunk[:80]) link = linkmapper(cs) if node in self.nodemap: - raise "already have %s" % hex(node[:4]) + # this can happen if two branches make the same change + if unique: + raise "already have %s" % hex(node[:4]) + continue delta = chunk[80:] if not chain: diff -r 85f0420fd848 -r 4f802588cdfb mercurial/ui.py diff -r 85f0420fd848 -r 4f802588cdfb templates/changelogentry.tmpl diff -r 85f0420fd848 -r 4f802588cdfb templates/fileannotate.tmpl diff -r 85f0420fd848 -r 4f802588cdfb templates/filelogentry.tmpl diff -r 85f0420fd848 -r 4f802588cdfb templates/map