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