remote-hg: unquote C-style paths when exporting
[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 c_style_unescape(string):
682     if string[0] == string[-1] == '"':
683         return string.decode('string-escape')[1:-1]
684     return string
685
686 def parse_commit(parser):
687     global marks, blob_marks, parsed_refs
688     global mode
689
690     from_mark = merge_mark = None
691
692     ref = parser[1]
693     parser.next()
694
695     commit_mark = parser.get_mark()
696     parser.next()
697     author = parser.get_author()
698     parser.next()
699     committer = parser.get_author()
700     parser.next()
701     data = parser.get_data()
702     parser.next()
703     if parser.check('from'):
704         from_mark = parser.get_mark()
705         parser.next()
706     if parser.check('merge'):
707         merge_mark = parser.get_mark()
708         parser.next()
709         if parser.check('merge'):
710             die('octopus merges are not supported yet')
711
712     # fast-export adds an extra newline
713     if data[-1] == '\n':
714         data = data[:-1]
715
716     files = {}
717
718     for line in parser:
719         if parser.check('M'):
720             t, m, mark_ref, path = line.split(' ', 3)
721             mark = int(mark_ref[1:])
722             f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
723         elif parser.check('D'):
724             t, path = line.split(' ', 1)
725             f = { 'deleted' : True }
726         else:
727             die('Unknown file command: %s' % line)
728         path = c_style_unescape(path).decode('utf-8')
729         files[path] = f
730
731     # only export the commits if we are on an internal proxy repo
732     if dry_run and not peer:
733         parsed_refs[ref] = None
734         return
735
736     def getfilectx(repo, memctx, f):
737         of = files[f]
738         if 'deleted' in of:
739             raise IOError
740         if 'ctx' in of:
741             return of['ctx']
742         is_exec = of['mode'] == 'x'
743         is_link = of['mode'] == 'l'
744         rename = of.get('rename', None)
745         return context.memfilectx(f, of['data'],
746                 is_link, is_exec, rename)
747
748     repo = parser.repo
749
750     user, date, tz = author
751     extra = {}
752
753     if committer != author:
754         extra['committer'] = "%s %u %u" % committer
755
756     if from_mark:
757         p1 = mark_to_rev(from_mark)
758     else:
759         p1 = '0' * 40
760
761     if merge_mark:
762         p2 = mark_to_rev(merge_mark)
763     else:
764         p2 = '0' * 40
765
766     #
767     # If files changed from any of the parents, hg wants to know, but in git if
768     # nothing changed from the first parent, nothing changed.
769     #
770     if merge_mark:
771         get_merge_files(repo, p1, p2, files)
772
773     # Check if the ref is supposed to be a named branch
774     if ref.startswith('refs/heads/branches/'):
775         branch = ref[len('refs/heads/branches/'):]
776         extra['branch'] = hgref(branch)
777
778     if mode == 'hg':
779         i = data.find('\n--HG--\n')
780         if i >= 0:
781             tmp = data[i + len('\n--HG--\n'):].strip()
782             for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
783                 if k == 'rename':
784                     old, new = v.split(' => ', 1)
785                     files[new]['rename'] = old
786                 elif k == 'branch':
787                     extra[k] = v
788                 elif k == 'extra':
789                     ek, ev = v.split(' : ', 1)
790                     extra[ek] = urllib.unquote(ev)
791             data = data[:i]
792
793     ctx = context.memctx(repo, (p1, p2), data,
794             files.keys(), getfilectx,
795             user, (date, tz), extra)
796
797     tmp = encoding.encoding
798     encoding.encoding = 'utf-8'
799
800     node = hghex(repo.commitctx(ctx))
801
802     encoding.encoding = tmp
803
804     parsed_refs[ref] = node
805     marks.new_mark(node, commit_mark)
806
807 def parse_reset(parser):
808     global parsed_refs
809
810     ref = parser[1]
811     parser.next()
812     # ugh
813     if parser.check('commit'):
814         parse_commit(parser)
815         return
816     if not parser.check('from'):
817         return
818     from_mark = parser.get_mark()
819     parser.next()
820
821     try:
822         rev = mark_to_rev(from_mark)
823     except KeyError:
824         rev = None
825     parsed_refs[ref] = rev
826
827 def parse_tag(parser):
828     name = parser[1]
829     parser.next()
830     from_mark = parser.get_mark()
831     parser.next()
832     tagger = parser.get_author()
833     parser.next()
834     data = parser.get_data()
835     parser.next()
836
837     parsed_tags[name] = (tagger, data)
838
839 def write_tag(repo, tag, node, msg, author):
840     branch = repo[node].branch()
841     tip = branch_tip(branch)
842     tip = repo[tip]
843
844     def getfilectx(repo, memctx, f):
845         try:
846             fctx = tip.filectx(f)
847             data = fctx.data()
848         except error.ManifestLookupError:
849             data = ""
850         content = data + "%s %s\n" % (node, tag)
851         return context.memfilectx(f, content, False, False, None)
852
853     p1 = tip.hex()
854     p2 = '0' * 40
855     if author:
856         user, date, tz = author
857         date_tz = (date, tz)
858     else:
859         cmd = ['git', 'var', 'GIT_COMMITTER_IDENT']
860         process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
861         output, _ = process.communicate()
862         m = re.match('^.* <.*>', output)
863         if m:
864             user = m.group(0)
865         else:
866             user = repo.ui.username()
867         date_tz = None
868
869     ctx = context.memctx(repo, (p1, p2), msg,
870             ['.hgtags'], getfilectx,
871             user, date_tz, {'branch' : branch})
872
873     tmp = encoding.encoding
874     encoding.encoding = 'utf-8'
875
876     tagnode = repo.commitctx(ctx)
877
878     encoding.encoding = tmp
879
880     return (tagnode, branch)
881
882 def checkheads_bmark(repo, ref, ctx):
883     bmark = ref[len('refs/heads/'):]
884     if not bmark in bmarks:
885         # new bmark
886         return True
887
888     ctx_old = bmarks[bmark]
889     ctx_new = ctx
890     if not repo.changelog.descendant(ctx_old.rev(), ctx_new.rev()):
891         if force_push:
892             print "ok %s forced update" % ref
893         else:
894             print "error %s non-fast forward" % ref
895             return False
896
897     return True
898
899 def checkheads(repo, remote, p_revs):
900
901     remotemap = remote.branchmap()
902     if not remotemap:
903         # empty repo
904         return True
905
906     new = {}
907     ret = True
908
909     for node, ref in p_revs.iteritems():
910         ctx = repo[node]
911         branch = ctx.branch()
912         if not branch in remotemap:
913             # new branch
914             continue
915         if not ref.startswith('refs/heads/branches'):
916             if ref.startswith('refs/heads/'):
917                 if not checkheads_bmark(repo, ref, ctx):
918                     ret = False
919
920             # only check branches
921             continue
922         new.setdefault(branch, []).append(ctx.rev())
923
924     for branch, heads in new.iteritems():
925         old = [repo.changelog.rev(x) for x in remotemap[branch]]
926         for rev in heads:
927             if check_version(2, 3):
928                 ancestors = repo.changelog.ancestors([rev], stoprev=min(old))
929             else:
930                 ancestors = repo.changelog.ancestors(rev)
931             found = False
932
933             for x in old:
934                 if x in ancestors:
935                     found = True
936                     break
937
938             if found:
939                 continue
940
941             node = repo.changelog.node(rev)
942             ref = p_revs[node]
943             if force_push:
944                 print "ok %s forced update" % ref
945             else:
946                 print "error %s non-fast forward" % ref
947                 ret = False
948
949     return ret
950
951 def push_unsafe(repo, remote, parsed_refs, p_revs):
952
953     force = force_push
954
955     fci = discovery.findcommonincoming
956     commoninc = fci(repo, remote, force=force)
957     common, _, remoteheads = commoninc
958
959     if not checkheads(repo, remote, p_revs):
960         return None
961
962     cg = repo.getbundle('push', heads=list(p_revs), common=common)
963
964     unbundle = remote.capable('unbundle')
965     if unbundle:
966         if force:
967             remoteheads = ['force']
968         return remote.unbundle(cg, remoteheads, 'push')
969     else:
970         return remote.addchangegroup(cg, 'push', repo.url())
971
972 def push(repo, remote, parsed_refs, p_revs):
973     if hasattr(remote, 'canpush') and not remote.canpush():
974         print "error cannot push"
975
976     if not p_revs:
977         # nothing to push
978         return
979
980     lock = None
981     unbundle = remote.capable('unbundle')
982     if not unbundle:
983         lock = remote.lock()
984     try:
985         ret = push_unsafe(repo, remote, parsed_refs, p_revs)
986     finally:
987         if lock is not None:
988             lock.release()
989
990     return ret
991
992 def check_tip(ref, kind, name, heads):
993     try:
994         ename = '%s/%s' % (kind, name)
995         tip = marks.get_tip(ename)
996     except KeyError:
997         return True
998     else:
999         return tip in heads
1000
1001 def do_export(parser):
1002     global parsed_refs, bmarks, peer
1003
1004     p_bmarks = []
1005     p_revs = {}
1006
1007     parser.next()
1008
1009     for line in parser.each_block('done'):
1010         if parser.check('blob'):
1011             parse_blob(parser)
1012         elif parser.check('commit'):
1013             parse_commit(parser)
1014         elif parser.check('reset'):
1015             parse_reset(parser)
1016         elif parser.check('tag'):
1017             parse_tag(parser)
1018         elif parser.check('feature'):
1019             pass
1020         else:
1021             die('unhandled export command: %s' % line)
1022
1023     need_fetch = False
1024
1025     for ref, node in parsed_refs.iteritems():
1026         bnode = hgbin(node) if node else None
1027         if ref.startswith('refs/heads/branches'):
1028             branch = ref[len('refs/heads/branches/'):]
1029             if branch in branches and bnode in branches[branch]:
1030                 # up to date
1031                 continue
1032
1033             if peer:
1034                 remotemap = peer.branchmap()
1035                 if remotemap and branch in remotemap:
1036                     heads = [hghex(e) for e in remotemap[branch]]
1037                     if not check_tip(ref, 'branches', branch, heads):
1038                         print "error %s fetch first" % ref
1039                         need_fetch = True
1040                         continue
1041
1042             p_revs[bnode] = ref
1043             print "ok %s" % ref
1044         elif ref.startswith('refs/heads/'):
1045             bmark = ref[len('refs/heads/'):]
1046             new = node
1047             old = bmarks[bmark].hex() if bmark in bmarks else ''
1048
1049             if old == new:
1050                 continue
1051
1052             print "ok %s" % ref
1053             if bmark != fake_bmark and \
1054                     not (bmark == 'master' and bmark not in parser.repo._bookmarks):
1055                 p_bmarks.append((ref, bmark, old, new))
1056
1057             if peer:
1058                 remote_old = peer.listkeys('bookmarks').get(bmark)
1059                 if remote_old:
1060                     if not check_tip(ref, 'bookmarks', bmark, remote_old):
1061                         print "error %s fetch first" % ref
1062                         need_fetch = True
1063                         continue
1064
1065             p_revs[bnode] = ref
1066         elif ref.startswith('refs/tags/'):
1067             if dry_run:
1068                 print "ok %s" % ref
1069                 continue
1070             tag = ref[len('refs/tags/'):]
1071             tag = hgref(tag)
1072             author, msg = parsed_tags.get(tag, (None, None))
1073             if mode == 'git':
1074                 if not msg:
1075                     msg = 'Added tag %s for changeset %s' % (tag, node[:12]);
1076                 tagnode, branch = write_tag(parser.repo, tag, node, msg, author)
1077                 p_revs[tagnode] = 'refs/heads/branches/' + gitref(branch)
1078             else:
1079                 fp = parser.repo.opener('localtags', 'a')
1080                 fp.write('%s %s\n' % (node, tag))
1081                 fp.close()
1082             p_revs[bnode] = ref
1083             print "ok %s" % ref
1084         else:
1085             # transport-helper/fast-export bugs
1086             continue
1087
1088     if need_fetch:
1089         print
1090         return
1091
1092     if dry_run:
1093         if peer and not force_push:
1094             checkheads(parser.repo, peer, p_revs)
1095         print
1096         return
1097
1098     if peer:
1099         if not push(parser.repo, peer, parsed_refs, p_revs):
1100             # do not update bookmarks
1101             print
1102             return
1103
1104         # update remote bookmarks
1105         remote_bmarks = peer.listkeys('bookmarks')
1106         for ref, bmark, old, new in p_bmarks:
1107             if force_push:
1108                 old = remote_bmarks.get(bmark, '')
1109             if not peer.pushkey('bookmarks', bmark, old, new):
1110                 print "error %s" % ref
1111     else:
1112         # update local bookmarks
1113         for ref, bmark, old, new in p_bmarks:
1114             if not bookmarks.pushbookmark(parser.repo, bmark, old, new):
1115                 print "error %s" % ref
1116
1117     print
1118
1119 def do_option(parser):
1120     global dry_run, force_push
1121     _, key, value = parser.line.split(' ')
1122     if key == 'dry-run':
1123         dry_run = (value == 'true')
1124         print 'ok'
1125     elif key == 'force':
1126         force_push = (value == 'true')
1127         print 'ok'
1128     else:
1129         print 'unsupported'
1130
1131 def fix_path(alias, repo, orig_url):
1132     url = urlparse.urlparse(orig_url, 'file')
1133     if url.scheme != 'file' or os.path.isabs(url.path):
1134         return
1135     abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
1136     cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
1137     subprocess.call(cmd)
1138
1139 def main(args):
1140     global prefix, gitdir, dirname, branches, bmarks
1141     global marks, blob_marks, parsed_refs
1142     global peer, mode, bad_mail, bad_name
1143     global track_branches, force_push, is_tmp
1144     global parsed_tags
1145     global filenodes
1146     global fake_bmark, hg_version
1147     global dry_run
1148
1149     alias = args[1]
1150     url = args[2]
1151     peer = None
1152
1153     hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
1154     track_branches = get_config_bool('remote-hg.track-branches', True)
1155     force_push = False
1156
1157     if hg_git_compat:
1158         mode = 'hg'
1159         bad_mail = 'none@none'
1160         bad_name = ''
1161     else:
1162         mode = 'git'
1163         bad_mail = 'unknown'
1164         bad_name = 'Unknown'
1165
1166     if alias[4:] == url:
1167         is_tmp = True
1168         alias = hashlib.sha1(alias).hexdigest()
1169     else:
1170         is_tmp = False
1171
1172     gitdir = os.environ['GIT_DIR']
1173     dirname = os.path.join(gitdir, 'hg', alias)
1174     branches = {}
1175     bmarks = {}
1176     blob_marks = {}
1177     parsed_refs = {}
1178     marks = None
1179     parsed_tags = {}
1180     filenodes = {}
1181     fake_bmark = None
1182     try:
1183         hg_version = tuple(int(e) for e in util.version().split('.'))
1184     except:
1185         hg_version = None
1186     dry_run = False
1187
1188     repo = get_repo(url, alias)
1189     prefix = 'refs/hg/%s' % alias
1190
1191     if not is_tmp:
1192         fix_path(alias, peer or repo, url)
1193
1194     marks_path = os.path.join(dirname, 'marks-hg')
1195     marks = Marks(marks_path, repo)
1196
1197     if sys.platform == 'win32':
1198         import msvcrt
1199         msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
1200
1201     parser = Parser(repo)
1202     for line in parser:
1203         if parser.check('capabilities'):
1204             do_capabilities(parser)
1205         elif parser.check('list'):
1206             do_list(parser)
1207         elif parser.check('import'):
1208             do_import(parser)
1209         elif parser.check('export'):
1210             do_export(parser)
1211         elif parser.check('option'):
1212             do_option(parser)
1213         else:
1214             die('unhandled command: %s' % line)
1215         sys.stdout.flush()
1216
1217 def bye():
1218     if not marks:
1219         return
1220     if not is_tmp:
1221         marks.store()
1222     else:
1223         shutil.rmtree(dirname)
1224
1225 atexit.register(bye)
1226 sys.exit(main(sys.argv))