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