remote-bzr, remote-hg: fix email address regular expression
[git.git] / contrib / remote-helpers / git-remote-hg
blob30402d55323488f7c2bc6599bbc4a471f2120b33
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
26 import time as ptime
29 # If you want to see Mercurial revisions as Git commit notes:
30 # git config core.notesRef refs/notes/hg
32 # If you are not in hg-git-compat mode and want to disable the tracking of
33 # named branches:
34 # git config --global remote-hg.track-branches false
36 # If you want the equivalent of hg's clone/pull--insecure option:
37 # git config --global remote-hg.insecure true
39 # If you want to switch to hg-git compatibility mode:
40 # git config --global remote-hg.hg-git-compat true
42 # git:
43 # Sensible defaults for git.
44 # hg bookmarks are exported as git branches, hg branches are prefixed
45 # with 'branches/', HEAD is a special case.
47 # hg:
48 # Emulate hg-git.
49 # Only hg bookmarks are exported as git branches.
50 # Commits are modified to preserve hg information and allow bidirectionality.
53 NAME_RE = re.compile('^([^<>]+)')
54 AUTHOR_RE = re.compile('^([^<>]+?)? ?[<>]([^<>]*)(?:$|>)')
55 EMAIL_RE = re.compile(r'([^ \t<>]+@[^ \t<>]+)')
56 AUTHOR_HG_RE = re.compile('^(.*?) ?<(.*?)(?:>(.+)?)?$')
57 RAW_AUTHOR_RE = re.compile('^(\w+) (?:(.+)? )?<(.*)> (\d+) ([+-]\d+)')
59 VERSION = 2
61 def die(msg, *args):
62 sys.stderr.write('ERROR: %s\n' % (msg % args))
63 sys.exit(1)
65 def warn(msg, *args):
66 sys.stderr.write('WARNING: %s\n' % (msg % args))
68 def gitmode(flags):
69 return 'l' in flags and '120000' or 'x' in flags and '100755' or '100644'
71 def gittz(tz):
72 return '%+03d%02d' % (-tz / 3600, -tz % 3600 / 60)
74 def hgmode(mode):
75 m = { '100755': 'x', '120000': 'l' }
76 return m.get(mode, '')
78 def hghex(n):
79 return node.hex(n)
81 def hgbin(n):
82 return node.bin(n)
84 def hgref(ref):
85 return ref.replace('___', ' ')
87 def gitref(ref):
88 return ref.replace(' ', '___')
90 def check_version(*check):
91 if not hg_version:
92 return True
93 return hg_version >= check
95 def get_config(config):
96 cmd = ['git', 'config', '--get', config]
97 process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
98 output, _ = process.communicate()
99 return output
101 def get_config_bool(config, default=False):
102 value = get_config(config).rstrip('\n')
103 if value == "true":
104 return True
105 elif value == "false":
106 return False
107 else:
108 return default
110 class Marks:
112 def __init__(self, path, repo):
113 self.path = path
114 self.repo = repo
115 self.clear()
116 self.load()
118 if self.version < VERSION:
119 if self.version == 1:
120 self.upgrade_one()
122 # upgraded?
123 if self.version < VERSION:
124 self.clear()
125 self.version = VERSION
127 def clear(self):
128 self.tips = {}
129 self.marks = {}
130 self.rev_marks = {}
131 self.last_mark = 0
132 self.version = 0
133 self.last_note = 0
135 def load(self):
136 if not os.path.exists(self.path):
137 return
139 tmp = json.load(open(self.path))
141 self.tips = tmp['tips']
142 self.marks = tmp['marks']
143 self.last_mark = tmp['last-mark']
144 self.version = tmp.get('version', 1)
145 self.last_note = tmp.get('last-note', 0)
147 for rev, mark in self.marks.iteritems():
148 self.rev_marks[mark] = rev
150 def upgrade_one(self):
151 def get_id(rev):
152 return hghex(self.repo.changelog.node(int(rev)))
153 self.tips = dict((name, get_id(rev)) for name, rev in self.tips.iteritems())
154 self.marks = dict((get_id(rev), mark) for rev, mark in self.marks.iteritems())
155 self.rev_marks = dict((mark, get_id(rev)) for mark, rev in self.rev_marks.iteritems())
156 self.version = 2
158 def dict(self):
159 return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark, 'version' : self.version, 'last-note' : self.last_note }
161 def store(self):
162 json.dump(self.dict(), open(self.path, 'w'))
164 def __str__(self):
165 return str(self.dict())
167 def from_rev(self, rev):
168 return self.marks[rev]
170 def to_rev(self, mark):
171 return str(self.rev_marks[mark])
173 def next_mark(self):
174 self.last_mark += 1
175 return self.last_mark
177 def get_mark(self, rev):
178 self.last_mark += 1
179 self.marks[rev] = self.last_mark
180 return self.last_mark
182 def new_mark(self, rev, mark):
183 self.marks[rev] = mark
184 self.rev_marks[mark] = rev
185 self.last_mark = mark
187 def is_marked(self, rev):
188 return rev in self.marks
190 def get_tip(self, branch):
191 return str(self.tips[branch])
193 def set_tip(self, branch, tip):
194 self.tips[branch] = tip
196 class Parser:
198 def __init__(self, repo):
199 self.repo = repo
200 self.line = self.get_line()
202 def get_line(self):
203 return sys.stdin.readline().strip()
205 def __getitem__(self, i):
206 return self.line.split()[i]
208 def check(self, word):
209 return self.line.startswith(word)
211 def each_block(self, separator):
212 while self.line != separator:
213 yield self.line
214 self.line = self.get_line()
216 def __iter__(self):
217 return self.each_block('')
219 def next(self):
220 self.line = self.get_line()
221 if self.line == 'done':
222 self.line = None
224 def get_mark(self):
225 i = self.line.index(':') + 1
226 return int(self.line[i:])
228 def get_data(self):
229 if not self.check('data'):
230 return None
231 i = self.line.index(' ') + 1
232 size = int(self.line[i:])
233 return sys.stdin.read(size)
235 def get_author(self):
236 ex = None
237 m = RAW_AUTHOR_RE.match(self.line)
238 if not m:
239 return None
240 _, name, email, date, tz = m.groups()
241 if name and 'ext:' in name:
242 m = re.match('^(.+?) ext:\((.+)\)$', name)
243 if m:
244 name = m.group(1)
245 ex = urllib.unquote(m.group(2))
247 if email != bad_mail:
248 if name:
249 user = '%s <%s>' % (name, email)
250 else:
251 user = '<%s>' % (email)
252 else:
253 user = name
255 if ex:
256 user += ex
258 tz = int(tz)
259 tz = ((tz / 100) * 3600) + ((tz % 100) * 60)
260 return (user, int(date), -tz)
262 def fix_file_path(path):
263 if not os.path.isabs(path):
264 return path
265 return os.path.relpath(path, '/')
267 def export_files(files):
268 final = []
269 for f in files:
270 fid = node.hex(f.filenode())
272 if fid in filenodes:
273 mark = filenodes[fid]
274 else:
275 mark = marks.next_mark()
276 filenodes[fid] = mark
277 d = f.data()
279 print "blob"
280 print "mark :%u" % mark
281 print "data %d" % len(d)
282 print d
284 path = fix_file_path(f.path())
285 final.append((gitmode(f.flags()), mark, path))
287 return final
289 def get_filechanges(repo, ctx, parent):
290 modified = set()
291 added = set()
292 removed = set()
294 # load earliest manifest first for caching reasons
295 prev = parent.manifest().copy()
296 cur = ctx.manifest()
298 for fn in cur:
299 if fn in prev:
300 if (cur.flags(fn) != prev.flags(fn) or cur[fn] != prev[fn]):
301 modified.add(fn)
302 del prev[fn]
303 else:
304 added.add(fn)
305 removed |= set(prev.keys())
307 return added | modified, removed
309 def fixup_user_git(user):
310 name = mail = None
311 user = user.replace('"', '')
312 m = AUTHOR_RE.match(user)
313 if m:
314 name = m.group(1)
315 mail = m.group(2).strip()
316 else:
317 m = EMAIL_RE.match(user)
318 if m:
319 mail = m.group(1)
320 else:
321 m = NAME_RE.match(user)
322 if m:
323 name = m.group(1).strip()
324 return (name, mail)
326 def fixup_user_hg(user):
327 def sanitize(name):
328 # stole this from hg-git
329 return re.sub('[<>\n]', '?', name.lstrip('< ').rstrip('> '))
331 m = AUTHOR_HG_RE.match(user)
332 if m:
333 name = sanitize(m.group(1))
334 mail = sanitize(m.group(2))
335 ex = m.group(3)
336 if ex:
337 name += ' ext:(' + urllib.quote(ex) + ')'
338 else:
339 name = sanitize(user)
340 if '@' in user:
341 mail = name
342 else:
343 mail = None
345 return (name, mail)
347 def fixup_user(user):
348 if mode == 'git':
349 name, mail = fixup_user_git(user)
350 else:
351 name, mail = fixup_user_hg(user)
353 if not name:
354 name = bad_name
355 if not mail:
356 mail = bad_mail
358 return '%s <%s>' % (name, mail)
360 def updatebookmarks(repo, peer):
361 remotemarks = peer.listkeys('bookmarks')
362 localmarks = repo._bookmarks
364 if not remotemarks:
365 return
367 for k, v in remotemarks.iteritems():
368 localmarks[k] = hgbin(v)
370 if hasattr(localmarks, 'write'):
371 localmarks.write()
372 else:
373 bookmarks.write(repo)
375 def get_repo(url, alias):
376 global peer
378 myui = ui.ui()
379 myui.setconfig('ui', 'interactive', 'off')
380 myui.fout = sys.stderr
382 if get_config_bool('remote-hg.insecure'):
383 myui.setconfig('web', 'cacerts', '')
385 extensions.loadall(myui)
387 if hg.islocal(url) and not os.environ.get('GIT_REMOTE_HG_TEST_REMOTE'):
388 repo = hg.repository(myui, url)
389 if not os.path.exists(dirname):
390 os.makedirs(dirname)
391 else:
392 shared_path = os.path.join(gitdir, 'hg')
394 # check and upgrade old organization
395 hg_path = os.path.join(shared_path, '.hg')
396 if os.path.exists(shared_path) and not os.path.exists(hg_path):
397 repos = os.listdir(shared_path)
398 for x in repos:
399 local_hg = os.path.join(shared_path, x, 'clone', '.hg')
400 if not os.path.exists(local_hg):
401 continue
402 if not os.path.exists(hg_path):
403 shutil.move(local_hg, hg_path)
404 shutil.rmtree(os.path.join(shared_path, x, 'clone'))
406 # setup shared repo (if not there)
407 try:
408 hg.peer(myui, {}, shared_path, create=True)
409 except error.RepoError:
410 pass
412 if not os.path.exists(dirname):
413 os.makedirs(dirname)
415 local_path = os.path.join(dirname, 'clone')
416 if not os.path.exists(local_path):
417 hg.share(myui, shared_path, local_path, update=False)
419 repo = hg.repository(myui, local_path)
420 try:
421 peer = hg.peer(myui, {}, url)
422 except:
423 die('Repository error')
424 repo.pull(peer, heads=None, force=True)
426 updatebookmarks(repo, peer)
428 return repo
430 def rev_to_mark(rev):
431 return marks.from_rev(rev.hex())
433 def mark_to_rev(mark):
434 return marks.to_rev(mark)
436 def export_ref(repo, name, kind, head):
437 ename = '%s/%s' % (kind, name)
438 try:
439 tip = marks.get_tip(ename)
440 tip = repo[tip].rev()
441 except:
442 tip = 0
444 revs = xrange(tip, head.rev() + 1)
445 total = len(revs)
447 for rev in revs:
449 c = repo[rev]
450 node = c.node()
452 if marks.is_marked(c.hex()):
453 continue
455 (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(node)
456 rev_branch = extra['branch']
458 author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
459 if 'committer' in extra:
460 user, time, tz = extra['committer'].rsplit(' ', 2)
461 committer = "%s %s %s" % (user, time, gittz(int(tz)))
462 else:
463 committer = author
465 parents = [repo[p] for p in repo.changelog.parentrevs(rev) if p >= 0]
467 if len(parents) == 0:
468 modified = c.manifest().keys()
469 removed = []
470 else:
471 modified, removed = get_filechanges(repo, c, parents[0])
473 desc += '\n'
475 if mode == 'hg':
476 extra_msg = ''
478 if rev_branch != 'default':
479 extra_msg += 'branch : %s\n' % rev_branch
481 renames = []
482 for f in c.files():
483 if f not in c.manifest():
484 continue
485 rename = c.filectx(f).renamed()
486 if rename:
487 renames.append((rename[0], f))
489 for e in renames:
490 extra_msg += "rename : %s => %s\n" % e
492 for key, value in extra.iteritems():
493 if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):
494 continue
495 else:
496 extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value))
498 if extra_msg:
499 desc += '\n--HG--\n' + extra_msg
501 if len(parents) == 0 and rev:
502 print 'reset %s/%s' % (prefix, ename)
504 modified_final = export_files(c.filectx(f) for f in modified)
506 print "commit %s/%s" % (prefix, ename)
507 print "mark :%d" % (marks.get_mark(c.hex()))
508 print "author %s" % (author)
509 print "committer %s" % (committer)
510 print "data %d" % (len(desc))
511 print desc
513 if len(parents) > 0:
514 print "from :%s" % (rev_to_mark(parents[0]))
515 if len(parents) > 1:
516 print "merge :%s" % (rev_to_mark(parents[1]))
518 for f in removed:
519 print "D %s" % (fix_file_path(f))
520 for f in modified_final:
521 print "M %s :%u %s" % f
522 print
524 progress = (rev - tip)
525 if (progress % 100 == 0):
526 print "progress revision %d '%s' (%d/%d)" % (rev, name, progress, total)
528 # make sure the ref is updated
529 print "reset %s/%s" % (prefix, ename)
530 print "from :%u" % rev_to_mark(head)
531 print
533 pending_revs = set(revs) - notes
534 if pending_revs:
535 note_mark = marks.next_mark()
536 ref = "refs/notes/hg"
538 print "commit %s" % ref
539 print "mark :%d" % (note_mark)
540 print "committer remote-hg <> %s" % (ptime.strftime('%s %z'))
541 desc = "Notes for %s\n" % (name)
542 print "data %d" % (len(desc))
543 print desc
544 if marks.last_note:
545 print "from :%u" % marks.last_note
547 for rev in pending_revs:
548 notes.add(rev)
549 c = repo[rev]
550 print "N inline :%u" % rev_to_mark(c)
551 msg = c.hex()
552 print "data %d" % (len(msg))
553 print msg
554 print
556 marks.last_note = note_mark
558 marks.set_tip(ename, head.hex())
560 def export_tag(repo, tag):
561 export_ref(repo, tag, 'tags', repo[hgref(tag)])
563 def export_bookmark(repo, bmark):
564 head = bmarks[hgref(bmark)]
565 export_ref(repo, bmark, 'bookmarks', head)
567 def export_branch(repo, branch):
568 tip = get_branch_tip(repo, branch)
569 head = repo[tip]
570 export_ref(repo, branch, 'branches', head)
572 def export_head(repo):
573 export_ref(repo, g_head[0], 'bookmarks', g_head[1])
575 def do_capabilities(parser):
576 print "import"
577 print "export"
578 print "refspec refs/heads/branches/*:%s/branches/*" % prefix
579 print "refspec refs/heads/*:%s/bookmarks/*" % prefix
580 print "refspec refs/tags/*:%s/tags/*" % prefix
582 path = os.path.join(dirname, 'marks-git')
584 if os.path.exists(path):
585 print "*import-marks %s" % path
586 print "*export-marks %s" % path
587 print "option"
589 print
591 def branch_tip(branch):
592 return branches[branch][-1]
594 def get_branch_tip(repo, branch):
595 heads = branches.get(hgref(branch), None)
596 if not heads:
597 return None
599 # verify there's only one head
600 if (len(heads) > 1):
601 warn("Branch '%s' has more than one head, consider merging" % branch)
602 return branch_tip(hgref(branch))
604 return heads[0]
606 def list_head(repo, cur):
607 global g_head, fake_bmark
609 if 'default' not in branches:
610 # empty repo
611 return
613 node = repo[branch_tip('default')]
614 head = 'master' if not 'master' in bmarks else 'default'
615 fake_bmark = head
616 bmarks[head] = node
618 head = gitref(head)
619 print "@refs/heads/%s HEAD" % head
620 g_head = (head, node)
622 def do_list(parser):
623 repo = parser.repo
624 for bmark, node in bookmarks.listbookmarks(repo).iteritems():
625 bmarks[bmark] = repo[node]
627 cur = repo.dirstate.branch()
628 orig = peer if peer else repo
630 for branch, heads in orig.branchmap().iteritems():
631 # only open heads
632 heads = [h for h in heads if 'close' not in repo.changelog.read(h)[5]]
633 if heads:
634 branches[branch] = heads
636 list_head(repo, cur)
638 if track_branches:
639 for branch in branches:
640 print "? refs/heads/branches/%s" % gitref(branch)
642 for bmark in bmarks:
643 print "? refs/heads/%s" % gitref(bmark)
645 for tag, node in repo.tagslist():
646 if tag == 'tip':
647 continue
648 print "? refs/tags/%s" % gitref(tag)
650 print
652 def do_import(parser):
653 repo = parser.repo
655 path = os.path.join(dirname, 'marks-git')
657 print "feature done"
658 if os.path.exists(path):
659 print "feature import-marks=%s" % path
660 print "feature export-marks=%s" % path
661 print "feature force"
662 sys.stdout.flush()
664 tmp = encoding.encoding
665 encoding.encoding = 'utf-8'
667 # lets get all the import lines
668 while parser.check('import'):
669 ref = parser[1]
671 if (ref == 'HEAD'):
672 export_head(repo)
673 elif ref.startswith('refs/heads/branches/'):
674 branch = ref[len('refs/heads/branches/'):]
675 export_branch(repo, branch)
676 elif ref.startswith('refs/heads/'):
677 bmark = ref[len('refs/heads/'):]
678 export_bookmark(repo, bmark)
679 elif ref.startswith('refs/tags/'):
680 tag = ref[len('refs/tags/'):]
681 export_tag(repo, tag)
683 parser.next()
685 encoding.encoding = tmp
687 print 'done'
689 def parse_blob(parser):
690 parser.next()
691 mark = parser.get_mark()
692 parser.next()
693 data = parser.get_data()
694 blob_marks[mark] = data
695 parser.next()
697 def get_merge_files(repo, p1, p2, files):
698 for e in repo[p1].files():
699 if e not in files:
700 if e not in repo[p1].manifest():
701 continue
702 f = { 'ctx' : repo[p1][e] }
703 files[e] = f
705 def c_style_unescape(string):
706 if string[0] == string[-1] == '"':
707 return string.decode('string-escape')[1:-1]
708 return string
710 def parse_commit(parser):
711 from_mark = merge_mark = None
713 ref = parser[1]
714 parser.next()
716 commit_mark = parser.get_mark()
717 parser.next()
718 author = parser.get_author()
719 parser.next()
720 committer = parser.get_author()
721 parser.next()
722 data = parser.get_data()
723 parser.next()
724 if parser.check('from'):
725 from_mark = parser.get_mark()
726 parser.next()
727 if parser.check('merge'):
728 merge_mark = parser.get_mark()
729 parser.next()
730 if parser.check('merge'):
731 die('octopus merges are not supported yet')
733 # fast-export adds an extra newline
734 if data[-1] == '\n':
735 data = data[:-1]
737 files = {}
739 for line in parser:
740 if parser.check('M'):
741 t, m, mark_ref, path = line.split(' ', 3)
742 mark = int(mark_ref[1:])
743 f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
744 elif parser.check('D'):
745 t, path = line.split(' ', 1)
746 f = { 'deleted' : True }
747 else:
748 die('Unknown file command: %s' % line)
749 path = c_style_unescape(path)
750 files[path] = f
752 # only export the commits if we are on an internal proxy repo
753 if dry_run and not peer:
754 parsed_refs[ref] = None
755 return
757 def getfilectx(repo, memctx, f):
758 of = files[f]
759 if 'deleted' in of:
760 raise IOError
761 if 'ctx' in of:
762 return of['ctx']
763 is_exec = of['mode'] == 'x'
764 is_link = of['mode'] == 'l'
765 rename = of.get('rename', None)
766 return context.memfilectx(f, of['data'],
767 is_link, is_exec, rename)
769 repo = parser.repo
771 user, date, tz = author
772 extra = {}
774 if committer != author:
775 extra['committer'] = "%s %u %u" % committer
777 if from_mark:
778 p1 = mark_to_rev(from_mark)
779 else:
780 p1 = '0' * 40
782 if merge_mark:
783 p2 = mark_to_rev(merge_mark)
784 else:
785 p2 = '0' * 40
788 # If files changed from any of the parents, hg wants to know, but in git if
789 # nothing changed from the first parent, nothing changed.
791 if merge_mark:
792 get_merge_files(repo, p1, p2, files)
794 # Check if the ref is supposed to be a named branch
795 if ref.startswith('refs/heads/branches/'):
796 branch = ref[len('refs/heads/branches/'):]
797 extra['branch'] = hgref(branch)
799 if mode == 'hg':
800 i = data.find('\n--HG--\n')
801 if i >= 0:
802 tmp = data[i + len('\n--HG--\n'):].strip()
803 for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
804 if k == 'rename':
805 old, new = v.split(' => ', 1)
806 files[new]['rename'] = old
807 elif k == 'branch':
808 extra[k] = v
809 elif k == 'extra':
810 ek, ev = v.split(' : ', 1)
811 extra[ek] = urllib.unquote(ev)
812 data = data[:i]
814 ctx = context.memctx(repo, (p1, p2), data,
815 files.keys(), getfilectx,
816 user, (date, tz), extra)
818 tmp = encoding.encoding
819 encoding.encoding = 'utf-8'
821 node = hghex(repo.commitctx(ctx))
823 encoding.encoding = tmp
825 parsed_refs[ref] = node
826 marks.new_mark(node, commit_mark)
828 def parse_reset(parser):
829 ref = parser[1]
830 parser.next()
831 # ugh
832 if parser.check('commit'):
833 parse_commit(parser)
834 return
835 if not parser.check('from'):
836 return
837 from_mark = parser.get_mark()
838 parser.next()
840 try:
841 rev = mark_to_rev(from_mark)
842 except KeyError:
843 rev = None
844 parsed_refs[ref] = rev
846 def parse_tag(parser):
847 name = parser[1]
848 parser.next()
849 from_mark = parser.get_mark()
850 parser.next()
851 tagger = parser.get_author()
852 parser.next()
853 data = parser.get_data()
854 parser.next()
856 parsed_tags[name] = (tagger, data)
858 def write_tag(repo, tag, node, msg, author):
859 branch = repo[node].branch()
860 tip = branch_tip(branch)
861 tip = repo[tip]
863 def getfilectx(repo, memctx, f):
864 try:
865 fctx = tip.filectx(f)
866 data = fctx.data()
867 except error.ManifestLookupError:
868 data = ""
869 content = data + "%s %s\n" % (node, tag)
870 return context.memfilectx(f, content, False, False, None)
872 p1 = tip.hex()
873 p2 = '0' * 40
874 if author:
875 user, date, tz = author
876 date_tz = (date, tz)
877 else:
878 cmd = ['git', 'var', 'GIT_COMMITTER_IDENT']
879 process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
880 output, _ = process.communicate()
881 m = re.match('^.* <.*>', output)
882 if m:
883 user = m.group(0)
884 else:
885 user = repo.ui.username()
886 date_tz = None
888 ctx = context.memctx(repo, (p1, p2), msg,
889 ['.hgtags'], getfilectx,
890 user, date_tz, {'branch' : branch})
892 tmp = encoding.encoding
893 encoding.encoding = 'utf-8'
895 tagnode = repo.commitctx(ctx)
897 encoding.encoding = tmp
899 return (tagnode, branch)
901 def checkheads_bmark(repo, ref, ctx):
902 bmark = ref[len('refs/heads/'):]
903 if not bmark in bmarks:
904 # new bmark
905 return True
907 ctx_old = bmarks[bmark]
908 ctx_new = ctx
909 if not repo.changelog.descendant(ctx_old.rev(), ctx_new.rev()):
910 if force_push:
911 print "ok %s forced update" % ref
912 else:
913 print "error %s non-fast forward" % ref
914 return False
916 return True
918 def checkheads(repo, remote, p_revs):
920 remotemap = remote.branchmap()
921 if not remotemap:
922 # empty repo
923 return True
925 new = {}
926 ret = True
928 for node, ref in p_revs.iteritems():
929 ctx = repo[node]
930 branch = ctx.branch()
931 if not branch in remotemap:
932 # new branch
933 continue
934 if not ref.startswith('refs/heads/branches'):
935 if ref.startswith('refs/heads/'):
936 if not checkheads_bmark(repo, ref, ctx):
937 ret = False
939 # only check branches
940 continue
941 new.setdefault(branch, []).append(ctx.rev())
943 for branch, heads in new.iteritems():
944 old = [repo.changelog.rev(x) for x in remotemap[branch]]
945 for rev in heads:
946 if check_version(2, 3):
947 ancestors = repo.changelog.ancestors([rev], stoprev=min(old))
948 else:
949 ancestors = repo.changelog.ancestors(rev)
950 found = False
952 for x in old:
953 if x in ancestors:
954 found = True
955 break
957 if found:
958 continue
960 node = repo.changelog.node(rev)
961 ref = p_revs[node]
962 if force_push:
963 print "ok %s forced update" % ref
964 else:
965 print "error %s non-fast forward" % ref
966 ret = False
968 return ret
970 def push_unsafe(repo, remote, parsed_refs, p_revs):
972 force = force_push
974 fci = discovery.findcommonincoming
975 commoninc = fci(repo, remote, force=force)
976 common, _, remoteheads = commoninc
978 if not checkheads(repo, remote, p_revs):
979 return None
981 cg = repo.getbundle('push', heads=list(p_revs), common=common)
983 unbundle = remote.capable('unbundle')
984 if unbundle:
985 if force:
986 remoteheads = ['force']
987 return remote.unbundle(cg, remoteheads, 'push')
988 else:
989 return remote.addchangegroup(cg, 'push', repo.url())
991 def push(repo, remote, parsed_refs, p_revs):
992 if hasattr(remote, 'canpush') and not remote.canpush():
993 print "error cannot push"
995 if not p_revs:
996 # nothing to push
997 return
999 lock = None
1000 unbundle = remote.capable('unbundle')
1001 if not unbundle:
1002 lock = remote.lock()
1003 try:
1004 ret = push_unsafe(repo, remote, parsed_refs, p_revs)
1005 finally:
1006 if lock is not None:
1007 lock.release()
1009 return ret
1011 def check_tip(ref, kind, name, heads):
1012 try:
1013 ename = '%s/%s' % (kind, name)
1014 tip = marks.get_tip(ename)
1015 except KeyError:
1016 return True
1017 else:
1018 return tip in heads
1020 def do_export(parser):
1021 p_bmarks = []
1022 p_revs = {}
1024 parser.next()
1026 for line in parser.each_block('done'):
1027 if parser.check('blob'):
1028 parse_blob(parser)
1029 elif parser.check('commit'):
1030 parse_commit(parser)
1031 elif parser.check('reset'):
1032 parse_reset(parser)
1033 elif parser.check('tag'):
1034 parse_tag(parser)
1035 elif parser.check('feature'):
1036 pass
1037 else:
1038 die('unhandled export command: %s' % line)
1040 need_fetch = False
1042 for ref, node in parsed_refs.iteritems():
1043 bnode = hgbin(node) if node else None
1044 if ref.startswith('refs/heads/branches'):
1045 branch = ref[len('refs/heads/branches/'):]
1046 if branch in branches and bnode in branches[branch]:
1047 # up to date
1048 continue
1050 if peer:
1051 remotemap = peer.branchmap()
1052 if remotemap and branch in remotemap:
1053 heads = [hghex(e) for e in remotemap[branch]]
1054 if not check_tip(ref, 'branches', branch, heads):
1055 print "error %s fetch first" % ref
1056 need_fetch = True
1057 continue
1059 p_revs[bnode] = ref
1060 print "ok %s" % ref
1061 elif ref.startswith('refs/heads/'):
1062 bmark = ref[len('refs/heads/'):]
1063 new = node
1064 old = bmarks[bmark].hex() if bmark in bmarks else ''
1066 if old == new:
1067 continue
1069 print "ok %s" % ref
1070 if bmark != fake_bmark and \
1071 not (bmark == 'master' and bmark not in parser.repo._bookmarks):
1072 p_bmarks.append((ref, bmark, old, new))
1074 if peer:
1075 remote_old = peer.listkeys('bookmarks').get(bmark)
1076 if remote_old:
1077 if not check_tip(ref, 'bookmarks', bmark, remote_old):
1078 print "error %s fetch first" % ref
1079 need_fetch = True
1080 continue
1082 p_revs[bnode] = ref
1083 elif ref.startswith('refs/tags/'):
1084 if dry_run:
1085 print "ok %s" % ref
1086 continue
1087 tag = ref[len('refs/tags/'):]
1088 tag = hgref(tag)
1089 author, msg = parsed_tags.get(tag, (None, None))
1090 if mode == 'git':
1091 if not msg:
1092 msg = 'Added tag %s for changeset %s' % (tag, node[:12])
1093 tagnode, branch = write_tag(parser.repo, tag, node, msg, author)
1094 p_revs[tagnode] = 'refs/heads/branches/' + gitref(branch)
1095 else:
1096 fp = parser.repo.opener('localtags', 'a')
1097 fp.write('%s %s\n' % (node, tag))
1098 fp.close()
1099 p_revs[bnode] = ref
1100 print "ok %s" % ref
1101 else:
1102 # transport-helper/fast-export bugs
1103 continue
1105 if need_fetch:
1106 print
1107 return
1109 if dry_run:
1110 if peer and not force_push:
1111 checkheads(parser.repo, peer, p_revs)
1112 print
1113 return
1115 if peer:
1116 if not push(parser.repo, peer, parsed_refs, p_revs):
1117 # do not update bookmarks
1118 print
1119 return
1121 # update remote bookmarks
1122 remote_bmarks = peer.listkeys('bookmarks')
1123 for ref, bmark, old, new in p_bmarks:
1124 if force_push:
1125 old = remote_bmarks.get(bmark, '')
1126 if not peer.pushkey('bookmarks', bmark, old, new):
1127 print "error %s" % ref
1128 else:
1129 # update local bookmarks
1130 for ref, bmark, old, new in p_bmarks:
1131 if not bookmarks.pushbookmark(parser.repo, bmark, old, new):
1132 print "error %s" % ref
1134 print
1136 def do_option(parser):
1137 global dry_run, force_push
1138 _, key, value = parser.line.split(' ')
1139 if key == 'dry-run':
1140 dry_run = (value == 'true')
1141 print 'ok'
1142 elif key == 'force':
1143 force_push = (value == 'true')
1144 print 'ok'
1145 else:
1146 print 'unsupported'
1148 def fix_path(alias, repo, orig_url):
1149 url = urlparse.urlparse(orig_url, 'file')
1150 if url.scheme != 'file' or os.path.isabs(os.path.expanduser(url.path)):
1151 return
1152 abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
1153 cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
1154 subprocess.call(cmd)
1156 def main(args):
1157 global prefix, gitdir, dirname, branches, bmarks
1158 global marks, blob_marks, parsed_refs
1159 global peer, mode, bad_mail, bad_name
1160 global track_branches, force_push, is_tmp
1161 global parsed_tags
1162 global filenodes
1163 global fake_bmark, hg_version
1164 global dry_run
1165 global notes, alias
1167 alias = args[1]
1168 url = args[2]
1169 peer = None
1171 hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
1172 track_branches = get_config_bool('remote-hg.track-branches', True)
1173 force_push = False
1175 if hg_git_compat:
1176 mode = 'hg'
1177 bad_mail = 'none@none'
1178 bad_name = ''
1179 else:
1180 mode = 'git'
1181 bad_mail = 'unknown'
1182 bad_name = 'Unknown'
1184 if alias[4:] == url:
1185 is_tmp = True
1186 alias = hashlib.sha1(alias).hexdigest()
1187 else:
1188 is_tmp = False
1190 gitdir = os.environ['GIT_DIR']
1191 dirname = os.path.join(gitdir, 'hg', alias)
1192 branches = {}
1193 bmarks = {}
1194 blob_marks = {}
1195 parsed_refs = {}
1196 marks = None
1197 parsed_tags = {}
1198 filenodes = {}
1199 fake_bmark = None
1200 try:
1201 hg_version = tuple(int(e) for e in util.version().split('.'))
1202 except:
1203 hg_version = None
1204 dry_run = False
1205 notes = set()
1207 repo = get_repo(url, alias)
1208 prefix = 'refs/hg/%s' % alias
1210 if not is_tmp:
1211 fix_path(alias, peer or repo, url)
1213 marks_path = os.path.join(dirname, 'marks-hg')
1214 marks = Marks(marks_path, repo)
1216 if sys.platform == 'win32':
1217 import msvcrt
1218 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
1220 parser = Parser(repo)
1221 for line in parser:
1222 if parser.check('capabilities'):
1223 do_capabilities(parser)
1224 elif parser.check('list'):
1225 do_list(parser)
1226 elif parser.check('import'):
1227 do_import(parser)
1228 elif parser.check('export'):
1229 do_export(parser)
1230 elif parser.check('option'):
1231 do_option(parser)
1232 else:
1233 die('unhandled command: %s' % line)
1234 sys.stdout.flush()
1236 def bye():
1237 if not marks:
1238 return
1239 if not is_tmp:
1240 marks.store()
1241 else:
1242 shutil.rmtree(dirname)
1244 atexit.register(bye)
1245 sys.exit(main(sys.argv))