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