.mailmap: update long-lost friends with multiple defunct addresses
[git/raj.git] / contrib / remote-helpers / git-remote-hg
blob0194c67fb1db1dc6fdd4bd90b67d04cfb7a34e1d
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')
394 if not os.path.exists(shared_path):
395 try:
396 hg.clone(myui, {}, url, shared_path, update=False, pull=True)
397 except:
398 die('Repository error')
400 if not os.path.exists(dirname):
401 os.makedirs(dirname)
403 local_path = os.path.join(dirname, 'clone')
404 if not os.path.exists(local_path):
405 hg.share(myui, shared_path, local_path, update=False)
407 repo = hg.repository(myui, local_path)
408 try:
409 peer = hg.peer(myui, {}, url)
410 except:
411 die('Repository error')
412 repo.pull(peer, heads=None, force=True)
414 updatebookmarks(repo, peer)
416 return repo
418 def rev_to_mark(rev):
419 global marks
420 return marks.from_rev(rev.hex())
422 def mark_to_rev(mark):
423 global marks
424 return marks.to_rev(mark)
426 def export_ref(repo, name, kind, head):
427 global prefix, marks, mode
429 ename = '%s/%s' % (kind, name)
430 try:
431 tip = marks.get_tip(ename)
432 tip = repo[tip].rev()
433 except:
434 tip = 0
436 revs = xrange(tip, head.rev() + 1)
437 total = len(revs)
439 for rev in revs:
441 c = repo[rev]
442 node = c.node()
444 if marks.is_marked(c.hex()):
445 continue
447 (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(node)
448 rev_branch = extra['branch']
450 author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
451 if 'committer' in extra:
452 user, time, tz = extra['committer'].rsplit(' ', 2)
453 committer = "%s %s %s" % (user, time, gittz(int(tz)))
454 else:
455 committer = author
457 parents = [repo[p] for p in repo.changelog.parentrevs(rev) if p >= 0]
459 if len(parents) == 0:
460 modified = c.manifest().keys()
461 removed = []
462 else:
463 modified, removed = get_filechanges(repo, c, parents[0])
465 desc += '\n'
467 if mode == 'hg':
468 extra_msg = ''
470 if rev_branch != 'default':
471 extra_msg += 'branch : %s\n' % rev_branch
473 renames = []
474 for f in c.files():
475 if f not in c.manifest():
476 continue
477 rename = c.filectx(f).renamed()
478 if rename:
479 renames.append((rename[0], f))
481 for e in renames:
482 extra_msg += "rename : %s => %s\n" % e
484 for key, value in extra.iteritems():
485 if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):
486 continue
487 else:
488 extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value))
490 if extra_msg:
491 desc += '\n--HG--\n' + extra_msg
493 if len(parents) == 0 and rev:
494 print 'reset %s/%s' % (prefix, ename)
496 modified_final = export_files(c.filectx(f) for f in modified)
498 print "commit %s/%s" % (prefix, ename)
499 print "mark :%d" % (marks.get_mark(c.hex()))
500 print "author %s" % (author)
501 print "committer %s" % (committer)
502 print "data %d" % (len(desc))
503 print desc
505 if len(parents) > 0:
506 print "from :%s" % (rev_to_mark(parents[0]))
507 if len(parents) > 1:
508 print "merge :%s" % (rev_to_mark(parents[1]))
510 for f in removed:
511 print "D %s" % (fix_file_path(f))
512 for f in modified_final:
513 print "M %s :%u %s" % f
514 print
516 progress = (rev - tip)
517 if (progress % 100 == 0):
518 print "progress revision %d '%s' (%d/%d)" % (rev, name, progress, total)
520 # make sure the ref is updated
521 print "reset %s/%s" % (prefix, ename)
522 print "from :%u" % rev_to_mark(head)
523 print
525 marks.set_tip(ename, head.hex())
527 def export_tag(repo, tag):
528 export_ref(repo, tag, 'tags', repo[hgref(tag)])
530 def export_bookmark(repo, bmark):
531 head = bmarks[hgref(bmark)]
532 export_ref(repo, bmark, 'bookmarks', head)
534 def export_branch(repo, branch):
535 tip = get_branch_tip(repo, branch)
536 head = repo[tip]
537 export_ref(repo, branch, 'branches', head)
539 def export_head(repo):
540 global g_head
541 export_ref(repo, g_head[0], 'bookmarks', g_head[1])
543 def do_capabilities(parser):
544 global prefix, dirname
546 print "import"
547 print "export"
548 print "refspec refs/heads/branches/*:%s/branches/*" % prefix
549 print "refspec refs/heads/*:%s/bookmarks/*" % prefix
550 print "refspec refs/tags/*:%s/tags/*" % prefix
552 path = os.path.join(dirname, 'marks-git')
554 if os.path.exists(path):
555 print "*import-marks %s" % path
556 print "*export-marks %s" % path
557 print "option"
559 print
561 def branch_tip(branch):
562 return branches[branch][-1]
564 def get_branch_tip(repo, branch):
565 global branches
567 heads = branches.get(hgref(branch), None)
568 if not heads:
569 return None
571 # verify there's only one head
572 if (len(heads) > 1):
573 warn("Branch '%s' has more than one head, consider merging" % branch)
574 return branch_tip(hgref(branch))
576 return heads[0]
578 def list_head(repo, cur):
579 global g_head, bmarks, fake_bmark
581 if 'default' not in branches:
582 # empty repo
583 return
585 node = repo[branch_tip('default')]
586 head = 'master' if not 'master' in bmarks else 'default'
587 fake_bmark = head
588 bmarks[head] = node
590 head = gitref(head)
591 print "@refs/heads/%s HEAD" % head
592 g_head = (head, node)
594 def do_list(parser):
595 global branches, bmarks, track_branches
597 repo = parser.repo
598 for bmark, node in bookmarks.listbookmarks(repo).iteritems():
599 bmarks[bmark] = repo[node]
601 cur = repo.dirstate.branch()
602 orig = peer if peer else repo
604 for branch, heads in orig.branchmap().iteritems():
605 # only open heads
606 heads = [h for h in heads if 'close' not in repo.changelog.read(h)[5]]
607 if heads:
608 branches[branch] = heads
610 list_head(repo, cur)
612 if track_branches:
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 # only export the commits if we are on an internal proxy repo
726 if dry_run and not peer:
727 parsed_refs[ref] = None
728 return
730 def getfilectx(repo, memctx, f):
731 of = files[f]
732 if 'deleted' in of:
733 raise IOError
734 if 'ctx' in of:
735 return of['ctx']
736 is_exec = of['mode'] == 'x'
737 is_link = of['mode'] == 'l'
738 rename = of.get('rename', None)
739 return context.memfilectx(f, of['data'],
740 is_link, is_exec, rename)
742 repo = parser.repo
744 user, date, tz = author
745 extra = {}
747 if committer != author:
748 extra['committer'] = "%s %u %u" % committer
750 if from_mark:
751 p1 = mark_to_rev(from_mark)
752 else:
753 p1 = '0' * 40
755 if merge_mark:
756 p2 = mark_to_rev(merge_mark)
757 else:
758 p2 = '0' * 40
761 # If files changed from any of the parents, hg wants to know, but in git if
762 # nothing changed from the first parent, nothing changed.
764 if merge_mark:
765 get_merge_files(repo, p1, p2, files)
767 # Check if the ref is supposed to be a named branch
768 if ref.startswith('refs/heads/branches/'):
769 branch = ref[len('refs/heads/branches/'):]
770 extra['branch'] = hgref(branch)
772 if mode == 'hg':
773 i = data.find('\n--HG--\n')
774 if i >= 0:
775 tmp = data[i + len('\n--HG--\n'):].strip()
776 for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
777 if k == 'rename':
778 old, new = v.split(' => ', 1)
779 files[new]['rename'] = old
780 elif k == 'branch':
781 extra[k] = v
782 elif k == 'extra':
783 ek, ev = v.split(' : ', 1)
784 extra[ek] = urllib.unquote(ev)
785 data = data[:i]
787 ctx = context.memctx(repo, (p1, p2), data,
788 files.keys(), getfilectx,
789 user, (date, tz), extra)
791 tmp = encoding.encoding
792 encoding.encoding = 'utf-8'
794 node = hghex(repo.commitctx(ctx))
796 encoding.encoding = tmp
798 parsed_refs[ref] = node
799 marks.new_mark(node, commit_mark)
801 def parse_reset(parser):
802 global parsed_refs
804 ref = parser[1]
805 parser.next()
806 # ugh
807 if parser.check('commit'):
808 parse_commit(parser)
809 return
810 if not parser.check('from'):
811 return
812 from_mark = parser.get_mark()
813 parser.next()
815 try:
816 rev = mark_to_rev(from_mark)
817 except KeyError:
818 rev = None
819 parsed_refs[ref] = rev
821 def parse_tag(parser):
822 name = parser[1]
823 parser.next()
824 from_mark = parser.get_mark()
825 parser.next()
826 tagger = parser.get_author()
827 parser.next()
828 data = parser.get_data()
829 parser.next()
831 parsed_tags[name] = (tagger, data)
833 def write_tag(repo, tag, node, msg, author):
834 branch = repo[node].branch()
835 tip = branch_tip(branch)
836 tip = repo[tip]
838 def getfilectx(repo, memctx, f):
839 try:
840 fctx = tip.filectx(f)
841 data = fctx.data()
842 except error.ManifestLookupError:
843 data = ""
844 content = data + "%s %s\n" % (node, tag)
845 return context.memfilectx(f, content, False, False, None)
847 p1 = tip.hex()
848 p2 = '0' * 40
849 if author:
850 user, date, tz = author
851 date_tz = (date, tz)
852 else:
853 cmd = ['git', 'var', 'GIT_COMMITTER_IDENT']
854 process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
855 output, _ = process.communicate()
856 m = re.match('^.* <.*>', output)
857 if m:
858 user = m.group(0)
859 else:
860 user = repo.ui.username()
861 date_tz = None
863 ctx = context.memctx(repo, (p1, p2), msg,
864 ['.hgtags'], getfilectx,
865 user, date_tz, {'branch' : branch})
867 tmp = encoding.encoding
868 encoding.encoding = 'utf-8'
870 tagnode = repo.commitctx(ctx)
872 encoding.encoding = tmp
874 return (tagnode, branch)
876 def checkheads_bmark(repo, ref, ctx):
877 bmark = ref[len('refs/heads/'):]
878 if not bmark in bmarks:
879 # new bmark
880 return True
882 ctx_old = bmarks[bmark]
883 ctx_new = ctx
884 if not repo.changelog.descendant(ctx_old.rev(), ctx_new.rev()):
885 if force_push:
886 print "ok %s forced update" % ref
887 else:
888 print "error %s non-fast forward" % ref
889 return False
891 return True
893 def checkheads(repo, remote, p_revs):
895 remotemap = remote.branchmap()
896 if not remotemap:
897 # empty repo
898 return True
900 new = {}
901 ret = True
903 for node, ref in p_revs.iteritems():
904 ctx = repo[node]
905 branch = ctx.branch()
906 if not branch in remotemap:
907 # new branch
908 continue
909 if not ref.startswith('refs/heads/branches'):
910 if ref.startswith('refs/heads/'):
911 if not checkheads_bmark(repo, ref, ctx):
912 ret = False
914 # only check branches
915 continue
916 new.setdefault(branch, []).append(ctx.rev())
918 for branch, heads in new.iteritems():
919 old = [repo.changelog.rev(x) for x in remotemap[branch]]
920 for rev in heads:
921 if check_version(2, 3):
922 ancestors = repo.changelog.ancestors([rev], stoprev=min(old))
923 else:
924 ancestors = repo.changelog.ancestors(rev)
925 found = False
927 for x in old:
928 if x in ancestors:
929 found = True
930 break
932 if found:
933 continue
935 node = repo.changelog.node(rev)
936 ref = p_revs[node]
937 if force_push:
938 print "ok %s forced update" % ref
939 else:
940 print "error %s non-fast forward" % ref
941 ret = False
943 return ret
945 def push_unsafe(repo, remote, parsed_refs, p_revs):
947 force = force_push
949 fci = discovery.findcommonincoming
950 commoninc = fci(repo, remote, force=force)
951 common, _, remoteheads = commoninc
953 if not checkheads(repo, remote, p_revs):
954 return None
956 cg = repo.getbundle('push', heads=list(p_revs), common=common)
958 unbundle = remote.capable('unbundle')
959 if unbundle:
960 if force:
961 remoteheads = ['force']
962 return remote.unbundle(cg, remoteheads, 'push')
963 else:
964 return remote.addchangegroup(cg, 'push', repo.url())
966 def push(repo, remote, parsed_refs, p_revs):
967 if hasattr(remote, 'canpush') and not remote.canpush():
968 print "error cannot push"
970 if not p_revs:
971 # nothing to push
972 return
974 lock = None
975 unbundle = remote.capable('unbundle')
976 if not unbundle:
977 lock = remote.lock()
978 try:
979 ret = push_unsafe(repo, remote, parsed_refs, p_revs)
980 finally:
981 if lock is not None:
982 lock.release()
984 return ret
986 def check_tip(ref, kind, name, heads):
987 try:
988 ename = '%s/%s' % (kind, name)
989 tip = marks.get_tip(ename)
990 except KeyError:
991 return True
992 else:
993 return tip in heads
995 def do_export(parser):
996 global parsed_refs, bmarks, peer
998 p_bmarks = []
999 p_revs = {}
1001 parser.next()
1003 for line in parser.each_block('done'):
1004 if parser.check('blob'):
1005 parse_blob(parser)
1006 elif parser.check('commit'):
1007 parse_commit(parser)
1008 elif parser.check('reset'):
1009 parse_reset(parser)
1010 elif parser.check('tag'):
1011 parse_tag(parser)
1012 elif parser.check('feature'):
1013 pass
1014 else:
1015 die('unhandled export command: %s' % line)
1017 need_fetch = False
1019 for ref, node in parsed_refs.iteritems():
1020 bnode = hgbin(node) if node else None
1021 if ref.startswith('refs/heads/branches'):
1022 branch = ref[len('refs/heads/branches/'):]
1023 if branch in branches and bnode in branches[branch]:
1024 # up to date
1025 continue
1027 if peer:
1028 remotemap = peer.branchmap()
1029 if remotemap and branch in remotemap:
1030 heads = [hghex(e) for e in remotemap[branch]]
1031 if not check_tip(ref, 'branches', branch, heads):
1032 print "error %s fetch first" % ref
1033 need_fetch = True
1034 continue
1036 p_revs[bnode] = ref
1037 print "ok %s" % ref
1038 elif ref.startswith('refs/heads/'):
1039 bmark = ref[len('refs/heads/'):]
1040 new = node
1041 old = bmarks[bmark].hex() if bmark in bmarks else ''
1043 if old == new:
1044 continue
1046 print "ok %s" % ref
1047 if bmark != fake_bmark and \
1048 not (bmark == 'master' and bmark not in parser.repo._bookmarks):
1049 p_bmarks.append((ref, bmark, old, new))
1051 if peer:
1052 remote_old = peer.listkeys('bookmarks').get(bmark)
1053 if remote_old:
1054 if not check_tip(ref, 'bookmarks', bmark, remote_old):
1055 print "error %s fetch first" % ref
1056 need_fetch = True
1057 continue
1059 p_revs[bnode] = ref
1060 elif ref.startswith('refs/tags/'):
1061 if dry_run:
1062 print "ok %s" % ref
1063 continue
1064 tag = ref[len('refs/tags/'):]
1065 tag = hgref(tag)
1066 author, msg = parsed_tags.get(tag, (None, None))
1067 if mode == 'git':
1068 if not msg:
1069 msg = 'Added tag %s for changeset %s' % (tag, node[:12]);
1070 tagnode, branch = write_tag(parser.repo, tag, node, msg, author)
1071 p_revs[tagnode] = 'refs/heads/branches/' + gitref(branch)
1072 else:
1073 fp = parser.repo.opener('localtags', 'a')
1074 fp.write('%s %s\n' % (node, tag))
1075 fp.close()
1076 p_revs[bnode] = ref
1077 print "ok %s" % ref
1078 else:
1079 # transport-helper/fast-export bugs
1080 continue
1082 if need_fetch:
1083 print
1084 return
1086 if dry_run:
1087 if peer and not force_push:
1088 checkheads(parser.repo, peer, p_revs)
1089 print
1090 return
1092 if peer:
1093 if not push(parser.repo, peer, parsed_refs, p_revs):
1094 # do not update bookmarks
1095 print
1096 return
1098 # update remote bookmarks
1099 remote_bmarks = peer.listkeys('bookmarks')
1100 for ref, bmark, old, new in p_bmarks:
1101 if force_push:
1102 old = remote_bmarks.get(bmark, '')
1103 if not peer.pushkey('bookmarks', bmark, old, new):
1104 print "error %s" % ref
1105 else:
1106 # update local bookmarks
1107 for ref, bmark, old, new in p_bmarks:
1108 if not bookmarks.pushbookmark(parser.repo, bmark, old, new):
1109 print "error %s" % ref
1111 print
1113 def do_option(parser):
1114 global dry_run, force_push
1115 _, key, value = parser.line.split(' ')
1116 if key == 'dry-run':
1117 dry_run = (value == 'true')
1118 print 'ok'
1119 elif key == 'force':
1120 force_push = (value == 'true')
1121 print 'ok'
1122 else:
1123 print 'unsupported'
1125 def fix_path(alias, repo, orig_url):
1126 url = urlparse.urlparse(orig_url, 'file')
1127 if url.scheme != 'file' or os.path.isabs(url.path):
1128 return
1129 abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
1130 cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
1131 subprocess.call(cmd)
1133 def main(args):
1134 global prefix, gitdir, dirname, branches, bmarks
1135 global marks, blob_marks, parsed_refs
1136 global peer, mode, bad_mail, bad_name
1137 global track_branches, force_push, is_tmp
1138 global parsed_tags
1139 global filenodes
1140 global fake_bmark, hg_version
1141 global dry_run
1143 alias = args[1]
1144 url = args[2]
1145 peer = None
1147 hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
1148 track_branches = get_config_bool('remote-hg.track-branches', True)
1149 force_push = False
1151 if hg_git_compat:
1152 mode = 'hg'
1153 bad_mail = 'none@none'
1154 bad_name = ''
1155 else:
1156 mode = 'git'
1157 bad_mail = 'unknown'
1158 bad_name = 'Unknown'
1160 if alias[4:] == url:
1161 is_tmp = True
1162 alias = hashlib.sha1(alias).hexdigest()
1163 else:
1164 is_tmp = False
1166 gitdir = os.environ['GIT_DIR']
1167 dirname = os.path.join(gitdir, 'hg', alias)
1168 branches = {}
1169 bmarks = {}
1170 blob_marks = {}
1171 parsed_refs = {}
1172 marks = None
1173 parsed_tags = {}
1174 filenodes = {}
1175 fake_bmark = None
1176 try:
1177 hg_version = tuple(int(e) for e in util.version().split('.'))
1178 except:
1179 hg_version = None
1180 dry_run = False
1182 repo = get_repo(url, alias)
1183 prefix = 'refs/hg/%s' % alias
1185 if not is_tmp:
1186 fix_path(alias, peer or repo, url)
1188 marks_path = os.path.join(dirname, 'marks-hg')
1189 marks = Marks(marks_path, repo)
1191 if sys.platform == 'win32':
1192 import msvcrt
1193 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
1195 parser = Parser(repo)
1196 for line in parser:
1197 if parser.check('capabilities'):
1198 do_capabilities(parser)
1199 elif parser.check('list'):
1200 do_list(parser)
1201 elif parser.check('import'):
1202 do_import(parser)
1203 elif parser.check('export'):
1204 do_export(parser)
1205 elif parser.check('option'):
1206 do_option(parser)
1207 else:
1208 die('unhandled command: %s' % line)
1209 sys.stdout.flush()
1211 def bye():
1212 if not marks:
1213 return
1214 if not is_tmp:
1215 marks.store()
1216 else:
1217 shutil.rmtree(dirname)
1219 atexit.register(bye)
1220 sys.exit(main(sys.argv))