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