Signed-off-by: Felipe Contreras <[email protected]>
---
contrib/remote-hg/git-remote-hg | 313 ++++++++++++++++++++++++++++++++++++++++
1 file changed, 313 insertions(+)
create mode 100755 contrib/remote-hg/git-remote-hg
diff --git a/contrib/remote-hg/git-remote-hg b/contrib/remote-hg/git-remote-hg
new file mode 100755
index 0000000..f0ce4a4
--- /dev/null
+++ b/contrib/remote-hg/git-remote-hg
@@ -0,0 +1,313 @@
+#!/usr/bin/python
+
+# Inspired by Rocco Rutte's hg-fast-export
+
+# Just copy to your ~/bin, or anywhere in your $PATH.
+# Then you can clone with:
+# git clone hg::/path/to/mercurial/repo/
+
+from mercurial import hg, ui
+
+import re
+import sys
+import os
+import json
+
+first = True
+
+AUTHOR_RE = re.compile('^((.+?) )?(<.+?>)$')
+
+def die(msg, *args):
+ sys.stderr.write('ERROR: %s\n' % (msg % args))
+ sys.exit(1)
+
+def warn(msg, *args):
+ sys.stderr.write('WARNING: %s\n' % (msg % args))
+
+def gitmode(flags):
+ return 'l' in flags and '120000' or 'x' in flags and '100755' or '100644'
+
+class Marks:
+
+ def __init__(self, path):
+ self.path = path
+ self.tips = {}
+ self.marks = {}
+ self.last_mark = 0
+
+ self.load()
+
+ def load(self):
+ if not os.path.exists(self.path):
+ return
+
+ tmp = json.load(open(self.path))
+
+ self.tips = tmp['tips']
+ self.marks = tmp['marks']
+ self.last_mark = tmp['last-mark']
+
+ def dict(self):
+ return { 'tips': self.tips, 'marks': self.marks, 'last-mark' :
self.last_mark }
+
+ def store(self):
+ json.dump(self.dict(), open(self.path, 'w'))
+
+ def __str__(self):
+ return str(self.dict())
+
+ def from_rev(self, rev):
+ return self.marks[str(rev)]
+
+ def next_mark(self, rev):
+ self.last_mark += 1
+ self.marks[str(rev)] = self.last_mark
+ return self.last_mark
+
+ def is_marked(self, rev):
+ return self.marks.has_key(str(rev))
+
+ def get_tip(self, branch):
+ return self.tips.get(branch, 0)
+
+ def set_tip(self, branch, tip):
+ self.tips[branch] = tip
+
+class Parser:
+
+ def __init__(self, repo):
+ self.repo = repo
+ self.line = self.get_line()
+
+ def get_line(self):
+ return sys.stdin.readline().strip()
+
+ def __getitem__(self, i):
+ return self.line.split()[i]
+
+ def check(self, word):
+ return self.line.startswith(word)
+
+ def each_block(self, separator):
+ while self.line != separator:
+ yield self.line
+ self.line = self.get_line()
+
+ def __iter__(self):
+ return self.each_block('')
+
+def export_file(fc):
+ if fc.path() == '.hgtags':
+ return
+ d = fc.data()
+ print "M %s inline %s" % (gitmode(fc.flags()), fc.path())
+ print "data %d" % len(d)
+ print d
+
+def get_filechanges(repo, ctx, parents):
+ l = [repo.status(p, ctx)[:3] for p in parents]
+ changed, added, removed = [sum(e, []) for e in zip(*l)]
+ return added + changed, removed
+
+def fixup_user(user):
+ user = user.replace('"', '')
+ m = AUTHOR_RE.match(user)
+ if m:
+ name = m.group(2)
+ mail = m.group(3)
+ else:
+ name = user
+ mail = None
+
+ if not name:
+ name = 'Unknown'
+ if not mail:
+ mail = '<unknown>'
+
+ return '%s %s' % (name, mail)
+
+def get_repo(url, alias):
+ global dirname
+
+ myui = ui.ui()
+ myui.setconfig('ui', 'interactive', 'off')
+
+ if hg.islocal(url):
+ repo = hg.repository(myui, url)
+ else:
+ local_path = os.path.join(dirname, 'clone')
+ if not os.path.exists(local_path):
+ srcpeer, dstpeer = hg.clone(myui, {}, url, local_path,
update=False, pull=True)
+ repo = dstpeer.local()
+ else:
+ repo = hg.repository(myui, local_path)
+ peer = hg.peer(myui, {}, url)
+ repo.pull(peer, heads=None, force=True)
+
+ return repo
+
+def rev_to_mark(rev):
+ global marks
+ return marks.from_rev(rev)
+
+def export_tag(repo, tag):
+ global prefix
+ print "reset %s/tags/%s" % (prefix, tag)
+ print "from :%s" % (rev_to_mark(repo[tag].rev()))
+ print
+
+def export_branch(repo, branch):
+ global prefix, marks, branches
+
+ heads = branches[branch]
+
+ # verify there's only one head
+ if (len(heads) > 1):
+ warn("Branch '%s' has more than one head, consider merging" % branch)
+ tip = repo.branchtip(branch)
+ head = repo[tip]
+ else:
+ head = repo[heads[0]]
+
+ tip = marks.get_tip(branch)
+ # mercurial takes too much time checking this
+ if tip and tip == head.rev():
+ # nothing to do
+ return
+ revs = repo.revs('%u:%u' % (tip, head))
+ count = 0
+
+ revs = [rev for rev in revs if not marks.is_marked(rev)]
+
+ for rev in revs:
+
+ c = repo[rev]
+ (manifest, user, (time, tz), files, desc, extra) =
repo.changelog.read(c.node())
+ rev_branch = extra['branch']
+
+ tz = '%+03d%02d' % (-tz / 3600, -tz % 3600 / 60)
+
+ print "commit %s/branches/%s" % (prefix, rev_branch)
+ print "mark :%d" % (marks.next_mark(rev))
+ print "committer %s %d %s" % (fixup_user(user), time, tz)
+ print "data %d" % (len(desc) + 1)
+ print desc
+ print
+
+ parents = [p for p in repo.changelog.parentrevs(rev) if p >= 0]
+
+ if len(parents) == 0:
+ modified = c.manifest().keys()
+ removed = []
+ else:
+ added = []
+ changed = []
+ print "from :%s" % (rev_to_mark(parents[0]))
+ if len(parents) > 1:
+ print "merge :%s" % (rev_to_mark(parents[1]))
+ modified, removed = get_filechanges(repo, c, parents)
+
+ for f in removed:
+ print "D %s" % (f)
+ for f in modified:
+ export_file(c.filectx(f))
+ print
+
+ count += 1
+ if (count % 100 == 0):
+ print "progress revision %d '%s' (%d/%d)" % (rev, branch, count,
len(revs))
+ print
"#############################################################"
+
+ # make sure the ref is updated
+ print "reset %s/branches/%s" % (prefix, branch)
+ print "from :%u" % rev_to_mark(rev)
+ print
+
+ marks.set_tip(branch, rev)
+
+def do_capabilities(parser):
+ global prefix, dirname
+
+ print "import"
+ print "refspec refs/heads/*:%s/branches/*" % prefix
+ print "refspec refs/tags/*:%s/tags/*" % prefix
+ print
+
+def do_list(parser):
+ global branches
+
+ repo = parser.repo
+ head = repo.dirstate.branch()
+ for branch in repo.branchmap():
+ heads = repo.branchheads(branch)
+ if len(heads):
+ branches[branch] = heads
+
+ for branch in branches:
+ print "? refs/heads/%s" % branch
+ for tag, node in repo.tagslist():
+ if tag == 'tip':
+ continue
+ print "? refs/tags/%s" % tag
+ print "@refs/heads/%s HEAD" % head
+ print
+
+def do_import(parser):
+ global first
+
+ repo = parser.repo
+ ref = parser[1]
+
+ if first:
+ path = os.path.join(dirname, 'marks-git')
+
+ if os.path.exists(path):
+ print "feature import-marks=%s" % path
+ print "feature export-marks=%s" % path
+ sys.stdout.flush()
+ first = False
+
+ if (ref == 'HEAD'):
+ return
+
+ if ref.startswith('refs/heads/'):
+ branch = ref[len('refs/heads/'):]
+ export_branch(repo, branch)
+ elif ref.startswith('refs/tags/'):
+ tag = ref[len('refs/tags/'):]
+ export_tag(repo, tag)
+
+def main(args):
+ global prefix, dirname, marks, branches
+
+ alias = args[1]
+ url = args[2]
+
+ gitdir = os.environ['GIT_DIR']
+ dirname = os.path.join(gitdir, 'hg', alias)
+ branches = {}
+
+ repo = get_repo(url, alias)
+ prefix = 'refs/hg/%s' % alias
+
+ if not os.path.exists(dirname):
+ os.makedirs(dirname)
+
+ marks_path = os.path.join(dirname, 'marks-hg')
+ marks = Marks(marks_path)
+
+ parser = Parser(repo)
+ for line in parser:
+ if parser.check('capabilities'):
+ do_capabilities(parser)
+ elif parser.check('list'):
+ do_list(parser)
+ elif parser.check('import'):
+ do_import(parser)
+ elif parser.check('export'):
+ do_export(parser)
+ sys.stdout.flush()
+
+ marks.store()
+
+sys.exit(main(sys.argv))
--
1.8.0.rc2.7.g0961fdf.dirty
--
To unsubscribe from this list: send the line "unsubscribe git" in
the body of a message to [email protected]
More majordomo info at http://vger.kernel.org/majordomo-info.html