1#!/usr/bin/env python
2#
3# Copyright (c) 2012 Felipe Contreras
4#
5
6# Inspired by Rocco Rutte's hg-fast-export
7
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/
11#
12# For remote repositories a local clone is stored in
13# "$GIT_DIR/hg/origin/clone/.hg/".
14
15from mercurial import hg, ui, bookmarks, context, encoding, node, error, extensions
16
17import re
18import sys
19import os
20import json
21import shutil
22import subprocess
23import urllib
24import atexit
25import urlparse, hashlib
26
27#
28# If you want to switch to hg-git compatibility mode:
29# git config --global remote-hg.hg-git-compat true
30#
31# If you are not in hg-git-compat mode and want to disable the tracking of
32# named branches:
33# git config --global remote-hg.track-branches false
34#
35# If you don't want to force pushes (and thus risk creating new remote heads):
36# git config --global remote-hg.force-push false
37#
38# If you want the equivalent of hg's clone/pull--insecure option:
39# git config remote-hg.insecure true
40#
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.
45#
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.
50#
51
52NAME_RE = re.compile('^([^<>]+)')
53AUTHOR_RE = re.compile('^([^<>]+?)? ?<([^<>]*)>$')
54EMAIL_RE = re.compile('^([^<>]+[^ \\\t<>])?\\b(?:[ \\t<>]*?)\\b([^ \\t<>]+@[^ \\t<>]+)')
55AUTHOR_HG_RE = re.compile('^(.*?) ?<(.*?)(?:>(.+)?)?$')
56RAW_AUTHOR_RE = re.compile('^(\w+) (?:(.+)? )?<(.*)> (\d+) ([+-]\d+)')
57
58def die(msg, *args):
59 sys.stderr.write('ERROR: %s\n' % (msg % args))
60 sys.exit(1)
61
62def warn(msg, *args):
63 sys.stderr.write('WARNING: %s\n' % (msg % args))
64
65def gitmode(flags):
66 return 'l' in flags and '120000' or 'x' in flags and '100755' or '100644'
67
68def gittz(tz):
69 return '%+03d%02d' % (-tz / 3600, -tz % 3600 / 60)
70
71def hgmode(mode):
72 m = { '100755': 'x', '120000': 'l' }
73 return m.get(mode, '')
74
75def hghex(node):
76 return hg.node.hex(node)
77
78def hgref(ref):
79 return ref.replace('___', ' ')
80
81def gitref(ref):
82 return ref.replace(' ', '___')
83
84def get_config(config):
85 cmd = ['git', 'config', '--get', config]
86 process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
87 output, _ = process.communicate()
88 return output
89
90class Marks:
91
92 def __init__(self, path):
93 self.path = path
94 self.tips = {}
95 self.marks = {}
96 self.rev_marks = {}
97 self.last_mark = 0
98
99 self.load()
100
101 def load(self):
102 if not os.path.exists(self.path):
103 return
104
105 tmp = json.load(open(self.path))
106
107 self.tips = tmp['tips']
108 self.marks = tmp['marks']
109 self.last_mark = tmp['last-mark']
110
111 for rev, mark in self.marks.iteritems():
112 self.rev_marks[mark] = int(rev)
113
114 def dict(self):
115 return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark }
116
117 def store(self):
118 json.dump(self.dict(), open(self.path, 'w'))
119
120 def __str__(self):
121 return str(self.dict())
122
123 def from_rev(self, rev):
124 return self.marks[str(rev)]
125
126 def to_rev(self, mark):
127 return self.rev_marks[mark]
128
129 def next_mark(self):
130 self.last_mark += 1
131 return self.last_mark
132
133 def get_mark(self, rev):
134 self.last_mark += 1
135 self.marks[str(rev)] = self.last_mark
136 return self.last_mark
137
138 def new_mark(self, rev, mark):
139 self.marks[str(rev)] = mark
140 self.rev_marks[mark] = rev
141 self.last_mark = mark
142
143 def is_marked(self, rev):
144 return str(rev) in self.marks
145
146 def get_tip(self, branch):
147 return self.tips.get(branch, 0)
148
149 def set_tip(self, branch, tip):
150 self.tips[branch] = tip
151
152class Parser:
153
154 def __init__(self, repo):
155 self.repo = repo
156 self.line = self.get_line()
157
158 def get_line(self):
159 return sys.stdin.readline().strip()
160
161 def __getitem__(self, i):
162 return self.line.split()[i]
163
164 def check(self, word):
165 return self.line.startswith(word)
166
167 def each_block(self, separator):
168 while self.line != separator:
169 yield self.line
170 self.line = self.get_line()
171
172 def __iter__(self):
173 return self.each_block('')
174
175 def next(self):
176 self.line = self.get_line()
177 if self.line == 'done':
178 self.line = None
179
180 def get_mark(self):
181 i = self.line.index(':') + 1
182 return int(self.line[i:])
183
184 def get_data(self):
185 if not self.check('data'):
186 return None
187 i = self.line.index(' ') + 1
188 size = int(self.line[i:])
189 return sys.stdin.read(size)
190
191 def get_author(self):
192 global bad_mail
193
194 ex = None
195 m = RAW_AUTHOR_RE.match(self.line)
196 if not m:
197 return None
198 _, name, email, date, tz = m.groups()
199 if name and 'ext:' in name:
200 m = re.match('^(.+?) ext:\((.+)\)$', name)
201 if m:
202 name = m.group(1)
203 ex = urllib.unquote(m.group(2))
204
205 if email != bad_mail:
206 if name:
207 user = '%s <%s>' % (name, email)
208 else:
209 user = '<%s>' % (email)
210 else:
211 user = name
212
213 if ex:
214 user += ex
215
216 tz = int(tz)
217 tz = ((tz / 100) * 3600) + ((tz % 100) * 60)
218 return (user, int(date), -tz)
219
220def fix_file_path(path):
221 if not os.path.isabs(path):
222 return path
223 return os.path.relpath(path, '/')
224
225def export_files(files):
226 global marks, filenodes
227
228 final = []
229 for f in files:
230 fid = node.hex(f.filenode())
231
232 if fid in filenodes:
233 mark = filenodes[fid]
234 else:
235 mark = marks.next_mark()
236 filenodes[fid] = mark
237 d = f.data()
238
239 print "blob"
240 print "mark :%u" % mark
241 print "data %d" % len(d)
242 print d
243
244 path = fix_file_path(f.path())
245 final.append((gitmode(f.flags()), mark, path))
246
247 return final
248
249def get_filechanges(repo, ctx, parent):
250 modified = set()
251 added = set()
252 removed = set()
253
254 # load earliest manifest first for caching reasons
255 prev = repo[parent].manifest().copy()
256 cur = ctx.manifest()
257
258 for fn in cur:
259 if fn in prev:
260 if (cur.flags(fn) != prev.flags(fn) or cur[fn] != prev[fn]):
261 modified.add(fn)
262 del prev[fn]
263 else:
264 added.add(fn)
265 removed |= set(prev.keys())
266
267 return added | modified, removed
268
269def fixup_user_git(user):
270 name = mail = None
271 user = user.replace('"', '')
272 m = AUTHOR_RE.match(user)
273 if m:
274 name = m.group(1)
275 mail = m.group(2).strip()
276 else:
277 m = EMAIL_RE.match(user)
278 if m:
279 name = m.group(1)
280 mail = m.group(2)
281 else:
282 m = NAME_RE.match(user)
283 if m:
284 name = m.group(1).strip()
285 return (name, mail)
286
287def fixup_user_hg(user):
288 def sanitize(name):
289 # stole this from hg-git
290 return re.sub('[<>\n]', '?', name.lstrip('< ').rstrip('> '))
291
292 m = AUTHOR_HG_RE.match(user)
293 if m:
294 name = sanitize(m.group(1))
295 mail = sanitize(m.group(2))
296 ex = m.group(3)
297 if ex:
298 name += ' ext:(' + urllib.quote(ex) + ')'
299 else:
300 name = sanitize(user)
301 if '@' in user:
302 mail = name
303 else:
304 mail = None
305
306 return (name, mail)
307
308def fixup_user(user):
309 global mode, bad_mail
310
311 if mode == 'git':
312 name, mail = fixup_user_git(user)
313 else:
314 name, mail = fixup_user_hg(user)
315
316 if not name:
317 name = bad_name
318 if not mail:
319 mail = bad_mail
320
321 return '%s <%s>' % (name, mail)
322
323def get_repo(url, alias):
324 global dirname, peer
325
326 myui = ui.ui()
327 myui.setconfig('ui', 'interactive', 'off')
328 myui.fout = sys.stderr
329
330 try:
331 if get_config('remote-hg.insecure') == 'true\n':
332 myui.setconfig('web', 'cacerts', '')
333 except subprocess.CalledProcessError:
334 pass
335
336 try:
337 mod = extensions.load(myui, 'hgext.schemes', None)
338 mod.extsetup(myui)
339 except ImportError:
340 pass
341
342 if hg.islocal(url):
343 repo = hg.repository(myui, url)
344 else:
345 local_path = os.path.join(dirname, 'clone')
346 if not os.path.exists(local_path):
347 try:
348 peer, dstpeer = hg.clone(myui, {}, url, local_path, update=True, pull=True)
349 except:
350 die('Repository error')
351 repo = dstpeer.local()
352 else:
353 repo = hg.repository(myui, local_path)
354 try:
355 peer = hg.peer(myui, {}, url)
356 except:
357 die('Repository error')
358 repo.pull(peer, heads=None, force=True)
359
360 return repo
361
362def rev_to_mark(rev):
363 global marks
364 return marks.from_rev(rev)
365
366def mark_to_rev(mark):
367 global marks
368 return marks.to_rev(mark)
369
370def export_ref(repo, name, kind, head):
371 global prefix, marks, mode
372
373 ename = '%s/%s' % (kind, name)
374 tip = marks.get_tip(ename)
375
376 revs = xrange(tip, head.rev() + 1)
377 count = 0
378
379 revs = [rev for rev in revs if not marks.is_marked(rev)]
380
381 for rev in revs:
382
383 c = repo[rev]
384 (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(c.node())
385 rev_branch = extra['branch']
386
387 author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
388 if 'committer' in extra:
389 user, time, tz = extra['committer'].rsplit(' ', 2)
390 committer = "%s %s %s" % (user, time, gittz(int(tz)))
391 else:
392 committer = author
393
394 parents = [p for p in repo.changelog.parentrevs(rev) if p >= 0]
395
396 if len(parents) == 0:
397 modified = c.manifest().keys()
398 removed = []
399 else:
400 modified, removed = get_filechanges(repo, c, parents[0])
401
402 desc += '\n'
403
404 if mode == 'hg':
405 extra_msg = ''
406
407 if rev_branch != 'default':
408 extra_msg += 'branch : %s\n' % rev_branch
409
410 renames = []
411 for f in c.files():
412 if f not in c.manifest():
413 continue
414 rename = c.filectx(f).renamed()
415 if rename:
416 renames.append((rename[0], f))
417
418 for e in renames:
419 extra_msg += "rename : %s => %s\n" % e
420
421 for key, value in extra.iteritems():
422 if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):
423 continue
424 else:
425 extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value))
426
427 if extra_msg:
428 desc += '\n--HG--\n' + extra_msg
429
430 if len(parents) == 0 and rev:
431 print 'reset %s/%s' % (prefix, ename)
432
433 modified_final = export_files(c.filectx(f) for f in modified)
434
435 print "commit %s/%s" % (prefix, ename)
436 print "mark :%d" % (marks.get_mark(rev))
437 print "author %s" % (author)
438 print "committer %s" % (committer)
439 print "data %d" % (len(desc))
440 print desc
441
442 if len(parents) > 0:
443 print "from :%s" % (rev_to_mark(parents[0]))
444 if len(parents) > 1:
445 print "merge :%s" % (rev_to_mark(parents[1]))
446
447 for f in modified_final:
448 print "M %s :%u %s" % f
449 for f in removed:
450 print "D %s" % (fix_file_path(f))
451 print
452
453 count += 1
454 if (count % 100 == 0):
455 print "progress revision %d '%s' (%d/%d)" % (rev, name, count, len(revs))
456 print "#############################################################"
457
458 # make sure the ref is updated
459 print "reset %s/%s" % (prefix, ename)
460 print "from :%u" % rev_to_mark(rev)
461 print
462
463 marks.set_tip(ename, rev)
464
465def export_tag(repo, tag):
466 export_ref(repo, tag, 'tags', repo[hgref(tag)])
467
468def export_bookmark(repo, bmark):
469 head = bmarks[hgref(bmark)]
470 export_ref(repo, bmark, 'bookmarks', head)
471
472def export_branch(repo, branch):
473 tip = get_branch_tip(repo, branch)
474 head = repo[tip]
475 export_ref(repo, branch, 'branches', head)
476
477def export_head(repo):
478 global g_head
479 export_ref(repo, g_head[0], 'bookmarks', g_head[1])
480
481def do_capabilities(parser):
482 global prefix, dirname
483
484 print "import"
485 print "export"
486 print "refspec refs/heads/branches/*:%s/branches/*" % prefix
487 print "refspec refs/heads/*:%s/bookmarks/*" % prefix
488 print "refspec refs/tags/*:%s/tags/*" % prefix
489
490 path = os.path.join(dirname, 'marks-git')
491
492 if os.path.exists(path):
493 print "*import-marks %s" % path
494 print "*export-marks %s" % path
495
496 print
497
498def branch_tip(repo, branch):
499 # older versions of mercurial don't have this
500 if hasattr(repo, 'branchtip'):
501 return repo.branchtip(branch)
502 else:
503 return repo.branchtags()[branch]
504
505def get_branch_tip(repo, branch):
506 global branches
507
508 heads = branches.get(hgref(branch), None)
509 if not heads:
510 return None
511
512 # verify there's only one head
513 if (len(heads) > 1):
514 warn("Branch '%s' has more than one head, consider merging" % branch)
515 return branch_tip(repo, hgref(branch))
516
517 return heads[0]
518
519def list_head(repo, cur):
520 global g_head, bmarks
521
522 head = bookmarks.readcurrent(repo)
523 if head:
524 node = repo[head]
525 else:
526 # fake bookmark from current branch
527 head = cur
528 node = repo['.']
529 if not node:
530 node = repo['tip']
531 if not node:
532 return
533 if head == 'default':
534 head = 'master'
535 bmarks[head] = node
536
537 head = gitref(head)
538 print "@refs/heads/%s HEAD" % head
539 g_head = (head, node)
540
541def do_list(parser):
542 global branches, bmarks, mode, track_branches
543
544 repo = parser.repo
545 for bmark, node in bookmarks.listbookmarks(repo).iteritems():
546 bmarks[bmark] = repo[node]
547
548 cur = repo.dirstate.branch()
549
550 list_head(repo, cur)
551
552 if track_branches:
553 for branch in repo.branchmap():
554 heads = repo.branchheads(branch)
555 if len(heads):
556 branches[branch] = heads
557
558 for branch in branches:
559 print "? refs/heads/branches/%s" % gitref(branch)
560
561 for bmark in bmarks:
562 print "? refs/heads/%s" % gitref(bmark)
563
564 for tag, node in repo.tagslist():
565 if tag == 'tip':
566 continue
567 print "? refs/tags/%s" % gitref(tag)
568
569 print
570
571def do_import(parser):
572 repo = parser.repo
573
574 path = os.path.join(dirname, 'marks-git')
575
576 print "feature done"
577 if os.path.exists(path):
578 print "feature import-marks=%s" % path
579 print "feature export-marks=%s" % path
580 sys.stdout.flush()
581
582 tmp = encoding.encoding
583 encoding.encoding = 'utf-8'
584
585 # lets get all the import lines
586 while parser.check('import'):
587 ref = parser[1]
588
589 if (ref == 'HEAD'):
590 export_head(repo)
591 elif ref.startswith('refs/heads/branches/'):
592 branch = ref[len('refs/heads/branches/'):]
593 export_branch(repo, branch)
594 elif ref.startswith('refs/heads/'):
595 bmark = ref[len('refs/heads/'):]
596 export_bookmark(repo, bmark)
597 elif ref.startswith('refs/tags/'):
598 tag = ref[len('refs/tags/'):]
599 export_tag(repo, tag)
600
601 parser.next()
602
603 encoding.encoding = tmp
604
605 print 'done'
606
607def parse_blob(parser):
608 global blob_marks
609
610 parser.next()
611 mark = parser.get_mark()
612 parser.next()
613 data = parser.get_data()
614 blob_marks[mark] = data
615 parser.next()
616
617def get_merge_files(repo, p1, p2, files):
618 for e in repo[p1].files():
619 if e not in files:
620 if e not in repo[p1].manifest():
621 continue
622 f = { 'ctx' : repo[p1][e] }
623 files[e] = f
624
625def parse_commit(parser):
626 global marks, blob_marks, parsed_refs
627 global mode
628
629 from_mark = merge_mark = None
630
631 ref = parser[1]
632 parser.next()
633
634 commit_mark = parser.get_mark()
635 parser.next()
636 author = parser.get_author()
637 parser.next()
638 committer = parser.get_author()
639 parser.next()
640 data = parser.get_data()
641 parser.next()
642 if parser.check('from'):
643 from_mark = parser.get_mark()
644 parser.next()
645 if parser.check('merge'):
646 merge_mark = parser.get_mark()
647 parser.next()
648 if parser.check('merge'):
649 die('octopus merges are not supported yet')
650
651 # fast-export adds an extra newline
652 if data[-1] == '\n':
653 data = data[:-1]
654
655 files = {}
656
657 for line in parser:
658 if parser.check('M'):
659 t, m, mark_ref, path = line.split(' ', 3)
660 mark = int(mark_ref[1:])
661 f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
662 elif parser.check('D'):
663 t, path = line.split(' ', 1)
664 f = { 'deleted' : True }
665 else:
666 die('Unknown file command: %s' % line)
667 files[path] = f
668
669 def getfilectx(repo, memctx, f):
670 of = files[f]
671 if 'deleted' in of:
672 raise IOError
673 if 'ctx' in of:
674 return of['ctx']
675 is_exec = of['mode'] == 'x'
676 is_link = of['mode'] == 'l'
677 rename = of.get('rename', None)
678 return context.memfilectx(f, of['data'],
679 is_link, is_exec, rename)
680
681 repo = parser.repo
682
683 user, date, tz = author
684 extra = {}
685
686 if committer != author:
687 extra['committer'] = "%s %u %u" % committer
688
689 if from_mark:
690 p1 = repo.changelog.node(mark_to_rev(from_mark))
691 else:
692 p1 = '\0' * 20
693
694 if merge_mark:
695 p2 = repo.changelog.node(mark_to_rev(merge_mark))
696 else:
697 p2 = '\0' * 20
698
699 #
700 # If files changed from any of the parents, hg wants to know, but in git if
701 # nothing changed from the first parent, nothing changed.
702 #
703 if merge_mark:
704 get_merge_files(repo, p1, p2, files)
705
706 # Check if the ref is supposed to be a named branch
707 if ref.startswith('refs/heads/branches/'):
708 branch = ref[len('refs/heads/branches/'):]
709 extra['branch'] = hgref(branch)
710
711 if mode == 'hg':
712 i = data.find('\n--HG--\n')
713 if i >= 0:
714 tmp = data[i + len('\n--HG--\n'):].strip()
715 for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
716 if k == 'rename':
717 old, new = v.split(' => ', 1)
718 files[new]['rename'] = old
719 elif k == 'branch':
720 extra[k] = v
721 elif k == 'extra':
722 ek, ev = v.split(' : ', 1)
723 extra[ek] = urllib.unquote(ev)
724 data = data[:i]
725
726 ctx = context.memctx(repo, (p1, p2), data,
727 files.keys(), getfilectx,
728 user, (date, tz), extra)
729
730 tmp = encoding.encoding
731 encoding.encoding = 'utf-8'
732
733 node = repo.commitctx(ctx)
734
735 encoding.encoding = tmp
736
737 rev = repo[node].rev()
738
739 parsed_refs[ref] = node
740 marks.new_mark(rev, commit_mark)
741
742def parse_reset(parser):
743 global parsed_refs
744
745 ref = parser[1]
746 parser.next()
747 # ugh
748 if parser.check('commit'):
749 parse_commit(parser)
750 return
751 if not parser.check('from'):
752 return
753 from_mark = parser.get_mark()
754 parser.next()
755
756 node = parser.repo.changelog.node(mark_to_rev(from_mark))
757 parsed_refs[ref] = node
758
759def parse_tag(parser):
760 name = parser[1]
761 parser.next()
762 from_mark = parser.get_mark()
763 parser.next()
764 tagger = parser.get_author()
765 parser.next()
766 data = parser.get_data()
767 parser.next()
768
769 parsed_tags[name] = (tagger, data)
770
771def write_tag(repo, tag, node, msg, author):
772 branch = repo[node].branch()
773 tip = branch_tip(repo, branch)
774 tip = repo[tip]
775
776 def getfilectx(repo, memctx, f):
777 try:
778 fctx = tip.filectx(f)
779 data = fctx.data()
780 except error.ManifestLookupError:
781 data = ""
782 content = data + "%s %s\n" % (hghex(node), tag)
783 return context.memfilectx(f, content, False, False, None)
784
785 p1 = tip.hex()
786 p2 = '\0' * 20
787 if not author:
788 author = (None, 0, 0)
789 user, date, tz = author
790
791 ctx = context.memctx(repo, (p1, p2), msg,
792 ['.hgtags'], getfilectx,
793 user, (date, tz), {'branch' : branch})
794
795 tmp = encoding.encoding
796 encoding.encoding = 'utf-8'
797
798 tagnode = repo.commitctx(ctx)
799
800 encoding.encoding = tmp
801
802 return tagnode
803
804def do_export(parser):
805 global parsed_refs, bmarks, peer
806
807 p_bmarks = []
808
809 parser.next()
810
811 for line in parser.each_block('done'):
812 if parser.check('blob'):
813 parse_blob(parser)
814 elif parser.check('commit'):
815 parse_commit(parser)
816 elif parser.check('reset'):
817 parse_reset(parser)
818 elif parser.check('tag'):
819 parse_tag(parser)
820 elif parser.check('feature'):
821 pass
822 else:
823 die('unhandled export command: %s' % line)
824
825 for ref, node in parsed_refs.iteritems():
826 if ref.startswith('refs/heads/branches'):
827 branch = ref[len('refs/heads/branches/'):]
828 if branch in branches and node in branches[branch]:
829 # up to date
830 continue
831 print "ok %s" % ref
832 elif ref.startswith('refs/heads/'):
833 bmark = ref[len('refs/heads/'):]
834 p_bmarks.append((bmark, node))
835 continue
836 elif ref.startswith('refs/tags/'):
837 tag = ref[len('refs/tags/'):]
838 tag = hgref(tag)
839 author, msg = parsed_tags.get(tag, (None, None))
840 if mode == 'git':
841 if not msg:
842 msg = 'Added tag %s for changeset %s' % (tag, hghex(node[:6]));
843 write_tag(parser.repo, tag, node, msg, author)
844 else:
845 fp = parser.repo.opener('localtags', 'a')
846 fp.write('%s %s\n' % (hghex(node), tag))
847 fp.close()
848 print "ok %s" % ref
849 else:
850 # transport-helper/fast-export bugs
851 continue
852
853 if peer:
854 parser.repo.push(peer, force=force_push)
855
856 # handle bookmarks
857 for bmark, node in p_bmarks:
858 ref = 'refs/heads/' + bmark
859 new = hghex(node)
860
861 if bmark in bmarks:
862 old = bmarks[bmark].hex()
863 else:
864 old = ''
865
866 if old == new:
867 continue
868
869 if bmark == 'master' and 'master' not in parser.repo._bookmarks:
870 # fake bookmark
871 pass
872 elif bookmarks.pushbookmark(parser.repo, bmark, old, new):
873 # updated locally
874 pass
875 else:
876 print "error %s" % ref
877 continue
878
879 if peer:
880 rb = peer.listkeys('bookmarks')
881 old = rb.get(bmark, '')
882 if not peer.pushkey('bookmarks', bmark, old, new):
883 print "error %s" % ref
884 continue
885
886 print "ok %s" % ref
887
888 print
889
890def fix_path(alias, repo, orig_url):
891 url = urlparse.urlparse(orig_url, 'file')
892 if url.scheme != 'file' or os.path.isabs(url.path):
893 return
894 abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
895 cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
896 subprocess.call(cmd)
897
898def main(args):
899 global prefix, dirname, branches, bmarks
900 global marks, blob_marks, parsed_refs
901 global peer, mode, bad_mail, bad_name
902 global track_branches, force_push, is_tmp
903 global parsed_tags
904 global filenodes
905
906 alias = args[1]
907 url = args[2]
908 peer = None
909
910 hg_git_compat = False
911 track_branches = True
912 force_push = True
913
914 try:
915 if get_config('remote-hg.hg-git-compat') == 'true\n':
916 hg_git_compat = True
917 track_branches = False
918 if get_config('remote-hg.track-branches') == 'false\n':
919 track_branches = False
920 if get_config('remote-hg.force-push') == 'false\n':
921 force_push = False
922 except subprocess.CalledProcessError:
923 pass
924
925 if hg_git_compat:
926 mode = 'hg'
927 bad_mail = 'none@none'
928 bad_name = ''
929 else:
930 mode = 'git'
931 bad_mail = 'unknown'
932 bad_name = 'Unknown'
933
934 if alias[4:] == url:
935 is_tmp = True
936 alias = hashlib.sha1(alias).hexdigest()
937 else:
938 is_tmp = False
939
940 gitdir = os.environ['GIT_DIR']
941 dirname = os.path.join(gitdir, 'hg', alias)
942 branches = {}
943 bmarks = {}
944 blob_marks = {}
945 parsed_refs = {}
946 marks = None
947 parsed_tags = {}
948 filenodes = {}
949
950 repo = get_repo(url, alias)
951 prefix = 'refs/hg/%s' % alias
952
953 if not is_tmp:
954 fix_path(alias, peer or repo, url)
955
956 if not os.path.exists(dirname):
957 os.makedirs(dirname)
958
959 marks_path = os.path.join(dirname, 'marks-hg')
960 marks = Marks(marks_path)
961
962 parser = Parser(repo)
963 for line in parser:
964 if parser.check('capabilities'):
965 do_capabilities(parser)
966 elif parser.check('list'):
967 do_list(parser)
968 elif parser.check('import'):
969 do_import(parser)
970 elif parser.check('export'):
971 do_export(parser)
972 else:
973 die('unhandled command: %s' % line)
974 sys.stdout.flush()
975
976def bye():
977 if not marks:
978 return
979 if not is_tmp:
980 marks.store()
981 else:
982 shutil.rmtree(dirname)
983
984atexit.register(bye)
985sys.exit(main(sys.argv))