remote-hg: simplify branch_tip()
[git/gitweb.git] / contrib / remote-helpers / git-remote-hg
blob8df72d90c879546cf12324bd4c6518c83a36c501
1 #!/usr/bin/env python
3 # Copyright (c) 2012 Felipe Contreras
6 # Inspired by Rocco Rutte's hg-fast-export
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/
12 # For remote repositories a local clone is stored in
13 # "$GIT_DIR/hg/origin/clone/.hg/".
15 from mercurial import hg, ui, bookmarks, context, encoding, node, error, extensions, discovery, util
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
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
32 # If you don't want to force pushes (and thus risk creating new remote heads):
33 # git config --global remote-hg.force-push false
35 # If you want the equivalent of hg's clone/pull--insecure option:
36 # git config --global remote-hg.insecure true
38 # If you want to switch to hg-git compatibility mode:
39 # git config --global remote-hg.hg-git-compat true
41 # git:
42 # Sensible defaults for git.
43 # hg bookmarks are exported as git branches, hg branches are prefixed
44 # with 'branches/', HEAD is a special case.
46 # hg:
47 # Emulate hg-git.
48 # Only hg bookmarks are exported as git branches.
49 # Commits are modified to preserve hg information and allow bidirectionality.
52 NAME_RE = re.compile('^([^<>]+)')
53 AUTHOR_RE = re.compile('^([^<>]+?)? ?<([^<>]*)>$')
54 EMAIL_RE = re.compile('^([^<>]+[^ \\\t<>])?\\b(?:[ \\t<>]*?)\\b([^ \\t<>]+@[^ \\t<>]+)')
55 AUTHOR_HG_RE = re.compile('^(.*?) ?<(.*?)(?:>(.+)?)?$')
56 RAW_AUTHOR_RE = re.compile('^(\w+) (?:(.+)? )?<(.*)> (\d+) ([+-]\d+)')
58 VERSION = 2
60 def die(msg, *args):
61 sys.stderr.write('ERROR: %s\n' % (msg % args))
62 sys.exit(1)
64 def warn(msg, *args):
65 sys.stderr.write('WARNING: %s\n' % (msg % args))
67 def gitmode(flags):
68 return 'l' in flags and '120000' or 'x' in flags and '100755' or '100644'
70 def gittz(tz):
71 return '%+03d%02d' % (-tz / 3600, -tz % 3600 / 60)
73 def hgmode(mode):
74 m = { '100755': 'x', '120000': 'l' }
75 return m.get(mode, '')
77 def hghex(n):
78 return node.hex(n)
80 def hgbin(n):
81 return node.bin(n)
83 def hgref(ref):
84 return ref.replace('___', ' ')
86 def gitref(ref):
87 return ref.replace(' ', '___')
89 def check_version(*check):
90 if not hg_version:
91 return True
92 return hg_version >= check
94 def get_config(config):
95 cmd = ['git', 'config', '--get', config]
96 process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
97 output, _ = process.communicate()
98 return output
100 def get_config_bool(config, default=False):
101 value = get_config(config).rstrip('\n')
102 if value == "true":
103 return True
104 elif value == "false":
105 return False
106 else:
107 return default
109 class Marks:
111 def __init__(self, path, repo):
112 self.path = path
113 self.repo = repo
114 self.clear()
115 self.load()
117 if self.version < VERSION:
118 if self.version == 1:
119 self.upgrade_one()
121 # upgraded?
122 if self.version < VERSION:
123 self.clear()
124 self.version = VERSION
126 def clear(self):
127 self.tips = {}
128 self.marks = {}
129 self.rev_marks = {}
130 self.last_mark = 0
131 self.version = 0
133 def load(self):
134 if not os.path.exists(self.path):
135 return
137 tmp = json.load(open(self.path))
139 self.tips = tmp['tips']
140 self.marks = tmp['marks']
141 self.last_mark = tmp['last-mark']
142 self.version = tmp.get('version', 1)
144 for rev, mark in self.marks.iteritems():
145 self.rev_marks[mark] = rev
147 def upgrade_one(self):
148 def get_id(rev):
149 return hghex(self.repo.changelog.node(int(rev)))
150 self.tips = dict((name, get_id(rev)) for name, rev in self.tips.iteritems())
151 self.marks = dict((get_id(rev), mark) for rev, mark in self.marks.iteritems())
152 self.rev_marks = dict((mark, get_id(rev)) for mark, rev in self.rev_marks.iteritems())
153 self.version = 2
155 def dict(self):
156 return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark, 'version' : self.version }
158 def store(self):
159 json.dump(self.dict(), open(self.path, 'w'))
161 def __str__(self):
162 return str(self.dict())
164 def from_rev(self, rev):
165 return self.marks[rev]
167 def to_rev(self, mark):
168 return self.rev_marks[mark]
170 def next_mark(self):
171 self.last_mark += 1
172 return self.last_mark
174 def get_mark(self, rev):
175 self.last_mark += 1
176 self.marks[rev] = self.last_mark
177 return self.last_mark
179 def new_mark(self, rev, mark):
180 self.marks[rev] = mark
181 self.rev_marks[mark] = rev
182 self.last_mark = mark
184 def is_marked(self, rev):
185 return rev in self.marks
187 def get_tip(self, branch):
188 return self.tips.get(branch, None)
190 def set_tip(self, branch, tip):
191 self.tips[branch] = tip
193 class Parser:
195 def __init__(self, repo):
196 self.repo = repo
197 self.line = self.get_line()
199 def get_line(self):
200 return sys.stdin.readline().strip()
202 def __getitem__(self, i):
203 return self.line.split()[i]
205 def check(self, word):
206 return self.line.startswith(word)
208 def each_block(self, separator):
209 while self.line != separator:
210 yield self.line
211 self.line = self.get_line()
213 def __iter__(self):
214 return self.each_block('')
216 def next(self):
217 self.line = self.get_line()
218 if self.line == 'done':
219 self.line = None
221 def get_mark(self):
222 i = self.line.index(':') + 1
223 return int(self.line[i:])
225 def get_data(self):
226 if not self.check('data'):
227 return None
228 i = self.line.index(' ') + 1
229 size = int(self.line[i:])
230 return sys.stdin.read(size)
232 def get_author(self):
233 global bad_mail
235 ex = None
236 m = RAW_AUTHOR_RE.match(self.line)
237 if not m:
238 return None
239 _, name, email, date, tz = m.groups()
240 if name and 'ext:' in name:
241 m = re.match('^(.+?) ext:\((.+)\)$', name)
242 if m:
243 name = m.group(1)
244 ex = urllib.unquote(m.group(2))
246 if email != bad_mail:
247 if name:
248 user = '%s <%s>' % (name, email)
249 else:
250 user = '<%s>' % (email)
251 else:
252 user = name
254 if ex:
255 user += ex
257 tz = int(tz)
258 tz = ((tz / 100) * 3600) + ((tz % 100) * 60)
259 return (user, int(date), -tz)
261 def fix_file_path(path):
262 if not os.path.isabs(path):
263 return path
264 return os.path.relpath(path, '/')
266 def export_files(files):
267 global marks, filenodes
269 final = []
270 for f in files:
271 fid = node.hex(f.filenode())
273 if fid in filenodes:
274 mark = filenodes[fid]
275 else:
276 mark = marks.next_mark()
277 filenodes[fid] = mark
278 d = f.data()
280 print "blob"
281 print "mark :%u" % mark
282 print "data %d" % len(d)
283 print d
285 path = fix_file_path(f.path())
286 final.append((gitmode(f.flags()), mark, path))
288 return final
290 def get_filechanges(repo, ctx, parent):
291 modified = set()
292 added = set()
293 removed = set()
295 # load earliest manifest first for caching reasons
296 prev = parent.manifest().copy()
297 cur = ctx.manifest()
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())
308 return added | modified, removed
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 name = m.group(1)
321 mail = m.group(2)
322 else:
323 m = NAME_RE.match(user)
324 if m:
325 name = m.group(1).strip()
326 return (name, mail)
328 def fixup_user_hg(user):
329 def sanitize(name):
330 # stole this from hg-git
331 return re.sub('[<>\n]', '?', name.lstrip('< ').rstrip('> '))
333 m = AUTHOR_HG_RE.match(user)
334 if m:
335 name = sanitize(m.group(1))
336 mail = sanitize(m.group(2))
337 ex = m.group(3)
338 if ex:
339 name += ' ext:(' + urllib.quote(ex) + ')'
340 else:
341 name = sanitize(user)
342 if '@' in user:
343 mail = name
344 else:
345 mail = None
347 return (name, mail)
349 def fixup_user(user):
350 global mode, bad_mail
352 if mode == 'git':
353 name, mail = fixup_user_git(user)
354 else:
355 name, mail = fixup_user_hg(user)
357 if not name:
358 name = bad_name
359 if not mail:
360 mail = bad_mail
362 return '%s <%s>' % (name, mail)
364 def updatebookmarks(repo, peer):
365 remotemarks = peer.listkeys('bookmarks')
366 localmarks = repo._bookmarks
368 if not remotemarks:
369 return
371 for k, v in remotemarks.iteritems():
372 localmarks[k] = hgbin(v)
374 if hasattr(localmarks, 'write'):
375 localmarks.write()
376 else:
377 bookmarks.write(repo)
379 def get_repo(url, alias):
380 global dirname, peer
382 myui = ui.ui()
383 myui.setconfig('ui', 'interactive', 'off')
384 myui.fout = sys.stderr
386 if get_config_bool('remote-hg.insecure'):
387 myui.setconfig('web', 'cacerts', '')
389 extensions.loadall(myui)
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 if not os.path.exists(shared_path):
398 try:
399 hg.clone(myui, {}, url, shared_path, update=False, pull=True)
400 except:
401 die('Repository error')
403 if not os.path.exists(dirname):
404 os.makedirs(dirname)
406 local_path = os.path.join(dirname, 'clone')
407 if not os.path.exists(local_path):
408 hg.share(myui, shared_path, local_path, update=False)
410 repo = hg.repository(myui, local_path)
411 try:
412 peer = hg.peer(myui, {}, url)
413 except:
414 die('Repository error')
415 repo.pull(peer, heads=None, force=True)
417 updatebookmarks(repo, peer)
419 return repo
421 def rev_to_mark(rev):
422 global marks
423 return marks.from_rev(rev.hex())
425 def mark_to_rev(mark):
426 global marks
427 return marks.to_rev(mark)
429 def export_ref(repo, name, kind, head):
430 global prefix, marks, mode
432 ename = '%s/%s' % (kind, name)
433 tip = marks.get_tip(ename)
434 if tip and tip in repo:
435 tip = repo[tip].rev()
436 else:
437 tip = 0
439 revs = xrange(tip, head.rev() + 1)
440 total = len(revs)
442 for rev in revs:
444 c = repo[rev]
445 node = c.node()
447 if marks.is_marked(c.hex()):
448 continue
450 (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(node)
451 rev_branch = extra['branch']
453 author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
454 if 'committer' in extra:
455 user, time, tz = extra['committer'].rsplit(' ', 2)
456 committer = "%s %s %s" % (user, time, gittz(int(tz)))
457 else:
458 committer = author
460 parents = [repo[p] for p in repo.changelog.parentrevs(rev) if p >= 0]
462 if len(parents) == 0:
463 modified = c.manifest().keys()
464 removed = []
465 else:
466 modified, removed = get_filechanges(repo, c, parents[0])
468 desc += '\n'
470 if mode == 'hg':
471 extra_msg = ''
473 if rev_branch != 'default':
474 extra_msg += 'branch : %s\n' % rev_branch
476 renames = []
477 for f in c.files():
478 if f not in c.manifest():
479 continue
480 rename = c.filectx(f).renamed()
481 if rename:
482 renames.append((rename[0], f))
484 for e in renames:
485 extra_msg += "rename : %s => %s\n" % e
487 for key, value in extra.iteritems():
488 if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):
489 continue
490 else:
491 extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value))
493 if extra_msg:
494 desc += '\n--HG--\n' + extra_msg
496 if len(parents) == 0 and rev:
497 print 'reset %s/%s' % (prefix, ename)
499 modified_final = export_files(c.filectx(f) for f in modified)
501 print "commit %s/%s" % (prefix, ename)
502 print "mark :%d" % (marks.get_mark(c.hex()))
503 print "author %s" % (author)
504 print "committer %s" % (committer)
505 print "data %d" % (len(desc))
506 print desc
508 if len(parents) > 0:
509 print "from :%s" % (rev_to_mark(parents[0]))
510 if len(parents) > 1:
511 print "merge :%s" % (rev_to_mark(parents[1]))
513 for f in modified_final:
514 print "M %s :%u %s" % f
515 for f in removed:
516 print "D %s" % (fix_file_path(f))
517 print
519 progress = (rev - tip)
520 if (progress % 100 == 0):
521 print "progress revision %d '%s' (%d/%d)" % (rev, name, progress, total)
523 # make sure the ref is updated
524 print "reset %s/%s" % (prefix, ename)
525 print "from :%u" % rev_to_mark(head)
526 print
528 marks.set_tip(ename, head.hex())
530 def export_tag(repo, tag):
531 export_ref(repo, tag, 'tags', repo[hgref(tag)])
533 def export_bookmark(repo, bmark):
534 head = bmarks[hgref(bmark)]
535 export_ref(repo, bmark, 'bookmarks', head)
537 def export_branch(repo, branch):
538 tip = get_branch_tip(repo, branch)
539 head = repo[tip]
540 export_ref(repo, branch, 'branches', head)
542 def export_head(repo):
543 global g_head
544 export_ref(repo, g_head[0], 'bookmarks', g_head[1])
546 def do_capabilities(parser):
547 global prefix, dirname
549 print "import"
550 print "export"
551 print "refspec refs/heads/branches/*:%s/branches/*" % prefix
552 print "refspec refs/heads/*:%s/bookmarks/*" % prefix
553 print "refspec refs/tags/*:%s/tags/*" % prefix
555 path = os.path.join(dirname, 'marks-git')
557 if os.path.exists(path):
558 print "*import-marks %s" % path
559 print "*export-marks %s" % path
561 print
563 def branch_tip(branch):
564 return branches[branch][-1]
566 def get_branch_tip(repo, branch):
567 global branches
569 heads = branches.get(hgref(branch), None)
570 if not heads:
571 return None
573 # verify there's only one head
574 if (len(heads) > 1):
575 warn("Branch '%s' has more than one head, consider merging" % branch)
576 return branch_tip(hgref(branch))
578 return heads[0]
580 def list_head(repo, cur):
581 global g_head, bmarks, fake_bmark
583 if 'default' not in repo:
584 # empty repo
585 return
587 node = repo['default']
588 head = 'master' if not 'master' in bmarks else 'default'
589 fake_bmark = head
590 bmarks[head] = node
592 head = gitref(head)
593 print "@refs/heads/%s HEAD" % head
594 g_head = (head, node)
596 def do_list(parser):
597 global branches, bmarks, track_branches
599 repo = parser.repo
600 for bmark, node in bookmarks.listbookmarks(repo).iteritems():
601 bmarks[bmark] = repo[node]
603 cur = repo.dirstate.branch()
605 list_head(repo, cur)
607 if track_branches:
608 for branch in repo.branchmap():
609 heads = repo.branchheads(branch)
610 if len(heads):
611 branches[branch] = heads
613 for branch in branches:
614 print "? refs/heads/branches/%s" % gitref(branch)
616 for bmark in bmarks:
617 print "? refs/heads/%s" % gitref(bmark)
619 for tag, node in repo.tagslist():
620 if tag == 'tip':
621 continue
622 print "? refs/tags/%s" % gitref(tag)
624 print
626 def do_import(parser):
627 repo = parser.repo
629 path = os.path.join(dirname, 'marks-git')
631 print "feature done"
632 if os.path.exists(path):
633 print "feature import-marks=%s" % path
634 print "feature export-marks=%s" % path
635 print "feature force"
636 sys.stdout.flush()
638 tmp = encoding.encoding
639 encoding.encoding = 'utf-8'
641 # lets get all the import lines
642 while parser.check('import'):
643 ref = parser[1]
645 if (ref == 'HEAD'):
646 export_head(repo)
647 elif ref.startswith('refs/heads/branches/'):
648 branch = ref[len('refs/heads/branches/'):]
649 export_branch(repo, branch)
650 elif ref.startswith('refs/heads/'):
651 bmark = ref[len('refs/heads/'):]
652 export_bookmark(repo, bmark)
653 elif ref.startswith('refs/tags/'):
654 tag = ref[len('refs/tags/'):]
655 export_tag(repo, tag)
657 parser.next()
659 encoding.encoding = tmp
661 print 'done'
663 def parse_blob(parser):
664 global blob_marks
666 parser.next()
667 mark = parser.get_mark()
668 parser.next()
669 data = parser.get_data()
670 blob_marks[mark] = data
671 parser.next()
673 def get_merge_files(repo, p1, p2, files):
674 for e in repo[p1].files():
675 if e not in files:
676 if e not in repo[p1].manifest():
677 continue
678 f = { 'ctx' : repo[p1][e] }
679 files[e] = f
681 def parse_commit(parser):
682 global marks, blob_marks, parsed_refs
683 global mode
685 from_mark = merge_mark = None
687 ref = parser[1]
688 parser.next()
690 commit_mark = parser.get_mark()
691 parser.next()
692 author = parser.get_author()
693 parser.next()
694 committer = parser.get_author()
695 parser.next()
696 data = parser.get_data()
697 parser.next()
698 if parser.check('from'):
699 from_mark = parser.get_mark()
700 parser.next()
701 if parser.check('merge'):
702 merge_mark = parser.get_mark()
703 parser.next()
704 if parser.check('merge'):
705 die('octopus merges are not supported yet')
707 # fast-export adds an extra newline
708 if data[-1] == '\n':
709 data = data[:-1]
711 files = {}
713 for line in parser:
714 if parser.check('M'):
715 t, m, mark_ref, path = line.split(' ', 3)
716 mark = int(mark_ref[1:])
717 f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
718 elif parser.check('D'):
719 t, path = line.split(' ', 1)
720 f = { 'deleted' : True }
721 else:
722 die('Unknown file command: %s' % line)
723 files[path] = f
725 def getfilectx(repo, memctx, f):
726 of = files[f]
727 if 'deleted' in of:
728 raise IOError
729 if 'ctx' in of:
730 return of['ctx']
731 is_exec = of['mode'] == 'x'
732 is_link = of['mode'] == 'l'
733 rename = of.get('rename', None)
734 return context.memfilectx(f, of['data'],
735 is_link, is_exec, rename)
737 repo = parser.repo
739 user, date, tz = author
740 extra = {}
742 if committer != author:
743 extra['committer'] = "%s %u %u" % committer
745 if from_mark:
746 p1 = mark_to_rev(from_mark)
747 else:
748 p1 = '0' * 40
750 if merge_mark:
751 p2 = mark_to_rev(merge_mark)
752 else:
753 p2 = '0' * 40
756 # If files changed from any of the parents, hg wants to know, but in git if
757 # nothing changed from the first parent, nothing changed.
759 if merge_mark:
760 get_merge_files(repo, p1, p2, files)
762 # Check if the ref is supposed to be a named branch
763 if ref.startswith('refs/heads/branches/'):
764 branch = ref[len('refs/heads/branches/'):]
765 extra['branch'] = hgref(branch)
767 if mode == 'hg':
768 i = data.find('\n--HG--\n')
769 if i >= 0:
770 tmp = data[i + len('\n--HG--\n'):].strip()
771 for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
772 if k == 'rename':
773 old, new = v.split(' => ', 1)
774 files[new]['rename'] = old
775 elif k == 'branch':
776 extra[k] = v
777 elif k == 'extra':
778 ek, ev = v.split(' : ', 1)
779 extra[ek] = urllib.unquote(ev)
780 data = data[:i]
782 ctx = context.memctx(repo, (p1, p2), data,
783 files.keys(), getfilectx,
784 user, (date, tz), extra)
786 tmp = encoding.encoding
787 encoding.encoding = 'utf-8'
789 node = hghex(repo.commitctx(ctx))
791 encoding.encoding = tmp
793 parsed_refs[ref] = node
794 marks.new_mark(node, commit_mark)
796 def parse_reset(parser):
797 global parsed_refs
799 ref = parser[1]
800 parser.next()
801 # ugh
802 if parser.check('commit'):
803 parse_commit(parser)
804 return
805 if not parser.check('from'):
806 return
807 from_mark = parser.get_mark()
808 parser.next()
810 rev = mark_to_rev(from_mark)
811 parsed_refs[ref] = rev
813 def parse_tag(parser):
814 name = parser[1]
815 parser.next()
816 from_mark = parser.get_mark()
817 parser.next()
818 tagger = parser.get_author()
819 parser.next()
820 data = parser.get_data()
821 parser.next()
823 parsed_tags[name] = (tagger, data)
825 def write_tag(repo, tag, node, msg, author):
826 branch = repo[node].branch()
827 tip = branch_tip(branch)
828 tip = repo[tip]
830 def getfilectx(repo, memctx, f):
831 try:
832 fctx = tip.filectx(f)
833 data = fctx.data()
834 except error.ManifestLookupError:
835 data = ""
836 content = data + "%s %s\n" % (node, tag)
837 return context.memfilectx(f, content, False, False, None)
839 p1 = tip.hex()
840 p2 = '0' * 40
841 if not author:
842 author = (None, 0, 0)
843 user, date, tz = author
845 ctx = context.memctx(repo, (p1, p2), msg,
846 ['.hgtags'], getfilectx,
847 user, (date, tz), {'branch' : branch})
849 tmp = encoding.encoding
850 encoding.encoding = 'utf-8'
852 tagnode = repo.commitctx(ctx)
854 encoding.encoding = tmp
856 return (tagnode, branch)
858 def checkheads_bmark(repo, ref, ctx):
859 if force_push:
860 return True
862 bmark = ref[len('refs/heads/'):]
863 if not bmark in bmarks:
864 # new bmark
865 return True
867 ctx_old = bmarks[bmark]
868 ctx_new = ctx
869 if not repo.changelog.descendant(ctx_old.rev(), ctx_new.rev()):
870 print "error %s non-fast forward" % ref
871 return False
873 return True
875 def checkheads(repo, remote, p_revs):
877 remotemap = remote.branchmap()
878 if not remotemap:
879 # empty repo
880 return True
882 new = {}
883 ret = True
885 for node, ref in p_revs.iteritems():
886 ctx = repo[node]
887 branch = ctx.branch()
888 if not branch in remotemap:
889 # new branch
890 continue
891 if not ref.startswith('refs/heads/branches'):
892 if ref.startswith('refs/heads/'):
893 if not checkheads_bmark(repo, ref, ctx):
894 ret = False
896 # only check branches
897 continue
898 new.setdefault(branch, []).append(ctx.rev())
900 for branch, heads in new.iteritems():
901 old = [repo.changelog.rev(x) for x in remotemap[branch]]
902 for rev in heads:
903 if check_version(2, 3):
904 ancestors = repo.changelog.ancestors([rev], stoprev=min(old))
905 else:
906 ancestors = repo.changelog.ancestors(rev)
907 found = False
909 for x in old:
910 if x in ancestors:
911 found = True
912 break
914 if found:
915 continue
917 node = repo.changelog.node(rev)
918 print "error %s non-fast forward" % p_revs[node]
919 ret = False
921 return ret
923 def push_unsafe(repo, remote, parsed_refs, p_revs):
925 force = force_push
927 fci = discovery.findcommonincoming
928 commoninc = fci(repo, remote, force=force)
929 common, _, remoteheads = commoninc
931 if not force and not checkheads(repo, remote, p_revs):
932 return None
934 cg = repo.getbundle('push', heads=list(p_revs), common=common)
936 unbundle = remote.capable('unbundle')
937 if unbundle:
938 if force:
939 remoteheads = ['force']
940 return remote.unbundle(cg, remoteheads, 'push')
941 else:
942 return remote.addchangegroup(cg, 'push', repo.url())
944 def push(repo, remote, parsed_refs, p_revs):
945 if hasattr(remote, 'canpush') and not remote.canpush():
946 print "error cannot push"
948 if not p_revs:
949 # nothing to push
950 return
952 lock = None
953 unbundle = remote.capable('unbundle')
954 if not unbundle:
955 lock = remote.lock()
956 try:
957 ret = push_unsafe(repo, remote, parsed_refs, p_revs)
958 finally:
959 if lock is not None:
960 lock.release()
962 return ret
964 def do_export(parser):
965 global parsed_refs, bmarks, peer
967 p_bmarks = []
968 p_revs = {}
970 parser.next()
972 for line in parser.each_block('done'):
973 if parser.check('blob'):
974 parse_blob(parser)
975 elif parser.check('commit'):
976 parse_commit(parser)
977 elif parser.check('reset'):
978 parse_reset(parser)
979 elif parser.check('tag'):
980 parse_tag(parser)
981 elif parser.check('feature'):
982 pass
983 else:
984 die('unhandled export command: %s' % line)
986 for ref, node in parsed_refs.iteritems():
987 bnode = hgbin(node)
988 if ref.startswith('refs/heads/branches'):
989 branch = ref[len('refs/heads/branches/'):]
990 if branch in branches and bnode in branches[branch]:
991 # up to date
992 continue
993 p_revs[bnode] = ref
994 print "ok %s" % ref
995 elif ref.startswith('refs/heads/'):
996 bmark = ref[len('refs/heads/'):]
997 new = node
998 old = bmarks[bmark].hex() if bmark in bmarks else ''
1000 if old == new:
1001 continue
1003 print "ok %s" % ref
1004 if bmark != fake_bmark and \
1005 not (bmark == 'master' and bmark not in parser.repo._bookmarks):
1006 p_bmarks.append((ref, bmark, old, new))
1008 p_revs[bnode] = ref
1009 elif ref.startswith('refs/tags/'):
1010 tag = ref[len('refs/tags/'):]
1011 tag = hgref(tag)
1012 author, msg = parsed_tags.get(tag, (None, None))
1013 if mode == 'git':
1014 if not msg:
1015 msg = 'Added tag %s for changeset %s' % (tag, node[:12]);
1016 tagnode, branch = write_tag(parser.repo, tag, node, msg, author)
1017 p_revs[tagnode] = 'refs/heads/branches/' + gitref(branch)
1018 else:
1019 fp = parser.repo.opener('localtags', 'a')
1020 fp.write('%s %s\n' % (node, tag))
1021 fp.close()
1022 p_revs[bnode] = ref
1023 print "ok %s" % ref
1024 else:
1025 # transport-helper/fast-export bugs
1026 continue
1028 if peer:
1029 if not push(parser.repo, peer, parsed_refs, p_revs):
1030 # do not update bookmarks
1031 print
1032 return
1034 # update remote bookmarks
1035 remote_bmarks = peer.listkeys('bookmarks')
1036 for ref, bmark, old, new in p_bmarks:
1037 if force_push:
1038 old = remote_bmarks.get(bmark, '')
1039 if not peer.pushkey('bookmarks', bmark, old, new):
1040 print "error %s" % ref
1041 else:
1042 # update local bookmarks
1043 for ref, bmark, old, new in p_bmarks:
1044 if not bookmarks.pushbookmark(parser.repo, bmark, old, new):
1045 print "error %s" % ref
1047 print
1049 def fix_path(alias, repo, orig_url):
1050 url = urlparse.urlparse(orig_url, 'file')
1051 if url.scheme != 'file' or os.path.isabs(url.path):
1052 return
1053 abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
1054 cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
1055 subprocess.call(cmd)
1057 def main(args):
1058 global prefix, gitdir, dirname, branches, bmarks
1059 global marks, blob_marks, parsed_refs
1060 global peer, mode, bad_mail, bad_name
1061 global track_branches, force_push, is_tmp
1062 global parsed_tags
1063 global filenodes
1064 global fake_bmark, hg_version
1066 alias = args[1]
1067 url = args[2]
1068 peer = None
1070 hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
1071 track_branches = get_config_bool('remote-hg.track-branches', True)
1072 force_push = get_config_bool('remote-hg.force-push')
1074 if hg_git_compat:
1075 mode = 'hg'
1076 bad_mail = 'none@none'
1077 bad_name = ''
1078 else:
1079 mode = 'git'
1080 bad_mail = 'unknown'
1081 bad_name = 'Unknown'
1083 if alias[4:] == url:
1084 is_tmp = True
1085 alias = hashlib.sha1(alias).hexdigest()
1086 else:
1087 is_tmp = False
1089 gitdir = os.environ['GIT_DIR']
1090 dirname = os.path.join(gitdir, 'hg', alias)
1091 branches = {}
1092 bmarks = {}
1093 blob_marks = {}
1094 parsed_refs = {}
1095 marks = None
1096 parsed_tags = {}
1097 filenodes = {}
1098 fake_bmark = None
1099 try:
1100 hg_version = tuple(int(e) for e in util.version().split('.'))
1101 except:
1102 hg_version = None
1104 repo = get_repo(url, alias)
1105 prefix = 'refs/hg/%s' % alias
1107 if not is_tmp:
1108 fix_path(alias, peer or repo, url)
1110 marks_path = os.path.join(dirname, 'marks-hg')
1111 marks = Marks(marks_path, repo)
1113 if sys.platform == 'win32':
1114 import msvcrt
1115 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
1117 parser = Parser(repo)
1118 for line in parser:
1119 if parser.check('capabilities'):
1120 do_capabilities(parser)
1121 elif parser.check('list'):
1122 do_list(parser)
1123 elif parser.check('import'):
1124 do_import(parser)
1125 elif parser.check('export'):
1126 do_export(parser)
1127 else:
1128 die('unhandled command: %s' % line)
1129 sys.stdout.flush()
1131 def bye():
1132 if not marks:
1133 return
1134 if not is_tmp:
1135 marks.store()
1136 else:
1137 shutil.rmtree(dirname)
1139 atexit.register(bye)
1140 sys.exit(main(sys.argv))