remote-hg: implement custom checkheads()
[git.git] / contrib / remote-helpers / git-remote-hg
blob7eb809b2ce1e1b090745fd8c3776ae938b53fa52
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(repo, branch):
564 # older versions of mercurial don't have this
565 if hasattr(repo, 'branchtip'):
566 return repo.branchtip(branch)
567 else:
568 return repo.branchtags()[branch]
570 def get_branch_tip(repo, branch):
571 global branches
573 heads = branches.get(hgref(branch), None)
574 if not heads:
575 return None
577 # verify there's only one head
578 if (len(heads) > 1):
579 warn("Branch '%s' has more than one head, consider merging" % branch)
580 return branch_tip(repo, hgref(branch))
582 return heads[0]
584 def list_head(repo, cur):
585 global g_head, bmarks, fake_bmark
587 if 'default' not in repo:
588 # empty repo
589 return
591 node = repo['default']
592 head = 'master' if not 'master' in bmarks else 'default'
593 fake_bmark = head
594 bmarks[head] = node
596 head = gitref(head)
597 print "@refs/heads/%s HEAD" % head
598 g_head = (head, node)
600 def do_list(parser):
601 global branches, bmarks, track_branches
603 repo = parser.repo
604 for bmark, node in bookmarks.listbookmarks(repo).iteritems():
605 bmarks[bmark] = repo[node]
607 cur = repo.dirstate.branch()
609 list_head(repo, cur)
611 if track_branches:
612 for branch in repo.branchmap():
613 heads = repo.branchheads(branch)
614 if len(heads):
615 branches[branch] = heads
617 for branch in branches:
618 print "? refs/heads/branches/%s" % gitref(branch)
620 for bmark in bmarks:
621 print "? refs/heads/%s" % gitref(bmark)
623 for tag, node in repo.tagslist():
624 if tag == 'tip':
625 continue
626 print "? refs/tags/%s" % gitref(tag)
628 print
630 def do_import(parser):
631 repo = parser.repo
633 path = os.path.join(dirname, 'marks-git')
635 print "feature done"
636 if os.path.exists(path):
637 print "feature import-marks=%s" % path
638 print "feature export-marks=%s" % path
639 print "feature force"
640 sys.stdout.flush()
642 tmp = encoding.encoding
643 encoding.encoding = 'utf-8'
645 # lets get all the import lines
646 while parser.check('import'):
647 ref = parser[1]
649 if (ref == 'HEAD'):
650 export_head(repo)
651 elif ref.startswith('refs/heads/branches/'):
652 branch = ref[len('refs/heads/branches/'):]
653 export_branch(repo, branch)
654 elif ref.startswith('refs/heads/'):
655 bmark = ref[len('refs/heads/'):]
656 export_bookmark(repo, bmark)
657 elif ref.startswith('refs/tags/'):
658 tag = ref[len('refs/tags/'):]
659 export_tag(repo, tag)
661 parser.next()
663 encoding.encoding = tmp
665 print 'done'
667 def parse_blob(parser):
668 global blob_marks
670 parser.next()
671 mark = parser.get_mark()
672 parser.next()
673 data = parser.get_data()
674 blob_marks[mark] = data
675 parser.next()
677 def get_merge_files(repo, p1, p2, files):
678 for e in repo[p1].files():
679 if e not in files:
680 if e not in repo[p1].manifest():
681 continue
682 f = { 'ctx' : repo[p1][e] }
683 files[e] = f
685 def parse_commit(parser):
686 global marks, blob_marks, parsed_refs
687 global mode
689 from_mark = merge_mark = None
691 ref = parser[1]
692 parser.next()
694 commit_mark = parser.get_mark()
695 parser.next()
696 author = parser.get_author()
697 parser.next()
698 committer = parser.get_author()
699 parser.next()
700 data = parser.get_data()
701 parser.next()
702 if parser.check('from'):
703 from_mark = parser.get_mark()
704 parser.next()
705 if parser.check('merge'):
706 merge_mark = parser.get_mark()
707 parser.next()
708 if parser.check('merge'):
709 die('octopus merges are not supported yet')
711 # fast-export adds an extra newline
712 if data[-1] == '\n':
713 data = data[:-1]
715 files = {}
717 for line in parser:
718 if parser.check('M'):
719 t, m, mark_ref, path = line.split(' ', 3)
720 mark = int(mark_ref[1:])
721 f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
722 elif parser.check('D'):
723 t, path = line.split(' ', 1)
724 f = { 'deleted' : True }
725 else:
726 die('Unknown file command: %s' % line)
727 files[path] = f
729 def getfilectx(repo, memctx, f):
730 of = files[f]
731 if 'deleted' in of:
732 raise IOError
733 if 'ctx' in of:
734 return of['ctx']
735 is_exec = of['mode'] == 'x'
736 is_link = of['mode'] == 'l'
737 rename = of.get('rename', None)
738 return context.memfilectx(f, of['data'],
739 is_link, is_exec, rename)
741 repo = parser.repo
743 user, date, tz = author
744 extra = {}
746 if committer != author:
747 extra['committer'] = "%s %u %u" % committer
749 if from_mark:
750 p1 = mark_to_rev(from_mark)
751 else:
752 p1 = '0' * 40
754 if merge_mark:
755 p2 = mark_to_rev(merge_mark)
756 else:
757 p2 = '0' * 40
760 # If files changed from any of the parents, hg wants to know, but in git if
761 # nothing changed from the first parent, nothing changed.
763 if merge_mark:
764 get_merge_files(repo, p1, p2, files)
766 # Check if the ref is supposed to be a named branch
767 if ref.startswith('refs/heads/branches/'):
768 branch = ref[len('refs/heads/branches/'):]
769 extra['branch'] = hgref(branch)
771 if mode == 'hg':
772 i = data.find('\n--HG--\n')
773 if i >= 0:
774 tmp = data[i + len('\n--HG--\n'):].strip()
775 for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
776 if k == 'rename':
777 old, new = v.split(' => ', 1)
778 files[new]['rename'] = old
779 elif k == 'branch':
780 extra[k] = v
781 elif k == 'extra':
782 ek, ev = v.split(' : ', 1)
783 extra[ek] = urllib.unquote(ev)
784 data = data[:i]
786 ctx = context.memctx(repo, (p1, p2), data,
787 files.keys(), getfilectx,
788 user, (date, tz), extra)
790 tmp = encoding.encoding
791 encoding.encoding = 'utf-8'
793 node = hghex(repo.commitctx(ctx))
795 encoding.encoding = tmp
797 parsed_refs[ref] = node
798 marks.new_mark(node, commit_mark)
800 def parse_reset(parser):
801 global parsed_refs
803 ref = parser[1]
804 parser.next()
805 # ugh
806 if parser.check('commit'):
807 parse_commit(parser)
808 return
809 if not parser.check('from'):
810 return
811 from_mark = parser.get_mark()
812 parser.next()
814 rev = mark_to_rev(from_mark)
815 parsed_refs[ref] = rev
817 def parse_tag(parser):
818 name = parser[1]
819 parser.next()
820 from_mark = parser.get_mark()
821 parser.next()
822 tagger = parser.get_author()
823 parser.next()
824 data = parser.get_data()
825 parser.next()
827 parsed_tags[name] = (tagger, data)
829 def write_tag(repo, tag, node, msg, author):
830 branch = repo[node].branch()
831 tip = branch_tip(repo, branch)
832 tip = repo[tip]
834 def getfilectx(repo, memctx, f):
835 try:
836 fctx = tip.filectx(f)
837 data = fctx.data()
838 except error.ManifestLookupError:
839 data = ""
840 content = data + "%s %s\n" % (node, tag)
841 return context.memfilectx(f, content, False, False, None)
843 p1 = tip.hex()
844 p2 = '0' * 40
845 if not author:
846 author = (None, 0, 0)
847 user, date, tz = author
849 ctx = context.memctx(repo, (p1, p2), msg,
850 ['.hgtags'], getfilectx,
851 user, (date, tz), {'branch' : branch})
853 tmp = encoding.encoding
854 encoding.encoding = 'utf-8'
856 tagnode = repo.commitctx(ctx)
858 encoding.encoding = tmp
860 return tagnode
862 def checkheads(repo, remote, p_revs):
864 remotemap = remote.branchmap()
865 if not remotemap:
866 # empty repo
867 return
869 new = {}
871 for node in p_revs:
872 ctx = repo[node]
873 branch = ctx.branch()
874 if not branch in remotemap:
875 # new branch
876 continue
877 new.setdefault(branch, []).append(ctx.rev())
879 for branch, heads in new.iteritems():
880 old = [repo.changelog.rev(x) for x in remotemap[branch]]
881 for rev in heads:
882 if check_version(2, 3):
883 ancestors = repo.changelog.ancestors([rev], stoprev=min(old))
884 else:
885 ancestors = repo.changelog.ancestors(rev)
886 found = False
888 for x in old:
889 if x in ancestors:
890 found = True
891 break
893 if found:
894 continue
896 raise Exception("non-fast-forward")
898 def push_unsafe(repo, remote, parsed_refs, p_revs):
900 force = force_push
902 fci = discovery.findcommonincoming
903 commoninc = fci(repo, remote, force=force)
904 common, _, remoteheads = commoninc
906 if not force:
907 checkheads(repo, remote, p_revs)
909 cg = repo.getbundle('push', heads=list(p_revs), common=common)
911 unbundle = remote.capable('unbundle')
912 if unbundle:
913 if force:
914 remoteheads = ['force']
915 return remote.unbundle(cg, remoteheads, 'push')
916 else:
917 return remote.addchangegroup(cg, 'push', repo.url())
919 def push(repo, remote, parsed_refs, p_revs):
920 if hasattr(remote, 'canpush') and not remote.canpush():
921 print "error cannot push"
923 if not p_revs:
924 # nothing to push
925 return
927 lock = None
928 unbundle = remote.capable('unbundle')
929 if not unbundle:
930 lock = remote.lock()
931 try:
932 ret = push_unsafe(repo, remote, parsed_refs, p_revs)
933 finally:
934 if lock is not None:
935 lock.release()
937 return ret
939 def do_export(parser):
940 global parsed_refs, bmarks, peer
942 p_bmarks = []
943 p_revs = set()
945 parser.next()
947 for line in parser.each_block('done'):
948 if parser.check('blob'):
949 parse_blob(parser)
950 elif parser.check('commit'):
951 parse_commit(parser)
952 elif parser.check('reset'):
953 parse_reset(parser)
954 elif parser.check('tag'):
955 parse_tag(parser)
956 elif parser.check('feature'):
957 pass
958 else:
959 die('unhandled export command: %s' % line)
961 for ref, node in parsed_refs.iteritems():
962 bnode = hgbin(node)
963 if ref.startswith('refs/heads/branches'):
964 branch = ref[len('refs/heads/branches/'):]
965 if branch in branches and bnode in branches[branch]:
966 # up to date
967 continue
968 p_revs.add(bnode)
969 print "ok %s" % ref
970 elif ref.startswith('refs/heads/'):
971 bmark = ref[len('refs/heads/'):]
972 new = node
973 old = bmarks[bmark].hex() if bmark in bmarks else ''
975 if old == new:
976 continue
978 print "ok %s" % ref
979 if bmark != fake_bmark and \
980 not (bmark == 'master' and bmark not in parser.repo._bookmarks):
981 p_bmarks.append((ref, bmark, old, new))
983 p_revs.add(bnode)
984 elif ref.startswith('refs/tags/'):
985 tag = ref[len('refs/tags/'):]
986 tag = hgref(tag)
987 author, msg = parsed_tags.get(tag, (None, None))
988 if mode == 'git':
989 if not msg:
990 msg = 'Added tag %s for changeset %s' % (tag, node[:12]);
991 tagnode = write_tag(parser.repo, tag, node, msg, author)
992 p_revs.add(tagnode)
993 else:
994 fp = parser.repo.opener('localtags', 'a')
995 fp.write('%s %s\n' % (node, tag))
996 fp.close()
997 p_revs.add(bnode)
998 print "ok %s" % ref
999 else:
1000 # transport-helper/fast-export bugs
1001 continue
1003 if peer:
1004 push(parser.repo, peer, parsed_refs, p_revs)
1006 # update remote bookmarks
1007 remote_bmarks = peer.listkeys('bookmarks')
1008 for ref, bmark, old, new in p_bmarks:
1009 if force_push:
1010 old = remote_bmarks.get(bmark, '')
1011 if not peer.pushkey('bookmarks', bmark, old, new):
1012 print "error %s" % ref
1013 else:
1014 # update local bookmarks
1015 for ref, bmark, old, new in p_bmarks:
1016 if not bookmarks.pushbookmark(parser.repo, bmark, old, new):
1017 print "error %s" % ref
1019 print
1021 def fix_path(alias, repo, orig_url):
1022 url = urlparse.urlparse(orig_url, 'file')
1023 if url.scheme != 'file' or os.path.isabs(url.path):
1024 return
1025 abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
1026 cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
1027 subprocess.call(cmd)
1029 def main(args):
1030 global prefix, gitdir, dirname, branches, bmarks
1031 global marks, blob_marks, parsed_refs
1032 global peer, mode, bad_mail, bad_name
1033 global track_branches, force_push, is_tmp
1034 global parsed_tags
1035 global filenodes
1036 global fake_bmark, hg_version
1038 alias = args[1]
1039 url = args[2]
1040 peer = None
1042 hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
1043 track_branches = get_config_bool('remote-hg.track-branches', True)
1044 force_push = get_config_bool('remote-hg.force-push')
1046 if hg_git_compat:
1047 mode = 'hg'
1048 bad_mail = 'none@none'
1049 bad_name = ''
1050 else:
1051 mode = 'git'
1052 bad_mail = 'unknown'
1053 bad_name = 'Unknown'
1055 if alias[4:] == url:
1056 is_tmp = True
1057 alias = hashlib.sha1(alias).hexdigest()
1058 else:
1059 is_tmp = False
1061 gitdir = os.environ['GIT_DIR']
1062 dirname = os.path.join(gitdir, 'hg', alias)
1063 branches = {}
1064 bmarks = {}
1065 blob_marks = {}
1066 parsed_refs = {}
1067 marks = None
1068 parsed_tags = {}
1069 filenodes = {}
1070 fake_bmark = None
1071 try:
1072 hg_version = tuple(int(e) for e in util.version().split('.'))
1073 except:
1074 hg_version = None
1076 repo = get_repo(url, alias)
1077 prefix = 'refs/hg/%s' % alias
1079 if not is_tmp:
1080 fix_path(alias, peer or repo, url)
1082 marks_path = os.path.join(dirname, 'marks-hg')
1083 marks = Marks(marks_path, repo)
1085 if sys.platform == 'win32':
1086 import msvcrt
1087 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
1089 parser = Parser(repo)
1090 for line in parser:
1091 if parser.check('capabilities'):
1092 do_capabilities(parser)
1093 elif parser.check('list'):
1094 do_list(parser)
1095 elif parser.check('import'):
1096 do_import(parser)
1097 elif parser.check('export'):
1098 do_export(parser)
1099 else:
1100 die('unhandled command: %s' % line)
1101 sys.stdout.flush()
1103 def bye():
1104 if not marks:
1105 return
1106 if not is_tmp:
1107 marks.store()
1108 else:
1109 shutil.rmtree(dirname)
1111 atexit.register(bye)
1112 sys.exit(main(sys.argv))