Merge branch 'fc/master' into travis-ci
[git] / git-remote-hg.py
1 #!/usr/bin/env python
2 #
3 # Copyright (c) 2012 Felipe Contreras
4 #
5
6 # Inspired by Rocco Rutte's hg-fast-export
7
8 # Just copy to your ~/bin, or anywhere in your $PATH.
9 # Then you can clone with:
10 # git clone hg::/path/to/mercurial/repo/
11 #
12 # For remote repositories a local clone is stored in
13 # "$GIT_DIR/hg/origin/clone/.hg/".
14
15 from mercurial import hg, ui, bookmarks, context, encoding, node, error, extensions, discovery, util
16
17 import re
18 import sys
19 import os
20 import json
21 import shutil
22 import subprocess
23 import urllib
24 import atexit
25 import urlparse, hashlib
26 import time as ptime
27
28 #
29 # If you want to see Mercurial revisions as Git commit notes:
30 # git config core.notesRef refs/notes/hg
31 #
32 # If you are not in hg-git-compat mode and want to disable the tracking of
33 # named branches:
34 # git config --global remote-hg.track-branches false
35 #
36 # If you want the equivalent of hg's clone/pull--insecure option:
37 # git config --global remote-hg.insecure true
38 #
39 # If you want to switch to hg-git compatibility mode:
40 # git config --global remote-hg.hg-git-compat true
41 #
42 # git:
43 # Sensible defaults for git.
44 # hg bookmarks are exported as git branches, hg branches are prefixed
45 # with 'branches/', HEAD is a special case.
46 #
47 # hg:
48 # Emulate hg-git.
49 # Only hg bookmarks are exported as git branches.
50 # Commits are modified to preserve hg information and allow bidirectionality.
51 #
52
53 NAME_RE = re.compile('^([^<>]+)')
54 AUTHOR_RE = re.compile('^([^<>]+?)? ?[<>]([^<>]*)(?:$|>)')
55 EMAIL_RE = re.compile(r'([^ \t<>]+@[^ \t<>]+)')
56 AUTHOR_HG_RE = re.compile('^(.*?) ?<(.*?)(?:>(.+)?)?$')
57 RAW_AUTHOR_RE = re.compile('^(\w+) (?:(.+)? )?<(.*)> (\d+) ([+-]\d+)')
58
59 VERSION = 2
60
61 def die(msg, *args):
62     sys.stderr.write('ERROR: %s\n' % (msg % args))
63     sys.exit(1)
64
65 def warn(msg, *args):
66     sys.stderr.write('WARNING: %s\n' % (msg % args))
67
68 def gitmode(flags):
69     return 'l' in flags and '120000' or 'x' in flags and '100755' or '100644'
70
71 def gittz(tz):
72     return '%+03d%02d' % (-tz / 3600, -tz % 3600 / 60)
73
74 def hgmode(mode):
75     m = { '100755': 'x', '120000': 'l' }
76     return m.get(mode, '')
77
78 def hghex(n):
79     return node.hex(n)
80
81 def hgbin(n):
82     return node.bin(n)
83
84 def hgref(ref):
85     return ref.replace('___', ' ')
86
87 def gitref(ref):
88     return ref.replace(' ', '___')
89
90 def check_version(*check):
91     if not hg_version:
92         return True
93     return hg_version >= check
94
95 def get_config(config):
96     cmd = ['git', 'config', '--get', config]
97     process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
98     output, _ = process.communicate()
99     return output
100
101 def get_config_bool(config, default=False):
102     value = get_config(config).rstrip('\n')
103     if value == "true":
104         return True
105     elif value == "false":
106         return False
107     else:
108         return default
109
110 class Marks:
111
112     def __init__(self, path, repo):
113         self.path = path
114         self.repo = repo
115         self.clear()
116         self.load()
117
118         if self.version < VERSION:
119             if self.version == 1:
120                 self.upgrade_one()
121
122             # upgraded?
123             if self.version < VERSION:
124                 self.clear()
125                 self.version = VERSION
126
127     def clear(self):
128         self.tips = {}
129         self.marks = {}
130         self.rev_marks = {}
131         self.last_mark = 0
132         self.version = 0
133         self.last_note = 0
134
135     def load(self):
136         if not os.path.exists(self.path):
137             return
138
139         tmp = json.load(open(self.path))
140
141         self.tips = tmp['tips']
142         self.marks = tmp['marks']
143         self.last_mark = tmp['last-mark']
144         self.version = tmp.get('version', 1)
145         self.last_note = tmp.get('last-note', 0)
146
147         for rev, mark in self.marks.iteritems():
148             self.rev_marks[mark] = rev
149
150     def upgrade_one(self):
151         def get_id(rev):
152             return hghex(self.repo.changelog.node(int(rev)))
153         self.tips = dict((name, get_id(rev)) for name, rev in self.tips.iteritems())
154         self.marks = dict((get_id(rev), mark) for rev, mark in self.marks.iteritems())
155         self.rev_marks = dict((mark, get_id(rev)) for mark, rev in self.rev_marks.iteritems())
156         self.version = 2
157
158     def dict(self):
159         return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark, 'version' : self.version, 'last-note' : self.last_note }
160
161     def store(self):
162         json.dump(self.dict(), open(self.path, 'w'))
163
164     def __str__(self):
165         return str(self.dict())
166
167     def from_rev(self, rev):
168         return self.marks[rev]
169
170     def to_rev(self, mark):
171         return str(self.rev_marks[mark])
172
173     def next_mark(self):
174         self.last_mark += 1
175         return self.last_mark
176
177     def get_mark(self, rev):
178         self.last_mark += 1
179         self.marks[rev] = self.last_mark
180         return self.last_mark
181
182     def new_mark(self, rev, mark):
183         self.marks[rev] = mark
184         self.rev_marks[mark] = rev
185         self.last_mark = mark
186
187     def is_marked(self, rev):
188         return rev in self.marks
189
190     def get_tip(self, branch):
191         return str(self.tips[branch])
192
193     def set_tip(self, branch, tip):
194         self.tips[branch] = tip
195
196 class Parser:
197
198     def __init__(self, repo):
199         self.repo = repo
200         self.line = self.get_line()
201
202     def get_line(self):
203         return sys.stdin.readline().strip()
204
205     def __getitem__(self, i):
206         return self.line.split()[i]
207
208     def check(self, word):
209         return self.line.startswith(word)
210
211     def each_block(self, separator):
212         while self.line != separator:
213             yield self.line
214             self.line = self.get_line()
215
216     def __iter__(self):
217         return self.each_block('')
218
219     def next(self):
220         self.line = self.get_line()
221         if self.line == 'done':
222             self.line = None
223
224     def get_mark(self):
225         i = self.line.index(':') + 1
226         return int(self.line[i:])
227
228     def get_data(self):
229         if not self.check('data'):
230             return None
231         i = self.line.index(' ') + 1
232         size = int(self.line[i:])
233         return sys.stdin.read(size)
234
235     def get_author(self):
236         ex = None
237         m = RAW_AUTHOR_RE.match(self.line)
238         if not m:
239             return None
240         _, name, email, date, tz = m.groups()
241         if name and 'ext:' in name:
242             m = re.match('^(.+?) ext:\((.+)\)$', name)
243             if m:
244                 name = m.group(1)
245                 ex = urllib.unquote(m.group(2))
246
247         if email != bad_mail:
248             if name:
249                 user = '%s <%s>' % (name, email)
250             else:
251                 user = '<%s>' % (email)
252         else:
253             user = name
254
255         if ex:
256             user += ex
257
258         tz = int(tz)
259         tz = ((tz / 100) * 3600) + ((tz % 100) * 60)
260         return (user, int(date), -tz)
261
262 def fix_file_path(path):
263     path = os.path.normpath(path)
264     if not os.path.isabs(path):
265         return path
266     return os.path.relpath(path, '/')
267
268 def export_files(files):
269     final = []
270     for f in files:
271         fid = node.hex(f.filenode())
272
273         if fid in filenodes:
274             mark = filenodes[fid]
275         else:
276             mark = marks.next_mark()
277             filenodes[fid] = mark
278             d = f.data()
279
280             print "blob"
281             print "mark :%u" % mark
282             print "data %d" % len(d)
283             print d
284
285         path = fix_file_path(f.path())
286         final.append((gitmode(f.flags()), mark, path))
287
288     return final
289
290 def get_filechanges(repo, ctx, parent):
291     modified = set()
292     added = set()
293     removed = set()
294
295     # load earliest manifest first for caching reasons
296     prev = parent.manifest().copy()
297     cur = ctx.manifest()
298
299     for fn in cur:
300         if fn in prev:
301             if (cur.flags(fn) != prev.flags(fn) or cur[fn] != prev[fn]):
302                 modified.add(fn)
303             del prev[fn]
304         else:
305             added.add(fn)
306     removed |= set(prev.keys())
307
308     return added | modified, removed
309
310 def fixup_user_git(user):
311     name = mail = None
312     user = user.replace('"', '')
313     m = AUTHOR_RE.match(user)
314     if m:
315         name = m.group(1)
316         mail = m.group(2).strip()
317     else:
318         m = EMAIL_RE.match(user)
319         if m:
320             mail = m.group(1)
321         else:
322             m = NAME_RE.match(user)
323             if m:
324                 name = m.group(1).strip()
325     return (name, mail)
326
327 def fixup_user_hg(user):
328     def sanitize(name):
329         # stole this from hg-git
330         return re.sub('[<>\n]', '?', name.lstrip('< ').rstrip('> '))
331
332     m = AUTHOR_HG_RE.match(user)
333     if m:
334         name = sanitize(m.group(1))
335         mail = sanitize(m.group(2))
336         ex = m.group(3)
337         if ex:
338             name += ' ext:(' + urllib.quote(ex) + ')'
339     else:
340         name = sanitize(user)
341         if '@' in user:
342             mail = name
343         else:
344             mail = None
345
346     return (name, mail)
347
348 def fixup_user(user):
349     if mode == 'git':
350         name, mail = fixup_user_git(user)
351     else:
352         name, mail = fixup_user_hg(user)
353
354     if not name:
355         name = bad_name
356     if not mail:
357         mail = bad_mail
358
359     return '%s <%s>' % (name, mail)
360
361 def updatebookmarks(repo, peer):
362     remotemarks = peer.listkeys('bookmarks')
363     localmarks = repo._bookmarks
364
365     if not remotemarks:
366         return
367
368     for k, v in remotemarks.iteritems():
369         localmarks[k] = hgbin(v)
370
371     if hasattr(localmarks, 'write'):
372         localmarks.write()
373     else:
374         bookmarks.write(repo)
375
376 def get_repo(url, alias):
377     global peer
378
379     myui = ui.ui()
380     myui.setconfig('ui', 'interactive', 'off')
381     myui.fout = sys.stderr
382
383     if get_config_bool('remote-hg.insecure'):
384         myui.setconfig('web', 'cacerts', '')
385
386     extensions.loadall(myui)
387
388     if hg.islocal(url) and not os.environ.get('GIT_REMOTE_HG_TEST_REMOTE'):
389         repo = hg.repository(myui, url)
390         if not os.path.exists(dirname):
391             os.makedirs(dirname)
392     else:
393         shared_path = os.path.join(gitdir, 'hg')
394
395         # check and upgrade old organization
396         hg_path = os.path.join(shared_path, '.hg')
397         if os.path.exists(shared_path) and not os.path.exists(hg_path):
398             repos = os.listdir(shared_path)
399             for x in repos:
400                 local_hg = os.path.join(shared_path, x, 'clone', '.hg')
401                 if not os.path.exists(local_hg):
402                     continue
403                 if not os.path.exists(hg_path):
404                     shutil.move(local_hg, hg_path)
405                 shutil.rmtree(os.path.join(shared_path, x, 'clone'))
406
407         # setup shared repo (if not there)
408         try:
409             hg.peer(myui, {}, shared_path, create=True)
410         except error.RepoError:
411             pass
412
413         if not os.path.exists(dirname):
414             os.makedirs(dirname)
415
416         local_path = os.path.join(dirname, 'clone')
417         if not os.path.exists(local_path):
418             hg.share(myui, shared_path, local_path, update=False)
419         else:
420             # make sure the shared path is always up-to-date
421             util.writefile(os.path.join(local_path, '.hg', 'sharedpath'), hg_path)
422
423         repo = hg.repository(myui, local_path)
424         try:
425             peer = hg.peer(myui, {}, url)
426         except:
427             die('Repository error')
428         repo.pull(peer, heads=None, force=True)
429
430         updatebookmarks(repo, peer)
431
432     return repo
433
434 def rev_to_mark(rev):
435     return marks.from_rev(rev.hex())
436
437 def mark_to_rev(mark):
438     return marks.to_rev(mark)
439
440 def export_ref(repo, name, kind, head):
441     ename = '%s/%s' % (kind, name)
442     try:
443         tip = marks.get_tip(ename)
444         tip = repo[tip].rev()
445     except:
446         tip = 0
447
448     revs = xrange(tip, head.rev() + 1)
449     total = len(revs)
450
451     for rev in revs:
452
453         c = repo[rev]
454         node = c.node()
455
456         if marks.is_marked(c.hex()):
457             continue
458
459         (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(node)
460         rev_branch = extra['branch']
461
462         author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
463         if 'committer' in extra:
464             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     if not repo.changelog.descendant(ctx_old.rev(), ctx_new.rev()):
918         if force_push:
919             print "ok %s forced update" % ref
920         else:
921             print "error %s non-fast forward" % ref
922             return False
923
924     return True
925
926 def checkheads(repo, remote, p_revs):
927
928     remotemap = remote.branchmap()
929     if not remotemap:
930         # empty repo
931         return True
932
933     new = {}
934     ret = True
935
936     for node, ref in p_revs.iteritems():
937         ctx = repo[node]
938         branch = ctx.branch()
939         if not branch in remotemap:
940             # new branch
941             continue
942         if not ref.startswith('refs/heads/branches'):
943             if ref.startswith('refs/heads/'):
944                 if not checkheads_bmark(repo, ref, ctx):
945                     ret = False
946
947             # only check branches
948             continue
949         new.setdefault(branch, []).append(ctx.rev())
950
951     for branch, heads in new.iteritems():
952         old = [repo.changelog.rev(x) for x in remotemap[branch]]
953         for rev in heads:
954             if check_version(2, 3):
955                 ancestors = repo.changelog.ancestors([rev], stoprev=min(old))
956             else:
957                 ancestors = repo.changelog.ancestors(rev)
958             found = False
959
960             for x in old:
961                 if x in ancestors:
962                     found = True
963                     break
964
965             if found:
966                 continue
967
968             node = repo.changelog.node(rev)
969             ref = p_revs[node]
970             if force_push:
971                 print "ok %s forced update" % ref
972             else:
973                 print "error %s non-fast forward" % ref
974                 ret = False
975
976     return ret
977
978 def push_unsafe(repo, remote, parsed_refs, p_revs):
979
980     force = force_push
981
982     fci = discovery.findcommonincoming
983     commoninc = fci(repo, remote, force=force)
984     common, _, remoteheads = commoninc
985
986     if not checkheads(repo, remote, p_revs):
987         return None
988
989     cg = repo.getbundle('push', heads=list(p_revs), common=common)
990
991     unbundle = remote.capable('unbundle')
992     if unbundle:
993         if force:
994             remoteheads = ['force']
995         return remote.unbundle(cg, remoteheads, 'push')
996     else:
997         return remote.addchangegroup(cg, 'push', repo.url())
998
999 def push(repo, remote, parsed_refs, p_revs):
1000     if hasattr(remote, 'canpush') and not remote.canpush():
1001         print "error cannot push"
1002
1003     if not p_revs:
1004         # nothing to push
1005         return
1006
1007     lock = None
1008     unbundle = remote.capable('unbundle')
1009     if not unbundle:
1010         lock = remote.lock()
1011     try:
1012         ret = push_unsafe(repo, remote, parsed_refs, p_revs)
1013     finally:
1014         if lock is not None:
1015             lock.release()
1016
1017     return ret
1018
1019 def check_tip(ref, kind, name, heads):
1020     try:
1021         ename = '%s/%s' % (kind, name)
1022         tip = marks.get_tip(ename)
1023     except KeyError:
1024         return True
1025     else:
1026         return tip in heads
1027
1028 def do_export(parser):
1029     p_bmarks = []
1030     p_revs = {}
1031
1032     parser.next()
1033
1034     for line in parser.each_block('done'):
1035         if parser.check('blob'):
1036             parse_blob(parser)
1037         elif parser.check('commit'):
1038             parse_commit(parser)
1039         elif parser.check('reset'):
1040             parse_reset(parser)
1041         elif parser.check('tag'):
1042             parse_tag(parser)
1043         elif parser.check('feature'):
1044             pass
1045         else:
1046             die('unhandled export command: %s' % line)
1047
1048     need_fetch = False
1049
1050     for ref, node in parsed_refs.iteritems():
1051         bnode = hgbin(node) if node else None
1052         if ref.startswith('refs/heads/branches'):
1053             branch = ref[len('refs/heads/branches/'):]
1054             if branch in branches and bnode in branches[branch]:
1055                 # up to date
1056                 continue
1057
1058             if peer:
1059                 remotemap = peer.branchmap()
1060                 if remotemap and branch in remotemap:
1061                     heads = [hghex(e) for e in remotemap[branch]]
1062                     if not check_tip(ref, 'branches', branch, heads):
1063                         print "error %s fetch first" % ref
1064                         need_fetch = True
1065                         continue
1066
1067             p_revs[bnode] = ref
1068             print "ok %s" % ref
1069         elif ref.startswith('refs/heads/'):
1070             bmark = ref[len('refs/heads/'):]
1071             new = node
1072             old = bmarks[bmark].hex() if bmark in bmarks else ''
1073
1074             if old == new:
1075                 continue
1076
1077             print "ok %s" % ref
1078             if bmark != fake_bmark and \
1079                     not (bmark == 'master' and bmark not in parser.repo._bookmarks):
1080                 p_bmarks.append((ref, bmark, old, new))
1081
1082             if peer:
1083                 remote_old = peer.listkeys('bookmarks').get(bmark)
1084                 if remote_old:
1085                     if not check_tip(ref, 'bookmarks', bmark, remote_old):
1086                         print "error %s fetch first" % ref
1087                         need_fetch = True
1088                         continue
1089
1090             p_revs[bnode] = ref
1091         elif ref.startswith('refs/tags/'):
1092             if dry_run:
1093                 print "ok %s" % ref
1094                 continue
1095             tag = ref[len('refs/tags/'):]
1096             tag = hgref(tag)
1097             author, msg = parsed_tags.get(tag, (None, None))
1098             if mode == 'git':
1099                 if not msg:
1100                     msg = 'Added tag %s for changeset %s' % (tag, node[:12])
1101                 tagnode, branch = write_tag(parser.repo, tag, node, msg, author)
1102                 p_revs[tagnode] = 'refs/heads/branches/' + gitref(branch)
1103             else:
1104                 fp = parser.repo.opener('localtags', 'a')
1105                 fp.write('%s %s\n' % (node, tag))
1106                 fp.close()
1107             p_revs[bnode] = ref
1108             print "ok %s" % ref
1109         else:
1110             # transport-helper/fast-export bugs
1111             continue
1112
1113     if need_fetch:
1114         print
1115         return
1116
1117     if dry_run:
1118         if peer and not force_push:
1119             checkheads(parser.repo, peer, p_revs)
1120         print
1121         return
1122
1123     if peer:
1124         if not push(parser.repo, peer, parsed_refs, p_revs):
1125             # do not update bookmarks
1126             print
1127             return
1128
1129         # update remote bookmarks
1130         remote_bmarks = peer.listkeys('bookmarks')
1131         for ref, bmark, old, new in p_bmarks:
1132             if force_push:
1133                 old = remote_bmarks.get(bmark, '')
1134             if not peer.pushkey('bookmarks', bmark, old, new):
1135                 print "error %s" % ref
1136     else:
1137         # update local bookmarks
1138         for ref, bmark, old, new in p_bmarks:
1139             if not bookmarks.pushbookmark(parser.repo, bmark, old, new):
1140                 print "error %s" % ref
1141
1142     print
1143
1144 def do_option(parser):
1145     global dry_run, force_push
1146     _, key, value = parser.line.split(' ')
1147     if key == 'dry-run':
1148         dry_run = (value == 'true')
1149         print 'ok'
1150     elif key == 'force':
1151         force_push = (value == 'true')
1152         print 'ok'
1153     else:
1154         print 'unsupported'
1155
1156 def fix_path(alias, repo, orig_url):
1157     url = urlparse.urlparse(orig_url, 'file')
1158     if url.scheme != 'file' or os.path.isabs(os.path.expanduser(url.path)):
1159         return
1160     abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
1161     cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
1162     subprocess.call(cmd)
1163
1164 def main(args):
1165     global prefix, gitdir, dirname, branches, bmarks
1166     global marks, blob_marks, parsed_refs
1167     global peer, mode, bad_mail, bad_name
1168     global track_branches, force_push, is_tmp
1169     global parsed_tags
1170     global filenodes
1171     global fake_bmark, hg_version
1172     global dry_run
1173     global notes, alias
1174
1175     marks = None
1176     is_tmp = False
1177     gitdir = os.environ.get('GIT_DIR', None)
1178
1179     if len(args) < 3:
1180         die('Not enough arguments.')
1181
1182     if not gitdir:
1183         die('GIT_DIR not set')
1184
1185     alias = args[1]
1186     url = args[2]
1187     peer = None
1188
1189     hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
1190     track_branches = get_config_bool('remote-hg.track-branches', True)
1191     force_push = False
1192
1193     if hg_git_compat:
1194         mode = 'hg'
1195         bad_mail = 'none@none'
1196         bad_name = ''
1197     else:
1198         mode = 'git'
1199         bad_mail = 'unknown'
1200         bad_name = 'Unknown'
1201
1202     if alias[4:] == url:
1203         is_tmp = True
1204         alias = hashlib.sha1(alias).hexdigest()
1205
1206     dirname = os.path.join(gitdir, 'hg', alias)
1207     branches = {}
1208     bmarks = {}
1209     blob_marks = {}
1210     parsed_refs = {}
1211     parsed_tags = {}
1212     filenodes = {}
1213     fake_bmark = None
1214     try:
1215         hg_version = tuple(int(e) for e in util.version().split('.'))
1216     except:
1217         hg_version = None
1218     dry_run = False
1219     notes = set()
1220
1221     repo = get_repo(url, alias)
1222     prefix = 'refs/hg/%s' % alias
1223
1224     if not is_tmp:
1225         fix_path(alias, peer or repo, url)
1226
1227     marks_path = os.path.join(dirname, 'marks-hg')
1228     marks = Marks(marks_path, repo)
1229
1230     if sys.platform == 'win32':
1231         import msvcrt
1232         msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
1233
1234     parser = Parser(repo)
1235     for line in parser:
1236         if parser.check('capabilities'):
1237             do_capabilities(parser)
1238         elif parser.check('list'):
1239             do_list(parser)
1240         elif parser.check('import'):
1241             do_import(parser)
1242         elif parser.check('export'):
1243             do_export(parser)
1244         elif parser.check('option'):
1245             do_option(parser)
1246         else:
1247             die('unhandled command: %s' % line)
1248         sys.stdout.flush()
1249
1250 def bye():
1251     if not marks:
1252         return
1253     if not is_tmp:
1254         marks.store()
1255     else:
1256         shutil.rmtree(dirname)
1257
1258 atexit.register(bye)
1259 sys.exit(main(sys.argv))