path.c: refactor relative_path(), not only strip prefix
[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         rb = peer.listkeys('bookmarks')
367         bookmarks.updatefromremote(myui, repo, rb, url)
368
369     return repo
370
371 def rev_to_mark(rev):
372     global marks
373     return marks.from_rev(rev)
374
375 def mark_to_rev(mark):
376     global marks
377     return marks.to_rev(mark)
378
379 def export_ref(repo, name, kind, head):
380     global prefix, marks, mode
381
382     ename = '%s/%s' % (kind, name)
383     tip = marks.get_tip(ename)
384
385     revs = xrange(tip, head.rev() + 1)
386     count = 0
387
388     revs = [rev for rev in revs if not marks.is_marked(rev)]
389
390     for rev in revs:
391
392         c = repo[rev]
393         (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(c.node())
394         rev_branch = extra['branch']
395
396         author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
397         if 'committer' in extra:
398             user, time, tz = extra['committer'].rsplit(' ', 2)
399             committer = "%s %s %s" % (user, time, gittz(int(tz)))
400         else:
401             committer = author
402
403         parents = [p for p in repo.changelog.parentrevs(rev) if p >= 0]
404
405         if len(parents) == 0:
406             modified = c.manifest().keys()
407             removed = []
408         else:
409             modified, removed = get_filechanges(repo, c, parents[0])
410
411         desc += '\n'
412
413         if mode == 'hg':
414             extra_msg = ''
415
416             if rev_branch != 'default':
417                 extra_msg += 'branch : %s\n' % rev_branch
418
419             renames = []
420             for f in c.files():
421                 if f not in c.manifest():
422                     continue
423                 rename = c.filectx(f).renamed()
424                 if rename:
425                     renames.append((rename[0], f))
426
427             for e in renames:
428                 extra_msg += "rename : %s => %s\n" % e
429
430             for key, value in extra.iteritems():
431                 if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):
432                     continue
433                 else:
434                     extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value))
435
436             if extra_msg:
437                 desc += '\n--HG--\n' + extra_msg
438
439         if len(parents) == 0 and rev:
440             print 'reset %s/%s' % (prefix, ename)
441
442         modified_final = export_files(c.filectx(f) for f in modified)
443
444         print "commit %s/%s" % (prefix, ename)
445         print "mark :%d" % (marks.get_mark(rev))
446         print "author %s" % (author)
447         print "committer %s" % (committer)
448         print "data %d" % (len(desc))
449         print desc
450
451         if len(parents) > 0:
452             print "from :%s" % (rev_to_mark(parents[0]))
453             if len(parents) > 1:
454                 print "merge :%s" % (rev_to_mark(parents[1]))
455
456         for f in modified_final:
457             print "M %s :%u %s" % f
458         for f in removed:
459             print "D %s" % (fix_file_path(f))
460         print
461
462         count += 1
463         if (count % 100 == 0):
464             print "progress revision %d '%s' (%d/%d)" % (rev, name, count, len(revs))
465
466     # make sure the ref is updated
467     print "reset %s/%s" % (prefix, ename)
468     print "from :%u" % rev_to_mark(rev)
469     print
470
471     marks.set_tip(ename, rev)
472
473 def export_tag(repo, tag):
474     export_ref(repo, tag, 'tags', repo[hgref(tag)])
475
476 def export_bookmark(repo, bmark):
477     head = bmarks[hgref(bmark)]
478     export_ref(repo, bmark, 'bookmarks', head)
479
480 def export_branch(repo, branch):
481     tip = get_branch_tip(repo, branch)
482     head = repo[tip]
483     export_ref(repo, branch, 'branches', head)
484
485 def export_head(repo):
486     global g_head
487     export_ref(repo, g_head[0], 'bookmarks', g_head[1])
488
489 def do_capabilities(parser):
490     global prefix, dirname
491
492     print "import"
493     print "export"
494     print "refspec refs/heads/branches/*:%s/branches/*" % prefix
495     print "refspec refs/heads/*:%s/bookmarks/*" % prefix
496     print "refspec refs/tags/*:%s/tags/*" % prefix
497
498     path = os.path.join(dirname, 'marks-git')
499
500     if os.path.exists(path):
501         print "*import-marks %s" % path
502     print "*export-marks %s" % path
503
504     print
505
506 def branch_tip(repo, branch):
507     # older versions of mercurial don't have this
508     if hasattr(repo, 'branchtip'):
509         return repo.branchtip(branch)
510     else:
511         return repo.branchtags()[branch]
512
513 def get_branch_tip(repo, branch):
514     global branches
515
516     heads = branches.get(hgref(branch), None)
517     if not heads:
518         return None
519
520     # verify there's only one head
521     if (len(heads) > 1):
522         warn("Branch '%s' has more than one head, consider merging" % branch)
523         return branch_tip(repo, hgref(branch))
524
525     return heads[0]
526
527 def list_head(repo, cur):
528     global g_head, bmarks
529
530     head = bookmarks.readcurrent(repo)
531     if head:
532         node = repo[head]
533     else:
534         # fake bookmark from current branch
535         head = cur
536         node = repo['.']
537         if not node:
538             node = repo['tip']
539         if not node:
540             return
541         if head == 'default':
542             head = 'master'
543         bmarks[head] = node
544
545     head = gitref(head)
546     print "@refs/heads/%s HEAD" % head
547     g_head = (head, node)
548
549 def do_list(parser):
550     global branches, bmarks, track_branches
551
552     repo = parser.repo
553     for bmark, node in bookmarks.listbookmarks(repo).iteritems():
554         bmarks[bmark] = repo[node]
555
556     cur = repo.dirstate.branch()
557
558     list_head(repo, cur)
559
560     if track_branches:
561         for branch in repo.branchmap():
562             heads = repo.branchheads(branch)
563             if len(heads):
564                 branches[branch] = heads
565
566         for branch in branches:
567             print "? refs/heads/branches/%s" % gitref(branch)
568
569     for bmark in bmarks:
570         print "? refs/heads/%s" % gitref(bmark)
571
572     for tag, node in repo.tagslist():
573         if tag == 'tip':
574             continue
575         print "? refs/tags/%s" % gitref(tag)
576
577     print
578
579 def do_import(parser):
580     repo = parser.repo
581
582     path = os.path.join(dirname, 'marks-git')
583
584     print "feature done"
585     if os.path.exists(path):
586         print "feature import-marks=%s" % path
587     print "feature export-marks=%s" % path
588     sys.stdout.flush()
589
590     tmp = encoding.encoding
591     encoding.encoding = 'utf-8'
592
593     # lets get all the import lines
594     while parser.check('import'):
595         ref = parser[1]
596
597         if (ref == 'HEAD'):
598             export_head(repo)
599         elif ref.startswith('refs/heads/branches/'):
600             branch = ref[len('refs/heads/branches/'):]
601             export_branch(repo, branch)
602         elif ref.startswith('refs/heads/'):
603             bmark = ref[len('refs/heads/'):]
604             export_bookmark(repo, bmark)
605         elif ref.startswith('refs/tags/'):
606             tag = ref[len('refs/tags/'):]
607             export_tag(repo, tag)
608
609         parser.next()
610
611     encoding.encoding = tmp
612
613     print 'done'
614
615 def parse_blob(parser):
616     global blob_marks
617
618     parser.next()
619     mark = parser.get_mark()
620     parser.next()
621     data = parser.get_data()
622     blob_marks[mark] = data
623     parser.next()
624
625 def get_merge_files(repo, p1, p2, files):
626     for e in repo[p1].files():
627         if e not in files:
628             if e not in repo[p1].manifest():
629                 continue
630             f = { 'ctx' : repo[p1][e] }
631             files[e] = f
632
633 def parse_commit(parser):
634     global marks, blob_marks, parsed_refs
635     global mode
636
637     from_mark = merge_mark = None
638
639     ref = parser[1]
640     parser.next()
641
642     commit_mark = parser.get_mark()
643     parser.next()
644     author = parser.get_author()
645     parser.next()
646     committer = parser.get_author()
647     parser.next()
648     data = parser.get_data()
649     parser.next()
650     if parser.check('from'):
651         from_mark = parser.get_mark()
652         parser.next()
653     if parser.check('merge'):
654         merge_mark = parser.get_mark()
655         parser.next()
656         if parser.check('merge'):
657             die('octopus merges are not supported yet')
658
659     # fast-export adds an extra newline
660     if data[-1] == '\n':
661         data = data[:-1]
662
663     files = {}
664
665     for line in parser:
666         if parser.check('M'):
667             t, m, mark_ref, path = line.split(' ', 3)
668             mark = int(mark_ref[1:])
669             f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
670         elif parser.check('D'):
671             t, path = line.split(' ', 1)
672             f = { 'deleted' : True }
673         else:
674             die('Unknown file command: %s' % line)
675         files[path] = f
676
677     def getfilectx(repo, memctx, f):
678         of = files[f]
679         if 'deleted' in of:
680             raise IOError
681         if 'ctx' in of:
682             return of['ctx']
683         is_exec = of['mode'] == 'x'
684         is_link = of['mode'] == 'l'
685         rename = of.get('rename', None)
686         return context.memfilectx(f, of['data'],
687                 is_link, is_exec, rename)
688
689     repo = parser.repo
690
691     user, date, tz = author
692     extra = {}
693
694     if committer != author:
695         extra['committer'] = "%s %u %u" % committer
696
697     if from_mark:
698         p1 = repo.changelog.node(mark_to_rev(from_mark))
699     else:
700         p1 = '\0' * 20
701
702     if merge_mark:
703         p2 = repo.changelog.node(mark_to_rev(merge_mark))
704     else:
705         p2 = '\0' * 20
706
707     #
708     # If files changed from any of the parents, hg wants to know, but in git if
709     # nothing changed from the first parent, nothing changed.
710     #
711     if merge_mark:
712         get_merge_files(repo, p1, p2, files)
713
714     # Check if the ref is supposed to be a named branch
715     if ref.startswith('refs/heads/branches/'):
716         branch = ref[len('refs/heads/branches/'):]
717         extra['branch'] = hgref(branch)
718
719     if mode == 'hg':
720         i = data.find('\n--HG--\n')
721         if i >= 0:
722             tmp = data[i + len('\n--HG--\n'):].strip()
723             for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
724                 if k == 'rename':
725                     old, new = v.split(' => ', 1)
726                     files[new]['rename'] = old
727                 elif k == 'branch':
728                     extra[k] = v
729                 elif k == 'extra':
730                     ek, ev = v.split(' : ', 1)
731                     extra[ek] = urllib.unquote(ev)
732             data = data[:i]
733
734     ctx = context.memctx(repo, (p1, p2), data,
735             files.keys(), getfilectx,
736             user, (date, tz), extra)
737
738     tmp = encoding.encoding
739     encoding.encoding = 'utf-8'
740
741     node = repo.commitctx(ctx)
742
743     encoding.encoding = tmp
744
745     rev = repo[node].rev()
746
747     parsed_refs[ref] = node
748     marks.new_mark(rev, commit_mark)
749
750 def parse_reset(parser):
751     global parsed_refs
752
753     ref = parser[1]
754     parser.next()
755     # ugh
756     if parser.check('commit'):
757         parse_commit(parser)
758         return
759     if not parser.check('from'):
760         return
761     from_mark = parser.get_mark()
762     parser.next()
763
764     node = parser.repo.changelog.node(mark_to_rev(from_mark))
765     parsed_refs[ref] = node
766
767 def parse_tag(parser):
768     name = parser[1]
769     parser.next()
770     from_mark = parser.get_mark()
771     parser.next()
772     tagger = parser.get_author()
773     parser.next()
774     data = parser.get_data()
775     parser.next()
776
777     parsed_tags[name] = (tagger, data)
778
779 def write_tag(repo, tag, node, msg, author):
780     branch = repo[node].branch()
781     tip = branch_tip(repo, branch)
782     tip = repo[tip]
783
784     def getfilectx(repo, memctx, f):
785         try:
786             fctx = tip.filectx(f)
787             data = fctx.data()
788         except error.ManifestLookupError:
789             data = ""
790         content = data + "%s %s\n" % (hghex(node), tag)
791         return context.memfilectx(f, content, False, False, None)
792
793     p1 = tip.hex()
794     p2 = '\0' * 20
795     if not author:
796         author = (None, 0, 0)
797     user, date, tz = author
798
799     ctx = context.memctx(repo, (p1, p2), msg,
800             ['.hgtags'], getfilectx,
801             user, (date, tz), {'branch' : branch})
802
803     tmp = encoding.encoding
804     encoding.encoding = 'utf-8'
805
806     tagnode = repo.commitctx(ctx)
807
808     encoding.encoding = tmp
809
810     return tagnode
811
812 def do_export(parser):
813     global parsed_refs, bmarks, peer
814
815     p_bmarks = []
816
817     parser.next()
818
819     for line in parser.each_block('done'):
820         if parser.check('blob'):
821             parse_blob(parser)
822         elif parser.check('commit'):
823             parse_commit(parser)
824         elif parser.check('reset'):
825             parse_reset(parser)
826         elif parser.check('tag'):
827             parse_tag(parser)
828         elif parser.check('feature'):
829             pass
830         else:
831             die('unhandled export command: %s' % line)
832
833     for ref, node in parsed_refs.iteritems():
834         if ref.startswith('refs/heads/branches'):
835             branch = ref[len('refs/heads/branches/'):]
836             if branch in branches and node in branches[branch]:
837                 # up to date
838                 continue
839             print "ok %s" % ref
840         elif ref.startswith('refs/heads/'):
841             bmark = ref[len('refs/heads/'):]
842             p_bmarks.append((bmark, node))
843             continue
844         elif ref.startswith('refs/tags/'):
845             tag = ref[len('refs/tags/'):]
846             tag = hgref(tag)
847             author, msg = parsed_tags.get(tag, (None, None))
848             if mode == 'git':
849                 if not msg:
850                     msg = 'Added tag %s for changeset %s' % (tag, hghex(node[:6]));
851                 write_tag(parser.repo, tag, node, msg, author)
852             else:
853                 fp = parser.repo.opener('localtags', 'a')
854                 fp.write('%s %s\n' % (hghex(node), tag))
855                 fp.close()
856             print "ok %s" % ref
857         else:
858             # transport-helper/fast-export bugs
859             continue
860
861     if peer:
862         parser.repo.push(peer, force=force_push, newbranch=True)
863
864     # handle bookmarks
865     for bmark, node in p_bmarks:
866         ref = 'refs/heads/' + bmark
867         new = hghex(node)
868
869         if bmark in bmarks:
870             old = bmarks[bmark].hex()
871         else:
872             old = ''
873
874         if old == new:
875             continue
876
877         if bmark == 'master' and 'master' not in parser.repo._bookmarks:
878             # fake bookmark
879             print "ok %s" % ref
880             continue
881         elif bookmarks.pushbookmark(parser.repo, bmark, old, new):
882             # updated locally
883             pass
884         else:
885             print "error %s" % ref
886             continue
887
888         if peer:
889             rb = peer.listkeys('bookmarks')
890             old = rb.get(bmark, '')
891             if not peer.pushkey('bookmarks', bmark, old, new):
892                 print "error %s" % ref
893                 continue
894
895         print "ok %s" % ref
896
897     print
898
899 def fix_path(alias, repo, orig_url):
900     url = urlparse.urlparse(orig_url, 'file')
901     if url.scheme != 'file' or os.path.isabs(url.path):
902         return
903     abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
904     cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
905     subprocess.call(cmd)
906
907 def main(args):
908     global prefix, dirname, branches, bmarks
909     global marks, blob_marks, parsed_refs
910     global peer, mode, bad_mail, bad_name
911     global track_branches, force_push, is_tmp
912     global parsed_tags
913     global filenodes
914
915     alias = args[1]
916     url = args[2]
917     peer = None
918
919     hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
920     track_branches = get_config_bool('remote-hg.track-branches', True)
921     force_push = get_config_bool('remote-hg.force-push')
922
923     if hg_git_compat:
924         mode = 'hg'
925         bad_mail = 'none@none'
926         bad_name = ''
927     else:
928         mode = 'git'
929         bad_mail = 'unknown'
930         bad_name = 'Unknown'
931
932     if alias[4:] == url:
933         is_tmp = True
934         alias = hashlib.sha1(alias).hexdigest()
935     else:
936         is_tmp = False
937
938     gitdir = os.environ['GIT_DIR']
939     dirname = os.path.join(gitdir, 'hg', alias)
940     branches = {}
941     bmarks = {}
942     blob_marks = {}
943     parsed_refs = {}
944     marks = None
945     parsed_tags = {}
946     filenodes = {}
947
948     repo = get_repo(url, alias)
949     prefix = 'refs/hg/%s' % alias
950
951     if not is_tmp:
952         fix_path(alias, peer or repo, url)
953
954     if not os.path.exists(dirname):
955         os.makedirs(dirname)
956
957     marks_path = os.path.join(dirname, 'marks-hg')
958     marks = Marks(marks_path)
959
960     parser = Parser(repo)
961     for line in parser:
962         if parser.check('capabilities'):
963             do_capabilities(parser)
964         elif parser.check('list'):
965             do_list(parser)
966         elif parser.check('import'):
967             do_import(parser)
968         elif parser.check('export'):
969             do_export(parser)
970         else:
971             die('unhandled command: %s' % line)
972         sys.stdout.flush()
973
974 def bye():
975     if not marks:
976         return
977     if not is_tmp:
978         marks.store()
979     else:
980         shutil.rmtree(dirname)
981
982 atexit.register(bye)
983 sys.exit(main(sys.argv))