remote-hg: do not fail on invalid bookmarks
[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, discovery, util
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 import time as ptime
27
28 #
29 # If you want to see Mercurial revisions as Git commit notes:
30 # git config core.notesRef refs/notes/hg
31 #
32 # If you are not in hg-git-compat mode and want to disable the tracking of
33 # named branches:
34 # git config --global remote-hg.track-branches false
35 #
36 # If you want the equivalent of hg's clone/pull--insecure option:
37 # git config --global remote-hg.insecure true
38 #
39 # If you want to switch to hg-git compatibility mode:
40 # git config --global remote-hg.hg-git-compat true
41 #
42 # git:
43 # Sensible defaults for git.
44 # hg bookmarks are exported as git branches, hg branches are prefixed
45 # with 'branches/', HEAD is a special case.
46 #
47 # hg:
48 # Emulate hg-git.
49 # Only hg bookmarks are exported as git branches.
50 # Commits are modified to preserve hg information and allow bidirectionality.
51 #
52
53 NAME_RE = re.compile('^([^<>]+)')
54 AUTHOR_RE = re.compile('^([^<>]+?)? ?<([^<>]*)>$')
55 EMAIL_RE = re.compile('^([^<>]+[^ \\\t<>])?\\b(?:[ \\t<>]*?)\\b([^ \\t<>]+@[^ \\t<>]+)')
56 AUTHOR_HG_RE = re.compile('^(.*?) ?<(.*?)(?:>(.+)?)?$')
57 RAW_AUTHOR_RE = re.compile('^(\w+) (?:(.+)? )?<(.*)> (\d+) ([+-]\d+)')
58
59 VERSION = 2
60
61 def die(msg, *args):
62     sys.stderr.write('ERROR: %s\n' % (msg % args))
63     sys.exit(1)
64
65 def warn(msg, *args):
66     sys.stderr.write('WARNING: %s\n' % (msg % args))
67
68 def gitmode(flags):
69     return 'l' in flags and '120000' or 'x' in flags and '100755' or '100644'
70
71 def gittz(tz):
72     return '%+03d%02d' % (-tz / 3600, -tz % 3600 / 60)
73
74 def hgmode(mode):
75     m = { '100755': 'x', '120000': 'l' }
76     return m.get(mode, '')
77
78 def hghex(n):
79     return node.hex(n)
80
81 def hgbin(n):
82     return node.bin(n)
83
84 def hgref(ref):
85     return ref.replace('___', ' ')
86
87 def gitref(ref):
88     return ref.replace(' ', '___')
89
90 def check_version(*check):
91     if not hg_version:
92         return True
93     return hg_version >= check
94
95 def get_config(config):
96     cmd = ['git', 'config', '--get', config]
97     process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
98     output, _ = process.communicate()
99     return output
100
101 def get_config_bool(config, default=False):
102     value = get_config(config).rstrip('\n')
103     if value == "true":
104         return True
105     elif value == "false":
106         return False
107     else:
108         return default
109
110 class Marks:
111
112     def __init__(self, path, repo):
113         self.path = path
114         self.repo = repo
115         self.clear()
116         self.load()
117
118         if self.version < VERSION:
119             if self.version == 1:
120                 self.upgrade_one()
121
122             # upgraded?
123             if self.version < VERSION:
124                 self.clear()
125                 self.version = VERSION
126
127     def clear(self):
128         self.tips = {}
129         self.marks = {}
130         self.rev_marks = {}
131         self.last_mark = 0
132         self.version = 0
133         self.last_note = 0
134
135     def load(self):
136         if not os.path.exists(self.path):
137             return
138
139         tmp = json.load(open(self.path))
140
141         self.tips = tmp['tips']
142         self.marks = tmp['marks']
143         self.last_mark = tmp['last-mark']
144         self.version = tmp.get('version', 1)
145         self.last_note = tmp.get('last-note', 0)
146
147         for rev, mark in self.marks.iteritems():
148             self.rev_marks[mark] = rev
149
150     def upgrade_one(self):
151         def get_id(rev):
152             return hghex(self.repo.changelog.node(int(rev)))
153         self.tips = dict((name, get_id(rev)) for name, rev in self.tips.iteritems())
154         self.marks = dict((get_id(rev), mark) for rev, mark in self.marks.iteritems())
155         self.rev_marks = dict((mark, get_id(rev)) for mark, rev in self.rev_marks.iteritems())
156         self.version = 2
157
158     def dict(self):
159         return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark, 'version' : self.version, 'last-note' : self.last_note }
160
161     def store(self):
162         json.dump(self.dict(), open(self.path, 'w'))
163
164     def __str__(self):
165         return str(self.dict())
166
167     def from_rev(self, rev):
168         return self.marks[rev]
169
170     def to_rev(self, mark):
171         return str(self.rev_marks[mark])
172
173     def next_mark(self):
174         self.last_mark += 1
175         return self.last_mark
176
177     def get_mark(self, rev):
178         self.last_mark += 1
179         self.marks[rev] = self.last_mark
180         return self.last_mark
181
182     def new_mark(self, rev, mark):
183         self.marks[rev] = mark
184         self.rev_marks[mark] = rev
185         self.last_mark = mark
186
187     def is_marked(self, rev):
188         return rev in self.marks
189
190     def get_tip(self, branch):
191         return str(self.tips[branch])
192
193     def set_tip(self, branch, tip):
194         self.tips[branch] = tip
195
196 class Parser:
197
198     def __init__(self, repo):
199         self.repo = repo
200         self.line = self.get_line()
201
202     def get_line(self):
203         return sys.stdin.readline().strip()
204
205     def __getitem__(self, i):
206         return self.line.split()[i]
207
208     def check(self, word):
209         return self.line.startswith(word)
210
211     def each_block(self, separator):
212         while self.line != separator:
213             yield self.line
214             self.line = self.get_line()
215
216     def __iter__(self):
217         return self.each_block('')
218
219     def next(self):
220         self.line = self.get_line()
221         if self.line == 'done':
222             self.line = None
223
224     def get_mark(self):
225         i = self.line.index(':') + 1
226         return int(self.line[i:])
227
228     def get_data(self):
229         if not self.check('data'):
230             return None
231         i = self.line.index(' ') + 1
232         size = int(self.line[i:])
233         return sys.stdin.read(size)
234
235     def get_author(self):
236         ex = None
237         m = RAW_AUTHOR_RE.match(self.line)
238         if not m:
239             return None
240         _, name, email, date, tz = m.groups()
241         if name and 'ext:' in name:
242             m = re.match('^(.+?) ext:\((.+)\)$', name)
243             if m:
244                 name = m.group(1)
245                 ex = urllib.unquote(m.group(2))
246
247         if email != bad_mail:
248             if name:
249                 user = '%s <%s>' % (name, email)
250             else:
251                 user = '<%s>' % (email)
252         else:
253             user = name
254
255         if ex:
256             user += ex
257
258         tz = int(tz)
259         tz = ((tz / 100) * 3600) + ((tz % 100) * 60)
260         return (user, int(date), -tz)
261
262 def fix_file_path(path):
263     if not os.path.isabs(path):
264         return path
265     return os.path.relpath(path, '/')
266
267 def export_files(files):
268     final = []
269     for f in files:
270         fid = node.hex(f.filenode())
271
272         if fid in filenodes:
273             mark = filenodes[fid]
274         else:
275             mark = marks.next_mark()
276             filenodes[fid] = mark
277             d = f.data()
278
279             print "blob"
280             print "mark :%u" % mark
281             print "data %d" % len(d)
282             print d
283
284         path = fix_file_path(f.path())
285         final.append((gitmode(f.flags()), mark, path))
286
287     return final
288
289 def get_filechanges(repo, ctx, parent):
290     modified = set()
291     added = set()
292     removed = set()
293
294     # load earliest manifest first for caching reasons
295     prev = parent.manifest().copy()
296     cur = ctx.manifest()
297
298     for fn in cur:
299         if fn in prev:
300             if (cur.flags(fn) != prev.flags(fn) or cur[fn] != prev[fn]):
301                 modified.add(fn)
302             del prev[fn]
303         else:
304             added.add(fn)
305     removed |= set(prev.keys())
306
307     return added | modified, removed
308
309 def fixup_user_git(user):
310     name = mail = None
311     user = user.replace('"', '')
312     m = AUTHOR_RE.match(user)
313     if m:
314         name = m.group(1)
315         mail = m.group(2).strip()
316     else:
317         m = EMAIL_RE.match(user)
318         if m:
319             name = m.group(1)
320             mail = m.group(2)
321         else:
322             m = NAME_RE.match(user)
323             if m:
324                 name = m.group(1).strip()
325     return (name, mail)
326
327 def fixup_user_hg(user):
328     def sanitize(name):
329         # stole this from hg-git
330         return re.sub('[<>\n]', '?', name.lstrip('< ').rstrip('> '))
331
332     m = AUTHOR_HG_RE.match(user)
333     if m:
334         name = sanitize(m.group(1))
335         mail = sanitize(m.group(2))
336         ex = m.group(3)
337         if ex:
338             name += ' ext:(' + urllib.quote(ex) + ')'
339     else:
340         name = sanitize(user)
341         if '@' in user:
342             mail = name
343         else:
344             mail = None
345
346     return (name, mail)
347
348 def fixup_user(user):
349     if mode == 'git':
350         name, mail = fixup_user_git(user)
351     else:
352         name, mail = fixup_user_hg(user)
353
354     if not name:
355         name = bad_name
356     if not mail:
357         mail = bad_mail
358
359     return '%s <%s>' % (name, mail)
360
361 def updatebookmarks(repo, peer):
362     remotemarks = peer.listkeys('bookmarks')
363     localmarks = repo._bookmarks
364
365     if not remotemarks:
366         return
367
368     for k, v in remotemarks.iteritems():
369         localmarks[k] = hgbin(v)
370
371     if hasattr(localmarks, 'write'):
372         localmarks.write()
373     else:
374         bookmarks.write(repo)
375
376 def get_repo(url, alias):
377     global peer
378
379     myui = ui.ui()
380     myui.setconfig('ui', 'interactive', 'off')
381     myui.fout = sys.stderr
382
383     if get_config_bool('remote-hg.insecure'):
384         myui.setconfig('web', 'cacerts', '')
385
386     extensions.loadall(myui)
387
388     if hg.islocal(url) and not os.environ.get('GIT_REMOTE_HG_TEST_REMOTE'):
389         repo = hg.repository(myui, url)
390         if not os.path.exists(dirname):
391             os.makedirs(dirname)
392     else:
393         shared_path = os.path.join(gitdir, 'hg')
394
395         # check and upgrade old organization
396         hg_path = os.path.join(shared_path, '.hg')
397         if os.path.exists(shared_path) and not os.path.exists(hg_path):
398             repos = os.listdir(shared_path)
399             for x in repos:
400                 local_hg = os.path.join(shared_path, x, 'clone', '.hg')
401                 if not os.path.exists(local_hg):
402                     continue
403                 if not os.path.exists(hg_path):
404                     shutil.move(local_hg, hg_path)
405                 shutil.rmtree(os.path.join(shared_path, x, 'clone'))
406
407         # setup shared repo (if not there)
408         try:
409             hg.peer(myui, {}, shared_path, create=True)
410         except error.RepoError:
411             pass
412
413         if not os.path.exists(dirname):
414             os.makedirs(dirname)
415
416         local_path = os.path.join(dirname, 'clone')
417         if not os.path.exists(local_path):
418             hg.share(myui, shared_path, local_path, update=False)
419
420         repo = hg.repository(myui, local_path)
421         try:
422             peer = hg.peer(myui, {}, url)
423         except:
424             die('Repository error')
425         repo.pull(peer, heads=None, force=True)
426
427         updatebookmarks(repo, peer)
428
429     return repo
430
431 def rev_to_mark(rev):
432     return marks.from_rev(rev.hex())
433
434 def mark_to_rev(mark):
435     return marks.to_rev(mark)
436
437 def export_ref(repo, name, kind, head):
438     ename = '%s/%s' % (kind, name)
439     try:
440         tip = marks.get_tip(ename)
441         tip = repo[tip].rev()
442     except:
443         tip = 0
444
445     revs = xrange(tip, head.rev() + 1)
446     total = len(revs)
447
448     for rev in revs:
449
450         c = repo[rev]
451         node = c.node()
452
453         if marks.is_marked(c.hex()):
454             continue
455
456         (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(node)
457         rev_branch = extra['branch']
458
459         author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
460         if 'committer' in extra:
461             user, time, tz = extra['committer'].rsplit(' ', 2)
462             committer = "%s %s %s" % (user, time, gittz(int(tz)))
463         else:
464             committer = author
465
466         parents = [repo[p] for p in repo.changelog.parentrevs(rev) if p >= 0]
467
468         if len(parents) == 0:
469             modified = c.manifest().keys()
470             removed = []
471         else:
472             modified, removed = get_filechanges(repo, c, parents[0])
473
474         desc += '\n'
475
476         if mode == 'hg':
477             extra_msg = ''
478
479             if rev_branch != 'default':
480                 extra_msg += 'branch : %s\n' % rev_branch
481
482             renames = []
483             for f in c.files():
484                 if f not in c.manifest():
485                     continue
486                 rename = c.filectx(f).renamed()
487                 if rename:
488                     renames.append((rename[0], f))
489
490             for e in renames:
491                 extra_msg += "rename : %s => %s\n" % e
492
493             for key, value in extra.iteritems():
494                 if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):
495                     continue
496                 else:
497                     extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value))
498
499             if extra_msg:
500                 desc += '\n--HG--\n' + extra_msg
501
502         if len(parents) == 0 and rev:
503             print 'reset %s/%s' % (prefix, ename)
504
505         modified_final = export_files(c.filectx(f) for f in modified)
506
507         print "commit %s/%s" % (prefix, ename)
508         print "mark :%d" % (marks.get_mark(c.hex()))
509         print "author %s" % (author)
510         print "committer %s" % (committer)
511         print "data %d" % (len(desc))
512         print desc
513
514         if len(parents) > 0:
515             print "from :%s" % (rev_to_mark(parents[0]))
516             if len(parents) > 1:
517                 print "merge :%s" % (rev_to_mark(parents[1]))
518
519         for f in removed:
520             print "D %s" % (fix_file_path(f))
521         for f in modified_final:
522             print "M %s :%u %s" % f
523         print
524
525         progress = (rev - tip)
526         if (progress % 100 == 0):
527             print "progress revision %d '%s' (%d/%d)" % (rev, name, progress, total)
528
529     # make sure the ref is updated
530     print "reset %s/%s" % (prefix, ename)
531     print "from :%u" % rev_to_mark(head)
532     print
533
534     pending_revs = set(revs) - notes
535     if pending_revs:
536         note_mark = marks.next_mark()
537         ref = "refs/notes/hg"
538
539         print "commit %s" % ref
540         print "mark :%d" % (note_mark)
541         print "committer remote-hg <> %s" % (ptime.strftime('%s %z'))
542         desc = "Notes for %s\n" % (name)
543         print "data %d" % (len(desc))
544         print desc
545         if marks.last_note:
546             print "from :%u" % marks.last_note
547
548         for rev in pending_revs:
549             notes.add(rev)
550             c = repo[rev]
551             print "N inline :%u" % rev_to_mark(c)
552             msg = c.hex()
553             print "data %d" % (len(msg))
554             print msg
555         print
556
557         marks.last_note = note_mark
558
559     marks.set_tip(ename, head.hex())
560
561 def export_tag(repo, tag):
562     export_ref(repo, tag, 'tags', repo[hgref(tag)])
563
564 def export_bookmark(repo, bmark):
565     head = bmarks[hgref(bmark)]
566     export_ref(repo, bmark, 'bookmarks', head)
567
568 def export_branch(repo, branch):
569     tip = get_branch_tip(repo, branch)
570     head = repo[tip]
571     export_ref(repo, branch, 'branches', head)
572
573 def export_head(repo):
574     export_ref(repo, g_head[0], 'bookmarks', g_head[1])
575
576 def do_capabilities(parser):
577     print "import"
578     print "export"
579     print "refspec refs/heads/branches/*:%s/branches/*" % prefix
580     print "refspec refs/heads/*:%s/bookmarks/*" % prefix
581     print "refspec refs/tags/*:%s/tags/*" % prefix
582
583     path = os.path.join(dirname, 'marks-git')
584
585     if os.path.exists(path):
586         print "*import-marks %s" % path
587     print "*export-marks %s" % path
588     print "option"
589
590     print
591
592 def branch_tip(branch):
593     return branches[branch][-1]
594
595 def get_branch_tip(repo, branch):
596     heads = branches.get(hgref(branch), None)
597     if not heads:
598         return None
599
600     # verify there's only one head
601     if (len(heads) > 1):
602         warn("Branch '%s' has more than one head, consider merging" % branch)
603         return branch_tip(hgref(branch))
604
605     return heads[0]
606
607 def list_head(repo, cur):
608     global g_head, fake_bmark
609
610     if 'default' not in branches:
611         # empty repo
612         return
613
614     node = repo[branch_tip('default')]
615     head = 'master' if not 'master' in bmarks else 'default'
616     fake_bmark = head
617     bmarks[head] = node
618
619     head = gitref(head)
620     print "@refs/heads/%s HEAD" % head
621     g_head = (head, node)
622
623 def do_list(parser):
624     repo = parser.repo
625     for bmark, node in bookmarks.listbookmarks(repo).iteritems():
626         bmarks[bmark] = repo[node]
627
628     cur = repo.dirstate.branch()
629     orig = peer if peer else repo
630
631     for branch, heads in orig.branchmap().iteritems():
632         # only open heads
633         heads = [h for h in heads if 'close' not in repo.changelog.read(h)[5]]
634         if heads:
635             branches[branch] = heads
636
637     list_head(repo, cur)
638
639     if track_branches:
640         for branch in branches:
641             print "? refs/heads/branches/%s" % gitref(branch)
642
643     for bmark in bmarks:
644         if  bmarks[bmark].hex() == '0000000000000000000000000000000000000000':
645             warn("Ignoring invalid bookmark '%s'", bmark)
646         else:
647             print "? refs/heads/%s" % gitref(bmark)
648
649     for tag, node in repo.tagslist():
650         if tag == 'tip':
651             continue
652         print "? refs/tags/%s" % gitref(tag)
653
654     print
655
656 def do_import(parser):
657     repo = parser.repo
658
659     path = os.path.join(dirname, 'marks-git')
660
661     print "feature done"
662     if os.path.exists(path):
663         print "feature import-marks=%s" % path
664     print "feature export-marks=%s" % path
665     print "feature force"
666     sys.stdout.flush()
667
668     tmp = encoding.encoding
669     encoding.encoding = 'utf-8'
670
671     # lets get all the import lines
672     while parser.check('import'):
673         ref = parser[1]
674
675         if (ref == 'HEAD'):
676             export_head(repo)
677         elif ref.startswith('refs/heads/branches/'):
678             branch = ref[len('refs/heads/branches/'):]
679             export_branch(repo, branch)
680         elif ref.startswith('refs/heads/'):
681             bmark = ref[len('refs/heads/'):]
682             export_bookmark(repo, bmark)
683         elif ref.startswith('refs/tags/'):
684             tag = ref[len('refs/tags/'):]
685             export_tag(repo, tag)
686
687         parser.next()
688
689     encoding.encoding = tmp
690
691     print 'done'
692
693 def parse_blob(parser):
694     parser.next()
695     mark = parser.get_mark()
696     parser.next()
697     data = parser.get_data()
698     blob_marks[mark] = data
699     parser.next()
700
701 def get_merge_files(repo, p1, p2, files):
702     for e in repo[p1].files():
703         if e not in files:
704             if e not in repo[p1].manifest():
705                 continue
706             f = { 'ctx' : repo[p1][e] }
707             files[e] = f
708
709 def c_style_unescape(string):
710     if string[0] == string[-1] == '"':
711         return string.decode('string-escape')[1:-1]
712     return string
713
714 def parse_commit(parser):
715     from_mark = merge_mark = None
716
717     ref = parser[1]
718     parser.next()
719
720     commit_mark = parser.get_mark()
721     parser.next()
722     author = parser.get_author()
723     parser.next()
724     committer = parser.get_author()
725     parser.next()
726     data = parser.get_data()
727     parser.next()
728     if parser.check('from'):
729         from_mark = parser.get_mark()
730         parser.next()
731     if parser.check('merge'):
732         merge_mark = parser.get_mark()
733         parser.next()
734         if parser.check('merge'):
735             die('octopus merges are not supported yet')
736
737     # fast-export adds an extra newline
738     if data[-1] == '\n':
739         data = data[:-1]
740
741     files = {}
742
743     for line in parser:
744         if parser.check('M'):
745             t, m, mark_ref, path = line.split(' ', 3)
746             mark = int(mark_ref[1:])
747             f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
748         elif parser.check('D'):
749             t, path = line.split(' ', 1)
750             f = { 'deleted' : True }
751         else:
752             die('Unknown file command: %s' % line)
753         path = c_style_unescape(path)
754         files[path] = f
755
756     # only export the commits if we are on an internal proxy repo
757     if dry_run and not peer:
758         parsed_refs[ref] = None
759         return
760
761     def getfilectx(repo, memctx, f):
762         of = files[f]
763         if 'deleted' in of:
764             raise IOError
765         if 'ctx' in of:
766             return of['ctx']
767         is_exec = of['mode'] == 'x'
768         is_link = of['mode'] == 'l'
769         rename = of.get('rename', None)
770         return context.memfilectx(f, of['data'],
771                 is_link, is_exec, rename)
772
773     repo = parser.repo
774
775     user, date, tz = author
776     extra = {}
777
778     if committer != author:
779         extra['committer'] = "%s %u %u" % committer
780
781     if from_mark:
782         p1 = mark_to_rev(from_mark)
783     else:
784         p1 = '0' * 40
785
786     if merge_mark:
787         p2 = mark_to_rev(merge_mark)
788     else:
789         p2 = '0' * 40
790
791     #
792     # If files changed from any of the parents, hg wants to know, but in git if
793     # nothing changed from the first parent, nothing changed.
794     #
795     if merge_mark:
796         get_merge_files(repo, p1, p2, files)
797
798     # Check if the ref is supposed to be a named branch
799     if ref.startswith('refs/heads/branches/'):
800         branch = ref[len('refs/heads/branches/'):]
801         extra['branch'] = hgref(branch)
802
803     if mode == 'hg':
804         i = data.find('\n--HG--\n')
805         if i >= 0:
806             tmp = data[i + len('\n--HG--\n'):].strip()
807             for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
808                 if k == 'rename':
809                     old, new = v.split(' => ', 1)
810                     files[new]['rename'] = old
811                 elif k == 'branch':
812                     extra[k] = v
813                 elif k == 'extra':
814                     ek, ev = v.split(' : ', 1)
815                     extra[ek] = urllib.unquote(ev)
816             data = data[:i]
817
818     ctx = context.memctx(repo, (p1, p2), data,
819             files.keys(), getfilectx,
820             user, (date, tz), extra)
821
822     tmp = encoding.encoding
823     encoding.encoding = 'utf-8'
824
825     node = hghex(repo.commitctx(ctx))
826
827     encoding.encoding = tmp
828
829     parsed_refs[ref] = node
830     marks.new_mark(node, commit_mark)
831
832 def parse_reset(parser):
833     ref = parser[1]
834     parser.next()
835     # ugh
836     if parser.check('commit'):
837         parse_commit(parser)
838         return
839     if not parser.check('from'):
840         return
841     from_mark = parser.get_mark()
842     parser.next()
843
844     try:
845         rev = mark_to_rev(from_mark)
846     except KeyError:
847         rev = None
848     parsed_refs[ref] = rev
849
850 def parse_tag(parser):
851     name = parser[1]
852     parser.next()
853     from_mark = parser.get_mark()
854     parser.next()
855     tagger = parser.get_author()
856     parser.next()
857     data = parser.get_data()
858     parser.next()
859
860     parsed_tags[name] = (tagger, data)
861
862 def write_tag(repo, tag, node, msg, author):
863     branch = repo[node].branch()
864     tip = branch_tip(branch)
865     tip = repo[tip]
866
867     def getfilectx(repo, memctx, f):
868         try:
869             fctx = tip.filectx(f)
870             data = fctx.data()
871         except error.ManifestLookupError:
872             data = ""
873         content = data + "%s %s\n" % (node, tag)
874         return context.memfilectx(f, content, False, False, None)
875
876     p1 = tip.hex()
877     p2 = '0' * 40
878     if author:
879         user, date, tz = author
880         date_tz = (date, tz)
881     else:
882         cmd = ['git', 'var', 'GIT_COMMITTER_IDENT']
883         process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
884         output, _ = process.communicate()
885         m = re.match('^.* <.*>', output)
886         if m:
887             user = m.group(0)
888         else:
889             user = repo.ui.username()
890         date_tz = None
891
892     ctx = context.memctx(repo, (p1, p2), msg,
893             ['.hgtags'], getfilectx,
894             user, date_tz, {'branch' : branch})
895
896     tmp = encoding.encoding
897     encoding.encoding = 'utf-8'
898
899     tagnode = repo.commitctx(ctx)
900
901     encoding.encoding = tmp
902
903     return (tagnode, branch)
904
905 def checkheads_bmark(repo, ref, ctx):
906     bmark = ref[len('refs/heads/'):]
907     if not bmark in bmarks:
908         # new bmark
909         return True
910
911     ctx_old = bmarks[bmark]
912     ctx_new = ctx
913     if not repo.changelog.descendant(ctx_old.rev(), ctx_new.rev()):
914         if force_push:
915             print "ok %s forced update" % ref
916         else:
917             print "error %s non-fast forward" % ref
918             return False
919
920     return True
921
922 def checkheads(repo, remote, p_revs):
923
924     remotemap = remote.branchmap()
925     if not remotemap:
926         # empty repo
927         return True
928
929     new = {}
930     ret = True
931
932     for node, ref in p_revs.iteritems():
933         ctx = repo[node]
934         branch = ctx.branch()
935         if not branch in remotemap:
936             # new branch
937             continue
938         if not ref.startswith('refs/heads/branches'):
939             if ref.startswith('refs/heads/'):
940                 if not checkheads_bmark(repo, ref, ctx):
941                     ret = False
942
943             # only check branches
944             continue
945         new.setdefault(branch, []).append(ctx.rev())
946
947     for branch, heads in new.iteritems():
948         old = [repo.changelog.rev(x) for x in remotemap[branch]]
949         for rev in heads:
950             if check_version(2, 3):
951                 ancestors = repo.changelog.ancestors([rev], stoprev=min(old))
952             else:
953                 ancestors = repo.changelog.ancestors(rev)
954             found = False
955
956             for x in old:
957                 if x in ancestors:
958                     found = True
959                     break
960
961             if found:
962                 continue
963
964             node = repo.changelog.node(rev)
965             ref = p_revs[node]
966             if force_push:
967                 print "ok %s forced update" % ref
968             else:
969                 print "error %s non-fast forward" % ref
970                 ret = False
971
972     return ret
973
974 def push_unsafe(repo, remote, parsed_refs, p_revs):
975
976     force = force_push
977
978     fci = discovery.findcommonincoming
979     commoninc = fci(repo, remote, force=force)
980     common, _, remoteheads = commoninc
981
982     if not checkheads(repo, remote, p_revs):
983         return None
984
985     cg = repo.getbundle('push', heads=list(p_revs), common=common)
986
987     unbundle = remote.capable('unbundle')
988     if unbundle:
989         if force:
990             remoteheads = ['force']
991         return remote.unbundle(cg, remoteheads, 'push')
992     else:
993         return remote.addchangegroup(cg, 'push', repo.url())
994
995 def push(repo, remote, parsed_refs, p_revs):
996     if hasattr(remote, 'canpush') and not remote.canpush():
997         print "error cannot push"
998
999     if not p_revs:
1000         # nothing to push
1001         return
1002
1003     lock = None
1004     unbundle = remote.capable('unbundle')
1005     if not unbundle:
1006         lock = remote.lock()
1007     try:
1008         ret = push_unsafe(repo, remote, parsed_refs, p_revs)
1009     finally:
1010         if lock is not None:
1011             lock.release()
1012
1013     return ret
1014
1015 def check_tip(ref, kind, name, heads):
1016     try:
1017         ename = '%s/%s' % (kind, name)
1018         tip = marks.get_tip(ename)
1019     except KeyError:
1020         return True
1021     else:
1022         return tip in heads
1023
1024 def do_export(parser):
1025     p_bmarks = []
1026     p_revs = {}
1027
1028     parser.next()
1029
1030     for line in parser.each_block('done'):
1031         if parser.check('blob'):
1032             parse_blob(parser)
1033         elif parser.check('commit'):
1034             parse_commit(parser)
1035         elif parser.check('reset'):
1036             parse_reset(parser)
1037         elif parser.check('tag'):
1038             parse_tag(parser)
1039         elif parser.check('feature'):
1040             pass
1041         else:
1042             die('unhandled export command: %s' % line)
1043
1044     need_fetch = False
1045
1046     for ref, node in parsed_refs.iteritems():
1047         bnode = hgbin(node) if node else None
1048         if ref.startswith('refs/heads/branches'):
1049             branch = ref[len('refs/heads/branches/'):]
1050             if branch in branches and bnode in branches[branch]:
1051                 # up to date
1052                 continue
1053
1054             if peer:
1055                 remotemap = peer.branchmap()
1056                 if remotemap and branch in remotemap:
1057                     heads = [hghex(e) for e in remotemap[branch]]
1058                     if not check_tip(ref, 'branches', branch, heads):
1059                         print "error %s fetch first" % ref
1060                         need_fetch = True
1061                         continue
1062
1063             p_revs[bnode] = ref
1064             print "ok %s" % ref
1065         elif ref.startswith('refs/heads/'):
1066             bmark = ref[len('refs/heads/'):]
1067             new = node
1068             old = bmarks[bmark].hex() if bmark in bmarks else ''
1069
1070             if old == new:
1071                 continue
1072
1073             print "ok %s" % ref
1074             if bmark != fake_bmark and \
1075                     not (bmark == 'master' and bmark not in parser.repo._bookmarks):
1076                 p_bmarks.append((ref, bmark, old, new))
1077
1078             if peer:
1079                 remote_old = peer.listkeys('bookmarks').get(bmark)
1080                 if remote_old:
1081                     if not check_tip(ref, 'bookmarks', bmark, remote_old):
1082                         print "error %s fetch first" % ref
1083                         need_fetch = True
1084                         continue
1085
1086             p_revs[bnode] = ref
1087         elif ref.startswith('refs/tags/'):
1088             if dry_run:
1089                 print "ok %s" % ref
1090                 continue
1091             tag = ref[len('refs/tags/'):]
1092             tag = hgref(tag)
1093             author, msg = parsed_tags.get(tag, (None, None))
1094             if mode == 'git':
1095                 if not msg:
1096                     msg = 'Added tag %s for changeset %s' % (tag, node[:12])
1097                 tagnode, branch = write_tag(parser.repo, tag, node, msg, author)
1098                 p_revs[tagnode] = 'refs/heads/branches/' + gitref(branch)
1099             else:
1100                 fp = parser.repo.opener('localtags', 'a')
1101                 fp.write('%s %s\n' % (node, tag))
1102                 fp.close()
1103             p_revs[bnode] = ref
1104             print "ok %s" % ref
1105         else:
1106             # transport-helper/fast-export bugs
1107             continue
1108
1109     if need_fetch:
1110         print
1111         return
1112
1113     if dry_run:
1114         if peer and not force_push:
1115             checkheads(parser.repo, peer, p_revs)
1116         print
1117         return
1118
1119     if peer:
1120         if not push(parser.repo, peer, parsed_refs, p_revs):
1121             # do not update bookmarks
1122             print
1123             return
1124
1125         # update remote bookmarks
1126         remote_bmarks = peer.listkeys('bookmarks')
1127         for ref, bmark, old, new in p_bmarks:
1128             if force_push:
1129                 old = remote_bmarks.get(bmark, '')
1130             if not peer.pushkey('bookmarks', bmark, old, new):
1131                 print "error %s" % ref
1132     else:
1133         # update local bookmarks
1134         for ref, bmark, old, new in p_bmarks:
1135             if not bookmarks.pushbookmark(parser.repo, bmark, old, new):
1136                 print "error %s" % ref
1137
1138     print
1139
1140 def do_option(parser):
1141     global dry_run, force_push
1142     _, key, value = parser.line.split(' ')
1143     if key == 'dry-run':
1144         dry_run = (value == 'true')
1145         print 'ok'
1146     elif key == 'force':
1147         force_push = (value == 'true')
1148         print 'ok'
1149     else:
1150         print 'unsupported'
1151
1152 def fix_path(alias, repo, orig_url):
1153     url = urlparse.urlparse(orig_url, 'file')
1154     if url.scheme != 'file' or os.path.isabs(os.path.expanduser(url.path)):
1155         return
1156     abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
1157     cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
1158     subprocess.call(cmd)
1159
1160 def main(args):
1161     global prefix, gitdir, dirname, branches, bmarks
1162     global marks, blob_marks, parsed_refs
1163     global peer, mode, bad_mail, bad_name
1164     global track_branches, force_push, is_tmp
1165     global parsed_tags
1166     global filenodes
1167     global fake_bmark, hg_version
1168     global dry_run
1169     global notes, alias
1170
1171     alias = args[1]
1172     url = args[2]
1173     peer = None
1174
1175     hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
1176     track_branches = get_config_bool('remote-hg.track-branches', True)
1177     force_push = False
1178
1179     if hg_git_compat:
1180         mode = 'hg'
1181         bad_mail = 'none@none'
1182         bad_name = ''
1183     else:
1184         mode = 'git'
1185         bad_mail = 'unknown'
1186         bad_name = 'Unknown'
1187
1188     if alias[4:] == url:
1189         is_tmp = True
1190         alias = hashlib.sha1(alias).hexdigest()
1191     else:
1192         is_tmp = False
1193
1194     gitdir = os.environ['GIT_DIR']
1195     dirname = os.path.join(gitdir, 'hg', alias)
1196     branches = {}
1197     bmarks = {}
1198     blob_marks = {}
1199     parsed_refs = {}
1200     marks = None
1201     parsed_tags = {}
1202     filenodes = {}
1203     fake_bmark = None
1204     try:
1205         hg_version = tuple(int(e) for e in util.version().split('.'))
1206     except:
1207         hg_version = None
1208     dry_run = False
1209     notes = set()
1210
1211     repo = get_repo(url, alias)
1212     prefix = 'refs/hg/%s' % alias
1213
1214     if not is_tmp:
1215         fix_path(alias, peer or repo, url)
1216
1217     marks_path = os.path.join(dirname, 'marks-hg')
1218     marks = Marks(marks_path, repo)
1219
1220     if sys.platform == 'win32':
1221         import msvcrt
1222         msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
1223
1224     parser = Parser(repo)
1225     for line in parser:
1226         if parser.check('capabilities'):
1227             do_capabilities(parser)
1228         elif parser.check('list'):
1229             do_list(parser)
1230         elif parser.check('import'):
1231             do_import(parser)
1232         elif parser.check('export'):
1233             do_export(parser)
1234         elif parser.check('option'):
1235             do_option(parser)
1236         else:
1237             die('unhandled command: %s' % line)
1238         sys.stdout.flush()
1239
1240 def bye():
1241     if not marks:
1242         return
1243     if not is_tmp:
1244         marks.store()
1245     else:
1246         shutil.rmtree(dirname)
1247
1248 atexit.register(bye)
1249 sys.exit(main(sys.argv))