remote-hg: add support for hg v3.0
[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 from mercurial import changegroup
17
18 import re
19 import sys
20 import os
21 import json
22 import shutil
23 import subprocess
24 import urllib
25 import atexit
26 import urlparse, hashlib
27 import time as ptime
28
29 #
30 # If you want to see Mercurial revisions as Git commit notes:
31 # git config core.notesRef refs/notes/hg
32 #
33 # If you are not in hg-git-compat mode and want to disable the tracking of
34 # named branches:
35 # git config --global remote-hg.track-branches false
36 #
37 # If you want the equivalent of hg's clone/pull--insecure option:
38 # git config --global remote-hg.insecure true
39 #
40 # If you want to switch to hg-git compatibility mode:
41 # git config --global remote-hg.hg-git-compat true
42 #
43 # git:
44 # Sensible defaults for git.
45 # hg bookmarks are exported as git branches, hg branches are prefixed
46 # with 'branches/', HEAD is a special case.
47 #
48 # hg:
49 # Emulate hg-git.
50 # Only hg bookmarks are exported as git branches.
51 # Commits are modified to preserve hg information and allow bidirectionality.
52 #
53
54 NAME_RE = re.compile('^([^<>]+)')
55 AUTHOR_RE = re.compile('^([^<>]+?)? ?[<>]([^<>]*)(?:$|>)')
56 EMAIL_RE = re.compile(r'([^ \t<>]+@[^ \t<>]+)')
57 AUTHOR_HG_RE = re.compile('^(.*?) ?<(.*?)(?:>(.*))?$')
58 RAW_AUTHOR_RE = re.compile('^(\w+) (?:(.+)? )?<(.*)> (\d+) ([+-]\d+)')
59
60 VERSION = 2
61
62 def die(msg, *args):
63     sys.stderr.write('ERROR: %s\n' % (msg % args))
64     sys.exit(1)
65
66 def warn(msg, *args):
67     sys.stderr.write('WARNING: %s\n' % (msg % args))
68
69 def gitmode(flags):
70     return 'l' in flags and '120000' or 'x' in flags and '100755' or '100644'
71
72 def gittz(tz):
73     return '%+03d%02d' % (-tz / 3600, -tz % 3600 / 60)
74
75 def hgmode(mode):
76     m = { '100755': 'x', '120000': 'l' }
77     return m.get(mode, '')
78
79 def hghex(n):
80     return node.hex(n)
81
82 def hgbin(n):
83     return node.bin(n)
84
85 def hgref(ref):
86     return ref.replace('___', ' ')
87
88 def gitref(ref):
89     return ref.replace(' ', '___')
90
91 def check_version(*check):
92     if not hg_version:
93         return True
94     return hg_version >= check
95
96 def get_config(config):
97     cmd = ['git', 'config', '--get', config]
98     process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
99     output, _ = process.communicate()
100     return output
101
102 def get_config_bool(config, default=False):
103     value = get_config(config).rstrip('\n')
104     if value == "true":
105         return True
106     elif value == "false":
107         return False
108     else:
109         return default
110
111 class Marks:
112
113     def __init__(self, path, repo):
114         self.path = path
115         self.repo = repo
116         self.clear()
117         self.load()
118
119         if self.version < VERSION:
120             if self.version == 1:
121                 self.upgrade_one()
122
123             # upgraded?
124             if self.version < VERSION:
125                 self.clear()
126                 self.version = VERSION
127
128     def clear(self):
129         self.tips = {}
130         self.marks = {}
131         self.rev_marks = {}
132         self.last_mark = 0
133         self.version = 0
134         self.last_note = 0
135
136     def load(self):
137         if not os.path.exists(self.path):
138             return
139
140         tmp = json.load(open(self.path))
141
142         self.tips = tmp['tips']
143         self.marks = tmp['marks']
144         self.last_mark = tmp['last-mark']
145         self.version = tmp.get('version', 1)
146         self.last_note = tmp.get('last-note', 0)
147
148         for rev, mark in self.marks.iteritems():
149             self.rev_marks[mark] = rev
150
151     def upgrade_one(self):
152         def get_id(rev):
153             return hghex(self.repo.changelog.node(int(rev)))
154         self.tips = dict((name, get_id(rev)) for name, rev in self.tips.iteritems())
155         self.marks = dict((get_id(rev), mark) for rev, mark in self.marks.iteritems())
156         self.rev_marks = dict((mark, get_id(rev)) for mark, rev in self.rev_marks.iteritems())
157         self.version = 2
158
159     def dict(self):
160         return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark, 'version' : self.version, 'last-note' : self.last_note }
161
162     def store(self):
163         json.dump(self.dict(), open(self.path, 'w'))
164
165     def __str__(self):
166         return str(self.dict())
167
168     def from_rev(self, rev):
169         return self.marks[rev]
170
171     def to_rev(self, mark):
172         return str(self.rev_marks[mark])
173
174     def next_mark(self):
175         self.last_mark += 1
176         return self.last_mark
177
178     def get_mark(self, rev):
179         self.last_mark += 1
180         self.marks[rev] = self.last_mark
181         return self.last_mark
182
183     def new_mark(self, rev, mark):
184         self.marks[rev] = mark
185         self.rev_marks[mark] = rev
186         self.last_mark = mark
187
188     def is_marked(self, rev):
189         return rev in self.marks
190
191     def get_tip(self, branch):
192         return str(self.tips[branch])
193
194     def set_tip(self, branch, tip):
195         self.tips[branch] = tip
196
197 class Parser:
198
199     def __init__(self, repo):
200         self.repo = repo
201         self.line = self.get_line()
202
203     def get_line(self):
204         return sys.stdin.readline().strip()
205
206     def __getitem__(self, i):
207         return self.line.split()[i]
208
209     def check(self, word):
210         return self.line.startswith(word)
211
212     def each_block(self, separator):
213         while self.line != separator:
214             yield self.line
215             self.line = self.get_line()
216
217     def __iter__(self):
218         return self.each_block('')
219
220     def next(self):
221         self.line = self.get_line()
222         if self.line == 'done':
223             self.line = None
224
225     def get_mark(self):
226         i = self.line.index(':') + 1
227         return int(self.line[i:])
228
229     def get_data(self):
230         if not self.check('data'):
231             return None
232         i = self.line.index(' ') + 1
233         size = int(self.line[i:])
234         return sys.stdin.read(size)
235
236     def get_author(self):
237         ex = None
238         m = RAW_AUTHOR_RE.match(self.line)
239         if not m:
240             return None
241         _, name, email, date, tz = m.groups()
242         if name and 'ext:' in name:
243             m = re.match('^(.+?) ext:\((.+)\)$', name)
244             if m:
245                 name = m.group(1)
246                 ex = urllib.unquote(m.group(2))
247
248         if email != bad_mail:
249             if name:
250                 user = '%s <%s>' % (name, email)
251             else:
252                 user = '<%s>' % (email)
253         else:
254             user = name
255
256         if ex:
257             user += ex
258
259         tz = int(tz)
260         tz = ((tz / 100) * 3600) + ((tz % 100) * 60)
261         return (user, int(date), -tz)
262
263 def fix_file_path(path):
264     path = os.path.normpath(path)
265     if not os.path.isabs(path):
266         return path
267     return os.path.relpath(path, '/')
268
269 def export_files(files):
270     final = []
271     for f in files:
272         fid = node.hex(f.filenode())
273
274         if fid in filenodes:
275             mark = filenodes[fid]
276         else:
277             mark = marks.next_mark()
278             filenodes[fid] = mark
279             d = f.data()
280
281             print "blob"
282             print "mark :%u" % mark
283             print "data %d" % len(d)
284             print d
285
286         path = fix_file_path(f.path())
287         final.append((gitmode(f.flags()), mark, path))
288
289     return final
290
291 def get_filechanges(repo, ctx, parent):
292     modified = set()
293     added = set()
294     removed = set()
295
296     # load earliest manifest first for caching reasons
297     prev = parent.manifest().copy()
298     cur = ctx.manifest()
299
300     for fn in cur:
301         if fn in prev:
302             if (cur.flags(fn) != prev.flags(fn) or cur[fn] != prev[fn]):
303                 modified.add(fn)
304             del prev[fn]
305         else:
306             added.add(fn)
307     removed |= set(prev.keys())
308
309     return added | modified, removed
310
311 def fixup_user_git(user):
312     name = mail = None
313     user = user.replace('"', '')
314     m = AUTHOR_RE.match(user)
315     if m:
316         name = m.group(1)
317         mail = m.group(2).strip()
318     else:
319         m = EMAIL_RE.match(user)
320         if m:
321             mail = m.group(1)
322         else:
323             m = NAME_RE.match(user)
324             if m:
325                 name = m.group(1).strip()
326     return (name, mail)
327
328 def fixup_user_hg(user):
329     def sanitize(name):
330         # stole this from hg-git
331         return re.sub('[<>\n]', '?', name.lstrip('< ').rstrip('> '))
332
333     m = AUTHOR_HG_RE.match(user)
334     if m:
335         name = sanitize(m.group(1))
336         mail = sanitize(m.group(2))
337         ex = m.group(3)
338         if ex:
339             name += ' ext:(' + urllib.quote(ex) + ')'
340     else:
341         name = sanitize(user)
342         if '@' in user:
343             mail = name
344         else:
345             mail = None
346
347     return (name, mail)
348
349 def fixup_user(user):
350     if mode == 'git':
351         name, mail = fixup_user_git(user)
352     else:
353         name, mail = fixup_user_hg(user)
354
355     if not name:
356         name = bad_name
357     if not mail:
358         mail = bad_mail
359
360     return '%s <%s>' % (name, mail)
361
362 def updatebookmarks(repo, peer):
363     remotemarks = peer.listkeys('bookmarks')
364     localmarks = repo._bookmarks
365
366     if not remotemarks:
367         return
368
369     for k, v in remotemarks.iteritems():
370         localmarks[k] = hgbin(v)
371
372     if hasattr(localmarks, 'write'):
373         localmarks.write()
374     else:
375         bookmarks.write(repo)
376
377 def get_repo(url, alias):
378     global peer
379
380     myui = ui.ui()
381     myui.setconfig('ui', 'interactive', 'off')
382     myui.fout = sys.stderr
383
384     if get_config_bool('remote-hg.insecure'):
385         myui.setconfig('web', 'cacerts', '')
386
387     extensions.loadall(myui)
388
389     if hg.islocal(url) and not os.environ.get('GIT_REMOTE_HG_TEST_REMOTE'):
390         repo = hg.repository(myui, url)
391         if not os.path.exists(dirname):
392             os.makedirs(dirname)
393     else:
394         shared_path = os.path.join(gitdir, 'hg')
395
396         # check and upgrade old organization
397         hg_path = os.path.join(shared_path, '.hg')
398         if os.path.exists(shared_path) and not os.path.exists(hg_path):
399             repos = os.listdir(shared_path)
400             for x in repos:
401                 local_hg = os.path.join(shared_path, x, 'clone', '.hg')
402                 if not os.path.exists(local_hg):
403                     continue
404                 if not os.path.exists(hg_path):
405                     shutil.move(local_hg, hg_path)
406                 shutil.rmtree(os.path.join(shared_path, x, 'clone'))
407
408         # setup shared repo (if not there)
409         try:
410             hg.peer(myui, {}, shared_path, create=True)
411         except error.RepoError:
412             pass
413
414         if not os.path.exists(dirname):
415             os.makedirs(dirname)
416
417         local_path = os.path.join(dirname, 'clone')
418         if not os.path.exists(local_path):
419             hg.share(myui, shared_path, local_path, update=False)
420         else:
421             # make sure the shared path is always up-to-date
422             util.writefile(os.path.join(local_path, '.hg', 'sharedpath'), hg_path)
423
424         repo = hg.repository(myui, local_path)
425         try:
426             peer = hg.peer(repo.ui, {}, url)
427         except:
428             die('Repository error')
429         repo.pull(peer, heads=None, force=True)
430
431         updatebookmarks(repo, peer)
432
433     return repo
434
435 def rev_to_mark(rev):
436     return marks.from_rev(rev.hex())
437
438 def mark_to_rev(mark):
439     return marks.to_rev(mark)
440
441 # Get a range of revisions in the form of a..b (git committish)
442 def gitrange(repo, a, b):
443     positive = []
444     pending = set([int(b)])
445     negative = set([int(a)])
446     for cur in xrange(b, -1, -1):
447         if not pending:
448             break
449
450         parents = [p for p in repo.changelog.parentrevs(cur) if p >= 0]
451
452         if cur in pending:
453             positive.append(cur)
454             pending.remove(cur)
455             for p in parents:
456                 if not p in negative:
457                     pending.add(p)
458         elif cur in negative:
459             negative.remove(cur)
460             for p in parents:
461                 if not p in pending:
462                     negative.add(p)
463                 else:
464                     pending.discard(p)
465
466     positive.reverse()
467     return positive
468
469 def export_ref(repo, name, kind, head):
470     ename = '%s/%s' % (kind, name)
471     try:
472         tip = marks.get_tip(ename)
473         tip = repo[tip]
474     except:
475         tip = repo[-1]
476
477     revs = gitrange(repo, tip, head)
478
479     total = len(revs)
480     tip = tip.rev()
481
482     for rev in revs:
483
484         c = repo[rev]
485         node = c.node()
486
487         if marks.is_marked(c.hex()):
488             continue
489
490         (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(node)
491         rev_branch = extra['branch']
492
493         author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
494         if 'committer' in extra:
495             try:
496                 cuser, ctime, ctz = extra['committer'].rsplit(' ', 2)
497                 committer = "%s %s %s" % (cuser, ctime, gittz(int(ctz)))
498             except ValueError:
499                 cuser = extra['committer']
500                 committer = "%s %d %s" % (fixup_user(cuser), time, gittz(tz))
501         else:
502             committer = author
503
504         parents = [repo[p] for p in repo.changelog.parentrevs(rev) if p >= 0]
505
506         if len(parents) == 0:
507             modified = c.manifest().keys()
508             removed = []
509         else:
510             modified, removed = get_filechanges(repo, c, parents[0])
511
512         desc += '\n'
513
514         if mode == 'hg':
515             extra_msg = ''
516
517             if rev_branch != 'default':
518                 extra_msg += 'branch : %s\n' % rev_branch
519
520             renames = []
521             for f in c.files():
522                 if f not in c.manifest():
523                     continue
524                 rename = c.filectx(f).renamed()
525                 if rename:
526                     renames.append((rename[0], f))
527
528             for e in renames:
529                 extra_msg += "rename : %s => %s\n" % e
530
531             for key, value in extra.iteritems():
532                 if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):
533                     continue
534                 else:
535                     extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value))
536
537             if extra_msg:
538                 desc += '\n--HG--\n' + extra_msg
539
540         if len(parents) == 0 and rev:
541             print 'reset %s/%s' % (prefix, ename)
542
543         modified_final = export_files(c.filectx(f) for f in modified)
544
545         print "commit %s/%s" % (prefix, ename)
546         print "mark :%d" % (marks.get_mark(c.hex()))
547         print "author %s" % (author)
548         print "committer %s" % (committer)
549         print "data %d" % (len(desc))
550         print desc
551
552         if len(parents) > 0:
553             print "from :%s" % (rev_to_mark(parents[0]))
554             if len(parents) > 1:
555                 print "merge :%s" % (rev_to_mark(parents[1]))
556
557         for f in removed:
558             print "D %s" % (fix_file_path(f))
559         for f in modified_final:
560             print "M %s :%u %s" % f
561         print
562
563         progress = (rev - tip)
564         if (progress % 100 == 0):
565             print "progress revision %d '%s' (%d/%d)" % (rev, name, progress, total)
566
567     # make sure the ref is updated
568     print "reset %s/%s" % (prefix, ename)
569     print "from :%u" % rev_to_mark(head)
570     print
571
572     pending_revs = set(revs) - notes
573     if pending_revs:
574         note_mark = marks.next_mark()
575         ref = "refs/notes/hg"
576
577         print "commit %s" % ref
578         print "mark :%d" % (note_mark)
579         print "committer remote-hg <> %d %s" % (ptime.time(), gittz(ptime.timezone))
580         desc = "Notes for %s\n" % (name)
581         print "data %d" % (len(desc))
582         print desc
583         if marks.last_note:
584             print "from :%u" % marks.last_note
585
586         for rev in pending_revs:
587             notes.add(rev)
588             c = repo[rev]
589             print "N inline :%u" % rev_to_mark(c)
590             msg = c.hex()
591             print "data %d" % (len(msg))
592             print msg
593         print
594
595         marks.last_note = note_mark
596
597     marks.set_tip(ename, head.hex())
598
599 def export_tag(repo, tag):
600     export_ref(repo, tag, 'tags', repo[hgref(tag)])
601
602 def export_bookmark(repo, bmark):
603     head = bmarks[hgref(bmark)]
604     export_ref(repo, bmark, 'bookmarks', head)
605
606 def export_branch(repo, branch):
607     tip = get_branch_tip(repo, branch)
608     head = repo[tip]
609     export_ref(repo, branch, 'branches', head)
610
611 def export_head(repo):
612     export_ref(repo, g_head[0], 'bookmarks', g_head[1])
613
614 def do_capabilities(parser):
615     print "import"
616     print "export"
617     print "refspec refs/heads/branches/*:%s/branches/*" % prefix
618     print "refspec refs/heads/*:%s/bookmarks/*" % prefix
619     print "refspec refs/tags/*:%s/tags/*" % prefix
620
621     path = os.path.join(dirname, 'marks-git')
622
623     if os.path.exists(path):
624         print "*import-marks %s" % path
625     print "*export-marks %s" % path
626     print "option"
627
628     print
629
630 def branch_tip(branch):
631     return branches[branch][-1]
632
633 def get_branch_tip(repo, branch):
634     heads = branches.get(hgref(branch), None)
635     if not heads:
636         return None
637
638     # verify there's only one head
639     if (len(heads) > 1):
640         warn("Branch '%s' has more than one head, consider merging" % branch)
641         return branch_tip(hgref(branch))
642
643     return heads[0]
644
645 def list_head(repo, cur):
646     global g_head, fake_bmark
647
648     if 'default' not in branches:
649         # empty repo
650         return
651
652     node = repo[branch_tip('default')]
653     head = 'master' if not 'master' in bmarks else 'default'
654     fake_bmark = head
655     bmarks[head] = node
656
657     head = gitref(head)
658     print "@refs/heads/%s HEAD" % head
659     g_head = (head, node)
660
661 def do_list(parser):
662     repo = parser.repo
663     for bmark, node in bookmarks.listbookmarks(repo).iteritems():
664         bmarks[bmark] = repo[node]
665
666     cur = repo.dirstate.branch()
667     orig = peer if peer else repo
668
669     for branch, heads in orig.branchmap().iteritems():
670         # only open heads
671         heads = [h for h in heads if 'close' not in repo.changelog.read(h)[5]]
672         if heads:
673             branches[branch] = heads
674
675     list_head(repo, cur)
676
677     if track_branches:
678         for branch in branches:
679             print "? refs/heads/branches/%s" % gitref(branch)
680
681     for bmark in bmarks:
682         if  bmarks[bmark].hex() == '0' * 40:
683             warn("Ignoring invalid bookmark '%s'", bmark)
684         else:
685             print "? refs/heads/%s" % gitref(bmark)
686
687     for tag, node in repo.tagslist():
688         if tag == 'tip':
689             continue
690         print "? refs/tags/%s" % gitref(tag)
691
692     print
693
694 def do_import(parser):
695     repo = parser.repo
696
697     path = os.path.join(dirname, 'marks-git')
698
699     print "feature done"
700     if os.path.exists(path):
701         print "feature import-marks=%s" % path
702     print "feature export-marks=%s" % path
703     print "feature force"
704     sys.stdout.flush()
705
706     tmp = encoding.encoding
707     encoding.encoding = 'utf-8'
708
709     # lets get all the import lines
710     while parser.check('import'):
711         ref = parser[1]
712
713         if (ref == 'HEAD'):
714             export_head(repo)
715         elif ref.startswith('refs/heads/branches/'):
716             branch = ref[len('refs/heads/branches/'):]
717             export_branch(repo, branch)
718         elif ref.startswith('refs/heads/'):
719             bmark = ref[len('refs/heads/'):]
720             export_bookmark(repo, bmark)
721         elif ref.startswith('refs/tags/'):
722             tag = ref[len('refs/tags/'):]
723             export_tag(repo, tag)
724
725         parser.next()
726
727     encoding.encoding = tmp
728
729     print 'done'
730
731 def parse_blob(parser):
732     parser.next()
733     mark = parser.get_mark()
734     parser.next()
735     data = parser.get_data()
736     blob_marks[mark] = data
737     parser.next()
738
739 def get_merge_files(repo, p1, p2, files):
740     for e in repo[p1].files():
741         if e not in files:
742             if e not in repo[p1].manifest():
743                 continue
744             f = { 'ctx' : repo[p1][e] }
745             files[e] = f
746
747 def c_style_unescape(string):
748     if string[0] == string[-1] == '"':
749         return string.decode('string-escape')[1:-1]
750     return string
751
752 def parse_commit(parser):
753     from_mark = merge_mark = None
754
755     ref = parser[1]
756     parser.next()
757
758     commit_mark = parser.get_mark()
759     parser.next()
760     author = parser.get_author()
761     parser.next()
762     committer = parser.get_author()
763     parser.next()
764     data = parser.get_data()
765     parser.next()
766     if parser.check('from'):
767         from_mark = parser.get_mark()
768         parser.next()
769     if parser.check('merge'):
770         merge_mark = parser.get_mark()
771         parser.next()
772         if parser.check('merge'):
773             die('octopus merges are not supported yet')
774
775     # fast-export adds an extra newline
776     if data[-1] == '\n':
777         data = data[:-1]
778
779     files = {}
780
781     for line in parser:
782         if parser.check('M'):
783             t, m, mark_ref, path = line.split(' ', 3)
784             mark = int(mark_ref[1:])
785             f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
786         elif parser.check('D'):
787             t, path = line.split(' ', 1)
788             f = { 'deleted' : True }
789         else:
790             die('Unknown file command: %s' % line)
791         path = c_style_unescape(path)
792         files[path] = f
793
794     # only export the commits if we are on an internal proxy repo
795     if dry_run and not peer:
796         parsed_refs[ref] = None
797         return
798
799     def getfilectx(repo, memctx, f):
800         of = files[f]
801         if 'deleted' in of:
802             raise IOError
803         if 'ctx' in of:
804             return of['ctx']
805         is_exec = of['mode'] == 'x'
806         is_link = of['mode'] == 'l'
807         rename = of.get('rename', None)
808         return context.memfilectx(f, of['data'],
809                 is_link, is_exec, rename)
810
811     repo = parser.repo
812
813     user, date, tz = author
814     extra = {}
815
816     if committer != author:
817         extra['committer'] = "%s %u %u" % committer
818
819     if from_mark:
820         p1 = mark_to_rev(from_mark)
821     else:
822         p1 = '0' * 40
823
824     if merge_mark:
825         p2 = mark_to_rev(merge_mark)
826     else:
827         p2 = '0' * 40
828
829     #
830     # If files changed from any of the parents, hg wants to know, but in git if
831     # nothing changed from the first parent, nothing changed.
832     #
833     if merge_mark:
834         get_merge_files(repo, p1, p2, files)
835
836     # Check if the ref is supposed to be a named branch
837     if ref.startswith('refs/heads/branches/'):
838         branch = ref[len('refs/heads/branches/'):]
839         extra['branch'] = hgref(branch)
840
841     if mode == 'hg':
842         i = data.find('\n--HG--\n')
843         if i >= 0:
844             tmp = data[i + len('\n--HG--\n'):].strip()
845             for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
846                 if k == 'rename':
847                     old, new = v.split(' => ', 1)
848                     files[new]['rename'] = old
849                 elif k == 'branch':
850                     extra[k] = v
851                 elif k == 'extra':
852                     ek, ev = v.split(' : ', 1)
853                     extra[ek] = urllib.unquote(ev)
854             data = data[:i]
855
856     ctx = context.memctx(repo, (p1, p2), data,
857             files.keys(), getfilectx,
858             user, (date, tz), extra)
859
860     tmp = encoding.encoding
861     encoding.encoding = 'utf-8'
862
863     node = hghex(repo.commitctx(ctx))
864
865     encoding.encoding = tmp
866
867     parsed_refs[ref] = node
868     marks.new_mark(node, commit_mark)
869
870 def parse_reset(parser):
871     ref = parser[1]
872     parser.next()
873     # ugh
874     if parser.check('commit'):
875         parse_commit(parser)
876         return
877     if not parser.check('from'):
878         return
879     from_mark = parser.get_mark()
880     parser.next()
881
882     try:
883         rev = mark_to_rev(from_mark)
884     except KeyError:
885         rev = None
886     parsed_refs[ref] = rev
887
888 def parse_tag(parser):
889     name = parser[1]
890     parser.next()
891     from_mark = parser.get_mark()
892     parser.next()
893     tagger = parser.get_author()
894     parser.next()
895     data = parser.get_data()
896     parser.next()
897
898     parsed_tags[name] = (tagger, data)
899
900 def write_tag(repo, tag, node, msg, author):
901     branch = repo[node].branch()
902     tip = branch_tip(branch)
903     tip = repo[tip]
904
905     def getfilectx(repo, memctx, f):
906         try:
907             fctx = tip.filectx(f)
908             data = fctx.data()
909         except error.ManifestLookupError:
910             data = ""
911         content = data + "%s %s\n" % (node, tag)
912         return context.memfilectx(f, content, False, False, None)
913
914     p1 = tip.hex()
915     p2 = '0' * 40
916     if author:
917         user, date, tz = author
918         date_tz = (date, tz)
919     else:
920         cmd = ['git', 'var', 'GIT_COMMITTER_IDENT']
921         process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
922         output, _ = process.communicate()
923         m = re.match('^.* <.*>', output)
924         if m:
925             user = m.group(0)
926         else:
927             user = repo.ui.username()
928         date_tz = None
929
930     ctx = context.memctx(repo, (p1, p2), msg,
931             ['.hgtags'], getfilectx,
932             user, date_tz, {'branch' : branch})
933
934     tmp = encoding.encoding
935     encoding.encoding = 'utf-8'
936
937     tagnode = repo.commitctx(ctx)
938
939     encoding.encoding = tmp
940
941     return (tagnode, branch)
942
943 def checkheads_bmark(repo, ref, ctx):
944     bmark = ref[len('refs/heads/'):]
945     if not bmark in bmarks:
946         # new bmark
947         return True
948
949     ctx_old = bmarks[bmark]
950     ctx_new = ctx
951
952     if not ctx.rev():
953         print "error %s unknown" % ref
954         return False
955
956     if not repo.changelog.descendant(ctx_old.rev(), ctx_new.rev()):
957         if force_push:
958             print "ok %s forced update" % ref
959         else:
960             print "error %s non-fast forward" % ref
961             return False
962
963     return True
964
965 def checkheads(repo, remote, p_revs):
966
967     remotemap = remote.branchmap()
968     if not remotemap:
969         # empty repo
970         return True
971
972     new = {}
973     ret = True
974
975     for node, ref in p_revs.iteritems():
976         ctx = repo[node]
977         branch = ctx.branch()
978         if not branch in remotemap:
979             # new branch
980             continue
981         if not ref.startswith('refs/heads/branches'):
982             if ref.startswith('refs/heads/'):
983                 if not checkheads_bmark(repo, ref, ctx):
984                     ret = False
985
986             # only check branches
987             continue
988         new.setdefault(branch, []).append(ctx.rev())
989
990     for branch, heads in new.iteritems():
991         old = [repo.changelog.rev(x) for x in remotemap[branch]]
992         for rev in heads:
993             if check_version(2, 3):
994                 ancestors = repo.changelog.ancestors([rev], stoprev=min(old))
995             else:
996                 ancestors = repo.changelog.ancestors(rev)
997             found = False
998
999             for x in old:
1000                 if x in ancestors:
1001                     found = True
1002                     break
1003
1004             if found:
1005                 continue
1006
1007             node = repo.changelog.node(rev)
1008             ref = p_revs[node]
1009             if force_push:
1010                 print "ok %s forced update" % ref
1011             else:
1012                 print "error %s non-fast forward" % ref
1013                 ret = False
1014
1015     return ret
1016
1017 def push_unsafe(repo, remote, parsed_refs, p_revs):
1018
1019     force = force_push
1020
1021     fci = discovery.findcommonincoming
1022     commoninc = fci(repo, remote, force=force)
1023     common, _, remoteheads = commoninc
1024
1025     if not checkheads(repo, remote, p_revs):
1026         return None
1027
1028     if check_version(3, 0):
1029         cg = changegroup.getbundle(repo, 'push', heads=list(p_revs), common=common)
1030     else:
1031         cg = repo.getbundle('push', heads=list(p_revs), common=common)
1032
1033     unbundle = remote.capable('unbundle')
1034     if unbundle:
1035         if force:
1036             remoteheads = ['force']
1037         ret = remote.unbundle(cg, remoteheads, 'push')
1038     else:
1039         ret = remote.addchangegroup(cg, 'push', repo.url())
1040
1041     phases = remote.listkeys('phases')
1042     if phases:
1043         for head in p_revs:
1044             # update to public
1045             remote.pushkey('phases', hghex(head), '1', '0')
1046
1047     return ret
1048
1049 def push(repo, remote, parsed_refs, p_revs):
1050     if hasattr(remote, 'canpush') and not remote.canpush():
1051         print "error cannot push"
1052
1053     if not p_revs:
1054         # nothing to push
1055         return
1056
1057     lock = None
1058     unbundle = remote.capable('unbundle')
1059     if not unbundle:
1060         lock = remote.lock()
1061     try:
1062         ret = push_unsafe(repo, remote, parsed_refs, p_revs)
1063     finally:
1064         if lock is not None:
1065             lock.release()
1066
1067     return ret
1068
1069 def check_tip(ref, kind, name, heads):
1070     try:
1071         ename = '%s/%s' % (kind, name)
1072         tip = marks.get_tip(ename)
1073     except KeyError:
1074         return True
1075     else:
1076         return tip in heads
1077
1078 def do_export(parser):
1079     p_bmarks = []
1080     p_revs = {}
1081
1082     parser.next()
1083
1084     for line in parser.each_block('done'):
1085         if parser.check('blob'):
1086             parse_blob(parser)
1087         elif parser.check('commit'):
1088             parse_commit(parser)
1089         elif parser.check('reset'):
1090             parse_reset(parser)
1091         elif parser.check('tag'):
1092             parse_tag(parser)
1093         elif parser.check('feature'):
1094             pass
1095         else:
1096             die('unhandled export command: %s' % line)
1097
1098     need_fetch = False
1099
1100     for ref, node in parsed_refs.iteritems():
1101         bnode = hgbin(node) if node else None
1102         if ref.startswith('refs/heads/branches'):
1103             branch = ref[len('refs/heads/branches/'):]
1104             if branch in branches and bnode in branches[branch]:
1105                 # up to date
1106                 continue
1107
1108             if peer:
1109                 remotemap = peer.branchmap()
1110                 if remotemap and branch in remotemap:
1111                     heads = [hghex(e) for e in remotemap[branch]]
1112                     if not check_tip(ref, 'branches', branch, heads):
1113                         print "error %s fetch first" % ref
1114                         need_fetch = True
1115                         continue
1116
1117             p_revs[bnode] = ref
1118             print "ok %s" % ref
1119         elif ref.startswith('refs/heads/'):
1120             bmark = ref[len('refs/heads/'):]
1121             new = node
1122             old = bmarks[bmark].hex() if bmark in bmarks else ''
1123
1124             if old == new:
1125                 continue
1126
1127             print "ok %s" % ref
1128             if bmark != fake_bmark and \
1129                     not (bmark == 'master' and bmark not in parser.repo._bookmarks):
1130                 p_bmarks.append((ref, bmark, old, new))
1131
1132             if peer:
1133                 remote_old = peer.listkeys('bookmarks').get(bmark)
1134                 if remote_old:
1135                     if not check_tip(ref, 'bookmarks', bmark, remote_old):
1136                         print "error %s fetch first" % ref
1137                         need_fetch = True
1138                         continue
1139
1140             p_revs[bnode] = ref
1141         elif ref.startswith('refs/tags/'):
1142             if dry_run:
1143                 print "ok %s" % ref
1144                 continue
1145             tag = ref[len('refs/tags/'):]
1146             tag = hgref(tag)
1147             author, msg = parsed_tags.get(tag, (None, None))
1148             if mode == 'git':
1149                 if not msg:
1150                     msg = 'Added tag %s for changeset %s' % (tag, node[:12])
1151                 tagnode, branch = write_tag(parser.repo, tag, node, msg, author)
1152                 p_revs[tagnode] = 'refs/heads/branches/' + gitref(branch)
1153             else:
1154                 fp = parser.repo.opener('localtags', 'a')
1155                 fp.write('%s %s\n' % (node, tag))
1156                 fp.close()
1157             p_revs[bnode] = ref
1158             print "ok %s" % ref
1159         else:
1160             # transport-helper/fast-export bugs
1161             continue
1162
1163     if need_fetch:
1164         print
1165         return
1166
1167     if dry_run:
1168         if peer and not force_push:
1169             checkheads(parser.repo, peer, p_revs)
1170         print
1171         return
1172
1173     if peer:
1174         if not push(parser.repo, peer, parsed_refs, p_revs):
1175             # do not update bookmarks
1176             print
1177             return
1178
1179         # update remote bookmarks
1180         remote_bmarks = peer.listkeys('bookmarks')
1181         for ref, bmark, old, new in p_bmarks:
1182             if force_push:
1183                 old = remote_bmarks.get(bmark, '')
1184             if not peer.pushkey('bookmarks', bmark, old, new):
1185                 print "error %s" % ref
1186     else:
1187         # update local bookmarks
1188         for ref, bmark, old, new in p_bmarks:
1189             if not bookmarks.pushbookmark(parser.repo, bmark, old, new):
1190                 print "error %s" % ref
1191
1192     print
1193
1194 def do_option(parser):
1195     global dry_run, force_push
1196     _, key, value = parser.line.split(' ')
1197     if key == 'dry-run':
1198         dry_run = (value == 'true')
1199         print 'ok'
1200     elif key == 'force':
1201         force_push = (value == 'true')
1202         print 'ok'
1203     else:
1204         print 'unsupported'
1205
1206 def fix_path(alias, repo, orig_url):
1207     url = urlparse.urlparse(orig_url, 'file')
1208     if url.scheme != 'file' or os.path.isabs(os.path.expanduser(url.path)):
1209         return
1210     abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
1211     cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
1212     subprocess.call(cmd)
1213
1214 def main(args):
1215     global prefix, gitdir, dirname, branches, bmarks
1216     global marks, blob_marks, parsed_refs
1217     global peer, mode, bad_mail, bad_name
1218     global track_branches, force_push, is_tmp
1219     global parsed_tags
1220     global filenodes
1221     global fake_bmark, hg_version
1222     global dry_run
1223     global notes, alias
1224
1225     marks = None
1226     is_tmp = False
1227     gitdir = os.environ.get('GIT_DIR', None)
1228
1229     if len(args) < 3:
1230         die('Not enough arguments.')
1231
1232     if not gitdir:
1233         die('GIT_DIR not set')
1234
1235     alias = args[1]
1236     url = args[2]
1237     peer = None
1238
1239     hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
1240     track_branches = get_config_bool('remote-hg.track-branches', True)
1241     force_push = False
1242
1243     if hg_git_compat:
1244         mode = 'hg'
1245         bad_mail = 'none@none'
1246         bad_name = ''
1247     else:
1248         mode = 'git'
1249         bad_mail = 'unknown'
1250         bad_name = 'Unknown'
1251
1252     if alias[4:] == url:
1253         is_tmp = True
1254         alias = hashlib.sha1(alias).hexdigest()
1255
1256     dirname = os.path.join(gitdir, 'hg', alias)
1257     branches = {}
1258     bmarks = {}
1259     blob_marks = {}
1260     parsed_refs = {}
1261     parsed_tags = {}
1262     filenodes = {}
1263     fake_bmark = None
1264     try:
1265         hg_version = tuple(int(e) for e in util.version().split('.'))
1266     except:
1267         hg_version = None
1268     dry_run = False
1269     notes = set()
1270
1271     repo = get_repo(url, alias)
1272     prefix = 'refs/hg/%s' % alias
1273
1274     if not is_tmp:
1275         fix_path(alias, peer or repo, url)
1276
1277     marks_path = os.path.join(dirname, 'marks-hg')
1278     marks = Marks(marks_path, repo)
1279
1280     if sys.platform == 'win32':
1281         import msvcrt
1282         msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
1283
1284     parser = Parser(repo)
1285     for line in parser:
1286         if parser.check('capabilities'):
1287             do_capabilities(parser)
1288         elif parser.check('list'):
1289             do_list(parser)
1290         elif parser.check('import'):
1291             do_import(parser)
1292         elif parser.check('export'):
1293             do_export(parser)
1294         elif parser.check('option'):
1295             do_option(parser)
1296         else:
1297             die('unhandled command: %s' % line)
1298         sys.stdout.flush()
1299
1300     marks.store()
1301
1302 def bye():
1303     if is_tmp:
1304         shutil.rmtree(dirname)
1305
1306 atexit.register(bye)
1307 sys.exit(main(sys.argv))