remote-hg: add version checks to the marks
[git/jrn.git] / contrib / remote-helpers / git-remote-hg
blobe2bef7ffa7de7383195d045fdb3e6b184bad3a02
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 = 1
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(node):
78 return hg.node.hex(node)
80 def hgbin(node):
81 return hg.node.bin(node)
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):
107 self.path = path
108 self.clear()
109 self.load()
111 if self.version < VERSION:
112 self.clear()
113 self.version = VERSION
115 def clear(self):
116 self.tips = {}
117 self.marks = {}
118 self.rev_marks = {}
119 self.last_mark = 0
120 self.version = 0
122 def load(self):
123 if not os.path.exists(self.path):
124 return
126 tmp = json.load(open(self.path))
128 self.tips = tmp['tips']
129 self.marks = tmp['marks']
130 self.last_mark = tmp['last-mark']
131 self.version = tmp.get('version', 1)
133 for rev, mark in self.marks.iteritems():
134 self.rev_marks[mark] = int(rev)
136 def dict(self):
137 return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark, 'version' : self.version }
139 def store(self):
140 json.dump(self.dict(), open(self.path, 'w'))
142 def __str__(self):
143 return str(self.dict())
145 def from_rev(self, rev):
146 return self.marks[str(rev)]
148 def to_rev(self, mark):
149 return self.rev_marks[mark]
151 def next_mark(self):
152 self.last_mark += 1
153 return self.last_mark
155 def get_mark(self, rev):
156 self.last_mark += 1
157 self.marks[str(rev)] = self.last_mark
158 return self.last_mark
160 def new_mark(self, rev, mark):
161 self.marks[str(rev)] = mark
162 self.rev_marks[mark] = rev
163 self.last_mark = mark
165 def is_marked(self, rev):
166 return str(rev) in self.marks
168 def get_tip(self, branch):
169 return self.tips.get(branch, 0)
171 def set_tip(self, branch, tip):
172 self.tips[branch] = tip
174 class Parser:
176 def __init__(self, repo):
177 self.repo = repo
178 self.line = self.get_line()
180 def get_line(self):
181 return sys.stdin.readline().strip()
183 def __getitem__(self, i):
184 return self.line.split()[i]
186 def check(self, word):
187 return self.line.startswith(word)
189 def each_block(self, separator):
190 while self.line != separator:
191 yield self.line
192 self.line = self.get_line()
194 def __iter__(self):
195 return self.each_block('')
197 def next(self):
198 self.line = self.get_line()
199 if self.line == 'done':
200 self.line = None
202 def get_mark(self):
203 i = self.line.index(':') + 1
204 return int(self.line[i:])
206 def get_data(self):
207 if not self.check('data'):
208 return None
209 i = self.line.index(' ') + 1
210 size = int(self.line[i:])
211 return sys.stdin.read(size)
213 def get_author(self):
214 global bad_mail
216 ex = None
217 m = RAW_AUTHOR_RE.match(self.line)
218 if not m:
219 return None
220 _, name, email, date, tz = m.groups()
221 if name and 'ext:' in name:
222 m = re.match('^(.+?) ext:\((.+)\)$', name)
223 if m:
224 name = m.group(1)
225 ex = urllib.unquote(m.group(2))
227 if email != bad_mail:
228 if name:
229 user = '%s <%s>' % (name, email)
230 else:
231 user = '<%s>' % (email)
232 else:
233 user = name
235 if ex:
236 user += ex
238 tz = int(tz)
239 tz = ((tz / 100) * 3600) + ((tz % 100) * 60)
240 return (user, int(date), -tz)
242 def fix_file_path(path):
243 if not os.path.isabs(path):
244 return path
245 return os.path.relpath(path, '/')
247 def export_files(files):
248 global marks, filenodes
250 final = []
251 for f in files:
252 fid = node.hex(f.filenode())
254 if fid in filenodes:
255 mark = filenodes[fid]
256 else:
257 mark = marks.next_mark()
258 filenodes[fid] = mark
259 d = f.data()
261 print "blob"
262 print "mark :%u" % mark
263 print "data %d" % len(d)
264 print d
266 path = fix_file_path(f.path())
267 final.append((gitmode(f.flags()), mark, path))
269 return final
271 def get_filechanges(repo, ctx, parent):
272 modified = set()
273 added = set()
274 removed = set()
276 # load earliest manifest first for caching reasons
277 prev = parent.manifest().copy()
278 cur = ctx.manifest()
280 for fn in cur:
281 if fn in prev:
282 if (cur.flags(fn) != prev.flags(fn) or cur[fn] != prev[fn]):
283 modified.add(fn)
284 del prev[fn]
285 else:
286 added.add(fn)
287 removed |= set(prev.keys())
289 return added | modified, removed
291 def fixup_user_git(user):
292 name = mail = None
293 user = user.replace('"', '')
294 m = AUTHOR_RE.match(user)
295 if m:
296 name = m.group(1)
297 mail = m.group(2).strip()
298 else:
299 m = EMAIL_RE.match(user)
300 if m:
301 name = m.group(1)
302 mail = m.group(2)
303 else:
304 m = NAME_RE.match(user)
305 if m:
306 name = m.group(1).strip()
307 return (name, mail)
309 def fixup_user_hg(user):
310 def sanitize(name):
311 # stole this from hg-git
312 return re.sub('[<>\n]', '?', name.lstrip('< ').rstrip('> '))
314 m = AUTHOR_HG_RE.match(user)
315 if m:
316 name = sanitize(m.group(1))
317 mail = sanitize(m.group(2))
318 ex = m.group(3)
319 if ex:
320 name += ' ext:(' + urllib.quote(ex) + ')'
321 else:
322 name = sanitize(user)
323 if '@' in user:
324 mail = name
325 else:
326 mail = None
328 return (name, mail)
330 def fixup_user(user):
331 global mode, bad_mail
333 if mode == 'git':
334 name, mail = fixup_user_git(user)
335 else:
336 name, mail = fixup_user_hg(user)
338 if not name:
339 name = bad_name
340 if not mail:
341 mail = bad_mail
343 return '%s <%s>' % (name, mail)
345 def get_repo(url, alias):
346 global dirname, peer
348 myui = ui.ui()
349 myui.setconfig('ui', 'interactive', 'off')
350 myui.fout = sys.stderr
352 if get_config_bool('remote-hg.insecure'):
353 myui.setconfig('web', 'cacerts', '')
355 extensions.loadall(myui)
357 if hg.islocal(url):
358 repo = hg.repository(myui, url)
359 if not os.path.exists(dirname):
360 os.makedirs(dirname)
361 else:
362 shared_path = os.path.join(gitdir, 'hg')
363 if not os.path.exists(shared_path):
364 try:
365 hg.clone(myui, {}, url, shared_path, update=False, pull=True)
366 except:
367 die('Repository error')
369 if not os.path.exists(dirname):
370 os.makedirs(dirname)
372 local_path = os.path.join(dirname, 'clone')
373 if not os.path.exists(local_path):
374 hg.share(myui, shared_path, local_path, update=False)
376 repo = hg.repository(myui, local_path)
377 try:
378 peer = hg.peer(myui, {}, url)
379 except:
380 die('Repository error')
381 repo.pull(peer, heads=None, force=True)
383 return repo
385 def rev_to_mark(rev):
386 global marks
387 return marks.from_rev(rev)
389 def mark_to_rev(mark):
390 global marks
391 return marks.to_rev(mark)
393 def export_ref(repo, name, kind, head):
394 global prefix, marks, mode
396 ename = '%s/%s' % (kind, name)
397 tip = marks.get_tip(ename)
399 revs = xrange(tip, head.rev() + 1)
400 count = 0
402 for rev in revs:
404 c = repo[rev]
405 node = c.node()
407 if marks.is_marked(c.hex()):
408 count += 1
409 continue
411 (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(node)
412 rev_branch = extra['branch']
414 author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
415 if 'committer' in extra:
416 user, time, tz = extra['committer'].rsplit(' ', 2)
417 committer = "%s %s %s" % (user, time, gittz(int(tz)))
418 else:
419 committer = author
421 parents = [repo[p] for p in repo.changelog.parentrevs(rev) if p >= 0]
423 if len(parents) == 0:
424 modified = c.manifest().keys()
425 removed = []
426 else:
427 modified, removed = get_filechanges(repo, c, parents[0])
429 desc += '\n'
431 if mode == 'hg':
432 extra_msg = ''
434 if rev_branch != 'default':
435 extra_msg += 'branch : %s\n' % rev_branch
437 renames = []
438 for f in c.files():
439 if f not in c.manifest():
440 continue
441 rename = c.filectx(f).renamed()
442 if rename:
443 renames.append((rename[0], f))
445 for e in renames:
446 extra_msg += "rename : %s => %s\n" % e
448 for key, value in extra.iteritems():
449 if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):
450 continue
451 else:
452 extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value))
454 if extra_msg:
455 desc += '\n--HG--\n' + extra_msg
457 if len(parents) == 0 and rev:
458 print 'reset %s/%s' % (prefix, ename)
460 modified_final = export_files(c.filectx(f) for f in modified)
462 print "commit %s/%s" % (prefix, ename)
463 print "mark :%d" % (marks.get_mark(rev))
464 print "author %s" % (author)
465 print "committer %s" % (committer)
466 print "data %d" % (len(desc))
467 print desc
469 if len(parents) > 0:
470 print "from :%s" % (rev_to_mark(parents[0].rev()))
471 if len(parents) > 1:
472 print "merge :%s" % (rev_to_mark(parents[1].rev()))
474 for f in modified_final:
475 print "M %s :%u %s" % f
476 for f in removed:
477 print "D %s" % (fix_file_path(f))
478 print
480 count += 1
481 if (count % 100 == 0):
482 print "progress revision %d '%s' (%d/%d)" % (rev, name, count, len(revs))
484 # make sure the ref is updated
485 print "reset %s/%s" % (prefix, ename)
486 print "from :%u" % rev_to_mark(head.rev())
487 print
489 marks.set_tip(ename, head.rev())
491 def export_tag(repo, tag):
492 export_ref(repo, tag, 'tags', repo[hgref(tag)])
494 def export_bookmark(repo, bmark):
495 head = bmarks[hgref(bmark)]
496 export_ref(repo, bmark, 'bookmarks', head)
498 def export_branch(repo, branch):
499 tip = get_branch_tip(repo, branch)
500 head = repo[tip]
501 export_ref(repo, branch, 'branches', head)
503 def export_head(repo):
504 global g_head
505 export_ref(repo, g_head[0], 'bookmarks', g_head[1])
507 def do_capabilities(parser):
508 global prefix, dirname
510 print "import"
511 print "export"
512 print "refspec refs/heads/branches/*:%s/branches/*" % prefix
513 print "refspec refs/heads/*:%s/bookmarks/*" % prefix
514 print "refspec refs/tags/*:%s/tags/*" % prefix
516 path = os.path.join(dirname, 'marks-git')
518 if os.path.exists(path):
519 print "*import-marks %s" % path
520 print "*export-marks %s" % path
522 print
524 def branch_tip(repo, branch):
525 # older versions of mercurial don't have this
526 if hasattr(repo, 'branchtip'):
527 return repo.branchtip(branch)
528 else:
529 return repo.branchtags()[branch]
531 def get_branch_tip(repo, branch):
532 global branches
534 heads = branches.get(hgref(branch), None)
535 if not heads:
536 return None
538 # verify there's only one head
539 if (len(heads) > 1):
540 warn("Branch '%s' has more than one head, consider merging" % branch)
541 return branch_tip(repo, hgref(branch))
543 return heads[0]
545 def list_head(repo, cur):
546 global g_head, bmarks
548 head = bookmarks.readcurrent(repo)
549 if head:
550 node = repo[head]
551 else:
552 # fake bookmark from current branch
553 head = cur
554 node = repo['.']
555 if not node:
556 node = repo['tip']
557 if not node:
558 return
559 if head == 'default':
560 head = 'master'
561 bmarks[head] = node
563 head = gitref(head)
564 print "@refs/heads/%s HEAD" % head
565 g_head = (head, node)
567 def do_list(parser):
568 global branches, bmarks, track_branches
570 repo = parser.repo
571 for bmark, node in bookmarks.listbookmarks(repo).iteritems():
572 bmarks[bmark] = repo[node]
574 cur = repo.dirstate.branch()
576 list_head(repo, cur)
578 if track_branches:
579 for branch in repo.branchmap():
580 heads = repo.branchheads(branch)
581 if len(heads):
582 branches[branch] = heads
584 for branch in branches:
585 print "? refs/heads/branches/%s" % gitref(branch)
587 for bmark in bmarks:
588 print "? refs/heads/%s" % gitref(bmark)
590 for tag, node in repo.tagslist():
591 if tag == 'tip':
592 continue
593 print "? refs/tags/%s" % gitref(tag)
595 print
597 def do_import(parser):
598 repo = parser.repo
600 path = os.path.join(dirname, 'marks-git')
602 print "feature done"
603 if os.path.exists(path):
604 print "feature import-marks=%s" % path
605 print "feature export-marks=%s" % path
606 sys.stdout.flush()
608 tmp = encoding.encoding
609 encoding.encoding = 'utf-8'
611 # lets get all the import lines
612 while parser.check('import'):
613 ref = parser[1]
615 if (ref == 'HEAD'):
616 export_head(repo)
617 elif ref.startswith('refs/heads/branches/'):
618 branch = ref[len('refs/heads/branches/'):]
619 export_branch(repo, branch)
620 elif ref.startswith('refs/heads/'):
621 bmark = ref[len('refs/heads/'):]
622 export_bookmark(repo, bmark)
623 elif ref.startswith('refs/tags/'):
624 tag = ref[len('refs/tags/'):]
625 export_tag(repo, tag)
627 parser.next()
629 encoding.encoding = tmp
631 print 'done'
633 def parse_blob(parser):
634 global blob_marks
636 parser.next()
637 mark = parser.get_mark()
638 parser.next()
639 data = parser.get_data()
640 blob_marks[mark] = data
641 parser.next()
643 def get_merge_files(repo, p1, p2, files):
644 for e in repo[p1].files():
645 if e not in files:
646 if e not in repo[p1].manifest():
647 continue
648 f = { 'ctx' : repo[p1][e] }
649 files[e] = f
651 def parse_commit(parser):
652 global marks, blob_marks, parsed_refs
653 global mode
655 from_mark = merge_mark = None
657 ref = parser[1]
658 parser.next()
660 commit_mark = parser.get_mark()
661 parser.next()
662 author = parser.get_author()
663 parser.next()
664 committer = parser.get_author()
665 parser.next()
666 data = parser.get_data()
667 parser.next()
668 if parser.check('from'):
669 from_mark = parser.get_mark()
670 parser.next()
671 if parser.check('merge'):
672 merge_mark = parser.get_mark()
673 parser.next()
674 if parser.check('merge'):
675 die('octopus merges are not supported yet')
677 # fast-export adds an extra newline
678 if data[-1] == '\n':
679 data = data[:-1]
681 files = {}
683 for line in parser:
684 if parser.check('M'):
685 t, m, mark_ref, path = line.split(' ', 3)
686 mark = int(mark_ref[1:])
687 f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
688 elif parser.check('D'):
689 t, path = line.split(' ', 1)
690 f = { 'deleted' : True }
691 else:
692 die('Unknown file command: %s' % line)
693 files[path] = f
695 def getfilectx(repo, memctx, f):
696 of = files[f]
697 if 'deleted' in of:
698 raise IOError
699 if 'ctx' in of:
700 return of['ctx']
701 is_exec = of['mode'] == 'x'
702 is_link = of['mode'] == 'l'
703 rename = of.get('rename', None)
704 return context.memfilectx(f, of['data'],
705 is_link, is_exec, rename)
707 repo = parser.repo
709 user, date, tz = author
710 extra = {}
712 if committer != author:
713 extra['committer'] = "%s %u %u" % committer
715 if from_mark:
716 p1 = repo.changelog.node(mark_to_rev(from_mark))
717 else:
718 p1 = '\0' * 20
720 if merge_mark:
721 p2 = repo.changelog.node(mark_to_rev(merge_mark))
722 else:
723 p2 = '\0' * 20
726 # If files changed from any of the parents, hg wants to know, but in git if
727 # nothing changed from the first parent, nothing changed.
729 if merge_mark:
730 get_merge_files(repo, p1, p2, files)
732 # Check if the ref is supposed to be a named branch
733 if ref.startswith('refs/heads/branches/'):
734 branch = ref[len('refs/heads/branches/'):]
735 extra['branch'] = hgref(branch)
737 if mode == 'hg':
738 i = data.find('\n--HG--\n')
739 if i >= 0:
740 tmp = data[i + len('\n--HG--\n'):].strip()
741 for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
742 if k == 'rename':
743 old, new = v.split(' => ', 1)
744 files[new]['rename'] = old
745 elif k == 'branch':
746 extra[k] = v
747 elif k == 'extra':
748 ek, ev = v.split(' : ', 1)
749 extra[ek] = urllib.unquote(ev)
750 data = data[:i]
752 ctx = context.memctx(repo, (p1, p2), data,
753 files.keys(), getfilectx,
754 user, (date, tz), extra)
756 tmp = encoding.encoding
757 encoding.encoding = 'utf-8'
759 node = hghex(repo.commitctx(ctx))
761 encoding.encoding = tmp
763 rev = repo[node].rev()
765 parsed_refs[ref] = node
766 marks.new_mark(rev, commit_mark)
768 def parse_reset(parser):
769 global parsed_refs
771 ref = parser[1]
772 parser.next()
773 # ugh
774 if parser.check('commit'):
775 parse_commit(parser)
776 return
777 if not parser.check('from'):
778 return
779 from_mark = parser.get_mark()
780 parser.next()
782 node = parser.repo.changelog.node(mark_to_rev(from_mark))
783 parsed_refs[ref] = hghex(node)
785 def parse_tag(parser):
786 name = parser[1]
787 parser.next()
788 from_mark = parser.get_mark()
789 parser.next()
790 tagger = parser.get_author()
791 parser.next()
792 data = parser.get_data()
793 parser.next()
795 parsed_tags[name] = (tagger, data)
797 def write_tag(repo, tag, node, msg, author):
798 branch = repo[node].branch()
799 tip = branch_tip(repo, branch)
800 tip = repo[tip]
802 def getfilectx(repo, memctx, f):
803 try:
804 fctx = tip.filectx(f)
805 data = fctx.data()
806 except error.ManifestLookupError:
807 data = ""
808 content = data + "%s %s\n" % (node, tag)
809 return context.memfilectx(f, content, False, False, None)
811 p1 = tip.hex()
812 p2 = '\0' * 20
813 if not author:
814 author = (None, 0, 0)
815 user, date, tz = author
817 ctx = context.memctx(repo, (p1, p2), msg,
818 ['.hgtags'], getfilectx,
819 user, (date, tz), {'branch' : branch})
821 tmp = encoding.encoding
822 encoding.encoding = 'utf-8'
824 tagnode = repo.commitctx(ctx)
826 encoding.encoding = tmp
828 return tagnode
830 def do_export(parser):
831 global parsed_refs, bmarks, peer
833 p_bmarks = []
835 parser.next()
837 for line in parser.each_block('done'):
838 if parser.check('blob'):
839 parse_blob(parser)
840 elif parser.check('commit'):
841 parse_commit(parser)
842 elif parser.check('reset'):
843 parse_reset(parser)
844 elif parser.check('tag'):
845 parse_tag(parser)
846 elif parser.check('feature'):
847 pass
848 else:
849 die('unhandled export command: %s' % line)
851 for ref, node in parsed_refs.iteritems():
852 bnode = hgbin(node)
853 if ref.startswith('refs/heads/branches'):
854 branch = ref[len('refs/heads/branches/'):]
855 if branch in branches and bnode in branches[branch]:
856 # up to date
857 continue
858 print "ok %s" % ref
859 elif ref.startswith('refs/heads/'):
860 bmark = ref[len('refs/heads/'):]
861 p_bmarks.append((bmark, node))
862 continue
863 elif ref.startswith('refs/tags/'):
864 tag = ref[len('refs/tags/'):]
865 tag = hgref(tag)
866 author, msg = parsed_tags.get(tag, (None, None))
867 if mode == 'git':
868 if not msg:
869 msg = 'Added tag %s for changeset %s' % (tag, node[:12]);
870 write_tag(parser.repo, tag, node, msg, author)
871 else:
872 fp = parser.repo.opener('localtags', 'a')
873 fp.write('%s %s\n' % (node, tag))
874 fp.close()
875 print "ok %s" % ref
876 else:
877 # transport-helper/fast-export bugs
878 continue
880 if peer:
881 parser.repo.push(peer, force=force_push, newbranch=True)
882 remote_bmarks = peer.listkeys('bookmarks')
884 # handle bookmarks
885 for bmark, node in p_bmarks:
886 ref = 'refs/heads/' + bmark
887 new = node
889 if bmark in bmarks:
890 old = bmarks[bmark].hex()
891 else:
892 old = ''
894 if old == new:
895 continue
897 if bmark == 'master' and 'master' not in parser.repo._bookmarks:
898 # fake bookmark
899 print "ok %s" % ref
900 continue
901 elif bookmarks.pushbookmark(parser.repo, bmark, old, new):
902 # updated locally
903 pass
904 else:
905 print "error %s" % ref
906 continue
908 if peer:
909 old = remote_bmarks.get(bmark, '')
910 if not peer.pushkey('bookmarks', bmark, old, new):
911 print "error %s" % ref
912 continue
914 print "ok %s" % ref
916 print
918 def fix_path(alias, repo, orig_url):
919 url = urlparse.urlparse(orig_url, 'file')
920 if url.scheme != 'file' or os.path.isabs(url.path):
921 return
922 abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
923 cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
924 subprocess.call(cmd)
926 def main(args):
927 global prefix, gitdir, dirname, branches, bmarks
928 global marks, blob_marks, parsed_refs
929 global peer, mode, bad_mail, bad_name
930 global track_branches, force_push, is_tmp
931 global parsed_tags
932 global filenodes
934 alias = args[1]
935 url = args[2]
936 peer = None
938 hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
939 track_branches = get_config_bool('remote-hg.track-branches', True)
940 force_push = get_config_bool('remote-hg.force-push')
942 if hg_git_compat:
943 mode = 'hg'
944 bad_mail = 'none@none'
945 bad_name = ''
946 else:
947 mode = 'git'
948 bad_mail = 'unknown'
949 bad_name = 'Unknown'
951 if alias[4:] == url:
952 is_tmp = True
953 alias = hashlib.sha1(alias).hexdigest()
954 else:
955 is_tmp = False
957 gitdir = os.environ['GIT_DIR']
958 dirname = os.path.join(gitdir, 'hg', alias)
959 branches = {}
960 bmarks = {}
961 blob_marks = {}
962 parsed_refs = {}
963 marks = None
964 parsed_tags = {}
965 filenodes = {}
967 repo = get_repo(url, alias)
968 prefix = 'refs/hg/%s' % alias
970 if not is_tmp:
971 fix_path(alias, peer or repo, url)
973 marks_path = os.path.join(dirname, 'marks-hg')
974 marks = Marks(marks_path)
976 if sys.platform == 'win32':
977 import msvcrt
978 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
980 parser = Parser(repo)
981 for line in parser:
982 if parser.check('capabilities'):
983 do_capabilities(parser)
984 elif parser.check('list'):
985 do_list(parser)
986 elif parser.check('import'):
987 do_import(parser)
988 elif parser.check('export'):
989 do_export(parser)
990 else:
991 die('unhandled command: %s' % line)
992 sys.stdout.flush()
994 def bye():
995 if not marks:
996 return
997 if not is_tmp:
998 marks.store()
999 else:
1000 shutil.rmtree(dirname)
1002 atexit.register(bye)
1003 sys.exit(main(sys.argv))