git/list[1] front-page[2] threads[3] people[4] search[5] about
 

[PATCH v4 03/13] remote-hg: add support for pushing

From
Felipe Contreras <felipe.contreras@gmail.com>
Date
Oct 28, 2012, 03:54 UTC
Message-ID
<1351396453-29042-4-git-send-email-felipe.contreras@gmail.com>
In-Reply-To
<1351396453-29042-1-git-send-email-felipe.contreras@gmail.com>
Signed-off-by: Felipe Contreras <felipe.contreras@gmail.com>
---
 contrib/remote-hg/git-remote-hg | 215 +++++++++++++++++++++++++++++++++++++++-
 1 file changed, 213 insertions(+), 2 deletions(-)
diff --git a/contrib/remote-hg/git-remote-hg b/contrib/remote-hg/git-remote-hg
index 4ba9ee6..4021a7d 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, bookmarks
+from mercurial import hg, ui, bookmarks, context
 
 import re
 import sys
@@ -15,6 +15,7 @@ import json
 
 NAME_RE = re.compile('^([^<>]+)')
 AUTHOR_RE = re.compile('^([^<>]+?)? ?<([^<>]+)>$')
+RAW_AUTHOR_RE = re.compile('^(\w+) (?:(.+)? )?<(.+)> (\d+) ([+-]\d+)')
 
 def die(msg, *args):
     sys.stderr.write('ERROR: %s\n' % (msg % args))
@@ -29,12 +30,17 @@ def gitmode(flags):
 def gittz(tz):
     return '%+03d%02d' % (-tz / 3600, -tz % 3600 / 60)
 
+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_marks = {}
         self.last_mark = 0
 
         self.load()
@@ -49,6 +55,9 @@ class Marks:
         self.marks = tmp['marks']
         self.last_mark = tmp['last-mark']
 
+        for rev, mark in self.marks.iteritems():
+            self.rev_marks[mark] = int(rev)
+
     def dict(self):
         return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark }
 
@@ -61,11 +70,19 @@ class Marks:
     def from_rev(self, rev):
         return self.marks[str(rev)]
 
