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