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