Mercurial > hg
changeset 4536:cc9b79216a76
Split convert extension into common and repository type modules
author | Brendan Cully <brendan@kublai.com> |
---|---|
date | Sun, 10 Jun 2007 20:08:47 -0700 |
parents | 36abb07c79d4 |
children | 2f489b00f8eb |
files | hgext/convert/__init__.py hgext/convert/common.py hgext/convert/cvs.py hgext/convert/git.py hgext/convert/hg.py |
diffstat | 5 files changed, 534 insertions(+), 513 deletions(-) [+] |
line wrap: on
line diff
--- a/hgext/convert/__init__.py Sat Jun 09 13:25:54 2007 +0200 +++ b/hgext/convert/__init__.py Sun Jun 10 20:08:47 2007 -0700 @@ -5,523 +5,16 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -import sys, os, zlib, sha, time, re, locale, socket +from common import NoRepo +from cvs import convert_cvs +from git import convert_git +from hg import convert_mercurial + +import os from mercurial import hg, ui, util, commands commands.norepo += " convert" -class NoRepo(Exception): pass - -class commit(object): - def __init__(self, **parts): - for x in "author date desc parents".split(): - if not x in parts: - raise util.Abort("commit missing field %s" % x) - self.__dict__.update(parts) - -def recode(s): - try: - return s.decode("utf-8").encode("utf-8") - except: - try: - return s.decode("latin-1").encode("utf-8") - except: - return s.decode("utf-8", "replace").encode("utf-8") - -class converter_source(object): - """Conversion source interface""" - - def __init__(self, ui, path): - """Initialize conversion source (or raise NoRepo("message") - exception if path is not a valid repository)""" - raise NotImplementedError() - - def getheads(self): - """Return a list of this repository's heads""" - raise NotImplementedError() - - def getfile(self, name, rev): - """Return file contents as a string""" - raise NotImplementedError() - - def getmode(self, name, rev): - """Return file mode, eg. '', 'x', or 'l'""" - raise NotImplementedError() - - def getchanges(self, version): - """Return sorted list of (filename, id) tuples for all files changed in rev. - - id just tells us which revision to return in getfile(), e.g. in - git it's an object hash.""" - raise NotImplementedError() - - def getcommit(self, version): - """Return the commit object for version""" - raise NotImplementedError() - - def gettags(self): - """Return the tags as a dictionary of name: revision""" - raise NotImplementedError() - -class converter_sink(object): - """Conversion sink (target) interface""" - - def __init__(self, ui, path): - """Initialize conversion sink (or raise NoRepo("message") - exception if path is not a valid repository)""" - raise NotImplementedError() - - def getheads(self): - """Return a list of this repository's heads""" - raise NotImplementedError() - - def mapfile(self): - """Path to a file that will contain lines - source_rev_id sink_rev_id - mapping equivalent revision identifiers for each system.""" - raise NotImplementedError() - - def putfile(self, f, e, data): - """Put file for next putcommit(). - f: path to file - e: '', 'x', or 'l' (regular file, executable, or symlink) - data: file contents""" - raise NotImplementedError() - - def delfile(self, f): - """Delete file for next putcommit(). - f: path to file""" - raise NotImplementedError() - - def putcommit(self, files, parents, commit): - """Create a revision with all changed files listed in 'files' - and having listed parents. 'commit' is a commit object containing - at a minimum the author, date, and message for this changeset. - Called after putfile() and delfile() calls. Note that the sink - repository is not told to update itself to a particular revision - (or even what that revision would be) before it receives the - file data.""" - raise NotImplementedError() - - def puttags(self, tags): - """Put tags into sink. - tags: {tagname: sink_rev_id, ...}""" - raise NotImplementedError() - - -# CVS conversion code inspired by hg-cvs-import and git-cvsimport -class convert_cvs(converter_source): - def __init__(self, ui, path): - self.path = path - self.ui = ui - 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 -u --cvs-direct -q"): - 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].rstrip() - 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 = [] - if branch == "HEAD": - branch = "" - c = commit(author=author, date=date, parents=p, - desc=log, branch=branch) - self.changeset[id] = c - self.files[id] = files - else: - colon = l.rfind(':') - file = l[1:colon] - rev = l[colon+1:-2] - rev = rev.split("->")[1] - files[file] = rev - - self.heads = self.lastbranch.values() - finally: - os.chdir(d) - - def _connect(self): - root = self.cvsroot - conntype = None - user, host = None, None - cmd = ['cvs', 'server'] - - self.ui.status("connecting to %s\n" % root) - - if root.startswith(":pserver:"): - root = root[9:] - m = re.match(r'(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?(.*)', - root) - if m: - conntype = "pserver" - user, passw, serv, port, root = m.groups() - if not user: - user = "anonymous" - rr = ":pserver:" + user + "@" + serv + ":" + root - if port: - rr2, port = "-", int(port) - else: - rr2, port = rr, 2401 - rr += str(port) - - if not passw: - passw = "A" - pf = open(os.path.join(os.environ["HOME"], ".cvspass")) - for l in pf: - # :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z - m = re.match(r'(/\d+\s+/)?(.*)', l) - l = m.group(2) - w, p = l.split(' ', 1) - if w in [rr, rr2]: - passw = p - break - pf.close() - - sck = socket.socket() - sck.connect((serv, port)) - sck.send("\n".join(["BEGIN AUTH REQUEST", root, user, passw, - "END AUTH REQUEST", ""])) - if sck.recv(128) != "I LOVE YOU\n": - raise NoRepo("CVS pserver authentication failed") - - self.writep = self.readp = sck.makefile('r+') - - if not conntype and root.startswith(":local:"): - conntype = "local" - root = root[7:] - - if not conntype: - # :ext:user@host/home/user/path/to/cvsroot - if root.startswith(":ext:"): - root = root[5:] - m = re.match(r'(?:([^@:/]+)@)?([^:/]+):?(.*)', root) - if not m: - conntype = "local" - else: - conntype = "rsh" - user, host, root = m.group(1), m.group(2), m.group(3) - - if conntype != "pserver": - if conntype == "rsh": - 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"): - raise util.Abort("server sucks") - 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 and "x" or "") - elif line.startswith("E "): - self.ui.warn("cvs server: %s\n" % line[2:]) - elif line.startswith("Remove"): - l = self.readp.readline() - l = self.readp.readline() - if l != "ok\n": - raise util.Abort("unknown CVS response: %s" % l) - else: - raise util.Abort("unknown CVS response: %s" % 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(converter_source): - def __init__(self, ui, path): - if os.path.isdir(path + "/.git"): - path += "/.git" - self.path = path - self.ui = ui - if not os.path.exists(path + "/objects"): - 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) - return [fh.read()[:-1]] - - def catfile(self, rev, type): - if rev == "0" * 40: raise IOError() - fh = os.popen("GIT_DIR=%s git-cat-file %s %s 2>/dev/null" - % (self.path, type, rev)) - return fh.read() - - 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: - if "\t" not in l: continue - m, f = l[:-1].split("\t") - m = m.split() - h = m[3] - p = (m[1] == "100755") - s = (m[1] == "120000") - self.modecache[(f, h)] = (p and "x") or (s and "l") or "" - changes.append((f, h)) - return changes - - def getcommit(self, version): - c = self.catfile(version, "commit") # read the commit hash - end = c.find("\n\n") - message = c[end+2:] - message = recode(message) - l = c[:end].splitlines() - manifest = l[0].split()[1] - parents = [] - for e in l[1:]: - n, v = e.split(" ", 1) - if n == "author": - p = v.split() - tm, tz = p[-2:] - author = " ".join(p[:-2]) - if author[0] == "<": author = author[1:-1] - author = recode(author) - if n == "committer": - p = v.split() - tm, tz = p[-2:] - committer = " ".join(p[:-2]) - if committer[0] == "<": committer = committer[1:-1] - committer = recode(committer) - message += "\ncommitter: %s\n" % committer - if n == "parent": parents.append(v) - - tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:] - tz = -int(tzs) * (int(tzh) * 3600 + int(tzm)) - date = tm + " " + str(tz) - - c = commit(parents=parents, date=date, author=author, desc=message) - return c - - def gettags(self): - tags = {} - fh = os.popen('git-ls-remote --tags "%s" 2>/dev/null' % self.path) - prefix = 'refs/tags/' - for line in fh: - line = line.strip() - if not line.endswith("^{}"): - continue - node, tag = line.split(None, 1) - if not tag.startswith(prefix): - continue - tag = tag[len(prefix):-3] - tags[tag] = node - - return tags - -class convert_mercurial(converter_sink): - def __init__(self, ui, path): - self.path = path - self.ui = ui - try: - self.repo = hg.repository(self.ui, path) - except: - raise NoRepo("could open hg repo %s" % path) - - def mapfile(self): - return os.path.join(self.path, ".hg", "shamap") - - def getheads(self): - h = self.repo.changelog.heads() - return [ hg.hex(x) for x in h ] - - def putfile(self, f, e, data): - self.repo.wwrite(f, data, e) - if self.repo.dirstate.state(f) == '?': - self.repo.dirstate.update([f], "a") - - def delfile(self, f): - try: - os.unlink(self.repo.wjoin(f)) - #self.repo.remove([f]) - except: - pass - - def putcommit(self, files, parents, commit): - seen = {} - pl = [] - for p in parents: - if p not in seen: - pl.append(p) - seen[p] = 1 - parents = pl - - if len(parents) < 2: parents.append("0" * 40) - 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) - 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()) - - return p2 - - def puttags(self, tags): - try: - old = self.repo.wfile(".hgtags").read() - oldlines = old.splitlines(1) - oldlines.sort() - except: - oldlines = [] - - k = tags.keys() - k.sort() - newlines = [] - for tag in k: - newlines.append("%s %s\n" % (tags[tag], tag)) - - newlines.sort() - - if newlines != oldlines: - self.ui.status("updating tags\n") - f = self.repo.wfile(".hgtags", "w") - f.write("".join(newlines)) - f.close() - if not oldlines: self.repo.add([".hgtags"]) - date = "%s 0" % int(time.mktime(time.gmtime())) - self.repo.rawcommit([".hgtags"], "update tags", "convert-repo", - date, self.repo.changelog.tip(), hg.nullid) - return hg.hex(self.repo.changelog.tip()) - converters = [convert_cvs, convert_git, convert_mercurial] def converter(ui, path):
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/convert/common.py Sun Jun 10 20:08:47 2007 -0700 @@ -0,0 +1,90 @@ +# common code for the convert extension + +class NoRepo(Exception): pass + +class commit(object): + def __init__(self, **parts): + for x in "author date desc parents".split(): + if not x in parts: + raise util.Abort("commit missing field %s" % x) + self.__dict__.update(parts) + +class converter_source(object): + """Conversion source interface""" + + def __init__(self, ui, path): + """Initialize conversion source (or raise NoRepo("message") + exception if path is not a valid repository)""" + raise NotImplementedError() + + def getheads(self): + """Return a list of this repository's heads""" + raise NotImplementedError() + + def getfile(self, name, rev): + """Return file contents as a string""" + raise NotImplementedError() + + def getmode(self, name, rev): + """Return file mode, eg. '', 'x', or 'l'""" + raise NotImplementedError() + + def getchanges(self, version): + """Return sorted list of (filename, id) tuples for all files changed in rev. + + id just tells us which revision to return in getfile(), e.g. in + git it's an object hash.""" + raise NotImplementedError() + + def getcommit(self, version): + """Return the commit object for version""" + raise NotImplementedError() + + def gettags(self): + """Return the tags as a dictionary of name: revision""" + raise NotImplementedError() + +class converter_sink(object): + """Conversion sink (target) interface""" + + def __init__(self, ui, path): + """Initialize conversion sink (or raise NoRepo("message") + exception if path is not a valid repository)""" + raise NotImplementedError() + + def getheads(self): + """Return a list of this repository's heads""" + raise NotImplementedError() + + def mapfile(self): + """Path to a file that will contain lines + source_rev_id sink_rev_id + mapping equivalent revision identifiers for each system.""" + raise NotImplementedError() + + def putfile(self, f, e, data): + """Put file for next putcommit(). + f: path to file + e: '', 'x', or 'l' (regular file, executable, or symlink) + data: file contents""" + raise NotImplementedError() + + def delfile(self, f): + """Delete file for next putcommit(). + f: path to file""" + raise NotImplementedError() + + def putcommit(self, files, parents, commit): + """Create a revision with all changed files listed in 'files' + and having listed parents. 'commit' is a commit object containing + at a minimum the author, date, and message for this changeset. + Called after putfile() and delfile() calls. Note that the sink + repository is not told to update itself to a particular revision + (or even what that revision would be) before it receives the + file data.""" + raise NotImplementedError() + + def puttags(self, tags): + """Put tags into sink. + tags: {tagname: sink_rev_id, ...}""" + raise NotImplementedError()
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/convert/cvs.py Sun Jun 10 20:08:47 2007 -0700 @@ -0,0 +1,244 @@ +# CVS conversion code inspired by hg-cvs-import and git-cvsimport + +import os, locale, re, socket +from mercurial import util + +from common import NoRepo, commit, converter_source + +class convert_cvs(converter_source): + def __init__(self, ui, path): + self.path = path + self.ui = ui + 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 -u --cvs-direct -q"): + 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].rstrip() + 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 = [] + if branch == "HEAD": + branch = "" + c = commit(author=author, date=date, parents=p, + desc=log, branch=branch) + self.changeset[id] = c + self.files[id] = files + else: + colon = l.rfind(':') + file = l[1:colon] + rev = l[colon+1:-2] + rev = rev.split("->")[1] + files[file] = rev + + self.heads = self.lastbranch.values() + finally: + os.chdir(d) + + def _connect(self): + root = self.cvsroot + conntype = None + user, host = None, None + cmd = ['cvs', 'server'] + + self.ui.status("connecting to %s\n" % root) + + if root.startswith(":pserver:"): + root = root[9:] + m = re.match(r'(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?(.*)', + root) + if m: + conntype = "pserver" + user, passw, serv, port, root = m.groups() + if not user: + user = "anonymous" + rr = ":pserver:" + user + "@" + serv + ":" + root + if port: + rr2, port = "-", int(port) + else: + rr2, port = rr, 2401 + rr += str(port) + + if not passw: + passw = "A" + pf = open(os.path.join(os.environ["HOME"], ".cvspass")) + for l in pf: + # :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah<Z + m = re.match(r'(/\d+\s+/)?(.*)', l) + l = m.group(2) + w, p = l.split(' ', 1) + if w in [rr, rr2]: + passw = p + break + pf.close() + + sck = socket.socket() + sck.connect((serv, port)) + sck.send("\n".join(["BEGIN AUTH REQUEST", root, user, passw, + "END AUTH REQUEST", ""])) + if sck.recv(128) != "I LOVE YOU\n": + raise NoRepo("CVS pserver authentication failed") + + self.writep = self.readp = sck.makefile('r+') + + if not conntype and root.startswith(":local:"): + conntype = "local" + root = root[7:] + + if not conntype: + # :ext:user@host/home/user/path/to/cvsroot + if root.startswith(":ext:"): + root = root[5:] + m = re.match(r'(?:([^@:/]+)@)?([^:/]+):?(.*)', root) + if not m: + conntype = "local" + else: + conntype = "rsh" + user, host, root = m.group(1), m.group(2), m.group(3) + + if conntype != "pserver": + if conntype == "rsh": + 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"): + raise util.Abort("server sucks") + 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 and "x" or "") + elif line.startswith("E "): + self.ui.warn("cvs server: %s\n" % line[2:]) + elif line.startswith("Remove"): + l = self.readp.readline() + l = self.readp.readline() + if l != "ok\n": + raise util.Abort("unknown CVS response: %s" % l) + else: + raise util.Abort("unknown CVS response: %s" % 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
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/convert/git.py Sun Jun 10 20:08:47 2007 -0700 @@ -0,0 +1,103 @@ +# git support for the convert extension + +import os + +from common import NoRepo, commit, converter_source + +def recode(s): + try: + return s.decode("utf-8").encode("utf-8") + except: + try: + return s.decode("latin-1").encode("utf-8") + except: + return s.decode("utf-8", "replace").encode("utf-8") + +class convert_git(converter_source): + def __init__(self, ui, path): + if os.path.isdir(path + "/.git"): + path += "/.git" + self.path = path + self.ui = ui + if not os.path.exists(path + "/objects"): + 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) + return [fh.read()[:-1]] + + def catfile(self, rev, type): + if rev == "0" * 40: raise IOError() + fh = os.popen("GIT_DIR=%s git-cat-file %s %s 2>/dev/null" + % (self.path, type, rev)) + return fh.read() + + 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: + if "\t" not in l: continue + m, f = l[:-1].split("\t") + m = m.split() + h = m[3] + p = (m[1] == "100755") + s = (m[1] == "120000") + self.modecache[(f, h)] = (p and "x") or (s and "l") or "" + changes.append((f, h)) + return changes + + def getcommit(self, version): + c = self.catfile(version, "commit") # read the commit hash + end = c.find("\n\n") + message = c[end+2:] + message = recode(message) + l = c[:end].splitlines() + manifest = l[0].split()[1] + parents = [] + for e in l[1:]: + n, v = e.split(" ", 1) + if n == "author": + p = v.split() + tm, tz = p[-2:] + author = " ".join(p[:-2]) + if author[0] == "<": author = author[1:-1] + author = recode(author) + if n == "committer": + p = v.split() + tm, tz = p[-2:] + committer = " ".join(p[:-2]) + if committer[0] == "<": committer = committer[1:-1] + committer = recode(committer) + message += "\ncommitter: %s\n" % committer + if n == "parent": parents.append(v) + + tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:] + tz = -int(tzs) * (int(tzh) * 3600 + int(tzm)) + date = tm + " " + str(tz) + + c = commit(parents=parents, date=date, author=author, desc=message) + return c + + def gettags(self): + tags = {} + fh = os.popen('git-ls-remote --tags "%s" 2>/dev/null' % self.path) + prefix = 'refs/tags/' + for line in fh: + line = line.strip() + if not line.endswith("^{}"): + continue + node, tag = line.split(None, 1) + if not tag.startswith(prefix): + continue + tag = tag[len(prefix):-3] + tags[tag] = node + + return tags
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/hgext/convert/hg.py Sun Jun 10 20:08:47 2007 -0700 @@ -0,0 +1,91 @@ +# hg backend for convert extension + +import os, time +from mercurial import hg + +from common import NoRepo, converter_sink + +class convert_mercurial(converter_sink): + def __init__(self, ui, path): + self.path = path + self.ui = ui + try: + self.repo = hg.repository(self.ui, path) + except: + raise NoRepo("could open hg repo %s" % path) + + def mapfile(self): + return os.path.join(self.path, ".hg", "shamap") + + def getheads(self): + h = self.repo.changelog.heads() + return [ hg.hex(x) for x in h ] + + def putfile(self, f, e, data): + self.repo.wwrite(f, data, e) + if self.repo.dirstate.state(f) == '?': + self.repo.dirstate.update([f], "a") + + def delfile(self, f): + try: + os.unlink(self.repo.wjoin(f)) + #self.repo.remove([f]) + except: + pass + + def putcommit(self, files, parents, commit): + seen = {} + pl = [] + for p in parents: + if p not in seen: + pl.append(p) + seen[p] = 1 + parents = pl + + if len(parents) < 2: parents.append("0" * 40) + 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) + 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()) + + return p2 + + def puttags(self, tags): + try: + old = self.repo.wfile(".hgtags").read() + oldlines = old.splitlines(1) + oldlines.sort() + except: + oldlines = [] + + k = tags.keys() + k.sort() + newlines = [] + for tag in k: + newlines.append("%s %s\n" % (tags[tag], tag)) + + newlines.sort() + + if newlines != oldlines: + self.ui.status("updating tags\n") + f = self.repo.wfile(".hgtags", "w") + f.write("".join(newlines)) + f.close() + if not oldlines: self.repo.add([".hgtags"]) + date = "%s 0" % int(time.mktime(time.gmtime())) + self.repo.rawcommit([".hgtags"], "update tags", "convert-repo", + date, self.repo.changelog.tip(), hg.nullid) + return hg.hex(self.repo.changelog.tip())