remote-hg: fix new branch creation
[git] / contrib / remote-helpers / git-remote-hg
1 #!/usr/bin/env python
2 #
3 # Copyright (c) 2012 Felipe Contreras
4 #
5
6 # Inspired by Rocco Rutte's hg-fast-export
7
8 # Just copy to your ~/bin, or anywhere in your $PATH.
9 # Then you can clone with:
10 # git clone hg::/path/to/mercurial/repo/
11 #
12 # For remote repositories a local clone is stored in
13 # "$GIT_DIR/hg/origin/clone/.hg/".
14
15 from mercurial import hg, ui, bookmarks, context, encoding, node, error, extensions
16
17 import re
18 import sys
19 import os
20 import json
21 import shutil
22 import subprocess
23 import urllib
24 import atexit
25 import urlparse, hashlib
26
27 #
28 # If you want to switch to hg-git compatibility mode:
29 # git config --global remote-hg.hg-git-compat true
30 #
31 # If you are not in hg-git-compat mode and want to disable the tracking of
32 # named branches:
33 # git config --global remote-hg.track-branches false
34 #
35 # If you don't want to force pushes (and thus risk creating new remote heads):
36 # git config --global remote-hg.force-push false
37 #
38 # If you want the equivalent of hg's clone/pull--insecure option:
39 # git config remote-hg.insecure true
40 #
41 # git:
42 # Sensible defaults for git.
43 # hg bookmarks are exported as git branches, hg branches are prefixed
44 # with 'branches/', HEAD is a special case.
45 #
46 # hg:
47 # Emulate hg-git.
48 # Only hg bookmarks are exported as git branches.
49 # Commits are modified to preserve hg information and allow bidirectionality.
50 #
51
52 NAME_RE = re.compile('^([^<>]+)')
53 AUTHOR_RE = re.compile('^([^<>]+?)? ?<([^<>]*)>$')
54 EMAIL_RE = re.compile('^([^<>]+[^ \\\t<>])?\\b(?:[ \\t<>]*?)\\b([^ \\t<>]+@[^ \\t<>]+)')
55 AUTHOR_HG_RE = re.compile('^(.*?) ?<(.*?)(?:>(.+)?)?$')
56 RAW_AUTHOR_RE = re.compile('^(\w+) (?:(.+)? )?<(.*)> (\d+) ([+-]\d+)')
57
58 def die(msg, *args):
59     sys.stderr.write('ERROR: %s\n' % (msg % args))
60     sys.exit(1)
61
62 def warn(msg, *args):
63     sys.stderr.write('WARNING: %s\n' % (msg % args))
64
65 def gitmode(flags):
66     return 'l' in flags and '120000' or 'x' in flags and '100755' or '100644'
67
68 def gittz(tz):
69     return '%+03d%02d' % (-tz / 3600, -tz % 3600 / 60)
70
71 def hgmode(mode):
72     m = { '100755': 'x', '120000': 'l' }
73     return m.get(mode, '')
74
75 def hghex(node):
76     return hg.node.hex(node)
77
78 def hgref(ref):
79     return ref.replace('___', ' ')
80
81 def gitref(ref):
82     return ref.replace(' ', '___')
83
84 def get_config(config):
85     cmd = ['git', 'config', '--get', config]
86     process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
87     output, _ = process.communicate()
88     return output
89
90 def get_config_bool(config, default=False):
91     value = get_config(config).rstrip('\n')
92     if value == "true":
93         return True
94     elif value == "false":
95         return False
96     else:
97         return default
98
99 class Marks:
100
101     def __init__(self, path):
102         self.path = path
103         self.tips = {}
104         self.marks = {}
105         self.rev_marks = {}
106         self.last_mark = 0
107
108         self.load()
109
110     def load(self):
111         if not os.path.exists(self.path):
112             return
113
114         tmp = json.load(open(self.path))
115
116         self.tips = tmp['tips']
117         self.marks = tmp['marks']
118         self.last_mark = tmp['last-mark']
119
120         for rev, mark in self.marks.iteritems():
121             self.rev_marks[mark] = int(rev)
122
123     def dict(self):
124         return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark }
125
126     def store(self):
127         json.dump(self.dict(), open(self.path, 'w'))
128
129     def __str__(self):
130         return str(self.dict())
131
132     def from_rev(self, rev):
133         return self.marks[str(rev)]
134
135     def to_rev(self, mark):
136         return self.rev_marks[mark]
137
138     def next_mark(self):
139         self.last_mark += 1
140         return self.last_mark
141
142     def get_mark(self, rev):
143         self.last_mark += 1
144         self.marks[str(rev)] = self.last_mark
145         return self.last_mark
146
147     def new_mark(self, rev, mark):
148         self.marks[str(rev)] = mark
149         self.rev_marks[mark] = rev
150         self.last_mark = mark
151
152     def is_marked(self, rev):
153         return str(rev) in self.marks
154
155     def get_tip(self, branch):
156         return self.tips.get(branch, 0)
157
158     def set_tip(self, branch, tip):
159         self.tips[branch] = tip
160
161 class Parser:
162
163     def __init__(self, repo):
164         self.repo = repo
165         self.line = self.get_line()
166
167     def get_line(self):
168         return sys.stdin.readline().strip()
169
170     def __getitem__(self, i):
171         return self.line.split()[i]
172
173     def check(self, word):
174         return self.line.startswith(word)
175
176     def each_block(self, separator):
177         while self.line != separator:
178             yield self.line
179             self.line = self.get_line()
180
181     def __iter__(self):
182         return self.each_block('')
183
184     def next(self):
185         self.line = self.get_line()
186         if self.line == 'done':
187             self.line = None
188
189     def get_mark(self):
190         i = self.line.index(':') + 1
191         return int(self.line[i:])
192
193     def get_data(self):
194         if not self.check('data'):
195             return None
196         i = self.line.index(' ') + 1
197         size = int(self.line[i:])
198         return sys.stdin.read(size)
199
200     def get_author(self):
201         global bad_mail
202
203         ex = None
204         m = RAW_AUTHOR_RE.match(self.line)
205         if not m:
206             return None
207         _, name, email, date, tz = m.groups()
208         if name and 'ext:' in name:
209             m = re.match('^(.+?) ext:\((.+)\)$', name)
210             if m:
211                 name = m.group(1)
212                 ex = urllib.unquote(m.group(2))
213
214         if email != bad_mail:
215             if name:
216                 user = '%s <%s>' % (name, email)
217             else:
218                 user = '<%s>' % (email)
219         else:
220             user = name
221
222         if ex:
223             user += ex
224
225         tz = int(tz)
226         tz = ((tz / 100) * 3600) + ((tz % 100) * 60)
227         return (user, int(date), -tz)
228
229 def fix_file_path(path):
230     if not os.path.isabs(path):
231         return path
232     return os.path.relpath(path, '/')
233
234 def export_files(files):
235     global marks, filenodes
236
237     final = []
238     for f in files:
239         fid = node.hex(f.filenode())
240
241         if fid in filenodes:
242             mark = filenodes[fid]
243         else:
244             mark = marks.next_mark()
245             filenodes[fid] = mark
246             d = f.data()
247
248             print "blob"
249             print "mark :%u" % mark
250             print "data %d" % len(d)
251             print d
252
253         path = fix_file_path(f.path())
254         final.append((gitmode(f.flags()), mark, path))
255
256     return final
257
258 def get_filechanges(repo, ctx, parent):
259     modified = set()
260     added = set()
261     removed = set()
262
263     # load earliest manifest first for caching reasons
264     prev = repo[parent].manifest().copy()
265     cur = ctx.manifest()
266
267     for fn in cur:
268         if fn in prev:
269             if (cur.flags(fn) != prev.flags(fn) or cur[fn] != prev[fn]):
270                 modified.add(fn)
271             del prev[fn]
272         else:
273             added.add(fn)
274     removed |= set(prev.keys())
275
276     return added | modified, removed
277
278 def fixup_user_git(user):
279     name = mail = None
280     user = user.replace('"', '')
281     m = AUTHOR_RE.match(user)
282     if m:
283         name = m.group(1)
284         mail = m.group(2).strip()
285     else:
286         m = EMAIL_RE.match(user)
287         if m:
288             name = m.group(1)
289             mail = m.group(2)
290         else:
291             m = NAME_RE.match(user)
292             if m:
293                 name = m.group(1).strip()
294     return (name, mail)
295
296 def fixup_user_hg(user):
297     def sanitize(name):
298         # stole this from hg-git
299         return re.sub('[<>\n]', '?', name.lstrip('< ').rstrip('> '))
300
301     m = AUTHOR_HG_RE.match(user)
302     if m:
303         name = sanitize(m.group(1))
304         mail = sanitize(m.group(2))
305         ex = m.group(3)
306         if ex:
307             name += ' ext:(' + urllib.quote(ex) + ')'
308     else:
309         name = sanitize(user)
310         if '@' in user:
311             mail = name
312         else:
313             mail = None
314
315     return (name, mail)
316
317 def fixup_user(user):
318     global mode, bad_mail
319
320     if mode == 'git':
321         name, mail = fixup_user_git(user)
322     else:
323         name, mail = fixup_user_hg(user)
324
325     if not name:
326         name = bad_name
327     if not mail:
328         mail = bad_mail
329
330     return '%s <%s>' % (name, mail)
331
332 def get_repo(url, alias):
333     global dirname, peer
334
335     myui = ui.ui()
336     myui.setconfig('ui', 'interactive', 'off')
337     myui.fout = sys.stderr
338
339     if get_config_bool('remote-hg.insecure'):
340         myui.setconfig('web', 'cacerts', '')
341
342     try:
343         mod = extensions.load(myui, 'hgext.schemes', None)
344         mod.extsetup(myui)
345     except ImportError:
346         pass
347
348     if hg.islocal(url):
349         repo = hg.repository(myui, url)
350     else:
351         local_path = os.path.join(dirname, 'clone')
352         if not os.path.exists(local_path):
353             try:
354                 peer, dstpeer = hg.clone(myui, {}, url, local_path, update=True, pull=True)
355             except:
356                 die('Repository error')
357             repo = dstpeer.local()
358         else:
359             repo = hg.repository(myui, local_path)
360             try:
361                 peer = hg.peer(myui, {}, url)
362             except:
363                 die('Repository error')
364             repo.pull(peer, heads=None, force=True)
365
366     return repo
367
368 def rev_to_mark(rev):
369     global marks
370     return marks.from_rev(rev)
371
372 def mark_to_rev(mark):
373     global marks
374     return marks.to_rev(mark)
375
376 def export_ref(repo, name, kind, head):
377     global prefix, marks, mode
378
379     ename = '%s/%s' % (kind, name)
380     tip = marks.get_tip(ename)
381
382     revs = xrange(tip, head.rev() + 1)
383     count = 0
384
385     revs = [rev for rev in revs if not marks.is_marked(rev)]
386
387     for rev in revs:
388
389         c = repo[rev]
390         (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(c.node())
391         rev_branch = extra['branch']
392
393         author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
394         if 'committer' in extra:
395             user, time, tz = extra['committer'].rsplit(' ', 2)
396             committer = "%s %s %s" % (user, time, gittz(int(tz)))
397         else:
398             committer = author
399
400         parents = [p for p in repo.changelog.parentrevs(rev) if p >= 0]
401
402         if len(parents) == 0:
403             modified = c.manifest().keys()
404             removed = []
405         else:
406             modified, removed = get_filechanges(repo, c, parents[0])
407
408         desc += '\n'
409
410         if mode == 'hg':
411             extra_msg = ''
412
413             if rev_branch != 'default':
414                 extra_msg += 'branch : %s\n' % rev_branch
415
416             renames = []
417             for f in c.files():
418                 if f not in c.manifest():
419                     continue
420                 rename = c.filectx(f).renamed()
421                 if rename:
422                     renames.append((rename[0], f))
423
424             for e in renames:
425                 extra_msg += "rename : %s => %s\n" % e
426
427             for key, value in extra.iteritems():
428                 if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):
429                     continue
430                 else:
431                     extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value))
432
433             if extra_msg:
434                 desc += '\n--HG--\n' + extra_msg
435
436         if len(parents) == 0 and rev:
437             print 'reset %s/%s' % (prefix, ename)
438
439         modified_final = export_files(c.filectx(f) for f in modified)
440
441         print "commit %s/%s" % (prefix, ename)
442         print "mark :%d" % (marks.get_mark(rev))
443         print "author %s" % (author)
444         print "committer %s" % (committer)
445         print "data %d" % (len(desc))
446         print desc
447
448         if len(parents) > 0:
449             print "from :%s" % (rev_to_mark(parents[0]))
450             if len(parents) > 1:
451                 print "merge :%s" % (rev_to_mark(parents[1]))
452
453         for f in modified_final:
454             print "M %s :%u %s" % f
455         for f in removed:
456             print "D %s" % (fix_file_path(f))
457         print
458
459         count += 1
460         if (count % 100 == 0):
461             print "progress revision %d '%s' (%d/%d)" % (rev, name, count, len(revs))
462
463     # make sure the ref is updated
464     print "reset %s/%s" % (prefix, ename)
465     print "from :%u" % rev_to_mark(rev)
466     print
467
468     marks.set_tip(ename, rev)
469
470 def export_tag(repo, tag):
471     export_ref(repo, tag, 'tags', repo[hgref(tag)])
472
473 def export_bookmark(repo, bmark):
474     head = bmarks[hgref(bmark)]
475     export_ref(repo, bmark, 'bookmarks', head)
476
477 def export_branch(repo, branch):
478     tip = get_branch_tip(repo, branch)
479     head = repo[tip]
480     export_ref(repo, branch, 'branches', head)
481
482 def export_head(repo):
483     global g_head
484     export_ref(repo, g_head[0], 'bookmarks', g_head[1])
485
486 def do_capabilities(parser):
487     global prefix, dirname
488
489     print "import"
490     print "export"
491     print "refspec refs/heads/branches/*:%s/branches/*" % prefix
492     print "refspec refs/heads/*:%s/bookmarks/*" % prefix
493     print "refspec refs/tags/*:%s/tags/*" % prefix
494
495     path = os.path.join(dirname, 'marks-git')
496
497     if os.path.exists(path):
498         print "*import-marks %s" % path
499     print "*export-marks %s" % path
500
501     print
502
503 def branch_tip(repo, branch):
504     # older versions of mercurial don't have this
505     if hasattr(repo, 'branchtip'):
506         return repo.branchtip(branch)
507     else:
508         return repo.branchtags()[branch]
509
510 def get_branch_tip(repo, branch):
511     global branches
512
513     heads = branches.get(hgref(branch), None)
514     if not heads:
515         return None
516
517     # verify there's only one head
518     if (len(heads) > 1):
519         warn("Branch '%s' has more than one head, consider merging" % branch)
520         return branch_tip(repo, hgref(branch))
521
522     return heads[0]
523
524 def list_head(repo, cur):
525     global g_head, bmarks
526
527     head = bookmarks.readcurrent(repo)
528     if head:
529         node = repo[head]
530     else:
531         # fake bookmark from current branch
532         head = cur
533         node = repo['.']
534         if not node:
535             node = repo['tip']
536         if not node:
537             return
538         if head == 'default':
539             head = 'master'
540         bmarks[head] = node
541
542     head = gitref(head)
543     print "@refs/heads/%s HEAD" % head
544     g_head = (head, node)
545
546 def do_list(parser):
547     global branches, bmarks, track_branches
548
549     repo = parser.repo
550     for bmark, node in bookmarks.listbookmarks(repo).iteritems():
551         bmarks[bmark] = repo[node]
552
553     cur = repo.dirstate.branch()
554
555     list_head(repo, cur)
556
557     if track_branches:
558         for branch in repo.branchmap():
559             heads = repo.branchheads(branch)
560             if len(heads):
561                 branches[branch] = heads
562
563         for branch in branches:
564             print "? refs/heads/branches/%s" % gitref(branch)
565
566     for bmark in bmarks:
567         print "? refs/heads/%s" % gitref(bmark)
568
569     for tag, node in repo.tagslist():
570         if tag == 'tip':
571             continue
572         print "? refs/tags/%s" % gitref(tag)
573
574     print
575
576 def do_import(parser):
577     repo = parser.repo
578
579     path = os.path.join(dirname, 'marks-git')
580
581     print "feature done"
582     if os.path.exists(path):
583         print "feature import-marks=%s" % path
584     print "feature export-marks=%s" % path
585     sys.stdout.flush()
586
587     tmp = encoding.encoding
588     encoding.encoding = 'utf-8'
589
590     # lets get all the import lines
591     while parser.check('import'):
592         ref = parser[1]
593
594         if (ref == 'HEAD'):
595             export_head(repo)
596         elif ref.startswith('refs/heads/branches/'):
597             branch = ref[len('refs/heads/branches/'):]
598             export_branch(repo, branch)
599         elif ref.startswith('refs/heads/'):
600             bmark = ref[len('refs/heads/'):]
601             export_bookmark(repo, bmark)
602         elif ref.startswith('refs/tags/'):
603             tag = ref[len('refs/tags/'):]
604             export_tag(repo, tag)
605
606         parser.next()
607
608     encoding.encoding = tmp
609
610     print 'done'
611
612 def parse_blob(parser):
613     global blob_marks
614
615     parser.next()
616     mark = parser.get_mark()
617     parser.next()
618     data = parser.get_data()
619     blob_marks[mark] = data
620     parser.next()
621
622 def get_merge_files(repo, p1, p2, files):
623     for e in repo[p1].files():
624         if e not in files:
625             if e not in repo[p1].manifest():
626                 continue
627             f = { 'ctx' : repo[p1][e] }
628             files[e] = f
629
630 def parse_commit(parser):
631     global marks, blob_marks, parsed_refs
632     global mode
633
634     from_mark = merge_mark = None
635
636     ref = parser[1]
637     parser.next()
638
639     commit_mark = parser.get_mark()
640     parser.next()
641     author = parser.get_author()
642     parser.next()
643     committer = parser.get_author()
644     parser.next()
645     data = parser.get_data()
646     parser.next()
647     if parser.check('from'):
648         from_mark = parser.get_mark()
649         parser.next()
650     if parser.check('merge'):
651         merge_mark = parser.get_mark()
652         parser.next()
653         if parser.check('merge'):
654             die('octopus merges are not supported yet')
655
656     # fast-export adds an extra newline
657     if data[-1] == '\n':
658         data = data[:-1]
659
660     files = {}
661
662     for line in parser:
663         if parser.check('M'):
664             t, m, mark_ref, path = line.split(' ', 3)
665             mark = int(mark_ref[1:])
666             f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
667         elif parser.check('D'):
668             t, path = line.split(' ', 1)
669             f = { 'deleted' : True }
670         else:
671             die('Unknown file command: %s' % line)
672         files[path] = f
673
674     def getfilectx(repo, memctx, f):
675         of = files[f]
676         if 'deleted' in of:
677             raise IOError
678         if 'ctx' in of:
679             return of['ctx']
680         is_exec = of['mode'] == 'x'
681         is_link = of['mode'] == 'l'
682         rename = of.get('rename', None)
683         return context.memfilectx(f, of['data'],
684                 is_link, is_exec, rename)
685
686     repo = parser.repo
687
688     user, date, tz = author
689     extra = {}
690
691     if committer != author:
692         extra['committer'] = "%s %u %u" % committer
693
694     if from_mark:
695         p1 = repo.changelog.node(mark_to_rev(from_mark))
696     else:
697         p1 = '\0' * 20
698
699     if merge_mark:
700         p2 = repo.changelog.node(mark_to_rev(merge_mark))
701     else:
702         p2 = '\0' * 20
703
704     #
705     # If files changed from any of the parents, hg wants to know, but in git if
706     # nothing changed from the first parent, nothing changed.
707     #
708     if merge_mark:
709         get_merge_files(repo, p1, p2, files)
710
711     # Check if the ref is supposed to be a named branch
712     if ref.startswith('refs/heads/branches/'):
713         branch = ref[len('refs/heads/branches/'):]
714         extra['branch'] = hgref(branch)
715
716     if mode == 'hg':
717         i = data.find('\n--HG--\n')
718         if i >= 0:
719             tmp = data[i + len('\n--HG--\n'):].strip()
720             for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
721                 if k == 'rename':
722                     old, new = v.split(' => ', 1)
723                     files[new]['rename'] = old
724                 elif k == 'branch':
725                     extra[k] = v
726                 elif k == 'extra':
727                     ek, ev = v.split(' : ', 1)
728                     extra[ek] = urllib.unquote(ev)
729             data = data[:i]
730
731     ctx = context.memctx(repo, (p1, p2), data,
732             files.keys(), getfilectx,
733             user, (date, tz), extra)
734
735     tmp = encoding.encoding
736     encoding.encoding = 'utf-8'
737
738     node = repo.commitctx(ctx)
739
740     encoding.encoding = tmp
741
742     rev = repo[node].rev()
743
744     parsed_refs[ref] = node
745     marks.new_mark(rev, commit_mark)
746
747 def parse_reset(parser):
748     global parsed_refs
749
750     ref = parser[1]
751     parser.next()
752     # ugh
753     if parser.check('commit'):
754         parse_commit(parser)
755         return
756     if not parser.check('from'):
757         return
758     from_mark = parser.get_mark()
759     parser.next()
760
761     node = parser.repo.changelog.node(mark_to_rev(from_mark))
762     parsed_refs[ref] = node
763
764 def parse_tag(parser):
765     name = parser[1]
766     parser.next()
767     from_mark = parser.get_mark()
768     parser.next()
769     tagger = parser.get_author()
770     parser.next()
771     data = parser.get_data()
772     parser.next()
773
774     parsed_tags[name] = (tagger, data)
775
776 def write_tag(repo, tag, node, msg, author):
777     branch = repo[node].branch()
778     tip = branch_tip(repo, branch)
779     tip = repo[tip]
780
781     def getfilectx(repo, memctx, f):
782         try:
783             fctx = tip.filectx(f)
784             data = fctx.data()
785         except error.ManifestLookupError:
786             data = ""
787         content = data + "%s %s\n" % (hghex(node), tag)
788         return context.memfilectx(f, content, False, False, None)
789
790     p1 = tip.hex()
791     p2 = '\0' * 20
792     if not author:
793         author = (None, 0, 0)
794     user, date, tz = author
795
796     ctx = context.memctx(repo, (p1, p2), msg,
797             ['.hgtags'], getfilectx,
798             user, (date, tz), {'branch' : branch})
799
800     tmp = encoding.encoding
801     encoding.encoding = 'utf-8'
802
803     tagnode = repo.commitctx(ctx)
804
805     encoding.encoding = tmp
806
807     return tagnode
808
809 def do_export(parser):
810     global parsed_refs, bmarks, peer
811
812     p_bmarks = []
813
814     parser.next()
815
816     for line in parser.each_block('done'):
817         if parser.check('blob'):
818             parse_blob(parser)
819         elif parser.check('commit'):
820             parse_commit(parser)
821         elif parser.check('reset'):
822             parse_reset(parser)
823         elif parser.check('tag'):
824             parse_tag(parser)
825         elif parser.check('feature'):
826             pass
827         else:
828             die('unhandled export command: %s' % line)
829
830     for ref, node in parsed_refs.iteritems():
831         if ref.startswith('refs/heads/branches'):
832             branch = ref[len('refs/heads/branches/'):]
833             if branch in branches and node in branches[branch]:
834                 # up to date
835                 continue
836             print "ok %s" % ref
837         elif ref.startswith('refs/heads/'):
838             bmark = ref[len('refs/heads/'):]
839             p_bmarks.append((bmark, node))
840             continue
841         elif ref.startswith('refs/tags/'):
842             tag = ref[len('refs/tags/'):]
843             tag = hgref(tag)
844             author, msg = parsed_tags.get(tag, (None, None))
845             if mode == 'git':
846                 if not msg:
847                     msg = 'Added tag %s for changeset %s' % (tag, hghex(node[:6]));
848                 write_tag(parser.repo, tag, node, msg, author)
849             else:
850                 fp = parser.repo.opener('localtags', 'a')
851                 fp.write('%s %s\n' % (hghex(node), tag))
852                 fp.close()
853             print "ok %s" % ref
854         else:
855             # transport-helper/fast-export bugs
856             continue
857
858     if peer:
859         parser.repo.push(peer, force=force_push, newbranch=True)
860
861     # handle bookmarks
862     for bmark, node in p_bmarks:
863         ref = 'refs/heads/' + bmark
864         new = hghex(node)
865
866         if bmark in bmarks:
867             old = bmarks[bmark].hex()
868         else:
869             old = ''
870
871         if old == new:
872             continue
873
874         if bmark == 'master' and 'master' not in parser.repo._bookmarks:
875             # fake bookmark
876             pass
877         elif bookmarks.pushbookmark(parser.repo, bmark, old, new):
878             # updated locally
879             pass
880         else:
881             print "error %s" % ref
882             continue
883
884         if peer:
885             rb = peer.listkeys('bookmarks')
886             old = rb.get(bmark, '')
887             if not peer.pushkey('bookmarks', bmark, old, new):
888                 print "error %s" % ref
889                 continue
890
891         print "ok %s" % ref
892
893     print
894
895 def fix_path(alias, repo, orig_url):
896     url = urlparse.urlparse(orig_url, 'file')
897     if url.scheme != 'file' or os.path.isabs(url.path):
898         return
899     abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
900     cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
901     subprocess.call(cmd)
902
903 def main(args):
904     global prefix, dirname, branches, bmarks
905     global marks, blob_marks, parsed_refs
906     global peer, mode, bad_mail, bad_name
907     global track_branches, force_push, is_tmp
908     global parsed_tags
909     global filenodes
910
911     alias = args[1]
912     url = args[2]
913     peer = None
914
915     hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
916     track_branches = get_config_bool('remote-hg.track-branches', True)
917     force_push = get_config_bool('remote-hg.force-push', True)
918
919     if hg_git_compat:
920         mode = 'hg'
921         bad_mail = 'none@none'
922         bad_name = ''
923     else:
924         mode = 'git'
925         bad_mail = 'unknown'
926         bad_name = 'Unknown'
927
928     if alias[4:] == url:
929         is_tmp = True
930         alias = hashlib.sha1(alias).hexdigest()
931     else:
932         is_tmp = False
933
934     gitdir = os.environ['GIT_DIR']
935     dirname = os.path.join(gitdir, 'hg', alias)
936     branches = {}
937     bmarks = {}
938     blob_marks = {}
939     parsed_refs = {}
940     marks = None
941     parsed_tags = {}
942     filenodes = {}
943
944     repo = get_repo(url, alias)
945     prefix = 'refs/hg/%s' % alias
946
947     if not is_tmp:
948         fix_path(alias, peer or repo, url)
949
950     if not os.path.exists(dirname):
951         os.makedirs(dirname)
952
953     marks_path = os.path.join(dirname, 'marks-hg')
954     marks = Marks(marks_path)
955
956     parser = Parser(repo)
957     for line in parser:
958         if parser.check('capabilities'):
959             do_capabilities(parser)
960         elif parser.check('list'):
961             do_list(parser)
962         elif parser.check('import'):
963             do_import(parser)
964         elif parser.check('export'):
965             do_export(parser)
966         else:
967             die('unhandled command: %s' % line)
968         sys.stdout.flush()
969
970 def bye():
971     if not marks:
972         return
973     if not is_tmp:
974         marks.store()
975     else:
976         shutil.rmtree(dirname)
977
978 atexit.register(bye)
979 sys.exit(main(sys.argv))