remote-hg: only update necessary revisions
[alt-git.git] / contrib / remote-helpers / git-remote-hg
blob7added3b332d1f36af3e18e80bbaa8abcdcbe4b0
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
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 do_export(parser):
858 global parsed_refs, bmarks, peer
860 p_bmarks = []
861 p_revs = set()
863 parser.next()
865 for line in parser.each_block('done'):
866 if parser.check('blob'):
867 parse_blob(parser)
868 elif parser.check('commit'):
869 parse_commit(parser)
870 elif parser.check('reset'):
871 parse_reset(parser)
872 elif parser.check('tag'):
873 parse_tag(parser)
874 elif parser.check('feature'):
875 pass
876 else:
877 die('unhandled export command: %s' % line)
879 for ref, node in parsed_refs.iteritems():
880 bnode = hgbin(node)
881 if ref.startswith('refs/heads/branches'):
882 branch = ref[len('refs/heads/branches/'):]
883 if branch in branches and bnode in branches[branch]:
884 # up to date
885 continue
886 p_revs.add(bnode)
887 print "ok %s" % ref
888 elif ref.startswith('refs/heads/'):
889 bmark = ref[len('refs/heads/'):]
890 new = node
891 old = bmarks[bmark].hex() if bmark in bmarks else ''
893 if old == new:
894 continue
896 print "ok %s" % ref
897 if bmark != fake_bmark and \
898 not (bmark == 'master' and bmark not in parser.repo._bookmarks):
899 p_bmarks.append((ref, bmark, old, new))
901 p_revs.add(bnode)
902 elif ref.startswith('refs/tags/'):
903 tag = ref[len('refs/tags/'):]
904 tag = hgref(tag)
905 author, msg = parsed_tags.get(tag, (None, None))
906 if mode == 'git':
907 if not msg:
908 msg = 'Added tag %s for changeset %s' % (tag, node[:12]);
909 tagnode = write_tag(parser.repo, tag, node, msg, author)
910 p_revs.add(tagnode)
911 else:
912 fp = parser.repo.opener('localtags', 'a')
913 fp.write('%s %s\n' % (node, tag))
914 fp.close()
915 p_revs.add(bnode)
916 print "ok %s" % ref
917 else:
918 # transport-helper/fast-export bugs
919 continue
921 if peer:
922 parser.repo.push(peer, force=force_push, newbranch=True, revs=list(p_revs))
924 # update remote bookmarks
925 remote_bmarks = peer.listkeys('bookmarks')
926 for ref, bmark, old, new in p_bmarks:
927 if force_push:
928 old = remote_bmarks.get(bmark, '')
929 if not peer.pushkey('bookmarks', bmark, old, new):
930 print "error %s" % ref
931 else:
932 # update local bookmarks
933 for ref, bmark, old, new in p_bmarks:
934 if not bookmarks.pushbookmark(parser.repo, bmark, old, new):
935 print "error %s" % ref
937 print
939 def fix_path(alias, repo, orig_url):
940 url = urlparse.urlparse(orig_url, 'file')
941 if url.scheme != 'file' or os.path.isabs(url.path):
942 return
943 abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
944 cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
945 subprocess.call(cmd)
947 def main(args):
948 global prefix, gitdir, dirname, branches, bmarks
949 global marks, blob_marks, parsed_refs
950 global peer, mode, bad_mail, bad_name
951 global track_branches, force_push, is_tmp
952 global parsed_tags
953 global filenodes
954 global fake_bmark
956 alias = args[1]
957 url = args[2]
958 peer = None
960 hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
961 track_branches = get_config_bool('remote-hg.track-branches', True)
962 force_push = get_config_bool('remote-hg.force-push')
964 if hg_git_compat:
965 mode = 'hg'
966 bad_mail = 'none@none'
967 bad_name = ''
968 else:
969 mode = 'git'
970 bad_mail = 'unknown'
971 bad_name = 'Unknown'
973 if alias[4:] == url:
974 is_tmp = True
975 alias = hashlib.sha1(alias).hexdigest()
976 else:
977 is_tmp = False
979 gitdir = os.environ['GIT_DIR']
980 dirname = os.path.join(gitdir, 'hg', alias)
981 branches = {}
982 bmarks = {}
983 blob_marks = {}
984 parsed_refs = {}
985 marks = None
986 parsed_tags = {}
987 filenodes = {}
988 fake_bmark = None
990 repo = get_repo(url, alias)
991 prefix = 'refs/hg/%s' % alias
993 if not is_tmp:
994 fix_path(alias, peer or repo, url)
996 marks_path = os.path.join(dirname, 'marks-hg')
997 marks = Marks(marks_path, repo)
999 if sys.platform == 'win32':
1000 import msvcrt
1001 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
1003 parser = Parser(repo)
1004 for line in parser:
1005 if parser.check('capabilities'):
1006 do_capabilities(parser)
1007 elif parser.check('list'):
1008 do_list(parser)
1009 elif parser.check('import'):
1010 do_import(parser)
1011 elif parser.check('export'):
1012 do_export(parser)
1013 else:
1014 die('unhandled command: %s' % line)
1015 sys.stdout.flush()
1017 def bye():
1018 if not marks:
1019 return
1020 if not is_tmp:
1021 marks.store()
1022 else:
1023 shutil.rmtree(dirname)
1025 atexit.register(bye)
1026 sys.exit(main(sys.argv))