# HG changeset patch # User Brendan Cully # Date 1166835910 28800 # Node ID f321d841dce5191737caf3608d0534dc68bba3d8 # Parent a8eb050c6489e9e1cea3ad325bf210bdfccc8c6f# Parent 2b87d3c5ab8e2ac79479c643c30e3949ca64690e Merge with mpm diff -r a8eb050c6489 -r f321d841dce5 contrib/convert-repo --- a/contrib/convert-repo Fri Dec 22 16:29:35 2006 -0800 +++ b/contrib/convert-repo Fri Dec 22 17:05:10 2006 -0800 @@ -7,7 +7,7 @@ # # convert-repo [ []] # -# Currently accepted source formats: git +# Currently accepted source formats: git, cvs # Currently accepted destination formats: hg # # If destination isn't given, a new Mercurial repo named -hg will @@ -23,11 +23,19 @@ # on each commit copied, so convert-repo can be interrupted and can # be run repeatedly to copy new commits. -import sys, os, zlib, sha, time +import sys, os, zlib, sha, time, re, locale os.environ["HGENCODING"] = "utf-8" from mercurial import hg, ui, util, fancyopts class Abort(Exception): pass +class NoRepo(Exception): pass + +class commit: + def __init__(self, **parts): + for x in "author date desc parents".split(): + if not x in parts: + abort("commit missing field %s\n" % x) + self.__dict__.update(parts) quiet = 0 def status(msg): @@ -48,13 +56,210 @@ except: return s.decode("utf-8", "replace").encode("utf-8") +# CVS conversion code inspired by hg-cvs-import and git-cvsimport +class convert_cvs: + def __init__(self, path): + self.path = path + cvs = os.path.join(path, "CVS") + if not os.path.exists(cvs): + raise NoRepo("couldn't open CVS repo %s" % path) + + self.changeset = {} + self.files = {} + self.tags = {} + self.lastbranch = {} + self.parent = {} + self.socket = None + self.cvsroot = file(os.path.join(cvs, "Root")).read()[:-1] + self.cvsrepo = file(os.path.join(cvs, "Repository")).read()[:-1] + self.encoding = locale.getpreferredencoding() + self._parse() + self._connect() + + def _parse(self): + if self.changeset: + return + + d = os.getcwd() + try: + os.chdir(self.path) + id = None + state = 0 + for l in os.popen("cvsps -A"): + if state == 0: # header + if l.startswith("PatchSet"): + id = l[9:-2] + elif l.startswith("Date"): + date = util.parsedate(l[6:-1], ["%Y/%m/%d %H:%M:%S"]) + date = util.datestr(date) + elif l.startswith("Branch"): + branch = l[8:-1] + self.parent[id] = self.lastbranch.get(branch,'bad') + self.lastbranch[branch] = id + elif l.startswith("Ancestor branch"): + ancestor = l[17:-1] + self.parent[id] = self.lastbranch[ancestor] + elif l.startswith("Author"): + author = self.recode(l[8:-1]) + elif l.startswith("Tag: "): + t = l[5:-1] + if t != "(none) ": + self.tags[t] = id + elif l.startswith("Log:"): + state = 1 + log = "" + elif state == 1: # log + if l == "Members: \n": + files = {} + log = self.recode(log[:-1]) + if log.isspace(): + log = "*** empty log message ***\n" + state = 2 + else: + log += l + elif state == 2: + if l == "\n": # + state = 0 + p = [self.parent[id]] + if id == "1": + p = [] + c = commit(author=author, date=date, parents=p, + desc=log, branch=branch) + self.changeset[id] = c + self.files[id] = files + else: + file,rev = l[1:-2].rsplit(':',1) + rev = rev.split("->")[1] + files[file] = rev + + self.heads = self.lastbranch.values() + finally: + os.chdir(d) + + def _connect(self): + root = self.cvsroot + local = False + user, host = None, None + cmd = ['cvs', 'server'] + + status("connecting to %s\n" % root) + + # only non-pserver for now + if root.startswith(":pserver"): + abort("can't handle pserver mode yet: %s\n" % root) + + if root.startswith(":local:"): + local = True + root = root[7:] + else: + # :ext:user@host/home/user/path/to/cvsroot + if root.startswith(":ext:"): + root = root[5:] + m = re.match(r'(?:([^@:/]+)@)?([^:/]+):?(.*)', root) + if not m: + local = True + else: + local = False + user, host, root = m.group(1), m.group(2), m.group(3) + + if not local: + rsh = os.environ.get("CVS_RSH" or "rsh") + if user: + cmd = [rsh, '-l', user, host] + cmd + else: + cmd = [rsh, host] + cmd + + self.writep, self.readp = os.popen2(cmd) + self.realroot = root + + self.writep.write("Root %s\n" % root) + self.writep.write("Valid-responses ok error Valid-requests Mode" + " M Mbinary E Checked-in Created Updated" + " Merged Removed\n") + self.writep.write("valid-requests\n") + self.writep.flush() + r = self.readp.readline() + if not r.startswith("Valid-requests"): + abort("server sucks\n") + if "UseUnchanged" in r: + self.writep.write("UseUnchanged\n") + self.writep.flush() + r = self.readp.readline() + + def getheads(self): + return self.heads + + def _getfile(self, name, rev): + if rev.endswith("(DEAD)"): + raise IOError + + args = ("-N -P -kk -r %s --" % rev).split() + args.append(os.path.join(self.cvsrepo, name)) + for x in args: + self.writep.write("Argument %s\n" % x) + self.writep.write("Directory .\n%s\nco\n" % self.realroot) + self.writep.flush() + + data = "" + while 1: + line = self.readp.readline() + if line.startswith("Created ") or line.startswith("Updated "): + self.readp.readline() # path + self.readp.readline() # entries + mode = self.readp.readline()[:-1] + count = int(self.readp.readline()[:-1]) + data = self.readp.read(count) + elif line.startswith(" "): + data += line[1:] + elif line.startswith("M "): + pass + elif line.startswith("Mbinary "): + count = int(self.readp.readline()[:-1]) + data = self.readp.read(count) + else: + if line == "ok\n": + return (data, "x" in mode) + elif line.startswith("E "): + warn("cvs server: %s\n" % line[2:]) + elif line.startswith("Remove"): + l = self.readp.readline() + l = self.readp.readline() + if l != "ok\n": + abort("unknown CVS response: %s\n" % l) + else: + abort("unknown CVS response: %s\n" % line) + + def getfile(self, file, rev): + data, mode = self._getfile(file, rev) + self.modecache[(file, rev)] = mode + return data + + def getmode(self, file, rev): + return self.modecache[(file, rev)] + + def getchanges(self, rev): + self.modecache = {} + files = self.files[rev] + cl = files.items() + cl.sort() + return cl + + def recode(self, text): + return text.decode(self.encoding, "replace").encode("utf-8") + + def getcommit(self, rev): + return self.changeset[rev] + + def gettags(self): + return self.tags + class convert_git: def __init__(self, path): if os.path.isdir(path + "/.git"): path += "/.git" self.path = path if not os.path.exists(path + "/HEAD"): - raise TypeError("couldn't open GIT repo %s" % path) + raise NoRepo("couldn't open GIT repo %s" % path) def getheads(self): fh = os.popen("GIT_DIR=%s git-rev-parse --verify HEAD" % self.path) @@ -68,7 +273,11 @@ def getfile(self, name, rev): return self.catfile(rev, "blob") + def getmode(self, name, rev): + return self.modecache[(name, rev)] + def getchanges(self, version): + self.modecache = {} fh = os.popen("GIT_DIR=%s git-diff-tree --root -m -r %s" % (self.path, version)) changes = [] for l in fh: @@ -77,7 +286,8 @@ m = m.split() h = m[3] p = (m[1] == "100755") - changes.append((f, h, p)) + self.modecache[(f, h)] = p + changes.append((f, h)) return changes def getcommit(self, version): @@ -108,7 +318,9 @@ tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:] tz = -int(tzs) * (int(tzh) * 3600 + int(tzm)) date = tm + " " + str(tz) - return (parents, author, date, message) + + c = commit(parents=parents, date=date, author=author, desc=message) + return c def gettags(self): tags = {} @@ -129,7 +341,7 @@ try: self.repo = hg.repository(u, path) except: - raise TypeError("could open hg repo %s" % path) + raise NoRepo("could open hg repo %s" % path) def mapfile(self): return os.path.join(self.path, ".hg", "shamap") @@ -152,7 +364,7 @@ except: pass - def putcommit(self, files, parents, author, dest, text): + def putcommit(self, files, parents, commit): seen = {} pl = [] for p in parents: @@ -165,11 +377,18 @@ if len(parents) < 2: parents.append("0" * 40) p2 = parents.pop(0) + text = commit.desc + extra = {} + try: + extra["branch"] = commit.branch + except AttributeError: + pass + while parents: p1 = p2 p2 = parents.pop(0) - self.repo.rawcommit(files, text, author, dest, - hg.bin(p1), hg.bin(p2)) + a = self.repo.rawcommit(files, text, commit.author, commit.date, + hg.bin(p1), hg.bin(p2), extra=extra) text = "(octopus merge fixup)\n" p2 = hg.hex(self.repo.changelog.tip()) @@ -202,7 +421,7 @@ date, self.repo.changelog.tip(), hg.nullid) return hg.hex(self.repo.changelog.tip()) -converters = [convert_git, convert_mercurial] +converters = [convert_cvs, convert_git, convert_mercurial] def converter(path): if not os.path.isdir(path): @@ -210,16 +429,17 @@ for c in converters: try: return c(path) - except TypeError: + except NoRepo: pass abort("%s: unknown repository type\n" % path) class convert: - def __init__(self, source, dest, mapfile): + def __init__(self, source, dest, mapfile, opts): self.source = source self.dest = dest self.mapfile = mapfile + self.opts = opts self.commitcache = {} self.map = {} @@ -239,7 +459,7 @@ if n in known or n in self.map: continue known[n] = 1 self.commitcache[n] = self.source.getcommit(n) - cp = self.commitcache[n][0] + cp = self.commitcache[n].parents for p in cp: parents.setdefault(n, []).append(p) visit.append(p) @@ -282,28 +502,42 @@ if not dep: # all n's parents are in the list removed[n] = 1 - s.append(n) + if n not in self.map: + s.append(n) if n in children: for c in children[n]: visit.insert(0, c) + if opts.get('datesort'): + depth = {} + for n in s: + depth[n] = 0 + pl = [p for p in self.commitcache[n].parents if p not in self.map] + if pl: + depth[n] = max([depth[p] for p in pl]) + 1 + + s = [(depth[n], self.commitcache[n].date, n) for n in s] + s.sort() + s = [e[2] for e in s] + return s def copy(self, rev): - p, a, d, t = self.commitcache[rev] + c = self.commitcache[rev] files = self.source.getchanges(rev) - for f,v,e in files: + for f,v in files: try: data = self.source.getfile(f, v) except IOError, inst: self.dest.delfile(f) else: + e = self.source.getmode(f, v) self.dest.putfile(f, e, data) - r = [self.map[v] for v in p] - f = [f for f,v,e in files] - self.map[rev] = self.dest.putcommit(f, r, a, d, t) + r = [self.map[v] for v in c.parents] + f = [f for f,v in files] + self.map[rev] = self.dest.putcommit(f, r, c) file(self.mapfile, "a").write("%s %s\n" % (rev, self.map[rev])) def convert(self): @@ -312,14 +546,15 @@ parents = self.walktree(heads) status("sorting...\n") t = self.toposort(parents) - t = [n for n in t if n not in self.map] num = len(t) c = None status("converting...\n") for c in t: num -= 1 - desc = self.commitcache[c][3].splitlines()[0] + desc = self.commitcache[c].desc + if "\n" in desc: + desc = desc.splitlines()[0] status("%d %s\n" % (num, desc)) self.copy(c) @@ -358,10 +593,11 @@ except: mapfile = os.path.join(destc, "map") - c = convert(srcc, destc, mapfile) + c = convert(srcc, destc, mapfile, opts) c.convert() -options = [('q', 'quiet', None, 'suppress output')] +options = [('q', 'quiet', None, 'suppress output'), + ('', 'datesort', None, 'try to sort changesets by date')] opts = {} args = fancyopts.fancyopts(sys.argv[1:], options, opts) diff -r a8eb050c6489 -r f321d841dce5 mercurial/localrepo.py --- a/mercurial/localrepo.py Fri Dec 22 16:29:35 2006 -0800 +++ b/mercurial/localrepo.py Fri Dec 22 17:05:10 2006 -0800 @@ -727,11 +727,13 @@ # update manifest m1.update(new) remove.sort() + removed = [] for f in remove: if f in m1: del m1[f] - mn = self.manifest.add(m1, tr, linkrev, c1[0], c2[0], (new, remove)) + removed.append(f) + mn = self.manifest.add(m1, tr, linkrev, c1[0], c2[0], (new, removed)) # add changeset new = new.keys() @@ -747,7 +749,7 @@ if p2 != nullid: edittext.append("HG: branch merge") edittext.extend(["HG: changed %s" % f for f in changed]) - edittext.extend(["HG: removed %s" % f for f in remove]) + edittext.extend(["HG: removed %s" % f for f in removed]) if not changed and not remove: edittext.append("HG: no files changed") edittext.append("") @@ -765,7 +767,7 @@ text = '\n'.join(lines) if branchname: extra["branch"] = branchname - n = self.changelog.add(mn, changed + remove, text, tr, p1, p2, + n = self.changelog.add(mn, changed + removed, text, tr, p1, p2, user, date, extra) self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1, parent2=xp2) @@ -775,7 +777,7 @@ self.dirstate.setparents(n) if use_dirstate: self.dirstate.update(new, "n") - self.dirstate.forget(remove) + self.dirstate.forget(removed) self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2) return n