Merge branch 'fr-po' of git://github.com/jnavila/git
[git/gitweb.git] / contrib / remote-helpers / git-remote-hg
blobeb89ef67798e70fd77eff341e3dda5385d3cde48
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)
418 else:
419 # make sure the shared path is always up-to-date
420 util.writefile(os.path.join(local_path, '.hg', 'sharedpath'), hg_path)
422 repo = hg.repository(myui, local_path)
423 try:
424 peer = hg.peer(myui, {}, url)
425 except:
426 die('Repository error')
427 repo.pull(peer, heads=None, force=True)
429 updatebookmarks(repo, peer)
431 return repo
433 def rev_to_mark(rev):
434 return marks.from_rev(rev.hex())
436 def mark_to_rev(mark):
437 return marks.to_rev(mark)
439 def export_ref(repo, name, kind, head):
440 ename = '%s/%s' % (kind, name)
441 try:
442 tip = marks.get_tip(ename)
443 tip = repo[tip].rev()
444 except:
445 tip = 0
447 revs = xrange(tip, head.rev() + 1)
448 total = len(revs)
450 for rev in revs:
452 c = repo[rev]
453 node = c.node()
455 if marks.is_marked(c.hex()):
456 continue
458 (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(node)
459 rev_branch = extra['branch']
461 author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
462 if 'committer' in extra:
463 user, time, tz = extra['committer'].rsplit(' ', 2)
464 committer = "%s %s %s" % (user, time, gittz(int(tz)))
465 else:
466 committer = author
468 parents = [repo[p] for p in repo.changelog.parentrevs(rev) if p >= 0]
470 if len(parents) == 0:
471 modified = c.manifest().keys()
472 removed = []
473 else:
474 modified, removed = get_filechanges(repo, c, parents[0])
476 desc += '\n'
478 if mode == 'hg':
479 extra_msg = ''
481 if rev_branch != 'default':
482 extra_msg += 'branch : %s\n' % rev_branch
484 renames = []
485 for f in c.files():
486 if f not in c.manifest():
487 continue
488 rename = c.filectx(f).renamed()
489 if rename:
490 renames.append((rename[0], f))
492 for e in renames:
493 extra_msg += "rename : %s => %s\n" % e
495 for key, value in extra.iteritems():
496 if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):
497 continue
498 else:
499 extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value))
501 if extra_msg:
502 desc += '\n--HG--\n' + extra_msg
504 if len(parents) == 0 and rev:
505 print 'reset %s/%s' % (prefix, ename)
507 modified_final = export_files(c.filectx(f) for f in modified)
509 print "commit %s/%s" % (prefix, ename)
510 print "mark :%d" % (marks.get_mark(c.hex()))
511 print "author %s" % (author)
512 print "committer %s" % (committer)
513 print "data %d" % (len(desc))
514 print desc
516 if len(parents) > 0:
517 print "from :%s" % (rev_to_mark(parents[0]))
518 if len(parents) > 1:
519 print "merge :%s" % (rev_to_mark(parents[1]))
521 for f in removed:
522 print "D %s" % (fix_file_path(f))
523 for f in modified_final:
524 print "M %s :%u %s" % f
525 print
527 progress = (rev - tip)
528 if (progress % 100 == 0):
529 print "progress revision %d '%s' (%d/%d)" % (rev, name, progress, total)
531 # make sure the ref is updated
532 print "reset %s/%s" % (prefix, ename)
533 print "from :%u" % rev_to_mark(head)
534 print
536 pending_revs = set(revs) - notes
537 if pending_revs:
538 note_mark = marks.next_mark()
539 ref = "refs/notes/hg"
541 print "commit %s" % ref
542 print "mark :%d" % (note_mark)
543 print "committer remote-hg <> %d %s" % (ptime.time(), gittz(ptime.timezone))
544 desc = "Notes for %s\n" % (name)
545 print "data %d" % (len(desc))
546 print desc
547 if marks.last_note:
548 print "from :%u" % marks.last_note
550 for rev in pending_revs:
551 notes.add(rev)
552 c = repo[rev]
553 print "N inline :%u" % rev_to_mark(c)
554 msg = c.hex()
555 print "data %d" % (len(msg))
556 print msg
557 print
559 marks.last_note = note_mark
561 marks.set_tip(ename, head.hex())
563 def export_tag(repo, tag):
564 export_ref(repo, tag, 'tags', repo[hgref(tag)])
566 def export_bookmark(repo, bmark):
567 head = bmarks[hgref(bmark)]
568 export_ref(repo, bmark, 'bookmarks', head)
570 def export_branch(repo, branch):
571 tip = get_branch_tip(repo, branch)
572 head = repo[tip]
573 export_ref(repo, branch, 'branches', head)
575 def export_head(repo):
576 export_ref(repo, g_head[0], 'bookmarks', g_head[1])
578 def do_capabilities(parser):
579 print "import"
580 print "export"
581 print "refspec refs/heads/branches/*:%s/branches/*" % prefix
582 print "refspec refs/heads/*:%s/bookmarks/*" % prefix
583 print "refspec refs/tags/*:%s/tags/*" % prefix
585 path = os.path.join(dirname, 'marks-git')
587 if os.path.exists(path):
588 print "*import-marks %s" % path
589 print "*export-marks %s" % path
590 print "option"
592 print
594 def branch_tip(branch):
595 return branches[branch][-1]
597 def get_branch_tip(repo, branch):
598 heads = branches.get(hgref(branch), None)
599 if not heads:
600 return None
602 # verify there's only one head
603 if (len(heads) > 1):
604 warn("Branch '%s' has more than one head, consider merging" % branch)
605 return branch_tip(hgref(branch))
607 return heads[0]
609 def list_head(repo, cur):
610 global g_head, fake_bmark
612 if 'default' not in branches:
613 # empty repo
614 return
616 node = repo[branch_tip('default')]
617 head = 'master' if not 'master' in bmarks else 'default'
618 fake_bmark = head
619 bmarks[head] = node
621 head = gitref(head)
622 print "@refs/heads/%s HEAD" % head
623 g_head = (head, node)
625 def do_list(parser):
626 repo = parser.repo
627 for bmark, node in bookmarks.listbookmarks(repo).iteritems():
628 bmarks[bmark] = repo[node]
630 cur = repo.dirstate.branch()
631 orig = peer if peer else repo
633 for branch, heads in orig.branchmap().iteritems():
634 # only open heads
635 heads = [h for h in heads if 'close' not in repo.changelog.read(h)[5]]
636 if heads:
637 branches[branch] = heads
639 list_head(repo, cur)
641 if track_branches:
642 for branch in branches:
643 print "? refs/heads/branches/%s" % gitref(branch)
645 for bmark in bmarks:
646 print "? refs/heads/%s" % gitref(bmark)
648 for tag, node in repo.tagslist():
649 if tag == 'tip':
650 continue
651 print "? refs/tags/%s" % gitref(tag)
653 print
655 def do_import(parser):
656 repo = parser.repo
658 path = os.path.join(dirname, 'marks-git')
660 print "feature done"
661 if os.path.exists(path):
662 print "feature import-marks=%s" % path
663 print "feature export-marks=%s" % path
664 print "feature force"
665 sys.stdout.flush()
667 tmp = encoding.encoding
668 encoding.encoding = 'utf-8'
670 # lets get all the import lines
671 while parser.check('import'):
672 ref = parser[1]
674 if (ref == 'HEAD'):
675 export_head(repo)
676 elif ref.startswith('refs/heads/branches/'):
677 branch = ref[len('refs/heads/branches/'):]
678 export_branch(repo, branch)
679 elif ref.startswith('refs/heads/'):
680 bmark = ref[len('refs/heads/'):]
681 export_bookmark(repo, bmark)
682 elif ref.startswith('refs/tags/'):
683 tag = ref[len('refs/tags/'):]
684 export_tag(repo, tag)
686 parser.next()
688 encoding.encoding = tmp
690 print 'done'
692 def parse_blob(parser):
693 parser.next()
694 mark = parser.get_mark()
695 parser.next()
696 data = parser.get_data()
697 blob_marks[mark] = data
698 parser.next()
700 def get_merge_files(repo, p1, p2, files):
701 for e in repo[p1].files():
702 if e not in files:
703 if e not in repo[p1].manifest():
704 continue
705 f = { 'ctx' : repo[p1][e] }
706 files[e] = f
708 def c_style_unescape(string):
709 if string[0] == string[-1] == '"':
710 return string.decode('string-escape')[1:-1]
711 return string
713 def parse_commit(parser):
714 from_mark = merge_mark = None
716 ref = parser[1]
717 parser.next()
719 commit_mark = parser.get_mark()
720 parser.next()
721 author = parser.get_author()
722 parser.next()
723 committer = parser.get_author()
724 parser.next()
725 data = parser.get_data()
726 parser.next()
727 if parser.check('from'):
728 from_mark = parser.get_mark()
729 parser.next()
730 if parser.check('merge'):
731 merge_mark = parser.get_mark()
732 parser.next()
733 if parser.check('merge'):
734 die('octopus merges are not supported yet')
736 # fast-export adds an extra newline
737 if data[-1] == '\n':
738 data = data[:-1]
740 files = {}
742 for line in parser:
743 if parser.check('M'):
744 t, m, mark_ref, path = line.split(' ', 3)
745 mark = int(mark_ref[1:])
746 f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
747 elif parser.check('D'):
748 t, path = line.split(' ', 1)
749 f = { 'deleted' : True }
750 else:
751 die('Unknown file command: %s' % line)
752 path = c_style_unescape(path)
753 files[path] = f
755 # only export the commits if we are on an internal proxy repo
756 if dry_run and not peer:
757 parsed_refs[ref] = None
758 return
760 def getfilectx(repo, memctx, f):
761 of = files[f]
762 if 'deleted' in of:
763 raise IOError
764 if 'ctx' in of:
765 return of['ctx']
766 is_exec = of['mode'] == 'x'
767 is_link = of['mode'] == 'l'
768 rename = of.get('rename', None)
769 return context.memfilectx(f, of['data'],
770 is_link, is_exec, rename)
772 repo = parser.repo
774 user, date, tz = author
775 extra = {}
777 if committer != author:
778 extra['committer'] = "%s %u %u" % committer
780 if from_mark:
781 p1 = mark_to_rev(from_mark)
782 else:
783 p1 = '0' * 40
785 if merge_mark:
786 p2 = mark_to_rev(merge_mark)
787 else:
788 p2 = '0' * 40
791 # If files changed from any of the parents, hg wants to know, but in git if
792 # nothing changed from the first parent, nothing changed.
794 if merge_mark:
795 get_merge_files(repo, p1, p2, files)
797 # Check if the ref is supposed to be a named branch
798 if ref.startswith('refs/heads/branches/'):
799 branch = ref[len('refs/heads/branches/'):]
800 extra['branch'] = hgref(branch)
802 if mode == 'hg':
803 i = data.find('\n--HG--\n')
804 if i >= 0:
805 tmp = data[i + len('\n--HG--\n'):].strip()
806 for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
807 if k == 'rename':
808 old, new = v.split(' => ', 1)
809 files[new]['rename'] = old
810 elif k == 'branch':
811 extra[k] = v
812 elif k == 'extra':
813 ek, ev = v.split(' : ', 1)
814 extra[ek] = urllib.unquote(ev)
815 data = data[:i]
817 ctx = context.memctx(repo, (p1, p2), data,
818 files.keys(), getfilectx,
819 user, (date, tz), extra)
821 tmp = encoding.encoding
822 encoding.encoding = 'utf-8'
824 node = hghex(repo.commitctx(ctx))
826 encoding.encoding = tmp
828 parsed_refs[ref] = node
829 marks.new_mark(node, commit_mark)
831 def parse_reset(parser):
832 ref = parser[1]
833 parser.next()
834 # ugh
835 if parser.check('commit'):
836 parse_commit(parser)
837 return
838 if not parser.check('from'):
839 return
840 from_mark = parser.get_mark()
841 parser.next()
843 try:
844 rev = mark_to_rev(from_mark)
845 except KeyError:
846 rev = None
847 parsed_refs[ref] = rev
849 def parse_tag(parser):
850 name = parser[1]
851 parser.next()
852 from_mark = parser.get_mark()
853 parser.next()
854 tagger = parser.get_author()
855 parser.next()
856 data = parser.get_data()
857 parser.next()
859 parsed_tags[name] = (tagger, data)
861 def write_tag(repo, tag, node, msg, author):
862 branch = repo[node].branch()
863 tip = branch_tip(branch)
864 tip = repo[tip]
866 def getfilectx(repo, memctx, f):
867 try:
868 fctx = tip.filectx(f)
869 data = fctx.data()
870 except error.ManifestLookupError:
871 data = ""
872 content = data + "%s %s\n" % (node, tag)
873 return context.memfilectx(f, content, False, False, None)
875 p1 = tip.hex()
876 p2 = '0' * 40
877 if author:
878 user, date, tz = author
879 date_tz = (date, tz)
880 else:
881 cmd = ['git', 'var', 'GIT_COMMITTER_IDENT']
882 process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
883 output, _ = process.communicate()
884 m = re.match('^.* <.*>', output)
885 if m:
886 user = m.group(0)
887 else:
888 user = repo.ui.username()
889 date_tz = None
891 ctx = context.memctx(repo, (p1, p2), msg,
892 ['.hgtags'], getfilectx,
893 user, date_tz, {'branch' : branch})
895 tmp = encoding.encoding
896 encoding.encoding = 'utf-8'
898 tagnode = repo.commitctx(ctx)
900 encoding.encoding = tmp
902 return (tagnode, branch)
904 def checkheads_bmark(repo, ref, ctx):
905 bmark = ref[len('refs/heads/'):]
906 if not bmark in bmarks:
907 # new bmark
908 return True
910 ctx_old = bmarks[bmark]
911 ctx_new = ctx
912 if not repo.changelog.descendant(ctx_old.rev(), ctx_new.rev()):
913 if force_push:
914 print "ok %s forced update" % ref
915 else:
916 print "error %s non-fast forward" % ref
917 return False
919 return True
921 def checkheads(repo, remote, p_revs):
923 remotemap = remote.branchmap()
924 if not remotemap:
925 # empty repo
926 return True
928 new = {}
929 ret = True
931 for node, ref in p_revs.iteritems():
932 ctx = repo[node]
933 branch = ctx.branch()
934 if not branch in remotemap:
935 # new branch
936 continue
937 if not ref.startswith('refs/heads/branches'):
938 if ref.startswith('refs/heads/'):
939 if not checkheads_bmark(repo, ref, ctx):
940 ret = False
942 # only check branches
943 continue
944 new.setdefault(branch, []).append(ctx.rev())
946 for branch, heads in new.iteritems():
947 old = [repo.changelog.rev(x) for x in remotemap[branch]]
948 for rev in heads:
949 if check_version(2, 3):
950 ancestors = repo.changelog.ancestors([rev], stoprev=min(old))
951 else:
952 ancestors = repo.changelog.ancestors(rev)
953 found = False
955 for x in old:
956 if x in ancestors:
957 found = True
958 break
960 if found:
961 continue
963 node = repo.changelog.node(rev)
964 ref = p_revs[node]
965 if force_push:
966 print "ok %s forced update" % ref
967 else:
968 print "error %s non-fast forward" % ref
969 ret = False
971 return ret
973 def push_unsafe(repo, remote, parsed_refs, p_revs):
975 force = force_push
977 fci = discovery.findcommonincoming
978 commoninc = fci(repo, remote, force=force)
979 common, _, remoteheads = commoninc
981 if not checkheads(repo, remote, p_revs):
982 return None
984 cg = repo.getbundle('push', heads=list(p_revs), common=common)
986 unbundle = remote.capable('unbundle')
987 if unbundle:
988 if force:
989 remoteheads = ['force']
990 return remote.unbundle(cg, remoteheads, 'push')
991 else:
992 return remote.addchangegroup(cg, 'push', repo.url())
994 def push(repo, remote, parsed_refs, p_revs):
995 if hasattr(remote, 'canpush') and not remote.canpush():
996 print "error cannot push"
998 if not p_revs:
999 # nothing to push
1000 return
1002 lock = None
1003 unbundle = remote.capable('unbundle')
1004 if not unbundle:
1005 lock = remote.lock()
1006 try:
1007 ret = push_unsafe(repo, remote, parsed_refs, p_revs)
1008 finally:
1009 if lock is not None:
1010 lock.release()
1012 return ret
1014 def check_tip(ref, kind, name, heads):
1015 try:
1016 ename = '%s/%s' % (kind, name)
1017 tip = marks.get_tip(ename)
1018 except KeyError:
1019 return True
1020 else:
1021 return tip in heads
1023 def do_export(parser):
1024 p_bmarks = []
1025 p_revs = {}
1027 parser.next()
1029 for line in parser.each_block('done'):
1030 if parser.check('blob'):
1031 parse_blob(parser)
1032 elif parser.check('commit'):
1033 parse_commit(parser)
1034 elif parser.check('reset'):
1035 parse_reset(parser)
1036 elif parser.check('tag'):
1037 parse_tag(parser)
1038 elif parser.check('feature'):
1039 pass
1040 else:
1041 die('unhandled export command: %s' % line)
1043 need_fetch = False
1045 for ref, node in parsed_refs.iteritems():
1046 bnode = hgbin(node) if node else None
1047 if ref.startswith('refs/heads/branches'):
1048 branch = ref[len('refs/heads/branches/'):]
1049 if branch in branches and bnode in branches[branch]:
1050 # up to date
1051 continue
1053 if peer:
1054 remotemap = peer.branchmap()
1055 if remotemap and branch in remotemap:
1056 heads = [hghex(e) for e in remotemap[branch]]
1057 if not check_tip(ref, 'branches', branch, heads):
1058 print "error %s fetch first" % ref
1059 need_fetch = True
1060 continue
1062 p_revs[bnode] = ref
1063 print "ok %s" % ref
1064 elif ref.startswith('refs/heads/'):
1065 bmark = ref[len('refs/heads/'):]
1066 new = node
1067 old = bmarks[bmark].hex() if bmark in bmarks else ''
1069 if old == new:
1070 continue
1072 print "ok %s" % ref
1073 if bmark != fake_bmark and \
1074 not (bmark == 'master' and bmark not in parser.repo._bookmarks):
1075 p_bmarks.append((ref, bmark, old, new))
1077 if peer:
1078 remote_old = peer.listkeys('bookmarks').get(bmark)
1079 if remote_old:
1080 if not check_tip(ref, 'bookmarks', bmark, remote_old):
1081 print "error %s fetch first" % ref
1082 need_fetch = True
1083 continue
1085 p_revs[bnode] = ref
1086 elif ref.startswith('refs/tags/'):
1087 if dry_run:
1088 print "ok %s" % ref
1089 continue
1090 tag = ref[len('refs/tags/'):]
1091 tag = hgref(tag)
1092 author, msg = parsed_tags.get(tag, (None, None))
1093 if mode == 'git':
1094 if not msg:
1095 msg = 'Added tag %s for changeset %s' % (tag, node[:12])
1096 tagnode, branch = write_tag(parser.repo, tag, node, msg, author)
1097 p_revs[tagnode] = 'refs/heads/branches/' + gitref(branch)
1098 else:
1099 fp = parser.repo.opener('localtags', 'a')
1100 fp.write('%s %s\n' % (node, tag))
1101 fp.close()
1102 p_revs[bnode] = ref
1103 print "ok %s" % ref
1104 else:
1105 # transport-helper/fast-export bugs
1106 continue
1108 if need_fetch:
1109 print
1110 return
1112 if dry_run:
1113 if peer and not force_push:
1114 checkheads(parser.repo, peer, p_revs)
1115 print
1116 return
1118 if peer:
1119 if not push(parser.repo, peer, parsed_refs, p_revs):
1120 # do not update bookmarks
1121 print
1122 return
1124 # update remote bookmarks
1125 remote_bmarks = peer.listkeys('bookmarks')
1126 for ref, bmark, old, new in p_bmarks:
1127 if force_push:
1128 old = remote_bmarks.get(bmark, '')
1129 if not peer.pushkey('bookmarks', bmark, old, new):
1130 print "error %s" % ref
1131 else:
1132 # update local bookmarks
1133 for ref, bmark, old, new in p_bmarks:
1134 if not bookmarks.pushbookmark(parser.repo, bmark, old, new):
1135 print "error %s" % ref
1137 print
1139 def do_option(parser):
1140 global dry_run, force_push
1141 _, key, value = parser.line.split(' ')
1142 if key == 'dry-run':
1143 dry_run = (value == 'true')
1144 print 'ok'
1145 elif key == 'force':
1146 force_push = (value == 'true')
1147 print 'ok'
1148 else:
1149 print 'unsupported'
1151 def fix_path(alias, repo, orig_url):
1152 url = urlparse.urlparse(orig_url, 'file')
1153 if url.scheme != 'file' or os.path.isabs(os.path.expanduser(url.path)):
1154 return
1155 abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
1156 cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
1157 subprocess.call(cmd)
1159 def main(args):
1160 global prefix, gitdir, dirname, branches, bmarks
1161 global marks, blob_marks, parsed_refs
1162 global peer, mode, bad_mail, bad_name
1163 global track_branches, force_push, is_tmp
1164 global parsed_tags
1165 global filenodes
1166 global fake_bmark, hg_version
1167 global dry_run
1168 global notes, alias
1170 marks = None
1171 is_tmp = False
1172 gitdir = os.environ.get('GIT_DIR', None)
1174 if len(args) < 3:
1175 die('Not enough arguments.')
1177 if not gitdir:
1178 die('GIT_DIR not set')
1180 alias = args[1]
1181 url = args[2]
1182 peer = None
1184 hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
1185 track_branches = get_config_bool('remote-hg.track-branches', True)
1186 force_push = False
1188 if hg_git_compat:
1189 mode = 'hg'
1190 bad_mail = 'none@none'
1191 bad_name = ''
1192 else:
1193 mode = 'git'
1194 bad_mail = 'unknown'
1195 bad_name = 'Unknown'
1197 if alias[4:] == url:
1198 is_tmp = True
1199 alias = hashlib.sha1(alias).hexdigest()
1201 dirname = os.path.join(gitdir, 'hg', alias)
1202 branches = {}
1203 bmarks = {}
1204 blob_marks = {}
1205 parsed_refs = {}
1206 parsed_tags = {}
1207 filenodes = {}
1208 fake_bmark = None
1209 try:
1210 hg_version = tuple(int(e) for e in util.version().split('.'))
1211 except:
1212 hg_version = None
1213 dry_run = False
1214 notes = set()
1216 repo = get_repo(url, alias)
1217 prefix = 'refs/hg/%s' % alias
1219 if not is_tmp:
1220 fix_path(alias, peer or repo, url)
1222 marks_path = os.path.join(dirname, 'marks-hg')
1223 marks = Marks(marks_path, repo)
1225 if sys.platform == 'win32':
1226 import msvcrt
1227 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
1229 parser = Parser(repo)
1230 for line in parser:
1231 if parser.check('capabilities'):
1232 do_capabilities(parser)
1233 elif parser.check('list'):
1234 do_list(parser)
1235 elif parser.check('import'):
1236 do_import(parser)
1237 elif parser.check('export'):
1238 do_export(parser)
1239 elif parser.check('option'):
1240 do_option(parser)
1241 else:
1242 die('unhandled command: %s' % line)
1243 sys.stdout.flush()
1245 def bye():
1246 if not marks:
1247 return
1248 if not is_tmp:
1249 marks.store()
1250 else:
1251 shutil.rmtree(dirname)
1253 atexit.register(bye)
1254 sys.exit(main(sys.argv))