remote-hg: implement custom push()
[git.git] / contrib / remote-helpers / git-remote-hg
blobc2c1cb8b52b307b6213e440c17075959757ab4fb
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
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 don't want to force pushes (and thus risk creating new remote heads):
33 # git config --global remote-hg.force-push false
35 # If you want the equivalent of hg's clone/pull--insecure option:
36 # git config --global remote-hg.insecure true
38 # If you want to switch to hg-git compatibility mode:
39 # git config --global remote-hg.hg-git-compat true
41 # git:
42 # Sensible defaults for git.
43 # hg bookmarks are exported as git branches, hg branches are prefixed
44 # with 'branches/', HEAD is a special case.
46 # hg:
47 # Emulate hg-git.
48 # Only hg bookmarks are exported as git branches.
49 # Commits are modified to preserve hg information and allow bidirectionality.
52 NAME_RE = re.compile('^([^<>]+)')
53 AUTHOR_RE = re.compile('^([^<>]+?)? ?<([^<>]*)>$')
54 EMAIL_RE = re.compile('^([^<>]+[^ \\\t<>])?\\b(?:[ \\t<>]*?)\\b([^ \\t<>]+@[^ \\t<>]+)')
55 AUTHOR_HG_RE = re.compile('^(.*?) ?<(.*?)(?:>(.+)?)?$')
56 RAW_AUTHOR_RE = re.compile('^(\w+) (?:(.+)? )?<(.*)> (\d+) ([+-]\d+)')
58 VERSION = 2
60 def die(msg, *args):
61 sys.stderr.write('ERROR: %s\n' % (msg % args))
62 sys.exit(1)
64 def warn(msg, *args):
65 sys.stderr.write('WARNING: %s\n' % (msg % args))
67 def gitmode(flags):
68 return 'l' in flags and '120000' or 'x' in flags and '100755' or '100644'
70 def gittz(tz):
71 return '%+03d%02d' % (-tz / 3600, -tz % 3600 / 60)
73 def hgmode(mode):
74 m = { '100755': 'x', '120000': 'l' }
75 return m.get(mode, '')
77 def hghex(n):
78 return node.hex(n)
80 def hgbin(n):
81 return node.bin(n)
83 def hgref(ref):
84 return ref.replace('___', ' ')
86 def gitref(ref):
87 return ref.replace(' ', '___')
89 def get_config(config):
90 cmd = ['git', 'config', '--get', config]
91 process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
92 output, _ = process.communicate()
93 return output
95 def get_config_bool(config, default=False):
96 value = get_config(config).rstrip('\n')
97 if value == "true":
98 return True
99 elif value == "false":
100 return False
101 else:
102 return default
104 class Marks:
106 def __init__(self, path, repo):
107 self.path = path
108 self.repo = repo
109 self.clear()
110 self.load()
112 if self.version < VERSION:
113 if self.version == 1:
114 self.upgrade_one()
116 # upgraded?
117 if self.version < VERSION:
118 self.clear()
119 self.version = VERSION
121 def clear(self):
122 self.tips = {}
123 self.marks = {}
124 self.rev_marks = {}
125 self.last_mark = 0
126 self.version = 0
128 def load(self):
129 if not os.path.exists(self.path):
130 return
132 tmp = json.load(open(self.path))
134 self.tips = tmp['tips']
135 self.marks = tmp['marks']
136 self.last_mark = tmp['last-mark']
137 self.version = tmp.get('version', 1)
139 for rev, mark in self.marks.iteritems():
140 self.rev_marks[mark] = rev
142 def upgrade_one(self):
143 def get_id(rev):
144 return hghex(self.repo.changelog.node(int(rev)))
145 self.tips = dict((name, get_id(rev)) for name, rev in self.tips.iteritems())
146 self.marks = dict((get_id(rev), mark) for rev, mark in self.marks.iteritems())
147 self.rev_marks = dict((mark, get_id(rev)) for mark, rev in self.rev_marks.iteritems())
148 self.version = 2
150 def dict(self):
151 return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark, 'version' : self.version }
153 def store(self):
154 json.dump(self.dict(), open(self.path, 'w'))
156 def __str__(self):
157 return str(self.dict())
159 def from_rev(self, rev):
160 return self.marks[rev]
162 def to_rev(self, mark):
163 return self.rev_marks[mark]
165 def next_mark(self):
166 self.last_mark += 1
167 return self.last_mark
169 def get_mark(self, rev):
170 self.last_mark += 1
171 self.marks[rev] = self.last_mark
172 return self.last_mark
174 def new_mark(self, rev, mark):
175 self.marks[rev] = mark
176 self.rev_marks[mark] = rev
177 self.last_mark = mark
179 def is_marked(self, rev):
180 return rev in self.marks
182 def get_tip(self, branch):
183 return self.tips.get(branch, None)
185 def set_tip(self, branch, tip):
186 self.tips[branch] = tip
188 class Parser:
190 def __init__(self, repo):
191 self.repo = repo
192 self.line = self.get_line()
194 def get_line(self):
195 return sys.stdin.readline().strip()
197 def __getitem__(self, i):
198 return self.line.split()[i]
200 def check(self, word):
201 return self.line.startswith(word)
203 def each_block(self, separator):
204 while self.line != separator:
205 yield self.line
206 self.line = self.get_line()
208 def __iter__(self):
209 return self.each_block('')
211 def next(self):
212 self.line = self.get_line()
213 if self.line == 'done':
214 self.line = None
216 def get_mark(self):
217 i = self.line.index(':') + 1
218 return int(self.line[i:])
220 def get_data(self):
221 if not self.check('data'):
222 return None
223 i = self.line.index(' ') + 1
224 size = int(self.line[i:])
225 return sys.stdin.read(size)
227 def get_author(self):
228 global bad_mail
230 ex = None
231 m = RAW_AUTHOR_RE.match(self.line)
232 if not m:
233 return None
234 _, name, email, date, tz = m.groups()
235 if name and 'ext:' in name:
236 m = re.match('^(.+?) ext:\((.+)\)$', name)
237 if m:
238 name = m.group(1)
239 ex = urllib.unquote(m.group(2))
241 if email != bad_mail:
242 if name:
243 user = '%s <%s>' % (name, email)
244 else:
245 user = '<%s>' % (email)
246 else:
247 user = name
249 if ex:
250 user += ex
252 tz = int(tz)
253 tz = ((tz / 100) * 3600) + ((tz % 100) * 60)
254 return (user, int(date), -tz)
256 def fix_file_path(path):
257 if not os.path.isabs(path):
258 return path
259 return os.path.relpath(path, '/')
261 def export_files(files):
262 global marks, filenodes
264 final = []
265 for f in files:
266 fid = node.hex(f.filenode())
268 if fid in filenodes:
269 mark = filenodes[fid]
270 else:
271 mark = marks.next_mark()
272 filenodes[fid] = mark
273 d = f.data()
275 print "blob"
276 print "mark :%u" % mark
277 print "data %d" % len(d)
278 print d
280 path = fix_file_path(f.path())
281 final.append((gitmode(f.flags()), mark, path))
283 return final
285 def get_filechanges(repo, ctx, parent):
286 modified = set()
287 added = set()
288 removed = set()
290 # load earliest manifest first for caching reasons
291 prev = parent.manifest().copy()
292 cur = ctx.manifest()
294 for fn in cur:
295 if fn in prev:
296 if (cur.flags(fn) != prev.flags(fn) or cur[fn] != prev[fn]):
297 modified.add(fn)
298 del prev[fn]
299 else:
300 added.add(fn)
301 removed |= set(prev.keys())
303 return added | modified, removed
305 def fixup_user_git(user):
306 name = mail = None
307 user = user.replace('"', '')
308 m = AUTHOR_RE.match(user)
309 if m:
310 name = m.group(1)
311 mail = m.group(2).strip()
312 else:
313 m = EMAIL_RE.match(user)
314 if m:
315 name = m.group(1)
316 mail = m.group(2)
317 else:
318 m = NAME_RE.match(user)
319 if m:
320 name = m.group(1).strip()
321 return (name, mail)
323 def fixup_user_hg(user):
324 def sanitize(name):
325 # stole this from hg-git
326 return re.sub('[<>\n]', '?', name.lstrip('< ').rstrip('> '))
328 m = AUTHOR_HG_RE.match(user)
329 if m:
330 name = sanitize(m.group(1))
331 mail = sanitize(m.group(2))
332 ex = m.group(3)
333 if ex:
334 name += ' ext:(' + urllib.quote(ex) + ')'
335 else:
336 name = sanitize(user)
337 if '@' in user:
338 mail = name
339 else:
340 mail = None
342 return (name, mail)
344 def fixup_user(user):
345 global mode, bad_mail
347 if mode == 'git':
348 name, mail = fixup_user_git(user)
349 else:
350 name, mail = fixup_user_hg(user)
352 if not name:
353 name = bad_name
354 if not mail:
355 mail = bad_mail
357 return '%s <%s>' % (name, mail)
359 def updatebookmarks(repo, peer):
360 remotemarks = peer.listkeys('bookmarks')
361 localmarks = repo._bookmarks
363 if not remotemarks:
364 return
366 for k, v in remotemarks.iteritems():
367 localmarks[k] = hgbin(v)
369 if hasattr(localmarks, 'write'):
370 localmarks.write()
371 else:
372 bookmarks.write(repo)
374 def get_repo(url, alias):
375 global dirname, peer
377 myui = ui.ui()
378 myui.setconfig('ui', 'interactive', 'off')
379 myui.fout = sys.stderr
381 if get_config_bool('remote-hg.insecure'):
382 myui.setconfig('web', 'cacerts', '')
384 extensions.loadall(myui)
386 if hg.islocal(url) and not os.environ.get('GIT_REMOTE_HG_TEST_REMOTE'):
387 repo = hg.repository(myui, url)
388 if not os.path.exists(dirname):
389 os.makedirs(dirname)
390 else:
391 shared_path = os.path.join(gitdir, 'hg')
392 if not os.path.exists(shared_path):
393 try:
394 hg.clone(myui, {}, url, shared_path, update=False, pull=True)
395 except:
396 die('Repository error')
398 if not os.path.exists(dirname):
399 os.makedirs(dirname)
401 local_path = os.path.join(dirname, 'clone')
402 if not os.path.exists(local_path):
403 hg.share(myui, shared_path, local_path, update=False)
405 repo = hg.repository(myui, local_path)
406 try:
407 peer = hg.peer(myui, {}, url)
408 except:
409 die('Repository error')
410 repo.pull(peer, heads=None, force=True)
412 updatebookmarks(repo, peer)
414 return repo
416 def rev_to_mark(rev):
417 global marks
418 return marks.from_rev(rev.hex())
420 def mark_to_rev(mark):
421 global marks
422 return marks.to_rev(mark)
424 def export_ref(repo, name, kind, head):
425 global prefix, marks, mode
427 ename = '%s/%s' % (kind, name)
428 tip = marks.get_tip(ename)
429 if tip and tip in repo:
430 tip = repo[tip].rev()
431 else:
432 tip = 0
434 revs = xrange(tip, head.rev() + 1)
435 total = len(revs)
437 for rev in revs:
439 c = repo[rev]
440 node = c.node()
442 if marks.is_marked(c.hex()):
443 continue
445 (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(node)
446 rev_branch = extra['branch']
448 author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
449 if 'committer' in extra:
450 user, time, tz = extra['committer'].rsplit(' ', 2)
451 committer = "%s %s %s" % (user, time, gittz(int(tz)))
452 else:
453 committer = author
455 parents = [repo[p] for p in repo.changelog.parentrevs(rev) if p >= 0]
457 if len(parents) == 0:
458 modified = c.manifest().keys()
459 removed = []
460 else:
461 modified, removed = get_filechanges(repo, c, parents[0])
463 desc += '\n'
465 if mode == 'hg':
466 extra_msg = ''
468 if rev_branch != 'default':
469 extra_msg += 'branch : %s\n' % rev_branch
471 renames = []
472 for f in c.files():
473 if f not in c.manifest():
474 continue
475 rename = c.filectx(f).renamed()
476 if rename:
477 renames.append((rename[0], f))
479 for e in renames:
480 extra_msg += "rename : %s => %s\n" % e
482 for key, value in extra.iteritems():
483 if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):
484 continue
485 else:
486 extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value))
488 if extra_msg:
489 desc += '\n--HG--\n' + extra_msg
491 if len(parents) == 0 and rev:
492 print 'reset %s/%s' % (prefix, ename)
494 modified_final = export_files(c.filectx(f) for f in modified)
496 print "commit %s/%s" % (prefix, ename)
497 print "mark :%d" % (marks.get_mark(c.hex()))
498 print "author %s" % (author)
499 print "committer %s" % (committer)
500 print "data %d" % (len(desc))
501 print desc
503 if len(parents) > 0:
504 print "from :%s" % (rev_to_mark(parents[0]))
505 if len(parents) > 1:
506 print "merge :%s" % (rev_to_mark(parents[1]))
508 for f in modified_final:
509 print "M %s :%u %s" % f
510 for f in removed:
511 print "D %s" % (fix_file_path(f))
512 print
514 progress = (rev - tip)
515 if (progress % 100 == 0):
516 print "progress revision %d '%s' (%d/%d)" % (rev, name, progress, total)
518 # make sure the ref is updated
519 print "reset %s/%s" % (prefix, ename)
520 print "from :%u" % rev_to_mark(head)
521 print
523 marks.set_tip(ename, head.hex())
525 def export_tag(repo, tag):
526 export_ref(repo, tag, 'tags', repo[hgref(tag)])
528 def export_bookmark(repo, bmark):
529 head = bmarks[hgref(bmark)]
530 export_ref(repo, bmark, 'bookmarks', head)
532 def export_branch(repo, branch):
533 tip = get_branch_tip(repo, branch)
534 head = repo[tip]
535 export_ref(repo, branch, 'branches', head)
537 def export_head(repo):
538 global g_head
539 export_ref(repo, g_head[0], 'bookmarks', g_head[1])
541 def do_capabilities(parser):
542 global prefix, dirname
544 print "import"
545 print "export"
546 print "refspec refs/heads/branches/*:%s/branches/*" % prefix
547 print "refspec refs/heads/*:%s/bookmarks/*" % prefix
548 print "refspec refs/tags/*:%s/tags/*" % prefix
550 path = os.path.join(dirname, 'marks-git')
552 if os.path.exists(path):
553 print "*import-marks %s" % path
554 print "*export-marks %s" % path
556 print
558 def branch_tip(repo, branch):
559 # older versions of mercurial don't have this
560 if hasattr(repo, 'branchtip'):
561 return repo.branchtip(branch)
562 else:
563 return repo.branchtags()[branch]
565 def get_branch_tip(repo, branch):
566 global branches
568 heads = branches.get(hgref(branch), None)
569 if not heads:
570 return None
572 # verify there's only one head
573 if (len(heads) > 1):
574 warn("Branch '%s' has more than one head, consider merging" % branch)
575 return branch_tip(repo, hgref(branch))
577 return heads[0]
579 def list_head(repo, cur):
580 global g_head, bmarks, fake_bmark
582 if 'default' not in repo:
583 # empty repo
584 return
586 node = repo['default']
587 head = 'master' if not 'master' in bmarks else 'default'
588 fake_bmark = head
589 bmarks[head] = node
591 head = gitref(head)
592 print "@refs/heads/%s HEAD" % head
593 g_head = (head, node)
595 def do_list(parser):
596 global branches, bmarks, track_branches
598 repo = parser.repo
599 for bmark, node in bookmarks.listbookmarks(repo).iteritems():
600 bmarks[bmark] = repo[node]
602 cur = repo.dirstate.branch()
604 list_head(repo, cur)
606 if track_branches:
607 for branch in repo.branchmap():
608 heads = repo.branchheads(branch)
609 if len(heads):
610 branches[branch] = heads
612 for branch in branches:
613 print "? refs/heads/branches/%s" % gitref(branch)
615 for bmark in bmarks:
616 print "? refs/heads/%s" % gitref(bmark)
618 for tag, node in repo.tagslist():
619 if tag == 'tip':
620 continue
621 print "? refs/tags/%s" % gitref(tag)
623 print
625 def do_import(parser):
626 repo = parser.repo
628 path = os.path.join(dirname, 'marks-git')
630 print "feature done"
631 if os.path.exists(path):
632 print "feature import-marks=%s" % path
633 print "feature export-marks=%s" % path
634 print "feature force"
635 sys.stdout.flush()
637 tmp = encoding.encoding
638 encoding.encoding = 'utf-8'
640 # lets get all the import lines
641 while parser.check('import'):
642 ref = parser[1]
644 if (ref == 'HEAD'):
645 export_head(repo)
646 elif ref.startswith('refs/heads/branches/'):
647 branch = ref[len('refs/heads/branches/'):]
648 export_branch(repo, branch)
649 elif ref.startswith('refs/heads/'):
650 bmark = ref[len('refs/heads/'):]
651 export_bookmark(repo, bmark)
652 elif ref.startswith('refs/tags/'):
653 tag = ref[len('refs/tags/'):]
654 export_tag(repo, tag)
656 parser.next()
658 encoding.encoding = tmp
660 print 'done'
662 def parse_blob(parser):
663 global blob_marks
665 parser.next()
666 mark = parser.get_mark()
667 parser.next()
668 data = parser.get_data()
669 blob_marks[mark] = data
670 parser.next()
672 def get_merge_files(repo, p1, p2, files):
673 for e in repo[p1].files():
674 if e not in files:
675 if e not in repo[p1].manifest():
676 continue
677 f = { 'ctx' : repo[p1][e] }
678 files[e] = f
680 def parse_commit(parser):
681 global marks, blob_marks, parsed_refs
682 global mode
684 from_mark = merge_mark = None
686 ref = parser[1]
687 parser.next()
689 commit_mark = parser.get_mark()
690 parser.next()
691 author = parser.get_author()
692 parser.next()
693 committer = parser.get_author()
694 parser.next()
695 data = parser.get_data()
696 parser.next()
697 if parser.check('from'):
698 from_mark = parser.get_mark()
699 parser.next()
700 if parser.check('merge'):
701 merge_mark = parser.get_mark()
702 parser.next()
703 if parser.check('merge'):
704 die('octopus merges are not supported yet')
706 # fast-export adds an extra newline
707 if data[-1] == '\n':
708 data = data[:-1]
710 files = {}
712 for line in parser:
713 if parser.check('M'):
714 t, m, mark_ref, path = line.split(' ', 3)
715 mark = int(mark_ref[1:])
716 f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
717 elif parser.check('D'):
718 t, path = line.split(' ', 1)
719 f = { 'deleted' : True }
720 else:
721 die('Unknown file command: %s' % line)
722 files[path] = f
724 def getfilectx(repo, memctx, f):
725 of = files[f]
726 if 'deleted' in of:
727 raise IOError
728 if 'ctx' in of:
729 return of['ctx']
730 is_exec = of['mode'] == 'x'
731 is_link = of['mode'] == 'l'
732 rename = of.get('rename', None)
733 return context.memfilectx(f, of['data'],
734 is_link, is_exec, rename)
736 repo = parser.repo
738 user, date, tz = author
739 extra = {}
741 if committer != author:
742 extra['committer'] = "%s %u %u" % committer
744 if from_mark:
745 p1 = mark_to_rev(from_mark)
746 else:
747 p1 = '0' * 40
749 if merge_mark:
750 p2 = mark_to_rev(merge_mark)
751 else:
752 p2 = '0' * 40
755 # If files changed from any of the parents, hg wants to know, but in git if
756 # nothing changed from the first parent, nothing changed.
758 if merge_mark:
759 get_merge_files(repo, p1, p2, files)
761 # Check if the ref is supposed to be a named branch
762 if ref.startswith('refs/heads/branches/'):
763 branch = ref[len('refs/heads/branches/'):]
764 extra['branch'] = hgref(branch)
766 if mode == 'hg':
767 i = data.find('\n--HG--\n')
768 if i >= 0:
769 tmp = data[i + len('\n--HG--\n'):].strip()
770 for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
771 if k == 'rename':
772 old, new = v.split(' => ', 1)
773 files[new]['rename'] = old
774 elif k == 'branch':
775 extra[k] = v
776 elif k == 'extra':
777 ek, ev = v.split(' : ', 1)
778 extra[ek] = urllib.unquote(ev)
779 data = data[:i]
781 ctx = context.memctx(repo, (p1, p2), data,
782 files.keys(), getfilectx,
783 user, (date, tz), extra)
785 tmp = encoding.encoding
786 encoding.encoding = 'utf-8'
788 node = hghex(repo.commitctx(ctx))
790 encoding.encoding = tmp
792 parsed_refs[ref] = node
793 marks.new_mark(node, commit_mark)
795 def parse_reset(parser):
796 global parsed_refs
798 ref = parser[1]
799 parser.next()
800 # ugh
801 if parser.check('commit'):
802 parse_commit(parser)
803 return
804 if not parser.check('from'):
805 return
806 from_mark = parser.get_mark()
807 parser.next()
809 rev = mark_to_rev(from_mark)
810 parsed_refs[ref] = rev
812 def parse_tag(parser):
813 name = parser[1]
814 parser.next()
815 from_mark = parser.get_mark()
816 parser.next()
817 tagger = parser.get_author()
818 parser.next()
819 data = parser.get_data()
820 parser.next()
822 parsed_tags[name] = (tagger, data)
824 def write_tag(repo, tag, node, msg, author):
825 branch = repo[node].branch()
826 tip = branch_tip(repo, branch)
827 tip = repo[tip]
829 def getfilectx(repo, memctx, f):
830 try:
831 fctx = tip.filectx(f)
832 data = fctx.data()
833 except error.ManifestLookupError:
834 data = ""
835 content = data + "%s %s\n" % (node, tag)
836 return context.memfilectx(f, content, False, False, None)
838 p1 = tip.hex()
839 p2 = '0' * 40
840 if not author:
841 author = (None, 0, 0)
842 user, date, tz = author
844 ctx = context.memctx(repo, (p1, p2), msg,
845 ['.hgtags'], getfilectx,
846 user, (date, tz), {'branch' : branch})
848 tmp = encoding.encoding
849 encoding.encoding = 'utf-8'
851 tagnode = repo.commitctx(ctx)
853 encoding.encoding = tmp
855 return tagnode
857 def push_unsafe(repo, remote, parsed_refs, p_revs):
859 force = force_push
861 fci = discovery.findcommonincoming
862 commoninc = fci(repo, remote, force=force)
863 common, _, remoteheads = commoninc
865 # TODO checkheads
867 cg = repo.getbundle('push', heads=list(p_revs), common=common)
869 unbundle = remote.capable('unbundle')
870 if unbundle:
871 if force:
872 remoteheads = ['force']
873 return remote.unbundle(cg, remoteheads, 'push')
874 else:
875 return remote.addchangegroup(cg, 'push', repo.url())
877 def push(repo, remote, parsed_refs, p_revs):
878 if hasattr(remote, 'canpush') and not remote.canpush():
879 print "error cannot push"
881 if not p_revs:
882 # nothing to push
883 return
885 lock = None
886 unbundle = remote.capable('unbundle')
887 if not unbundle:
888 lock = remote.lock()
889 try:
890 ret = push_unsafe(repo, remote, parsed_refs, p_revs)
891 finally:
892 if lock is not None:
893 lock.release()
895 return ret
897 def do_export(parser):
898 global parsed_refs, bmarks, peer
900 p_bmarks = []
901 p_revs = set()
903 parser.next()
905 for line in parser.each_block('done'):
906 if parser.check('blob'):
907 parse_blob(parser)
908 elif parser.check('commit'):
909 parse_commit(parser)
910 elif parser.check('reset'):
911 parse_reset(parser)
912 elif parser.check('tag'):
913 parse_tag(parser)
914 elif parser.check('feature'):
915 pass
916 else:
917 die('unhandled export command: %s' % line)
919 for ref, node in parsed_refs.iteritems():
920 bnode = hgbin(node)
921 if ref.startswith('refs/heads/branches'):
922 branch = ref[len('refs/heads/branches/'):]
923 if branch in branches and bnode in branches[branch]:
924 # up to date
925 continue
926 p_revs.add(bnode)
927 print "ok %s" % ref
928 elif ref.startswith('refs/heads/'):
929 bmark = ref[len('refs/heads/'):]
930 new = node
931 old = bmarks[bmark].hex() if bmark in bmarks else ''
933 if old == new:
934 continue
936 print "ok %s" % ref
937 if bmark != fake_bmark and \
938 not (bmark == 'master' and bmark not in parser.repo._bookmarks):
939 p_bmarks.append((ref, bmark, old, new))
941 p_revs.add(bnode)
942 elif ref.startswith('refs/tags/'):
943 tag = ref[len('refs/tags/'):]
944 tag = hgref(tag)
945 author, msg = parsed_tags.get(tag, (None, None))
946 if mode == 'git':
947 if not msg:
948 msg = 'Added tag %s for changeset %s' % (tag, node[:12]);
949 tagnode = write_tag(parser.repo, tag, node, msg, author)
950 p_revs.add(tagnode)
951 else:
952 fp = parser.repo.opener('localtags', 'a')
953 fp.write('%s %s\n' % (node, tag))
954 fp.close()
955 p_revs.add(bnode)
956 print "ok %s" % ref
957 else:
958 # transport-helper/fast-export bugs
959 continue
961 if peer:
962 push(parser.repo, peer, parsed_refs, p_revs)
964 # update remote bookmarks
965 remote_bmarks = peer.listkeys('bookmarks')
966 for ref, bmark, old, new in p_bmarks:
967 if force_push:
968 old = remote_bmarks.get(bmark, '')
969 if not peer.pushkey('bookmarks', bmark, old, new):
970 print "error %s" % ref
971 else:
972 # update local bookmarks
973 for ref, bmark, old, new in p_bmarks:
974 if not bookmarks.pushbookmark(parser.repo, bmark, old, new):
975 print "error %s" % ref
977 print
979 def fix_path(alias, repo, orig_url):
980 url = urlparse.urlparse(orig_url, 'file')
981 if url.scheme != 'file' or os.path.isabs(url.path):
982 return
983 abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
984 cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
985 subprocess.call(cmd)
987 def main(args):
988 global prefix, gitdir, dirname, branches, bmarks
989 global marks, blob_marks, parsed_refs
990 global peer, mode, bad_mail, bad_name
991 global track_branches, force_push, is_tmp
992 global parsed_tags
993 global filenodes
994 global fake_bmark
996 alias = args[1]
997 url = args[2]
998 peer = None
1000 hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
1001 track_branches = get_config_bool('remote-hg.track-branches', True)
1002 force_push = get_config_bool('remote-hg.force-push')
1004 if hg_git_compat:
1005 mode = 'hg'
1006 bad_mail = 'none@none'
1007 bad_name = ''
1008 else:
1009 mode = 'git'
1010 bad_mail = 'unknown'
1011 bad_name = 'Unknown'
1013 if alias[4:] == url:
1014 is_tmp = True
1015 alias = hashlib.sha1(alias).hexdigest()
1016 else:
1017 is_tmp = False
1019 gitdir = os.environ['GIT_DIR']
1020 dirname = os.path.join(gitdir, 'hg', alias)
1021 branches = {}
1022 bmarks = {}
1023 blob_marks = {}
1024 parsed_refs = {}
1025 marks = None
1026 parsed_tags = {}
1027 filenodes = {}
1028 fake_bmark = None
1030 repo = get_repo(url, alias)
1031 prefix = 'refs/hg/%s' % alias
1033 if not is_tmp:
1034 fix_path(alias, peer or repo, url)
1036 marks_path = os.path.join(dirname, 'marks-hg')
1037 marks = Marks(marks_path, repo)
1039 if sys.platform == 'win32':
1040 import msvcrt
1041 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
1043 parser = Parser(repo)
1044 for line in parser:
1045 if parser.check('capabilities'):
1046 do_capabilities(parser)
1047 elif parser.check('list'):
1048 do_list(parser)
1049 elif parser.check('import'):
1050 do_import(parser)
1051 elif parser.check('export'):
1052 do_export(parser)
1053 else:
1054 die('unhandled command: %s' % line)
1055 sys.stdout.flush()
1057 def bye():
1058 if not marks:
1059 return
1060 if not is_tmp:
1061 marks.store()
1062 else:
1063 shutil.rmtree(dirname)
1065 atexit.register(bye)
1066 sys.exit(main(sys.argv))