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