Merge branch 'jk/t5000-gzip-simplify'
[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(r'([^ \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             mail = m.group(1)
320         else:
321             m = NAME_RE.match(user)
322             if m:
323                 name = m.group(1).strip()
324     return (name, mail)
325
326 def fixup_user_hg(user):
327     def sanitize(name):
328         # stole this from hg-git
329         return re.sub('[<>\n]', '?', name.lstrip('< ').rstrip('> '))
330
331     m = AUTHOR_HG_RE.match(user)
332     if m:
333         name = sanitize(m.group(1))
334         mail = sanitize(m.group(2))
335         ex = m.group(3)
336         if ex:
337             name += ' ext:(' + urllib.quote(ex) + ')'
338     else:
339         name = sanitize(user)
340         if '@' in user:
341             mail = name
342         else:
343             mail = None
344
345     return (name, mail)
346
347 def fixup_user(user):
348     if mode == 'git':
349         name, mail = fixup_user_git(user)
350     else:
351         name, mail = fixup_user_hg(user)
352
353     if not name:
354         name = bad_name
355     if not mail:
356         mail = bad_mail
357
358     return '%s <%s>' % (name, mail)
359
360 def updatebookmarks(repo, peer):
361     remotemarks = peer.listkeys('bookmarks')
362     localmarks = repo._bookmarks
363
364     if not remotemarks:
365         return
366
367     for k, v in remotemarks.iteritems():
368         localmarks[k] = hgbin(v)
369
370     if hasattr(localmarks, 'write'):
371         localmarks.write()
372     else:
373         bookmarks.write(repo)
374
375 def get_repo(url, alias):
376     global peer
377
378     myui = ui.ui()
379     myui.setconfig('ui', 'interactive', 'off')
380     myui.fout = sys.stderr
381
382     if get_config_bool('remote-hg.insecure'):
383         myui.setconfig('web', 'cacerts', '')
384
385     extensions.loadall(myui)
386
387     if hg.islocal(url) and not os.environ.get('GIT_REMOTE_HG_TEST_REMOTE'):
388         repo = hg.repository(myui, url)
389         if not os.path.exists(dirname):
390             os.makedirs(dirname)
391     else:
392         shared_path = os.path.join(gitdir, 'hg')
393
394         # check and upgrade old organization
395         hg_path = os.path.join(shared_path, '.hg')
396         if os.path.exists(shared_path) and not os.path.exists(hg_path):
397             repos = os.listdir(shared_path)
398             for x in repos:
399                 local_hg = os.path.join(shared_path, x, 'clone', '.hg')
400                 if not os.path.exists(local_hg):
401                     continue
402                 if not os.path.exists(hg_path):
403                     shutil.move(local_hg, hg_path)
404                 shutil.rmtree(os.path.join(shared_path, x, 'clone'))
405
406         # setup shared repo (if not there)
407         try:
408             hg.peer(myui, {}, shared_path, create=True)
409         except error.RepoError:
410             pass
411
412         if not os.path.exists(dirname):
413             os.makedirs(dirname)
414
415         local_path = os.path.join(dirname, 'clone')
416         if not os.path.exists(local_path):
417             hg.share(myui, shared_path, local_path, update=False)
418
419         repo = hg.repository(myui, local_path)
420         try:
421             peer = hg.peer(myui, {}, url)
422         except:
423             die('Repository error')
424         repo.pull(peer, heads=None, force=True)
425
426         updatebookmarks(repo, peer)
427
428     return repo
429
430 def rev_to_mark(rev):
431     return marks.from_rev(rev.hex())
432
433 def mark_to_rev(mark):
434     return marks.to_rev(mark)
435
436 def export_ref(repo, name, kind, head):
437     ename = '%s/%s' % (kind, name)
438     try:
439         tip = marks.get_tip(ename)
440         tip = repo[tip].rev()
441     except:
442         tip = 0
443
444     revs = xrange(tip, head.rev() + 1)
445     total = len(revs)
446
447     for rev in revs:
448
449         c = repo[rev]
450         node = c.node()
451
452         if marks.is_marked(c.hex()):
453             continue
454
455         (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(node)
456         rev_branch = extra['branch']
457
458         author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
459         if 'committer' in extra:
460             user, time, tz = extra['committer'].rsplit(' ', 2)
461             committer = "%s %s %s" % (user, time, gittz(int(tz)))
462         else:
463             committer = author
464
465         parents = [repo[p] for p in repo.changelog.parentrevs(rev) if p >= 0]
466
467         if len(parents) == 0:
468             modified = c.manifest().keys()
469             removed = []
470         else:
471             modified, removed = get_filechanges(repo, c, parents[0])
472
473         desc += '\n'
474
475         if mode == 'hg':
476             extra_msg = ''
477
478             if rev_branch != 'default':
479                 extra_msg += 'branch : %s\n' % rev_branch
480
481             renames = []
482             for f in c.files():
483                 if f not in c.manifest():
484                     continue
485                 rename = c.filectx(f).renamed()
486                 if rename:
487                     renames.append((rename[0], f))
488
489             for e in renames:
490                 extra_msg += "rename : %s => %s\n" % e
491
492             for key, value in extra.iteritems():
493                 if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):
494                     continue
495                 else:
496                     extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value))
497
498             if extra_msg:
499                 desc += '\n--HG--\n' + extra_msg
500
501         if len(parents) == 0 and rev:
502             print 'reset %s/%s' % (prefix, ename)
503
504         modified_final = export_files(c.filectx(f) for f in modified)
505
506         print "commit %s/%s" % (prefix, ename)
507         print "mark :%d" % (marks.get_mark(c.hex()))
508         print "author %s" % (author)
509         print "committer %s" % (committer)
510         print "data %d" % (len(desc))
511         print desc
512
513         if len(parents) > 0:
514             print "from :%s" % (rev_to_mark(parents[0]))
515             if len(parents) > 1:
516                 print "merge :%s" % (rev_to_mark(parents[1]))
517
518         for f in removed:
519             print "D %s" % (fix_file_path(f))
520         for f in modified_final:
521             print "M %s :%u %s" % f
522         print
523
524         progress = (rev - tip)
525         if (progress % 100 == 0):
526             print "progress revision %d '%s' (%d/%d)" % (rev, name, progress, total)
527
528     # make sure the ref is updated
529     print "reset %s/%s" % (prefix, ename)
530     print "from :%u" % rev_to_mark(head)
531     print
532
533     pending_revs = set(revs) - notes
534     if pending_revs:
535         note_mark = marks.next_mark()
536         ref = "refs/notes/hg"
537
538         print "commit %s" % ref
539         print "mark :%d" % (note_mark)
540         print "committer remote-hg <> %s" % (ptime.strftime('%s %z'))
541         desc = "Notes for %s\n" % (name)
542         print "data %d" % (len(desc))
543         print desc
544         if marks.last_note:
545             print "from :%u" % marks.last_note
546
547         for rev in pending_revs:
548             notes.add(rev)
549             c = repo[rev]
550             print "N inline :%u" % rev_to_mark(c)
551             msg = c.hex()
552             print "data %d" % (len(msg))
553             print msg
554         print
555
556         marks.last_note = note_mark
557
558     marks.set_tip(ename, head.hex())
559
560 def export_tag(repo, tag):
561     export_ref(repo, tag, 'tags', repo[hgref(tag)])
562
563 def export_bookmark(repo, bmark):
564     head = bmarks[hgref(bmark)]
565     export_ref(repo, bmark, 'bookmarks', head)
566
567 def export_branch(repo, branch):
568     tip = get_branch_tip(repo, branch)
569     head = repo[tip]
570     export_ref(repo, branch, 'branches', head)
571
572 def export_head(repo):
573     export_ref(repo, g_head[0], 'bookmarks', g_head[1])
574
575 def do_capabilities(parser):
576     print "import"
577     print "export"
578     print "refspec refs/heads/branches/*:%s/branches/*" % prefix
579     print "refspec refs/heads/*:%s/bookmarks/*" % prefix
580     print "refspec refs/tags/*:%s/tags/*" % prefix
581
582     path = os.path.join(dirname, 'marks-git')
583
584     if os.path.exists(path):
585         print "*import-marks %s" % path
586     print "*export-marks %s" % path
587     print "option"
588
589     print
590
591 def branch_tip(branch):
592     return branches[branch][-1]
593
594 def get_branch_tip(repo, branch):
595     heads = branches.get(hgref(branch), None)
596     if not heads:
597         return None
598
599     # verify there's only one head
600     if (len(heads) > 1):
601         warn("Branch '%s' has more than one head, consider merging" % branch)
602         return branch_tip(hgref(branch))
603
604     return heads[0]
605
606 def list_head(repo, cur):
607     global g_head, fake_bmark
608
609     if 'default' not in branches:
610         # empty repo
611         return
612
613     node = repo[branch_tip('default')]
614     head = 'master' if not 'master' in bmarks else 'default'
615     fake_bmark = head
616     bmarks[head] = node
617
618     head = gitref(head)
619     print "@refs/heads/%s HEAD" % head
620     g_head = (head, node)
621
622 def do_list(parser):
623     repo = parser.repo
624     for bmark, node in bookmarks.listbookmarks(repo).iteritems():
625         bmarks[bmark] = repo[node]
626
627     cur = repo.dirstate.branch()
628     orig = peer if peer else repo
629
630     for branch, heads in orig.branchmap().iteritems():
631         # only open heads
632         heads = [h for h in heads if 'close' not in repo.changelog.read(h)[5]]
633         if heads:
634             branches[branch] = heads
635
636     list_head(repo, cur)
637
638     if track_branches:
639         for branch in branches:
640             print "? refs/heads/branches/%s" % gitref(branch)
641
642     for bmark in bmarks:
643         print "? refs/heads/%s" % gitref(bmark)
644
645     for tag, node in repo.tagslist():
646         if tag == 'tip':
647             continue
648         print "? refs/tags/%s" % gitref(tag)
649
650     print
651
652 def do_import(parser):
653     repo = parser.repo
654
655     path = os.path.join(dirname, 'marks-git')
656
657     print "feature done"
658     if os.path.exists(path):
659         print "feature import-marks=%s" % path
660     print "feature export-marks=%s" % path
661     print "feature force"
662     sys.stdout.flush()
663
664     tmp = encoding.encoding
665     encoding.encoding = 'utf-8'
666
667     # lets get all the import lines
668     while parser.check('import'):
669         ref = parser[1]
670
671         if (ref == 'HEAD'):
672             export_head(repo)
673         elif ref.startswith('refs/heads/branches/'):
674             branch = ref[len('refs/heads/branches/'):]
675             export_branch(repo, branch)
676         elif ref.startswith('refs/heads/'):
677             bmark = ref[len('refs/heads/'):]
678             export_bookmark(repo, bmark)
679         elif ref.startswith('refs/tags/'):
680             tag = ref[len('refs/tags/'):]
681             export_tag(repo, tag)
682
683         parser.next()
684
685     encoding.encoding = tmp
686
687     print 'done'
688
689 def parse_blob(parser):
690     parser.next()
691     mark = parser.get_mark()
692     parser.next()
693     data = parser.get_data()
694     blob_marks[mark] = data
695     parser.next()
696
697 def get_merge_files(repo, p1, p2, files):
698     for e in repo[p1].files():
699         if e not in files:
700             if e not in repo[p1].manifest():
701                 continue
702             f = { 'ctx' : repo[p1][e] }
703             files[e] = f
704
705 def c_style_unescape(string):
706     if string[0] == string[-1] == '"':
707         return string.decode('string-escape')[1:-1]
708     return string
709
710 def parse_commit(parser):
711     from_mark = merge_mark = None
712
713     ref = parser[1]
714     parser.next()
715
716     commit_mark = parser.get_mark()
717     parser.next()
718     author = parser.get_author()
719     parser.next()
720     committer = parser.get_author()
721     parser.next()
722     data = parser.get_data()
723     parser.next()
724     if parser.check('from'):
725         from_mark = parser.get_mark()
726         parser.next()
727     if parser.check('merge'):
728         merge_mark = parser.get_mark()
729         parser.next()
730         if parser.check('merge'):
731             die('octopus merges are not supported yet')
732
733     # fast-export adds an extra newline
734     if data[-1] == '\n':
735         data = data[:-1]
736
737     files = {}
738
739     for line in parser:
740         if parser.check('M'):
741             t, m, mark_ref, path = line.split(' ', 3)
742             mark = int(mark_ref[1:])
743             f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
744         elif parser.check('D'):
745             t, path = line.split(' ', 1)
746             f = { 'deleted' : True }
747         else:
748             die('Unknown file command: %s' % line)
749         path = c_style_unescape(path)
750         files[path] = f
751
752     # only export the commits if we are on an internal proxy repo
753     if dry_run and not peer:
754         parsed_refs[ref] = None
755         return
756
757     def getfilectx(repo, memctx, f):
758         of = files[f]
759         if 'deleted' in of:
760             raise IOError
761         if 'ctx' in of:
762             return of['ctx']
763         is_exec = of['mode'] == 'x'
764         is_link = of['mode'] == 'l'
765         rename = of.get('rename', None)
766         return context.memfilectx(f, of['data'],
767                 is_link, is_exec, rename)
768
769     repo = parser.repo
770
771     user, date, tz = author
772     extra = {}
773
774     if committer != author:
775         extra['committer'] = "%s %u %u" % committer
776
777     if from_mark:
778         p1 = mark_to_rev(from_mark)
779     else:
780         p1 = '0' * 40
781
782     if merge_mark:
783         p2 = mark_to_rev(merge_mark)
784     else:
785         p2 = '0' * 40
786
787     #
788     # If files changed from any of the parents, hg wants to know, but in git if
789     # nothing changed from the first parent, nothing changed.
790     #
791     if merge_mark:
792         get_merge_files(repo, p1, p2, files)
793
794     # Check if the ref is supposed to be a named branch
795     if ref.startswith('refs/heads/branches/'):
796         branch = ref[len('refs/heads/branches/'):]
797         extra['branch'] = hgref(branch)
798
799     if mode == 'hg':
800         i = data.find('\n--HG--\n')
801         if i >= 0:
802             tmp = data[i + len('\n--HG--\n'):].strip()
803             for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
804                 if k == 'rename':
805                     old, new = v.split(' => ', 1)
806                     files[new]['rename'] = old
807                 elif k == 'branch':
808                     extra[k] = v
809                 elif k == 'extra':
810                     ek, ev = v.split(' : ', 1)
811                     extra[ek] = urllib.unquote(ev)
812             data = data[:i]
813
814     ctx = context.memctx(repo, (p1, p2), data,
815             files.keys(), getfilectx,
816             user, (date, tz), extra)
817
818     tmp = encoding.encoding
819     encoding.encoding = 'utf-8'
820
821     node = hghex(repo.commitctx(ctx))
822
823     encoding.encoding = tmp
824
825     parsed_refs[ref] = node
826     marks.new_mark(node, commit_mark)
827
828 def parse_reset(parser):
829     ref = parser[1]
830     parser.next()
831     # ugh
832     if parser.check('commit'):
833         parse_commit(parser)
834         return
835     if not parser.check('from'):
836         return
837     from_mark = parser.get_mark()
838     parser.next()
839
840     try:
841         rev = mark_to_rev(from_mark)
842     except KeyError:
843         rev = None
844     parsed_refs[ref] = rev
845
846 def parse_tag(parser):
847     name = parser[1]
848     parser.next()
849     from_mark = parser.get_mark()
850     parser.next()
851     tagger = parser.get_author()
852     parser.next()
853     data = parser.get_data()
854     parser.next()
855
856     parsed_tags[name] = (tagger, data)
857
858 def write_tag(repo, tag, node, msg, author):
859     branch = repo[node].branch()
860     tip = branch_tip(branch)
861     tip = repo[tip]
862
863     def getfilectx(repo, memctx, f):
864         try:
865             fctx = tip.filectx(f)
866             data = fctx.data()
867         except error.ManifestLookupError:
868             data = ""
869         content = data + "%s %s\n" % (node, tag)
870         return context.memfilectx(f, content, False, False, None)
871
872     p1 = tip.hex()
873     p2 = '0' * 40
874     if author:
875         user, date, tz = author
876         date_tz = (date, tz)
877     else:
878         cmd = ['git', 'var', 'GIT_COMMITTER_IDENT']
879         process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
880         output, _ = process.communicate()
881         m = re.match('^.* <.*>', output)
882         if m:
883             user = m.group(0)
884         else:
885             user = repo.ui.username()
886         date_tz = None
887
888     ctx = context.memctx(repo, (p1, p2), msg,
889             ['.hgtags'], getfilectx,
890             user, date_tz, {'branch' : branch})
891
892     tmp = encoding.encoding
893     encoding.encoding = 'utf-8'
894
895     tagnode = repo.commitctx(ctx)
896
897     encoding.encoding = tmp
898
899     return (tagnode, branch)
900
901 def checkheads_bmark(repo, ref, ctx):
902     bmark = ref[len('refs/heads/'):]
903     if not bmark in bmarks:
904         # new bmark
905         return True
906
907     ctx_old = bmarks[bmark]
908     ctx_new = ctx
909     if not repo.changelog.descendant(ctx_old.rev(), ctx_new.rev()):
910         if force_push:
911             print "ok %s forced update" % ref
912         else:
913             print "error %s non-fast forward" % ref
914             return False
915
916     return True
917
918 def checkheads(repo, remote, p_revs):
919
920     remotemap = remote.branchmap()
921     if not remotemap:
922         # empty repo
923         return True
924
925     new = {}
926     ret = True
927
928     for node, ref in p_revs.iteritems():
929         ctx = repo[node]
930         branch = ctx.branch()
931         if not branch in remotemap:
932             # new branch
933             continue
934         if not ref.startswith('refs/heads/branches'):
935             if ref.startswith('refs/heads/'):
936                 if not checkheads_bmark(repo, ref, ctx):
937                     ret = False
938
939             # only check branches
940             continue
941         new.setdefault(branch, []).append(ctx.rev())
942
943     for branch, heads in new.iteritems():
944         old = [repo.changelog.rev(x) for x in remotemap[branch]]
945         for rev in heads:
946             if check_version(2, 3):
947                 ancestors = repo.changelog.ancestors([rev], stoprev=min(old))
948             else:
949                 ancestors = repo.changelog.ancestors(rev)
950             found = False
951
952             for x in old:
953                 if x in ancestors:
954                     found = True
955                     break
956
957             if found:
958                 continue
959
960             node = repo.changelog.node(rev)
961             ref = p_revs[node]
962             if force_push:
963                 print "ok %s forced update" % ref
964             else:
965                 print "error %s non-fast forward" % ref
966                 ret = False
967
968     return ret
969
970 def push_unsafe(repo, remote, parsed_refs, p_revs):
971
972     force = force_push
973
974     fci = discovery.findcommonincoming
975     commoninc = fci(repo, remote, force=force)
976     common, _, remoteheads = commoninc
977
978     if not checkheads(repo, remote, p_revs):
979         return None
980
981     cg = repo.getbundle('push', heads=list(p_revs), common=common)
982
983     unbundle = remote.capable('unbundle')
984     if unbundle:
985         if force:
986             remoteheads = ['force']
987         return remote.unbundle(cg, remoteheads, 'push')
988     else:
989         return remote.addchangegroup(cg, 'push', repo.url())
990
991 def push(repo, remote, parsed_refs, p_revs):
992     if hasattr(remote, 'canpush') and not remote.canpush():
993         print "error cannot push"
994
995     if not p_revs:
996         # nothing to push
997         return
998
999     lock = None
1000     unbundle = remote.capable('unbundle')
1001     if not unbundle:
1002         lock = remote.lock()
1003     try:
1004         ret = push_unsafe(repo, remote, parsed_refs, p_revs)
1005     finally:
1006         if lock is not None:
1007             lock.release()
1008
1009     return ret
1010
1011 def check_tip(ref, kind, name, heads):
1012     try:
1013         ename = '%s/%s' % (kind, name)
1014         tip = marks.get_tip(ename)
1015     except KeyError:
1016         return True
1017     else:
1018         return tip in heads
1019
1020 def do_export(parser):
1021     p_bmarks = []
1022     p_revs = {}
1023
1024     parser.next()
1025
1026     for line in parser.each_block('done'):
1027         if parser.check('blob'):
1028             parse_blob(parser)
1029         elif parser.check('commit'):
1030             parse_commit(parser)
1031         elif parser.check('reset'):
1032             parse_reset(parser)
1033         elif parser.check('tag'):
1034             parse_tag(parser)
1035         elif parser.check('feature'):
1036             pass
1037         else:
1038             die('unhandled export command: %s' % line)
1039
1040     need_fetch = False
1041
1042     for ref, node in parsed_refs.iteritems():
1043         bnode = hgbin(node) if node else None
1044         if ref.startswith('refs/heads/branches'):
1045             branch = ref[len('refs/heads/branches/'):]
1046             if branch in branches and bnode in branches[branch]:
1047                 # up to date
1048                 continue
1049
1050             if peer:
1051                 remotemap = peer.branchmap()
1052                 if remotemap and branch in remotemap:
1053                     heads = [hghex(e) for e in remotemap[branch]]
1054                     if not check_tip(ref, 'branches', branch, heads):
1055                         print "error %s fetch first" % ref
1056                         need_fetch = True
1057                         continue
1058
1059             p_revs[bnode] = ref
1060             print "ok %s" % ref
1061         elif ref.startswith('refs/heads/'):
1062             bmark = ref[len('refs/heads/'):]
1063             new = node
1064             old = bmarks[bmark].hex() if bmark in bmarks else ''
1065
1066             if old == new:
1067                 continue
1068
1069             print "ok %s" % ref
1070             if bmark != fake_bmark and \
1071                     not (bmark == 'master' and bmark not in parser.repo._bookmarks):
1072                 p_bmarks.append((ref, bmark, old, new))
1073
1074             if peer:
1075                 remote_old = peer.listkeys('bookmarks').get(bmark)
1076                 if remote_old:
1077                     if not check_tip(ref, 'bookmarks', bmark, remote_old):
1078                         print "error %s fetch first" % ref
1079                         need_fetch = True
1080                         continue
1081
1082             p_revs[bnode] = ref
1083         elif ref.startswith('refs/tags/'):
1084             if dry_run:
1085                 print "ok %s" % ref
1086                 continue
1087             tag = ref[len('refs/tags/'):]
1088             tag = hgref(tag)
1089             author, msg = parsed_tags.get(tag, (None, None))
1090             if mode == 'git':
1091                 if not msg:
1092                     msg = 'Added tag %s for changeset %s' % (tag, node[:12])
1093                 tagnode, branch = write_tag(parser.repo, tag, node, msg, author)
1094                 p_revs[tagnode] = 'refs/heads/branches/' + gitref(branch)
1095             else:
1096                 fp = parser.repo.opener('localtags', 'a')
1097                 fp.write('%s %s\n' % (node, tag))
1098                 fp.close()
1099             p_revs[bnode] = ref
1100             print "ok %s" % ref
1101         else:
1102             # transport-helper/fast-export bugs
1103             continue
1104
1105     if need_fetch:
1106         print
1107         return
1108
1109     if dry_run:
1110         if peer and not force_push:
1111             checkheads(parser.repo, peer, p_revs)
1112         print
1113         return
1114
1115     if peer:
1116         if not push(parser.repo, peer, parsed_refs, p_revs):
1117             # do not update bookmarks
1118             print
1119             return
1120
1121         # update remote bookmarks
1122         remote_bmarks = peer.listkeys('bookmarks')
1123         for ref, bmark, old, new in p_bmarks:
1124             if force_push:
1125                 old = remote_bmarks.get(bmark, '')
1126             if not peer.pushkey('bookmarks', bmark, old, new):
1127                 print "error %s" % ref
1128     else:
1129         # update local bookmarks
1130         for ref, bmark, old, new in p_bmarks:
1131             if not bookmarks.pushbookmark(parser.repo, bmark, old, new):
1132                 print "error %s" % ref
1133
1134     print
1135
1136 def do_option(parser):
1137     global dry_run, force_push
1138     _, key, value = parser.line.split(' ')
1139     if key == 'dry-run':
1140         dry_run = (value == 'true')
1141         print 'ok'
1142     elif key == 'force':
1143         force_push = (value == 'true')
1144         print 'ok'
1145     else:
1146         print 'unsupported'
1147
1148 def fix_path(alias, repo, orig_url):
1149     url = urlparse.urlparse(orig_url, 'file')
1150     if url.scheme != 'file' or os.path.isabs(os.path.expanduser(url.path)):
1151         return
1152     abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
1153     cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
1154     subprocess.call(cmd)
1155
1156 def main(args):
1157     global prefix, gitdir, dirname, branches, bmarks
1158     global marks, blob_marks, parsed_refs
1159     global peer, mode, bad_mail, bad_name
1160     global track_branches, force_push, is_tmp
1161     global parsed_tags
1162     global filenodes
1163     global fake_bmark, hg_version
1164     global dry_run
1165     global notes, alias
1166
1167     alias = args[1]
1168     url = args[2]
1169     peer = None
1170
1171     hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
1172     track_branches = get_config_bool('remote-hg.track-branches', True)
1173     force_push = False
1174
1175     if hg_git_compat:
1176         mode = 'hg'
1177         bad_mail = 'none@none'
1178         bad_name = ''
1179     else:
1180         mode = 'git'
1181         bad_mail = 'unknown'
1182         bad_name = 'Unknown'
1183
1184     if alias[4:] == url:
1185         is_tmp = True
1186         alias = hashlib.sha1(alias).hexdigest()
1187     else:
1188         is_tmp = False
1189
1190     gitdir = os.environ['GIT_DIR']
1191     dirname = os.path.join(gitdir, 'hg', alias)
1192     branches = {}
1193     bmarks = {}
1194     blob_marks = {}
1195     parsed_refs = {}
1196     marks = None
1197     parsed_tags = {}
1198     filenodes = {}
1199     fake_bmark = None
1200     try:
1201         hg_version = tuple(int(e) for e in util.version().split('.'))
1202     except:
1203         hg_version = None
1204     dry_run = False
1205     notes = set()
1206
1207     repo = get_repo(url, alias)
1208     prefix = 'refs/hg/%s' % alias
1209
1210     if not is_tmp:
1211         fix_path(alias, peer or repo, url)
1212
1213     marks_path = os.path.join(dirname, 'marks-hg')
1214     marks = Marks(marks_path, repo)
1215
1216     if sys.platform == 'win32':
1217         import msvcrt
1218         msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
1219
1220     parser = Parser(repo)
1221     for line in parser:
1222         if parser.check('capabilities'):
1223             do_capabilities(parser)
1224         elif parser.check('list'):
1225             do_list(parser)
1226         elif parser.check('import'):
1227             do_import(parser)
1228         elif parser.check('export'):
1229             do_export(parser)
1230         elif parser.check('option'):
1231             do_option(parser)
1232         else:
1233             die('unhandled command: %s' % line)
1234         sys.stdout.flush()
1235
1236 def bye():
1237     if not marks:
1238         return
1239     if not is_tmp:
1240         marks.store()
1241     else:
1242         shutil.rmtree(dirname)
1243
1244 atexit.register(bye)
1245 sys.exit(main(sys.argv))