remote-hg: don't decode UTF-8 paths into Unicode objects
[alt-git.git] / contrib / remote-helpers / git-remote-hg
blob1c713e0af1894edba1166898c4301932fd7f2595
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 c_style_unescape(string):
682 if string[0] == string[-1] == '"':
683 return string.decode('string-escape')[1:-1]
684 return string
686 def parse_commit(parser):
687 global marks, blob_marks, parsed_refs
688 global mode
690 from_mark = merge_mark = None
692 ref = parser[1]
693 parser.next()
695 commit_mark = parser.get_mark()
696 parser.next()
697 author = parser.get_author()
698 parser.next()
699 committer = parser.get_author()
700 parser.next()
701 data = parser.get_data()
702 parser.next()
703 if parser.check('from'):
704 from_mark = parser.get_mark()
705 parser.next()
706 if parser.check('merge'):
707 merge_mark = parser.get_mark()
708 parser.next()
709 if parser.check('merge'):
710 die('octopus merges are not supported yet')
712 # fast-export adds an extra newline
713 if data[-1] == '\n':
714 data = data[:-1]
716 files = {}
718 for line in parser:
719 if parser.check('M'):
720 t, m, mark_ref, path = line.split(' ', 3)
721 mark = int(mark_ref[1:])
722 f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
723 elif parser.check('D'):
724 t, path = line.split(' ', 1)
725 f = { 'deleted' : True }
726 else:
727 die('Unknown file command: %s' % line)
728 path = c_style_unescape(path)
729 files[path] = f
731 # only export the commits if we are on an internal proxy repo
732 if dry_run and not peer:
733 parsed_refs[ref] = None
734 return
736 def getfilectx(repo, memctx, f):
737 of = files[f]
738 if 'deleted' in of:
739 raise IOError
740 if 'ctx' in of:
741 return of['ctx']
742 is_exec = of['mode'] == 'x'
743 is_link = of['mode'] == 'l'
744 rename = of.get('rename', None)
745 return context.memfilectx(f, of['data'],
746 is_link, is_exec, rename)
748 repo = parser.repo
750 user, date, tz = author
751 extra = {}
753 if committer != author:
754 extra['committer'] = "%s %u %u" % committer
756 if from_mark:
757 p1 = mark_to_rev(from_mark)
758 else:
759 p1 = '0' * 40
761 if merge_mark:
762 p2 = mark_to_rev(merge_mark)
763 else:
764 p2 = '0' * 40
767 # If files changed from any of the parents, hg wants to know, but in git if
768 # nothing changed from the first parent, nothing changed.
770 if merge_mark:
771 get_merge_files(repo, p1, p2, files)
773 # Check if the ref is supposed to be a named branch
774 if ref.startswith('refs/heads/branches/'):
775 branch = ref[len('refs/heads/branches/'):]
776 extra['branch'] = hgref(branch)
778 if mode == 'hg':
779 i = data.find('\n--HG--\n')
780 if i >= 0:
781 tmp = data[i + len('\n--HG--\n'):].strip()
782 for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
783 if k == 'rename':
784 old, new = v.split(' => ', 1)
785 files[new]['rename'] = old
786 elif k == 'branch':
787 extra[k] = v
788 elif k == 'extra':
789 ek, ev = v.split(' : ', 1)
790 extra[ek] = urllib.unquote(ev)
791 data = data[:i]
793 ctx = context.memctx(repo, (p1, p2), data,
794 files.keys(), getfilectx,
795 user, (date, tz), extra)
797 tmp = encoding.encoding
798 encoding.encoding = 'utf-8'
800 node = hghex(repo.commitctx(ctx))
802 encoding.encoding = tmp
804 parsed_refs[ref] = node
805 marks.new_mark(node, commit_mark)
807 def parse_reset(parser):
808 global parsed_refs
810 ref = parser[1]
811 parser.next()
812 # ugh
813 if parser.check('commit'):
814 parse_commit(parser)
815 return
816 if not parser.check('from'):
817 return
818 from_mark = parser.get_mark()
819 parser.next()
821 try:
822 rev = mark_to_rev(from_mark)
823 except KeyError:
824 rev = None
825 parsed_refs[ref] = rev
827 def parse_tag(parser):
828 name = parser[1]
829 parser.next()
830 from_mark = parser.get_mark()
831 parser.next()
832 tagger = parser.get_author()
833 parser.next()
834 data = parser.get_data()
835 parser.next()
837 parsed_tags[name] = (tagger, data)
839 def write_tag(repo, tag, node, msg, author):
840 branch = repo[node].branch()
841 tip = branch_tip(branch)
842 tip = repo[tip]
844 def getfilectx(repo, memctx, f):
845 try:
846 fctx = tip.filectx(f)
847 data = fctx.data()
848 except error.ManifestLookupError:
849 data = ""
850 content = data + "%s %s\n" % (node, tag)
851 return context.memfilectx(f, content, False, False, None)
853 p1 = tip.hex()
854 p2 = '0' * 40
855 if author:
856 user, date, tz = author
857 date_tz = (date, tz)
858 else:
859 cmd = ['git', 'var', 'GIT_COMMITTER_IDENT']
860 process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
861 output, _ = process.communicate()
862 m = re.match('^.* <.*>', output)
863 if m:
864 user = m.group(0)
865 else:
866 user = repo.ui.username()
867 date_tz = None
869 ctx = context.memctx(repo, (p1, p2), msg,
870 ['.hgtags'], getfilectx,
871 user, date_tz, {'branch' : branch})
873 tmp = encoding.encoding
874 encoding.encoding = 'utf-8'
876 tagnode = repo.commitctx(ctx)
878 encoding.encoding = tmp
880 return (tagnode, branch)
882 def checkheads_bmark(repo, ref, ctx):
883 bmark = ref[len('refs/heads/'):]
884 if not bmark in bmarks:
885 # new bmark
886 return True
888 ctx_old = bmarks[bmark]
889 ctx_new = ctx
890 if not repo.changelog.descendant(ctx_old.rev(), ctx_new.rev()):
891 if force_push:
892 print "ok %s forced update" % ref
893 else:
894 print "error %s non-fast forward" % ref
895 return False
897 return True
899 def checkheads(repo, remote, p_revs):
901 remotemap = remote.branchmap()
902 if not remotemap:
903 # empty repo
904 return True
906 new = {}
907 ret = True
909 for node, ref in p_revs.iteritems():
910 ctx = repo[node]
911 branch = ctx.branch()
912 if not branch in remotemap:
913 # new branch
914 continue
915 if not ref.startswith('refs/heads/branches'):
916 if ref.startswith('refs/heads/'):
917 if not checkheads_bmark(repo, ref, ctx):
918 ret = False
920 # only check branches
921 continue
922 new.setdefault(branch, []).append(ctx.rev())
924 for branch, heads in new.iteritems():
925 old = [repo.changelog.rev(x) for x in remotemap[branch]]
926 for rev in heads:
927 if check_version(2, 3):
928 ancestors = repo.changelog.ancestors([rev], stoprev=min(old))
929 else:
930 ancestors = repo.changelog.ancestors(rev)
931 found = False
933 for x in old:
934 if x in ancestors:
935 found = True
936 break
938 if found:
939 continue
941 node = repo.changelog.node(rev)
942 ref = p_revs[node]
943 if force_push:
944 print "ok %s forced update" % ref
945 else:
946 print "error %s non-fast forward" % ref
947 ret = False
949 return ret
951 def push_unsafe(repo, remote, parsed_refs, p_revs):
953 force = force_push
955 fci = discovery.findcommonincoming
956 commoninc = fci(repo, remote, force=force)
957 common, _, remoteheads = commoninc
959 if not checkheads(repo, remote, p_revs):
960 return None
962 cg = repo.getbundle('push', heads=list(p_revs), common=common)
964 unbundle = remote.capable('unbundle')
965 if unbundle:
966 if force:
967 remoteheads = ['force']
968 return remote.unbundle(cg, remoteheads, 'push')
969 else:
970 return remote.addchangegroup(cg, 'push', repo.url())
972 def push(repo, remote, parsed_refs, p_revs):
973 if hasattr(remote, 'canpush') and not remote.canpush():
974 print "error cannot push"
976 if not p_revs:
977 # nothing to push
978 return
980 lock = None
981 unbundle = remote.capable('unbundle')
982 if not unbundle:
983 lock = remote.lock()
984 try:
985 ret = push_unsafe(repo, remote, parsed_refs, p_revs)
986 finally:
987 if lock is not None:
988 lock.release()
990 return ret
992 def check_tip(ref, kind, name, heads):
993 try:
994 ename = '%s/%s' % (kind, name)
995 tip = marks.get_tip(ename)
996 except KeyError:
997 return True
998 else:
999 return tip in heads
1001 def do_export(parser):
1002 global parsed_refs, bmarks, peer
1004 p_bmarks = []
1005 p_revs = {}
1007 parser.next()
1009 for line in parser.each_block('done'):
1010 if parser.check('blob'):
1011 parse_blob(parser)
1012 elif parser.check('commit'):
1013 parse_commit(parser)
1014 elif parser.check('reset'):
1015 parse_reset(parser)
1016 elif parser.check('tag'):
1017 parse_tag(parser)
1018 elif parser.check('feature'):
1019 pass
1020 else:
1021 die('unhandled export command: %s' % line)
1023 need_fetch = False
1025 for ref, node in parsed_refs.iteritems():
1026 bnode = hgbin(node) if node else None
1027 if ref.startswith('refs/heads/branches'):
1028 branch = ref[len('refs/heads/branches/'):]
1029 if branch in branches and bnode in branches[branch]:
1030 # up to date
1031 continue
1033 if peer:
1034 remotemap = peer.branchmap()
1035 if remotemap and branch in remotemap:
1036 heads = [hghex(e) for e in remotemap[branch]]
1037 if not check_tip(ref, 'branches', branch, heads):
1038 print "error %s fetch first" % ref
1039 need_fetch = True
1040 continue
1042 p_revs[bnode] = ref
1043 print "ok %s" % ref
1044 elif ref.startswith('refs/heads/'):
1045 bmark = ref[len('refs/heads/'):]
1046 new = node
1047 old = bmarks[bmark].hex() if bmark in bmarks else ''
1049 if old == new:
1050 continue
1052 print "ok %s" % ref
1053 if bmark != fake_bmark and \
1054 not (bmark == 'master' and bmark not in parser.repo._bookmarks):
1055 p_bmarks.append((ref, bmark, old, new))
1057 if peer:
1058 remote_old = peer.listkeys('bookmarks').get(bmark)
1059 if remote_old:
1060 if not check_tip(ref, 'bookmarks', bmark, remote_old):
1061 print "error %s fetch first" % ref
1062 need_fetch = True
1063 continue
1065 p_revs[bnode] = ref
1066 elif ref.startswith('refs/tags/'):
1067 if dry_run:
1068 print "ok %s" % ref
1069 continue
1070 tag = ref[len('refs/tags/'):]
1071 tag = hgref(tag)
1072 author, msg = parsed_tags.get(tag, (None, None))
1073 if mode == 'git':
1074 if not msg:
1075 msg = 'Added tag %s for changeset %s' % (tag, node[:12]);
1076 tagnode, branch = write_tag(parser.repo, tag, node, msg, author)
1077 p_revs[tagnode] = 'refs/heads/branches/' + gitref(branch)
1078 else:
1079 fp = parser.repo.opener('localtags', 'a')
1080 fp.write('%s %s\n' % (node, tag))
1081 fp.close()
1082 p_revs[bnode] = ref
1083 print "ok %s" % ref
1084 else:
1085 # transport-helper/fast-export bugs
1086 continue
1088 if need_fetch:
1089 print
1090 return
1092 if dry_run:
1093 if peer and not force_push:
1094 checkheads(parser.repo, peer, p_revs)
1095 print
1096 return
1098 if peer:
1099 if not push(parser.repo, peer, parsed_refs, p_revs):
1100 # do not update bookmarks
1101 print
1102 return
1104 # update remote bookmarks
1105 remote_bmarks = peer.listkeys('bookmarks')
1106 for ref, bmark, old, new in p_bmarks:
1107 if force_push:
1108 old = remote_bmarks.get(bmark, '')
1109 if not peer.pushkey('bookmarks', bmark, old, new):
1110 print "error %s" % ref
1111 else:
1112 # update local bookmarks
1113 for ref, bmark, old, new in p_bmarks:
1114 if not bookmarks.pushbookmark(parser.repo, bmark, old, new):
1115 print "error %s" % ref
1117 print
1119 def do_option(parser):
1120 global dry_run, force_push
1121 _, key, value = parser.line.split(' ')
1122 if key == 'dry-run':
1123 dry_run = (value == 'true')
1124 print 'ok'
1125 elif key == 'force':
1126 force_push = (value == 'true')
1127 print 'ok'
1128 else:
1129 print 'unsupported'
1131 def fix_path(alias, repo, orig_url):
1132 url = urlparse.urlparse(orig_url, 'file')
1133 if url.scheme != 'file' or os.path.isabs(url.path):
1134 return
1135 abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
1136 cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
1137 subprocess.call(cmd)
1139 def main(args):
1140 global prefix, gitdir, dirname, branches, bmarks
1141 global marks, blob_marks, parsed_refs
1142 global peer, mode, bad_mail, bad_name
1143 global track_branches, force_push, is_tmp
1144 global parsed_tags
1145 global filenodes
1146 global fake_bmark, hg_version
1147 global dry_run
1149 alias = args[1]
1150 url = args[2]
1151 peer = None
1153 hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
1154 track_branches = get_config_bool('remote-hg.track-branches', True)
1155 force_push = False
1157 if hg_git_compat:
1158 mode = 'hg'
1159 bad_mail = 'none@none'
1160 bad_name = ''
1161 else:
1162 mode = 'git'
1163 bad_mail = 'unknown'
1164 bad_name = 'Unknown'
1166 if alias[4:] == url:
1167 is_tmp = True
1168 alias = hashlib.sha1(alias).hexdigest()
1169 else:
1170 is_tmp = False
1172 gitdir = os.environ['GIT_DIR']
1173 dirname = os.path.join(gitdir, 'hg', alias)
1174 branches = {}
1175 bmarks = {}
1176 blob_marks = {}
1177 parsed_refs = {}
1178 marks = None
1179 parsed_tags = {}
1180 filenodes = {}
1181 fake_bmark = None
1182 try:
1183 hg_version = tuple(int(e) for e in util.version().split('.'))
1184 except:
1185 hg_version = None
1186 dry_run = False
1188 repo = get_repo(url, alias)
1189 prefix = 'refs/hg/%s' % alias
1191 if not is_tmp:
1192 fix_path(alias, peer or repo, url)
1194 marks_path = os.path.join(dirname, 'marks-hg')
1195 marks = Marks(marks_path, repo)
1197 if sys.platform == 'win32':
1198 import msvcrt
1199 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
1201 parser = Parser(repo)
1202 for line in parser:
1203 if parser.check('capabilities'):
1204 do_capabilities(parser)
1205 elif parser.check('list'):
1206 do_list(parser)
1207 elif parser.check('import'):
1208 do_import(parser)
1209 elif parser.check('export'):
1210 do_export(parser)
1211 elif parser.check('option'):
1212 do_option(parser)
1213 else:
1214 die('unhandled command: %s' % line)
1215 sys.stdout.flush()
1217 def bye():
1218 if not marks:
1219 return
1220 if not is_tmp:
1221 marks.store()
1222 else:
1223 shutil.rmtree(dirname)
1225 atexit.register(bye)
1226 sys.exit(main(sys.argv))