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