+    def to_rev(self, mark):
+        return self.rev_marks[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_marks[mark] = rev
+        self.last_mark = mark
+
     def is_marked(self, rev):
         return self.marks.has_key(str(rev))
 
@@ -103,6 +120,35 @@ class Parser:
         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
+        _, name, email, date, tz = m.groups()
+
+        if email != 'unknown':
+            if name:
+                user = '%s <%s>' % (name, email)
+            else:
+                user = '<%s>' % (email)
+        else:
+            user = name
+
+        tz = int(tz)
+        tz = ((tz / 100) * 3600) + ((tz % 100) * 60)
+        return (user, int(date), -tz)
+
 def export_file(fc):
     d = fc.data()
     print "M %s inline %s" % (gitmode(fc.flags()), fc.path())
@@ -157,6 +203,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_ref(repo, name, kind, head):
     global prefix, marks
 
@@ -246,9 +296,17 @@ def do_capabilities(parser):
     global prefix, dirname
 
     print "import"
+    print "export"
     print "refspec refs/heads/branches/*:%s/branches/*" % prefix
     print "refspec refs/heads/*:%s/bookmarks/*" % 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 get_branch_tip(repo, branch):
@@ -331,8 +389,159 @@ def do_import(parser):
 
     print 'done'
 
+def parse_blob(parser):
+    global blob_marks
+
+    parser.next()
+    mark = parser.get_mark()
+    parser.next()
+    data = parser.get_data()
+    blob_marks[mark] = data
+    parser.next()
+    return
+
+def parse_commit(parser):
+    global marks, blob_marks, bmarks, parsed_refs
+
+    from_mark = merge_mark = None
+
+    a = parser.line.split(' ')
+    ref = a[1]
+    parser.next()
+
+    commit_mark = parser.get_mark()
+    parser.next()
+    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, m, mark_ref, path = line.split(' ')
+            mark = int(mark_ref[1:])
+            f = { 'mode' : hgmode(m), '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
+        is_exec = of['mode'] == 'x'
+        is_link = of['mode'] == 'l'
+        return context.memfilectx(f, of['data'], is_link, is_exec, None)
+
+    repo = parser.repo
+
+    user, date, tz = author
+    extra = {}
+
+    if committer != author:
+        extra['committer'] = "%s %u %u" % committer
+
+    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,
+            user, (date, tz), extra)
+
+    node = repo.commitctx(ctx)
+
+    rev = repo[node].rev()
+
+    parsed_refs[ref] = node
+
+    marks.new_mark(rev, commit_mark)
+
+def parse_reset(parser):
+    a = parser.line.split(' ')
+    ref = a[1]
+    parser.next()
+    # ugh
+    if parser.check('commit'):
+        parse_commit(parser)
+        return
+    if not parser.check('from'):
+        return
+    from_mark = parser.get_mark()
+    parser.next()
+
+    node = parser.repo.changelog.node(mark_to_rev(from_mark))
+    parsed_refs[ref] = node
+
+def parse_tag(parser):
+    a = parser.line.split(' ')
+    name = a[1]
+    parser.next()
+    from_mark = parser.get_mark()
+    parser.next()
+    tagger = parser.get_author()
+    parser.next()
+    data = parser.get_data()
+    parser.next()
+
+    # nothing to do
+
+def do_export(parser):
+    global parsed_refs
+
+    parser.next()
+
+    for line in parser.each_block('done'):
+        if parser.check('blob'):
+            parse_blob(parser)
+        elif parser.check('commit'):
+            parse_commit(parser)
+        elif parser.check('reset'):
+            parse_reset(parser)
+        elif parser.check('tag'):
+            parse_tag(parser)
+        elif parser.check('feature'):
+            pass
+        else:
+            die('unhandled export command: %s' % line)
+
+    for ref, node in parsed_refs.iteritems():
+        if ref.startswith('refs/heads/branches'):
+            pass
+        elif ref.startswith('refs/heads/'):
+            bmark = ref[len('refs/heads/'):]
+            bookmarks.pushbookmark(parser.repo, bmark, '', node)
+        elif ref.startswith('refs/tags/'):
+            tag = ref[len('refs/tags/'):]
+            parser.repo.tag([tag], node, None, True, None, {})
+        print "ok %s" % ref
+
+    print
+
 def main(args):
-    global prefix, dirname, marks, branches, bmarks
+    global prefix, dirname, branches, bmarks
+    global marks, blob_marks, parsed_refs
 
     alias = args[1]
     url = args[2]
@@ -341,6 +550,8 @@ def main(args):
     dirname = os.path.join(gitdir, 'hg', alias)
     branches = {}
     bmarks = {}
+    blob_marks = {}
+    parsed_refs = {}
 
     repo = get_repo(url, alias)
     prefix = 'refs/hg/%s' % alias
-- 
1.8.0
Previous: Felipe ContrerasNext: Felipe Contreras
Message 4 of 75 in “New remote-hg helper”
  1. 00/13 New remote-hg helperFelipe Contreras, Oct 28, 2012
  2. 01/13 Add new remote-hg transport helperFelipe Contreras, Oct 28, 2012
  3. 02/13 remote-hg: add support for bookmarksFelipe Contreras, Oct 28, 2012
  4. 03/13 remote-hg: add support for pushingFelipe Contreras, Oct 28, 2012
  5. 04/13 remote-hg: add support for remote pushingFelipe Contreras, Oct 28, 2012
  6. 05/13 remote-hg: add support to push URLsFelipe Contreras, Oct 28, 2012
  7. 06/13 remote-hg: make sure the encoding is correctFelipe Contreras, Oct 28, 2012
  8. 07/13 remote-hg: match hg merge behaviorFelipe Contreras, Oct 28, 2012
  9. 08/13 remote-hg: add support for hg-git compat modeFelipe Contreras, Oct 28, 2012
  10. 09/13 remote-hg: add compat for hg-git author fixesFelipe Contreras, Oct 28, 2012
  11. 10/13 remote-hg: fake bookmark when there's noneFelipe Contreras, Oct 28, 2012
  12. 11/13 remote-hg: add support for fake remoteFelipe Contreras, Oct 28, 2012
  13. 12/13 remote-hg: add tests to compare with hg-gitFelipe Contreras, Oct 28, 2012
  14. 13/13 remote-hg: add extra author testFelipe Contreras, Oct 28, 2012
  15. Jeff KingOct 29, 2012
  16. Felipe ContrerasOct 29, 2012
  17. Jeff KingOct 29, 2012
  18. Felipe ContrerasOct 29, 2012
  19. Jeff KingOct 29, 2012
  20. Felipe ContrerasOct 29, 2012
  21. Jeff KingOct 29, 2012
  22. Felipe ContrerasOct 30, 2012
  23. Johannes SchindelinOct 30, 2012
  24. Felipe ContrerasOct 30, 2012
  25. Johannes SchindelinOct 30, 2012
  26. Felipe ContrerasOct 30, 2012
  27. Michael J GruberOct 31, 2012
  28. Jeff KingOct 31, 2012
  29. Felipe ContrerasOct 31, 2012
  30. Johannes SchindelinOct 31, 2012
  31. Felipe ContrerasOct 31, 2012
  32. Jonathan NiederOct 31, 2012
  33. Felipe ContrerasOct 31, 2012
  34. Lack of netiquette, was Re: [PATCH v4 00/13] New remote-hg helperJohannes Schindelin, Oct 31, 2012
  35. Felipe ContrerasOct 31, 2012
  36. Junio C HamanoNov 1, 2012
  37. Felipe ContrerasNov 1, 2012
  38. René ScharfeNov 1, 2012
  39. Tomas CarneckyNov 1, 2012
  40. Martin LanghoffNov 1, 2012
  41. Felipe ContrerasNov 1, 2012
  42. Martin LanghoffNov 1, 2012
  43. Felipe ContrerasNov 1, 2012
  44. Andreas EricssonNov 2, 2012
  45. Michael J GruberNov 2, 2012
  46. Felipe ContrerasNov 2, 2012
  47. Michael J GruberNov 5, 2012
  48. Felipe ContrerasNov 5, 2012
  49. Felipe ContrerasNov 5, 2012
  50. Michael J GruberNov 5, 2012
  51. Felipe ContrerasNov 5, 2012
  52. Jonathan NiederNov 1, 2012
  53. Daniel BarkalowOct 31, 2012
  54. Felipe ContrerasNov 1, 2012
  55. Junio C HamanoNov 1, 2012
  56. Felipe ContrerasNov 1, 2012
  57. Felipe ContrerasOct 31, 2012
  58. Michael J GruberOct 31, 2012
  59. Felipe ContrerasOct 31, 2012
  60. Jeff KingNov 2, 2012
  61. Felipe ContrerasNov 2, 2012
  62. Felipe ContrerasNov 2, 2012
  63. Felipe ContrerasNov 4, 2012
  64. Thomas AdamNov 2, 2012
  65. Felipe ContrerasNov 2, 2012
  66. Felipe ContrerasOct 31, 2012
  67. Felipe ContrerasOct 31, 2012
  68. Felipe ContrerasNov 1, 2012
  69. Jeff KingNov 2, 2012
  70. Felipe ContrerasNov 2, 2012
  71. Felipe ContrerasNov 2, 2012
  72. Michael J GruberNov 5, 2012
  73. Felipe ContrerasNov 5, 2012
  74. Junio C HamanoNov 1, 2012
  75. Felipe ContrerasNov 1, 2012

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.