remote-hg: always point HEAD to master
[git/gitweb.git] / contrib / remote-helpers / git-remote-hg
blob7c859b82e896de04976b6a87499733151bb6ba6a
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 get_repo(url, alias):
360 global dirname, peer
362 myui = ui.ui()
363 myui.setconfig('ui', 'interactive', 'off')
364 myui.fout = sys.stderr
366 if get_config_bool('remote-hg.insecure'):
367 myui.setconfig('web', 'cacerts', '')
369 extensions.loadall(myui)
371 if hg.islocal(url):
372 repo = hg.repository(myui, url)
373 if not os.path.exists(dirname):
374 os.makedirs(dirname)
375 else:
376 shared_path = os.path.join(gitdir, 'hg')
377 if not os.path.exists(shared_path):
378 try:
379 hg.clone(myui, {}, url, shared_path, update=False, pull=True)
380 except:
381 die('Repository error')
383 if not os.path.exists(dirname):
384 os.makedirs(dirname)
386 local_path = os.path.join(dirname, 'clone')
387 if not os.path.exists(local_path):
388 hg.share(myui, shared_path, local_path, update=False)
390 repo = hg.repository(myui, local_path)
391 try:
392 peer = hg.peer(myui, {}, url)
393 except:
394 die('Repository error')
395 repo.pull(peer, heads=None, force=True)
397 return repo
399 def rev_to_mark(rev):
400 global marks
401 return marks.from_rev(rev.hex())
403 def mark_to_rev(mark):
404 global marks
405 return marks.to_rev(mark)
407 def export_ref(repo, name, kind, head):
408 global prefix, marks, mode
410 ename = '%s/%s' % (kind, name)
411 tip = marks.get_tip(ename)
412 if tip and tip in repo:
413 tip = repo[tip].rev()
414 else:
415 tip = 0
417 revs = xrange(tip, head.rev() + 1)
418 total = len(revs)
420 for rev in revs:
422 c = repo[rev]
423 node = c.node()
425 if marks.is_marked(c.hex()):
426 continue
428 (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(node)
429 rev_branch = extra['branch']
431 author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
432 if 'committer' in extra:
433 user, time, tz = extra['committer'].rsplit(' ', 2)
434 committer = "%s %s %s" % (user, time, gittz(int(tz)))
435 else:
436 committer = author
438 parents = [repo[p] for p in repo.changelog.parentrevs(rev) if p >= 0]
440 if len(parents) == 0:
441 modified = c.manifest().keys()
442 removed = []
443 else:
444 modified, removed = get_filechanges(repo, c, parents[0])
446 desc += '\n'
448 if mode == 'hg':
449 extra_msg = ''
451 if rev_branch != 'default':
452 extra_msg += 'branch : %s\n' % rev_branch
454 renames = []
455 for f in c.files():
456 if f not in c.manifest():
457 continue
458 rename = c.filectx(f).renamed()
459 if rename:
460 renames.append((rename[0], f))
462 for e in renames:
463 extra_msg += "rename : %s => %s\n" % e
465 for key, value in extra.iteritems():
466 if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):
467 continue
468 else:
469 extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value))
471 if extra_msg:
472 desc += '\n--HG--\n' + extra_msg
474 if len(parents) == 0 and rev:
475 print 'reset %s/%s' % (prefix, ename)
477 modified_final = export_files(c.filectx(f) for f in modified)
479 print "commit %s/%s" % (prefix, ename)
480 print "mark :%d" % (marks.get_mark(c.hex()))
481 print "author %s" % (author)
482 print "committer %s" % (committer)
483 print "data %d" % (len(desc))
484 print desc
486 if len(parents) > 0:
487 print "from :%s" % (rev_to_mark(parents[0]))
488 if len(parents) > 1:
489 print "merge :%s" % (rev_to_mark(parents[1]))
491 for f in modified_final:
492 print "M %s :%u %s" % f
493 for f in removed:
494 print "D %s" % (fix_file_path(f))
495 print
497 progress = (rev - tip)
498 if (progress % 100 == 0):
499 print "progress revision %d '%s' (%d/%d)" % (rev, name, progress, total)
501 # make sure the ref is updated
502 print "reset %s/%s" % (prefix, ename)
503 print "from :%u" % rev_to_mark(head)
504 print
506 marks.set_tip(ename, head.hex())
508 def export_tag(repo, tag):
509 export_ref(repo, tag, 'tags', repo[hgref(tag)])
511 def export_bookmark(repo, bmark):
512 head = bmarks[hgref(bmark)]
513 export_ref(repo, bmark, 'bookmarks', head)
515 def export_branch(repo, branch):
516 tip = get_branch_tip(repo, branch)
517 head = repo[tip]
518 export_ref(repo, branch, 'branches', head)
520 def export_head(repo):
521 global g_head
522 export_ref(repo, g_head[0], 'bookmarks', g_head[1])
524 def do_capabilities(parser):
525 global prefix, dirname
527 print "import"
528 print "export"
529 print "refspec refs/heads/branches/*:%s/branches/*" % prefix
530 print "refspec refs/heads/*:%s/bookmarks/*" % prefix
531 print "refspec refs/tags/*:%s/tags/*" % prefix
533 path = os.path.join(dirname, 'marks-git')
535 if os.path.exists(path):
536 print "*import-marks %s" % path
537 print "*export-marks %s" % path
539 print
541 def branch_tip(repo, branch):
542 # older versions of mercurial don't have this
543 if hasattr(repo, 'branchtip'):
544 return repo.branchtip(branch)
545 else:
546 return repo.branchtags()[branch]
548 def get_branch_tip(repo, branch):
549 global branches
551 heads = branches.get(hgref(branch), None)
552 if not heads:
553 return None
555 # verify there's only one head
556 if (len(heads) > 1):
557 warn("Branch '%s' has more than one head, consider merging" % branch)
558 return branch_tip(repo, hgref(branch))
560 return heads[0]
562 def list_head(repo, cur):
563 global g_head, bmarks, fake_bmark
565 if 'default' not in repo:
566 # empty repo
567 return
569 node = repo['default']
570 head = 'master' if not 'master' in bmarks else 'default'
571 fake_bmark = head
572 bmarks[head] = node
574 head = gitref(head)
575 print "@refs/heads/%s HEAD" % head
576 g_head = (head, node)
578 def do_list(parser):
579 global branches, bmarks, track_branches
581 repo = parser.repo
582 for bmark, node in bookmarks.listbookmarks(repo).iteritems():
583 bmarks[bmark] = repo[node]
585 cur = repo.dirstate.branch()
587 list_head(repo, cur)
589 if track_branches:
590 for branch in repo.branchmap():
591 heads = repo.branchheads(branch)
592 if len(heads):
593 branches[branch] = heads
595 for branch in branches:
596 print "? refs/heads/branches/%s" % gitref(branch)
598 for bmark in bmarks:
599 print "? refs/heads/%s" % gitref(bmark)
601 for tag, node in repo.tagslist():
602 if tag == 'tip':
603 continue
604 print "? refs/tags/%s" % gitref(tag)
606 print
608 def do_import(parser):
609 repo = parser.repo
611 path = os.path.join(dirname, 'marks-git')
613 print "feature done"
614 if os.path.exists(path):
615 print "feature import-marks=%s" % path
616 print "feature export-marks=%s" % path
617 print "feature force"
618 sys.stdout.flush()
620 tmp = encoding.encoding
621 encoding.encoding = 'utf-8'
623 # lets get all the import lines
624 while parser.check('import'):
625 ref = parser[1]
627 if (ref == 'HEAD'):
628 export_head(repo)
629 elif ref.startswith('refs/heads/branches/'):
630 branch = ref[len('refs/heads/branches/'):]
631 export_branch(repo, branch)
632 elif ref.startswith('refs/heads/'):
633 bmark = ref[len('refs/heads/'):]
634 export_bookmark(repo, bmark)
635 elif ref.startswith('refs/tags/'):
636 tag = ref[len('refs/tags/'):]
637 export_tag(repo, tag)
639 parser.next()
641 encoding.encoding = tmp
643 print 'done'
645 def parse_blob(parser):
646 global blob_marks
648 parser.next()
649 mark = parser.get_mark()
650 parser.next()
651 data = parser.get_data()
652 blob_marks[mark] = data
653 parser.next()
655 def get_merge_files(repo, p1, p2, files):
656 for e in repo[p1].files():
657 if e not in files:
658 if e not in repo[p1].manifest():
659 continue
660 f = { 'ctx' : repo[p1][e] }
661 files[e] = f
663 def parse_commit(parser):
664 global marks, blob_marks, parsed_refs
665 global mode
667 from_mark = merge_mark = None
669 ref = parser[1]
670 parser.next()
672 commit_mark = parser.get_mark()
673 parser.next()
674 author = parser.get_author()
675 parser.next()
676 committer = parser.get_author()
677 parser.next()
678 data = parser.get_data()
679 parser.next()
680 if parser.check('from'):
681 from_mark = parser.get_mark()
682 parser.next()
683 if parser.check('merge'):
684 merge_mark = parser.get_mark()
685 parser.next()
686 if parser.check('merge'):
687 die('octopus merges are not supported yet')
689 # fast-export adds an extra newline
690 if data[-1] == '\n':
691 data = data[:-1]
693 files = {}
695 for line in parser:
696 if parser.check('M'):
697 t, m, mark_ref, path = line.split(' ', 3)
698 mark = int(mark_ref[1:])
699 f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
700 elif parser.check('D'):
701 t, path = line.split(' ', 1)
702 f = { 'deleted' : True }
703 else:
704 die('Unknown file command: %s' % line)
705 files[path] = f
707 def getfilectx(repo, memctx, f):
708 of = files[f]
709 if 'deleted' in of:
710 raise IOError
711 if 'ctx' in of:
712 return of['ctx']
713 is_exec = of['mode'] == 'x'
714 is_link = of['mode'] == 'l'
715 rename = of.get('rename', None)
716 return context.memfilectx(f, of['data'],
717 is_link, is_exec, rename)
719 repo = parser.repo
721 user, date, tz = author
722 extra = {}
724 if committer != author:
725 extra['committer'] = "%s %u %u" % committer
727 if from_mark:
728 p1 = mark_to_rev(from_mark)
729 else:
730 p1 = '0' * 40
732 if merge_mark:
733 p2 = mark_to_rev(merge_mark)
734 else:
735 p2 = '0' * 40
738 # If files changed from any of the parents, hg wants to know, but in git if
739 # nothing changed from the first parent, nothing changed.
741 if merge_mark:
742 get_merge_files(repo, p1, p2, files)
744 # Check if the ref is supposed to be a named branch
745 if ref.startswith('refs/heads/branches/'):
746 branch = ref[len('refs/heads/branches/'):]
747 extra['branch'] = hgref(branch)
749 if mode == 'hg':
750 i = data.find('\n--HG--\n')
751 if i >= 0:
752 tmp = data[i + len('\n--HG--\n'):].strip()
753 for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
754 if k == 'rename':
755 old, new = v.split(' => ', 1)
756 files[new]['rename'] = old
757 elif k == 'branch':
758 extra[k] = v
759 elif k == 'extra':
760 ek, ev = v.split(' : ', 1)
761 extra[ek] = urllib.unquote(ev)
762 data = data[:i]
764 ctx = context.memctx(repo, (p1, p2), data,
765 files.keys(), getfilectx,
766 user, (date, tz), extra)
768 tmp = encoding.encoding
769 encoding.encoding = 'utf-8'
771 node = hghex(repo.commitctx(ctx))
773 encoding.encoding = tmp
775 parsed_refs[ref] = node
776 marks.new_mark(node, commit_mark)
778 def parse_reset(parser):
779 global parsed_refs
781 ref = parser[1]
782 parser.next()
783 # ugh
784 if parser.check('commit'):
785 parse_commit(parser)
786 return
787 if not parser.check('from'):
788 return
789 from_mark = parser.get_mark()
790 parser.next()
792 rev = mark_to_rev(from_mark)
793 parsed_refs[ref] = rev
795 def parse_tag(parser):
796 name = parser[1]
797 parser.next()
798 from_mark = parser.get_mark()
799 parser.next()
800 tagger = parser.get_author()
801 parser.next()
802 data = parser.get_data()
803 parser.next()
805 parsed_tags[name] = (tagger, data)
807 def write_tag(repo, tag, node, msg, author):
808 branch = repo[node].branch()
809 tip = branch_tip(repo, branch)
810 tip = repo[tip]
812 def getfilectx(repo, memctx, f):
813 try:
814 fctx = tip.filectx(f)
815 data = fctx.data()
816 except error.ManifestLookupError:
817 data = ""
818 content = data + "%s %s\n" % (node, tag)
819 return context.memfilectx(f, content, False, False, None)
821 p1 = tip.hex()
822 p2 = '0' * 40
823 if not author:
824 author = (None, 0, 0)
825 user, date, tz = author
827 ctx = context.memctx(repo, (p1, p2), msg,
828 ['.hgtags'], getfilectx,
829 user, (date, tz), {'branch' : branch})
831 tmp = encoding.encoding
832 encoding.encoding = 'utf-8'
834 tagnode = repo.commitctx(ctx)
836 encoding.encoding = tmp
838 return tagnode
840 def do_export(parser):
841 global parsed_refs, bmarks, peer
843 p_bmarks = []
845 parser.next()
847 for line in parser.each_block('done'):
848 if parser.check('blob'):
849 parse_blob(parser)
850 elif parser.check('commit'):
851 parse_commit(parser)
852 elif parser.check('reset'):
853 parse_reset(parser)
854 elif parser.check('tag'):
855 parse_tag(parser)
856 elif parser.check('feature'):
857 pass
858 else:
859 die('unhandled export command: %s' % line)
861 for ref, node in parsed_refs.iteritems():
862 bnode = hgbin(node)
863 if ref.startswith('refs/heads/branches'):
864 branch = ref[len('refs/heads/branches/'):]
865 if branch in branches and bnode in branches[branch]:
866 # up to date
867 continue
868 print "ok %s" % ref
869 elif ref.startswith('refs/heads/'):
870 bmark = ref[len('refs/heads/'):]
871 p_bmarks.append((bmark, node))
872 continue
873 elif ref.startswith('refs/tags/'):
874 tag = ref[len('refs/tags/'):]
875 tag = hgref(tag)
876 author, msg = parsed_tags.get(tag, (None, None))
877 if mode == 'git':
878 if not msg:
879 msg = 'Added tag %s for changeset %s' % (tag, node[:12]);
880 write_tag(parser.repo, tag, node, msg, author)
881 else:
882 fp = parser.repo.opener('localtags', 'a')
883 fp.write('%s %s\n' % (node, tag))
884 fp.close()
885 print "ok %s" % ref
886 else:
887 # transport-helper/fast-export bugs
888 continue
890 if peer:
891 parser.repo.push(peer, force=force_push, newbranch=True)
892 remote_bmarks = peer.listkeys('bookmarks')
894 # handle bookmarks
895 for bmark, node in p_bmarks:
896 ref = 'refs/heads/' + bmark
897 new = node
899 if bmark in bmarks:
900 old = bmarks[bmark].hex()
901 else:
902 old = ''
904 if old == new:
905 continue
907 if bmark == fake_bmark or \
908 bmark == 'master' and 'master' not in parser.repo._bookmarks:
909 print "ok %s" % ref
910 continue
911 elif bookmarks.pushbookmark(parser.repo, bmark, old, new):
912 # updated locally
913 pass
914 else:
915 print "error %s" % ref
916 continue
918 if peer:
919 old = remote_bmarks.get(bmark, '')
920 if not peer.pushkey('bookmarks', bmark, old, new):
921 print "error %s" % ref
922 continue
924 print "ok %s" % ref
926 print
928 def fix_path(alias, repo, orig_url):
929 url = urlparse.urlparse(orig_url, 'file')
930 if url.scheme != 'file' or os.path.isabs(url.path):
931 return
932 abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
933 cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
934 subprocess.call(cmd)
936 def main(args):
937 global prefix, gitdir, dirname, branches, bmarks
938 global marks, blob_marks, parsed_refs
939 global peer, mode, bad_mail, bad_name
940 global track_branches, force_push, is_tmp
941 global parsed_tags
942 global filenodes
943 global fake_bmark
945 alias = args[1]
946 url = args[2]
947 peer = None
949 hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
950 track_branches = get_config_bool('remote-hg.track-branches', True)
951 force_push = get_config_bool('remote-hg.force-push')
953 if hg_git_compat:
954 mode = 'hg'
955 bad_mail = 'none@none'
956 bad_name = ''
957 else:
958 mode = 'git'
959 bad_mail = 'unknown'
960 bad_name = 'Unknown'
962 if alias[4:] == url:
963 is_tmp = True
964 alias = hashlib.sha1(alias).hexdigest()
965 else:
966 is_tmp = False
968 gitdir = os.environ['GIT_DIR']
969 dirname = os.path.join(gitdir, 'hg', alias)
970 branches = {}
971 bmarks = {}
972 blob_marks = {}
973 parsed_refs = {}
974 marks = None
975 parsed_tags = {}
976 filenodes = {}
977 fake_bmark = None
979 repo = get_repo(url, alias)
980 prefix = 'refs/hg/%s' % alias
982 if not is_tmp:
983 fix_path(alias, peer or repo, url)
985 marks_path = os.path.join(dirname, 'marks-hg')
986 marks = Marks(marks_path, repo)
988 if sys.platform == 'win32':
989 import msvcrt
990 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
992 parser = Parser(repo)
993 for line in parser:
994 if parser.check('capabilities'):
995 do_capabilities(parser)
996 elif parser.check('list'):
997 do_list(parser)
998 elif parser.check('import'):
999 do_import(parser)
1000 elif parser.check('export'):
1001 do_export(parser)
1002 else:
1003 die('unhandled command: %s' % line)
1004 sys.stdout.flush()
1006 def bye():
1007 if not marks:
1008 return
1009 if not is_tmp:
1010 marks.store()
1011 else:
1012 shutil.rmtree(dirname)
1014 atexit.register(bye)
1015 sys.exit(main(sys.argv))