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