Some parsing of fast-export parsing might be missing, but I couldn't find any. Signed-off-by: Felipe Contreras <felipe.contreras@xxxxxxxxx> --- contrib/remote-hg/git-remote-hg | 156 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 154 insertions(+), 2 deletions(-) diff --git a/contrib/remote-hg/git-remote-hg b/contrib/remote-hg/git-remote-hg index f0ce4a4..fc4510c 100755 --- a/contrib/remote-hg/git-remote-hg +++ b/contrib/remote-hg/git-remote-hg @@ -6,7 +6,7 @@ # Then you can clone with: # git clone hg::/path/to/mercurial/repo/ -from mercurial import hg, ui +from mercurial import hg, ui, context import re import sys @@ -16,6 +16,7 @@ import json first = True AUTHOR_RE = re.compile('^((.+?) )?(<.+?>)$') +RAW_AUTHOR_RE = re.compile('^(\w+) (.+) <(.+)> (\d+) ([+-]\d+)') def die(msg, *args): sys.stderr.write('ERROR: %s\n' % (msg % args)) @@ -27,12 +28,17 @@ def warn(msg, *args): def gitmode(flags): return 'l' in flags and '120000' or 'x' in flags and '100755' or '100644' +def hgmode(mode): + m = { '0100755': 'x', '0120000': 'l' } + return m.get(mode, '') + class Marks: def __init__(self, path): self.path = path self.tips = {} self.marks = {} + self.rev_hgmarks = {} self.last_mark = 0 self.load() @@ -47,6 +53,9 @@ class Marks: self.marks = tmp['marks'] self.last_mark = tmp['last-mark'] + for rev, mark in self.marks.iteritems(): + self.rev_hgmarks[mark] = int(rev) + def dict(self): return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark } @@ -59,11 +68,19 @@ class Marks: def from_rev(self, rev): return self.marks[str(rev)] + def to_rev(self, mark): + return self.rev_hgmarks[mark] + def next_mark(self, rev): self.last_mark += 1 self.marks[str(rev)] = self.last_mark return self.last_mark + def new_mark(self, rev, mark): + self.marks[str(rev)] = mark + self.rev_hgmarks[mark] = rev + self.last_mark = mark + def is_marked(self, rev): return self.marks.has_key(str(rev)) @@ -96,6 +113,28 @@ class Parser: def __iter__(self): return self.each_block('') + def next(self): + self.line = self.get_line() + if self.line == 'done': + self.line = None + + def get_mark(self): + i = self.line.index(':') + 1 + return int(self.line[i:]) + + def get_data(self): + if not self.check('data'): + return None + i = self.line.index(' ') + 1 + size = int(self.line[i:]) + return sys.stdin.read(size) + + def get_author(self): + m = RAW_AUTHOR_RE.match(self.line) + if not m: + return None + return list(m.groups())[1:] + def export_file(fc): if fc.path() == '.hgtags': return @@ -150,6 +189,10 @@ def rev_to_mark(rev): global marks return marks.from_rev(rev) +def mark_to_rev(mark): + global marks + return marks.to_rev(mark) + def export_tag(repo, tag): global prefix print "reset %s/tags/%s" % (prefix, tag) @@ -229,8 +272,16 @@ def do_capabilities(parser): global prefix, dirname print "import" + print "export" print "refspec refs/heads/*:%s/branches/*" % prefix print "refspec refs/tags/*:%s/tags/*" % prefix + + path = os.path.join(dirname, 'marks-git') + + if os.path.exists(path): + print "*import-marks %s" % path + print "*export-marks %s" % path + print def do_list(parser): @@ -277,8 +328,108 @@ def do_import(parser): tag = ref[len('refs/tags/'):] export_tag(repo, tag) +def parse_blob(parser): + global blob_marks + + parser.next() + if parser.check('mark'): + mark = parser.get_mark() + parser.next() + data = parser.get_data() + blob_marks[mark] = data + return + +def parse_commit(parser): + global marks, blob_marks + + from_mark = merge_mark = None + + a = parser.line.split(' ') + ref = a[1] + if ref.startswith('refs/heads/'): + branch = ref[len('refs/heads/'):] + parser.next() + + if parser.check('mark'): + commit_mark = parser.get_mark() + parser.next() + if parser.check('author'): + author = parser.get_author() + parser.next() + committer = parser.get_author() + parser.next() + data = parser.get_data() + parser.next() + if parser.check('from'): + from_mark = parser.get_mark() + parser.next() + if parser.check('merge'): + merge_mark = parser.get_mark() + parser.next() + if parser.check('merge'): + die('octopus merges are not supported yet') + + files = {} + + for line in parser: + if parser.check('M'): + t, mode, mark_ref, path = line.split(' ') + mark = int(mark_ref[1:]) + f = { 'mode' : hgmode(mode), 'data' : blob_marks[mark] } + elif parser.check('D'): + t, path = line.split(' ') + f = { 'deleted' : True } + else: + die('Unknown file command: %s' % line) + files[path] = f + + def getfilectx(repo, memctx, f): + of = files[f] + if 'deleted' in of: + raise IOError + return context.memfilectx(f, of['data'], False, False, None) + + repo = parser.repo + + committer_name, committer_email, date, tz = committer + date = int(date) + tz = int(tz) + tz = ((tz / 100) * 3600) + ((tz % 100) * 60) + extra = {} + extra['branch'] = branch + + if from_mark: + p1 = repo.changelog.node(mark_to_rev(from_mark)) + else: + p1 = '\0' * 20 + + if merge_mark: + p2 = repo.changelog.node(mark_to_rev(merge_mark)) + else: + p2 = '\0' * 20 + + ctx = context.memctx(repo, (p1, p2), data, + files.keys(), getfilectx, + '%s <%s>' % (committer_name, committer_email), (date, -tz), extra) + + node = repo.commitctx(ctx) + rev = repo[node].rev() + + marks.new_mark(rev, commit_mark) + + print "ok %s" % ref + +def do_export(parser): + for line in parser.each_block('done'): + if parser.check('blob'): + parse_blob(parser) + elif parser.check('commit'): + parse_commit(parser) + print + def main(args): - global prefix, dirname, marks, branches + global prefix, dirname, branches + global marks, blob_marks alias = args[1] url = args[2] @@ -286,6 +437,7 @@ def main(args): gitdir = os.environ['GIT_DIR'] dirname = os.path.join(gitdir, 'hg', alias) branches = {} + blob_marks = {} repo = get_repo(url, alias) prefix = 'refs/hg/%s' % alias -- 1.8.0.rc2.7.g0961fdf.dirty -- To unsubscribe from this list: send the line "unsubscribe git" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html