Merge branch 'jk/shortlog-tolerate-broken-commit'
[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         print "? refs/heads/%s" % gitref(bmark)
645
646     for tag, node in repo.tagslist():
647         if tag == 'tip':
648             continue
649         print "? refs/tags/%s" % gitref(tag)
650
651     print
652
653 def do_import(parser):
654     repo = parser.repo
655
656     path = os.path.join(dirname, 'marks-git')
657
658     print "feature done"
659     if os.path.exists(path):
660         print "feature import-marks=%s" % path
661     print "feature export-marks=%s" % path
662     print "feature force"
663     sys.stdout.flush()
664
665     tmp = encoding.encoding
666     encoding.encoding = 'utf-8'
667
668     # lets get all the import lines
669     while parser.check('import'):
670         ref = parser[1]
671
672         if (ref == 'HEAD'):
673             export_head(repo)
674         elif ref.startswith('refs/heads/branches/'):
675             branch = ref[len('refs/heads/branches/'):]
676             export_branch(repo, branch)
677         elif ref.startswith('refs/heads/'):
678             bmark = ref[len('refs/heads/'):]
679             export_bookmark(repo, bmark)
680         elif ref.startswith('refs/tags/'):
681             tag = ref[len('refs/tags/'):]
682             export_tag(repo, tag)
683
684         parser.next()
685
686     encoding.encoding = tmp
687
688     print 'done'
689
690 def parse_blob(parser):
691     parser.next()
692     mark = parser.get_mark()
693     parser.next()
694     data = parser.get_data()
695     blob_marks[mark] = data
696     parser.next()
697
698 def get_merge_files(repo, p1, p2, files):
699     for e in repo[p1].files():
700         if e not in files:
701             if e not in repo[p1].manifest():
702                 continue
703             f = { 'ctx' : repo[p1][e] }
704             files[e] = f
705
706 def parse_commit(parser):
707     from_mark = merge_mark = None
708
709     ref = parser[1]
710     parser.next()
711
712     commit_mark = parser.get_mark()
713     parser.next()
714     author = parser.get_author()
715     parser.next()
716     committer = parser.get_author()
717     parser.next()
718     data = parser.get_data()
719     parser.next()
720     if parser.check('from'):
721         from_mark = parser.get_mark()
722         parser.next()
723     if parser.check('merge'):
724         merge_mark = parser.get_mark()
725         parser.next()
726         if parser.check('merge'):
727             die('octopus merges are not supported yet')
728
729     # fast-export adds an extra newline
730     if data[-1] == '\n':
731         data = data[:-1]
732
733     files = {}
734
735     for line in parser:
736         if parser.check('M'):
737             t, m, mark_ref, path = line.split(' ', 3)
738             mark = int(mark_ref[1:])
739             f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
740         elif parser.check('D'):
741             t, path = line.split(' ', 1)
742             f = { 'deleted' : True }
743         else:
744             die('Unknown file command: %s' % line)
745         files[path] = f
746
747     # only export the commits if we are on an internal proxy repo
748     if dry_run and not peer:
749         parsed_refs[ref] = None
750         return
751
752     def getfilectx(repo, memctx, f):
753         of = files[f]
754         if 'deleted' in of:
755             raise IOError
756         if 'ctx' in of:
757             return of['ctx']
758         is_exec = of['mode'] == 'x'
759         is_link = of['mode'] == 'l'
760         rename = of.get('rename', None)
761         return context.memfilectx(f, of['data'],
762                 is_link, is_exec, rename)
763
764     repo = parser.repo
765
766     user, date, tz = author
767     extra = {}
768
769     if committer != author:
770         extra['committer'] = "%s %u %u" % committer
771
772     if from_mark:
773         p1 = mark_to_rev(from_mark)
774     else:
775         p1 = '0' * 40
776
777     if merge_mark:
778         p2 = mark_to_rev(merge_mark)
779     else:
780         p2 = '0' * 40
781
782     #
783     # If files changed from any of the parents, hg wants to know, but in git if
784     # nothing changed from the first parent, nothing changed.
785     #
786     if merge_mark:
787         get_merge_files(repo, p1, p2, files)
788
789     # Check if the ref is supposed to be a named branch
790     if ref.startswith('refs/heads/branches/'):
791         branch = ref[len('refs/heads/branches/'):]
792         extra['branch'] = hgref(branch)
793
794     if mode == 'hg':
795         i = data.find('\n--HG--\n')
796         if i >= 0:
797             tmp = data[i + len('\n--HG--\n'):].strip()
798             for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
799                 if k == 'rename':
800                     old, new = v.split(' => ', 1)
801                     files[new]['rename'] = old
802                 elif k == 'branch':
803                     extra[k] = v
804                 elif k == 'extra':
805                     ek, ev = v.split(' : ', 1)
806                     extra[ek] = urllib.unquote(ev)
807             data = data[:i]
808
809     ctx = context.memctx(repo, (p1, p2), data,
810             files.keys(), getfilectx,
811             user, (date, tz), extra)
812
813     tmp = encoding.encoding
814     encoding.encoding = 'utf-8'
815
816     node = hghex(repo.commitctx(ctx))
817
818     encoding.encoding = tmp
819
820     parsed_refs[ref] = node
821     marks.new_mark(node, commit_mark)
822
823 def parse_reset(parser):
824     ref = parser[1]
825     parser.next()
826     # ugh
827     if parser.check('commit'):
828         parse_commit(parser)
829         return
830     if not parser.check('from'):
831         return
832     from_mark = parser.get_mark()
833     parser.next()
834
835     try:
836         rev = mark_to_rev(from_mark)
837     except KeyError:
838         rev = None
839     parsed_refs[ref] = rev
840
841 def parse_tag(parser):
842     name = parser[1]
843     parser.next()
844     from_mark = parser.get_mark()
845     parser.next()
846     tagger = parser.get_author()
847     parser.next()
848     data = parser.get_data()
849     parser.next()
850
851     parsed_tags[name] = (tagger, data)
852
853 def write_tag(repo, tag, node, msg, author):
854     branch = repo[node].branch()
855     tip = branch_tip(branch)
856     tip = repo[tip]
857
858     def getfilectx(repo, memctx, f):
859         try:
860             fctx = tip.filectx(f)
861             data = fctx.data()
862         except error.ManifestLookupError:
863             data = ""
864         content = data + "%s %s\n" % (node, tag)
865         return context.memfilectx(f, content, False, False, None)
866
867     p1 = tip.hex()
868     p2 = '0' * 40
869     if author:
870         user, date, tz = author
871         date_tz = (date, tz)
872     else:
873         cmd = ['git', 'var', 'GIT_COMMITTER_IDENT']
874         process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
875         output, _ = process.communicate()
876         m = re.match('^.* <.*>', output)
877         if m:
878             user = m.group(0)
879         else:
880             user = repo.ui.username()
881         date_tz = None
882
883     ctx = context.memctx(repo, (p1, p2), msg,
884             ['.hgtags'], getfilectx,
885             user, date_tz, {'branch' : branch})
886
887     tmp = encoding.encoding
888     encoding.encoding = 'utf-8'
889
890     tagnode = repo.commitctx(ctx)
891
892     encoding.encoding = tmp
893
894     return (tagnode, branch)
895
896 def checkheads_bmark(repo, ref, ctx):
897     bmark = ref[len('refs/heads/'):]
898     if not bmark in bmarks:
899         # new bmark
900         return True
901
902     ctx_old = bmarks[bmark]
903     ctx_new = ctx
904     if not repo.changelog.descendant(ctx_old.rev(), ctx_new.rev()):
905         if force_push:
906             print "ok %s forced update" % ref
907         else:
908             print "error %s non-fast forward" % ref
909             return False
910
911     return True
912
913 def checkheads(repo, remote, p_revs):
914
915     remotemap = remote.branchmap()
916     if not remotemap:
917         # empty repo
918         return True
919
920     new = {}
921     ret = True
922
923     for node, ref in p_revs.iteritems():
924         ctx = repo[node]
925         branch = ctx.branch()
926         if not branch in remotemap:
927             # new branch
928             continue
929         if not ref.startswith('refs/heads/branches'):
930             if ref.startswith('refs/heads/'):
931                 if not checkheads_bmark(repo, ref, ctx):
932                     ret = False
933
934             # only check branches
935             continue
936         new.setdefault(branch, []).append(ctx.rev())
937
938     for branch, heads in new.iteritems():
939         old = [repo.changelog.rev(x) for x in remotemap[branch]]
940         for rev in heads:
941             if check_version(2, 3):
942                 ancestors = repo.changelog.ancestors([rev], stoprev=min(old))
943             else:
944                 ancestors = repo.changelog.ancestors(rev)
945             found = False
946
947             for x in old:
948                 if x in ancestors:
949                     found = True
950                     break
951
952             if found:
953                 continue
954
955             node = repo.changelog.node(rev)
956             ref = p_revs[node]
957             if force_push:
958                 print "ok %s forced update" % ref
959             else:
960                 print "error %s non-fast forward" % ref
961                 ret = False
962
963     return ret
964
965 def push_unsafe(repo, remote, parsed_refs, p_revs):
966
967     force = force_push
968
969     fci = discovery.findcommonincoming
970     commoninc = fci(repo, remote, force=force)
971     common, _, remoteheads = commoninc
972
973     if not checkheads(repo, remote, p_revs):
974         return None
975
976     cg = repo.getbundle('push', heads=list(p_revs), common=common)
977
978     unbundle = remote.capable('unbundle')
979     if unbundle:
980         if force:
981             remoteheads = ['force']
982         return remote.unbundle(cg, remoteheads, 'push')
983     else:
984         return remote.addchangegroup(cg, 'push', repo.url())
985
986 def push(repo, remote, parsed_refs, p_revs):
987     if hasattr(remote, 'canpush') and not remote.canpush():
988         print "error cannot push"
989
990     if not p_revs:
991         # nothing to push
992         return
993
994     lock = None
995     unbundle = remote.capable('unbundle')
996     if not unbundle:
997         lock = remote.lock()
998     try:
999         ret = push_unsafe(repo, remote, parsed_refs, p_revs)
1000     finally:
1001         if lock is not None:
1002             lock.release()
1003
1004     return ret
1005
1006 def check_tip(ref, kind, name, heads):
1007     try:
1008         ename = '%s/%s' % (kind, name)
1009         tip = marks.get_tip(ename)
1010     except KeyError:
1011         return True
1012     else:
1013         return tip in heads
1014
1015 def do_export(parser):
1016     p_bmarks = []
1017     p_revs = {}
1018
1019     parser.next()
1020
1021     for line in parser.each_block('done'):
1022         if parser.check('blob'):
1023             parse_blob(parser)
1024         elif parser.check('commit'):
1025             parse_commit(parser)
1026         elif parser.check('reset'):
1027             parse_reset(parser)
1028         elif parser.check('tag'):
1029             parse_tag(parser)
1030         elif parser.check('feature'):
1031             pass
1032         else:
1033             die('unhandled export command: %s' % line)
1034
1035     need_fetch = False
1036
1037     for ref, node in parsed_refs.iteritems():
1038         bnode = hgbin(node) if node else None
1039         if ref.startswith('refs/heads/branches'):
1040             branch = ref[len('refs/heads/branches/'):]
1041             if branch in branches and bnode in branches[branch]:
1042                 # up to date
1043                 continue
1044
1045             if peer:
1046                 remotemap = peer.branchmap()
1047                 if remotemap and branch in remotemap:
1048                     heads = [hghex(e) for e in remotemap[branch]]
1049                     if not check_tip(ref, 'branches', branch, heads):
1050                         print "error %s fetch first" % ref
1051                         need_fetch = True
1052                         continue
1053
1054             p_revs[bnode] = ref
1055             print "ok %s" % ref
1056         elif ref.startswith('refs/heads/'):
1057             bmark = ref[len('refs/heads/'):]
1058             new = node
1059             old = bmarks[bmark].hex() if bmark in bmarks else ''
1060
1061             if old == new:
1062                 continue
1063
1064             print "ok %s" % ref
1065             if bmark != fake_bmark and \
1066                     not (bmark == 'master' and bmark not in parser.repo._bookmarks):
1067                 p_bmarks.append((ref, bmark, old, new))
1068
1069             if peer:
1070                 remote_old = peer.listkeys('bookmarks').get(bmark)
1071                 if remote_old:
1072                     if not check_tip(ref, 'bookmarks', bmark, remote_old):
1073                         print "error %s fetch first" % ref
1074                         need_fetch = True
1075                         continue
1076
1077             p_revs[bnode] = ref
1078         elif ref.startswith('refs/tags/'):
1079             if dry_run:
1080                 print "ok %s" % ref
1081                 continue
1082             tag = ref[len('refs/tags/'):]
1083             tag = hgref(tag)
1084             author, msg = parsed_tags.get(tag, (None, None))
1085             if mode == 'git':
1086                 if not msg:
1087                     msg = 'Added tag %s for changeset %s' % (tag, node[:12])
1088                 tagnode, branch = write_tag(parser.repo, tag, node, msg, author)
1089                 p_revs[tagnode] = 'refs/heads/branches/' + gitref(branch)
1090             else:
1091                 fp = parser.repo.opener('localtags', 'a')
1092                 fp.write('%s %s\n' % (node, tag))
1093                 fp.close()
1094             p_revs[bnode] = ref
1095             print "ok %s" % ref
1096         else:
1097             # transport-helper/fast-export bugs
1098             continue
1099
1100     if need_fetch:
1101         print
1102         return
1103
1104     if dry_run:
1105         if peer and not force_push:
1106             checkheads(parser.repo, peer, p_revs)
1107         print
1108         return
1109
1110     if peer:
1111         if not push(parser.repo, peer, parsed_refs, p_revs):
1112             # do not update bookmarks
1113             print
1114             return
1115
1116         # update remote bookmarks
1117         remote_bmarks = peer.listkeys('bookmarks')
1118         for ref, bmark, old, new in p_bmarks:
1119             if force_push:
1120                 old = remote_bmarks.get(bmark, '')
1121             if not peer.pushkey('bookmarks', bmark, old, new):
1122                 print "error %s" % ref
1123     else:
1124         # update local bookmarks
1125         for ref, bmark, old, new in p_bmarks:
1126             if not bookmarks.pushbookmark(parser.repo, bmark, old, new):
1127                 print "error %s" % ref
1128
1129     print
1130
1131 def do_option(parser):
1132     global dry_run, force_push
1133     _, key, value = parser.line.split(' ')
1134     if key == 'dry-run':
1135         dry_run = (value == 'true')
1136         print 'ok'
1137     elif key == 'force':
1138         force_push = (value == 'true')
1139         print 'ok'
1140     else:
1141         print 'unsupported'
1142
1143 def fix_path(alias, repo, orig_url):
1144     url = urlparse.urlparse(orig_url, 'file')
1145     if url.scheme != 'file' or os.path.isabs(os.path.expanduser(url.path)):
1146         return
1147     abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
1148     cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
1149     subprocess.call(cmd)
1150
1151 def main(args):
1152     global prefix, gitdir, dirname, branches, bmarks
1153     global marks, blob_marks, parsed_refs
1154     global peer, mode, bad_mail, bad_name
1155     global track_branches, force_push, is_tmp
1156     global parsed_tags
1157     global filenodes
1158     global fake_bmark, hg_version
1159     global dry_run
1160     global notes, alias
1161
1162     alias = args[1]
1163     url = args[2]
1164     peer = None
1165
1166     hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
1167     track_branches = get_config_bool('remote-hg.track-branches', True)
1168     force_push = False
1169
1170     if hg_git_compat:
1171         mode = 'hg'
1172         bad_mail = 'none@none'
1173         bad_name = ''
1174     else:
1175         mode = 'git'
1176         bad_mail = 'unknown'
1177         bad_name = 'Unknown'
1178
1179     if alias[4:] == url:
1180         is_tmp = True
1181         alias = hashlib.sha1(alias).hexdigest()
1182     else:
1183         is_tmp = False
1184
1185     gitdir = os.environ['GIT_DIR']
1186     dirname = os.path.join(gitdir, 'hg', alias)
1187     branches = {}
1188     bmarks = {}
1189     blob_marks = {}
1190     parsed_refs = {}
1191     marks = None
1192     parsed_tags = {}
1193     filenodes = {}
1194     fake_bmark = None
1195     try:
1196         hg_version = tuple(int(e) for e in util.version().split('.'))
1197     except:
1198         hg_version = None
1199     dry_run = False
1200     notes = set()
1201
1202     repo = get_repo(url, alias)
1203     prefix = 'refs/hg/%s' % alias
1204
1205     if not is_tmp:
1206         fix_path(alias, peer or repo, url)
1207
1208     marks_path = os.path.join(dirname, 'marks-hg')
1209     marks = Marks(marks_path, repo)
1210
1211     if sys.platform == 'win32':
1212         import msvcrt
1213         msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
1214
1215     parser = Parser(repo)
1216     for line in parser:
1217         if parser.check('capabilities'):
1218             do_capabilities(parser)
1219         elif parser.check('list'):
1220             do_list(parser)
1221         elif parser.check('import'):
1222             do_import(parser)
1223         elif parser.check('export'):
1224             do_export(parser)
1225         elif parser.check('option'):
1226             do_option(parser)
1227         else:
1228             die('unhandled command: %s' % line)
1229         sys.stdout.flush()
1230
1231 def bye():
1232     if not marks:
1233         return
1234     if not is_tmp:
1235         marks.store()
1236     else:
1237         shutil.rmtree(dirname)
1238
1239 atexit.register(bye)
1240 sys.exit(main(sys.argv))