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