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