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