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