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