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