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