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 if get_config('remote-hg.insecure') == 'true\n':
331 myui.setconfig('web', 'cacerts', '')
332
333 try:
334 mod = extensions.load(myui, 'hgext.schemes', None)
335 mod.extsetup(myui)
336 except ImportError:
337 pass
338
339 if hg.islocal(url):
340 repo = hg.repository(myui, url)
341 else:
342 local_path = os.path.join(dirname, 'clone')
343 if not os.path.exists(local_path):
344 try:
345 peer, dstpeer = hg.clone(myui, {}, url, local_path, update=True, pull=True)
346 except:
347 die('Repository error')
348 repo = dstpeer.local()
349 else:
350 repo = hg.repository(myui, local_path)
351 try:
352 peer = hg.peer(myui, {}, url)
353 except:
354 die('Repository error')
355 repo.pull(peer, heads=None, force=True)
356
357 return repo
358
359def rev_to_mark(rev):
360 global marks
361 return marks.from_rev(rev)
362
363def mark_to_rev(mark):
364 global marks
365 return marks.to_rev(mark)
366
367def export_ref(repo, name, kind, head):
368 global prefix, marks, mode
369
370 ename = '%s/%s' % (kind, name)
371 tip = marks.get_tip(ename)
372
373 revs = xrange(tip, head.rev() + 1)
374 count = 0
375
376 revs = [rev for rev in revs if not marks.is_marked(rev)]
377
378 for rev in revs:
379
380 c = repo[rev]
381 (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(c.node())
382 rev_branch = extra['branch']
383
384 author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
385 if 'committer' in extra:
386 user, time, tz = extra['committer'].rsplit(' ', 2)
387 committer = "%s %s %s" % (user, time, gittz(int(tz)))
388 else:
389 committer = author
390
391 parents = [p for p in repo.changelog.parentrevs(rev) if p >= 0]
392
393 if len(parents) == 0:
394 modified = c.manifest().keys()
395 removed = []
396 else:
397 modified, removed = get_filechanges(repo, c, parents[0])
398
399 desc += '\n'
400
401 if mode == 'hg':
402 extra_msg = ''
403
404 if rev_branch != 'default':
405 extra_msg += 'branch : %s\n' % rev_branch
406
407 renames = []
408 for f in c.files():
409 if f not in c.manifest():
410 continue
411 rename = c.filectx(f).renamed()
412 if rename:
413 renames.append((rename[0], f))
414
415 for e in renames:
416 extra_msg += "rename : %s => %s\n" % e
417
418 for key, value in extra.iteritems():
419 if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):
420 continue
421 else:
422 extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value))
423
424 if extra_msg:
425 desc += '\n--HG--\n' + extra_msg
426
427 if len(parents) == 0 and rev:
428 print 'reset %s/%s' % (prefix, ename)
429
430 modified_final = export_files(c.filectx(f) for f in modified)
431
432 print "commit %s/%s" % (prefix, ename)
433 print "mark :%d" % (marks.get_mark(rev))
434 print "author %s" % (author)
435 print "committer %s" % (committer)
436 print "data %d" % (len(desc))
437 print desc
438
439 if len(parents) > 0:
440 print "from :%s" % (rev_to_mark(parents[0]))
441 if len(parents) > 1:
442 print "merge :%s" % (rev_to_mark(parents[1]))
443
444 for f in modified_final:
445 print "M %s :%u %s" % f
446 for f in removed:
447 print "D %s" % (fix_file_path(f))
448 print
449
450 count += 1
451 if (count % 100 == 0):
452 print "progress revision %d '%s' (%d/%d)" % (rev, name, count, len(revs))
453
454 # make sure the ref is updated
455 print "reset %s/%s" % (prefix, ename)
456 print "from :%u" % rev_to_mark(rev)
457 print
458
459 marks.set_tip(ename, rev)
460
461def export_tag(repo, tag):
462 export_ref(repo, tag, 'tags', repo[hgref(tag)])
463
464def export_bookmark(repo, bmark):
465 head = bmarks[hgref(bmark)]
466 export_ref(repo, bmark, 'bookmarks', head)
467
468def export_branch(repo, branch):
469 tip = get_branch_tip(repo, branch)
470 head = repo[tip]
471 export_ref(repo, branch, 'branches', head)
472
473def export_head(repo):
474 global g_head
475 export_ref(repo, g_head[0], 'bookmarks', g_head[1])
476
477def do_capabilities(parser):
478 global prefix, dirname
479
480 print "import"
481 print "export"
482 print "refspec refs/heads/branches/*:%s/branches/*" % prefix
483 print "refspec refs/heads/*:%s/bookmarks/*" % prefix
484 print "refspec refs/tags/*:%s/tags/*" % prefix
485
486 path = os.path.join(dirname, 'marks-git')
487
488 if os.path.exists(path):
489 print "*import-marks %s" % path
490 print "*export-marks %s" % path
491
492 print
493
494def branch_tip(repo, branch):
495 # older versions of mercurial don't have this
496 if hasattr(repo, 'branchtip'):
497 return repo.branchtip(branch)
498 else:
499 return repo.branchtags()[branch]
500
501def get_branch_tip(repo, branch):
502 global branches
503
504 heads = branches.get(hgref(branch), None)
505 if not heads:
506 return None
507
508 # verify there's only one head
509 if (len(heads) > 1):
510 warn("Branch '%s' has more than one head, consider merging" % branch)
511 return branch_tip(repo, hgref(branch))
512
513 return heads[0]
514
515def list_head(repo, cur):
516 global g_head, bmarks
517
518 head = bookmarks.readcurrent(repo)
519 if head:
520 node = repo[head]
521 else:
522 # fake bookmark from current branch
523 head = cur
524 node = repo['.']
525 if not node:
526 node = repo['tip']
527 if not node:
528 return
529 if head == 'default':
530 head = 'master'
531 bmarks[head] = node
532
533 head = gitref(head)
534 print "@refs/heads/%s HEAD" % head
535 g_head = (head, node)
536
537def do_list(parser):
538 global branches, bmarks, track_branches
539
540 repo = parser.repo
541 for bmark, node in bookmarks.listbookmarks(repo).iteritems():
542 bmarks[bmark] = repo[node]
543
544 cur = repo.dirstate.branch()
545
546 list_head(repo, cur)
547
548 if track_branches:
549 for branch in repo.branchmap():
550 heads = repo.branchheads(branch)
551 if len(heads):
552 branches[branch] = heads
553
554 for branch in branches:
555 print "? refs/heads/branches/%s" % gitref(branch)
556
557 for bmark in bmarks:
558 print "? refs/heads/%s" % gitref(bmark)
559
560 for tag, node in repo.tagslist():
561 if tag == 'tip':
562 continue
563 print "? refs/tags/%s" % gitref(tag)
564
565 print
566
567def do_import(parser):
568 repo = parser.repo
569
570 path = os.path.join(dirname, 'marks-git')
571
572 print "feature done"
573 if os.path.exists(path):
574 print "feature import-marks=%s" % path
575 print "feature export-marks=%s" % path
576 sys.stdout.flush()
577
578 tmp = encoding.encoding
579 encoding.encoding = 'utf-8'
580
581 # lets get all the import lines
582 while parser.check('import'):
583 ref = parser[1]
584
585 if (ref == 'HEAD'):
586 export_head(repo)
587 elif ref.startswith('refs/heads/branches/'):
588 branch = ref[len('refs/heads/branches/'):]
589 export_branch(repo, branch)
590 elif ref.startswith('refs/heads/'):
591 bmark = ref[len('refs/heads/'):]
592 export_bookmark(repo, bmark)
593 elif ref.startswith('refs/tags/'):
594 tag = ref[len('refs/tags/'):]
595 export_tag(repo, tag)
596
597 parser.next()
598
599 encoding.encoding = tmp
600
601 print 'done'
602
603def parse_blob(parser):
604 global blob_marks
605
606 parser.next()
607 mark = parser.get_mark()
608 parser.next()
609 data = parser.get_data()
610 blob_marks[mark] = data
611 parser.next()
612
613def get_merge_files(repo, p1, p2, files):
614 for e in repo[p1].files():
615 if e not in files:
616 if e not in repo[p1].manifest():
617 continue
618 f = { 'ctx' : repo[p1][e] }
619 files[e] = f
620
621def parse_commit(parser):
622 global marks, blob_marks, parsed_refs
623 global mode
624
625 from_mark = merge_mark = None
626
627 ref = parser[1]
628 parser.next()
629
630 commit_mark = parser.get_mark()
631 parser.next()
632 author = parser.get_author()
633 parser.next()
634 committer = parser.get_author()
635 parser.next()
636 data = parser.get_data()
637 parser.next()
638 if parser.check('from'):
639 from_mark = parser.get_mark()
640 parser.next()
641 if parser.check('merge'):
642 merge_mark = parser.get_mark()
643 parser.next()
644 if parser.check('merge'):
645 die('octopus merges are not supported yet')
646
647 # fast-export adds an extra newline
648 if data[-1] == '\n':
649 data = data[:-1]
650
651 files = {}
652
653 for line in parser:
654 if parser.check('M'):
655 t, m, mark_ref, path = line.split(' ', 3)
656 mark = int(mark_ref[1:])
657 f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
658 elif parser.check('D'):
659 t, path = line.split(' ', 1)
660 f = { 'deleted' : True }
661 else:
662 die('Unknown file command: %s' % line)
663 files[path] = f
664
665 def getfilectx(repo, memctx, f):
666 of = files[f]
667 if 'deleted' in of:
668 raise IOError
669 if 'ctx' in of:
670 return of['ctx']
671 is_exec = of['mode'] == 'x'
672 is_link = of['mode'] == 'l'
673 rename = of.get('rename', None)
674 return context.memfilectx(f, of['data'],
675 is_link, is_exec, rename)
676
677 repo = parser.repo
678
679 user, date, tz = author
680 extra = {}
681
682 if committer != author:
683 extra['committer'] = "%s %u %u" % committer
684
685 if from_mark:
686 p1 = repo.changelog.node(mark_to_rev(from_mark))
687 else:
688 p1 = '\0' * 20
689
690 if merge_mark:
691 p2 = repo.changelog.node(mark_to_rev(merge_mark))
692 else:
693 p2 = '\0' * 20
694
695 #
696 # If files changed from any of the parents, hg wants to know, but in git if
697 # nothing changed from the first parent, nothing changed.
698 #
699 if merge_mark:
700 get_merge_files(repo, p1, p2, files)
701
702 # Check if the ref is supposed to be a named branch
703 if ref.startswith('refs/heads/branches/'):
704 branch = ref[len('refs/heads/branches/'):]
705 extra['branch'] = hgref(branch)
706
707 if mode == 'hg':
708 i = data.find('\n--HG--\n')
709 if i >= 0:
710 tmp = data[i + len('\n--HG--\n'):].strip()
711 for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
712 if k == 'rename':
713 old, new = v.split(' => ', 1)
714 files[new]['rename'] = old
715 elif k == 'branch':
716 extra[k] = v
717 elif k == 'extra':
718 ek, ev = v.split(' : ', 1)
719 extra[ek] = urllib.unquote(ev)
720 data = data[:i]
721
722 ctx = context.memctx(repo, (p1, p2), data,
723 files.keys(), getfilectx,
724 user, (date, tz), extra)
725
726 tmp = encoding.encoding
727 encoding.encoding = 'utf-8'
728
729 node = repo.commitctx(ctx)
730
731 encoding.encoding = tmp
732
733 rev = repo[node].rev()
734
735 parsed_refs[ref] = node
736 marks.new_mark(rev, commit_mark)
737
738def parse_reset(parser):
739 global parsed_refs
740
741 ref = parser[1]
742 parser.next()
743 # ugh
744 if parser.check('commit'):
745 parse_commit(parser)
746 return
747 if not parser.check('from'):
748 return
749 from_mark = parser.get_mark()
750 parser.next()
751
752 node = parser.repo.changelog.node(mark_to_rev(from_mark))
753 parsed_refs[ref] = node
754
755def parse_tag(parser):
756 name = parser[1]
757 parser.next()
758 from_mark = parser.get_mark()
759 parser.next()
760 tagger = parser.get_author()
761 parser.next()
762 data = parser.get_data()
763 parser.next()
764
765 parsed_tags[name] = (tagger, data)
766
767def write_tag(repo, tag, node, msg, author):
768 branch = repo[node].branch()
769 tip = branch_tip(repo, branch)
770 tip = repo[tip]
771
772 def getfilectx(repo, memctx, f):
773 try:
774 fctx = tip.filectx(f)
775 data = fctx.data()
776 except error.ManifestLookupError:
777 data = ""
778 content = data + "%s %s\n" % (hghex(node), tag)
779 return context.memfilectx(f, content, False, False, None)
780
781 p1 = tip.hex()
782 p2 = '\0' * 20
783 if not author:
784 author = (None, 0, 0)
785 user, date, tz = author
786
787 ctx = context.memctx(repo, (p1, p2), msg,
788 ['.hgtags'], getfilectx,
789 user, (date, tz), {'branch' : branch})
790
791 tmp = encoding.encoding
792 encoding.encoding = 'utf-8'
793
794 tagnode = repo.commitctx(ctx)
795
796 encoding.encoding = tmp
797
798 return tagnode
799
800def do_export(parser):
801 global parsed_refs, bmarks, peer
802
803 p_bmarks = []
804
805 parser.next()
806
807 for line in parser.each_block('done'):
808 if parser.check('blob'):
809 parse_blob(parser)
810 elif parser.check('commit'):
811 parse_commit(parser)
812 elif parser.check('reset'):
813 parse_reset(parser)
814 elif parser.check('tag'):
815 parse_tag(parser)
816 elif parser.check('feature'):
817 pass
818 else:
819 die('unhandled export command: %s' % line)
820
821 for ref, node in parsed_refs.iteritems():
822 if ref.startswith('refs/heads/branches'):
823 branch = ref[len('refs/heads/branches/'):]
824 if branch in branches and node in branches[branch]:
825 # up to date
826 continue
827 print "ok %s" % ref
828 elif ref.startswith('refs/heads/'):
829 bmark = ref[len('refs/heads/'):]
830 p_bmarks.append((bmark, node))
831 continue
832 elif ref.startswith('refs/tags/'):
833 tag = ref[len('refs/tags/'):]
834 tag = hgref(tag)
835 author, msg = parsed_tags.get(tag, (None, None))
836 if mode == 'git':
837 if not msg:
838 msg = 'Added tag %s for changeset %s' % (tag, hghex(node[:6]));
839 write_tag(parser.repo, tag, node, msg, author)
840 else:
841 fp = parser.repo.opener('localtags', 'a')
842 fp.write('%s %s\n' % (hghex(node), tag))
843 fp.close()
844 print "ok %s" % ref
845 else:
846 # transport-helper/fast-export bugs
847 continue
848
849 if peer:
850 parser.repo.push(peer, force=force_push)
851
852 # handle bookmarks
853 for bmark, node in p_bmarks:
854 ref = 'refs/heads/' + bmark
855 new = hghex(node)
856
857 if bmark in bmarks:
858 old = bmarks[bmark].hex()
859 else:
860 old = ''
861
862 if old == new:
863 continue
864
865 if bmark == 'master' and 'master' not in parser.repo._bookmarks:
866 # fake bookmark
867 pass
868 elif bookmarks.pushbookmark(parser.repo, bmark, old, new):
869 # updated locally
870 pass
871 else:
872 print "error %s" % ref
873 continue
874
875 if peer:
876 rb = peer.listkeys('bookmarks')
877 old = rb.get(bmark, '')
878 if not peer.pushkey('bookmarks', bmark, old, new):
879 print "error %s" % ref
880 continue
881
882 print "ok %s" % ref
883
884 print
885
886def fix_path(alias, repo, orig_url):
887 url = urlparse.urlparse(orig_url, 'file')
888 if url.scheme != 'file' or os.path.isabs(url.path):
889 return
890 abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
891 cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
892 subprocess.call(cmd)
893
894def main(args):
895 global prefix, dirname, branches, bmarks
896 global marks, blob_marks, parsed_refs
897 global peer, mode, bad_mail, bad_name
898 global track_branches, force_push, is_tmp
899 global parsed_tags
900 global filenodes
901
902 alias = args[1]
903 url = args[2]
904 peer = None
905
906 hg_git_compat = False
907 track_branches = True
908 force_push = True
909
910 if get_config('remote-hg.hg-git-compat') == 'true\n':
911 hg_git_compat = True
912 track_branches = False
913 if get_config('remote-hg.track-branches') == 'false\n':
914 track_branches = False
915 if get_config('remote-hg.force-push') == 'false\n':
916 force_push = False
917
918 if hg_git_compat:
919 mode = 'hg'
920 bad_mail = 'none@none'
921 bad_name = ''
922 else:
923 mode = 'git'
924 bad_mail = 'unknown'
925 bad_name = 'Unknown'
926
927 if alias[4:] == url:
928 is_tmp = True
929 alias = hashlib.sha1(alias).hexdigest()
930 else:
931 is_tmp = False
932
933 gitdir = os.environ['GIT_DIR']
934 dirname = os.path.join(gitdir, 'hg', alias)
935 branches = {}
936 bmarks = {}
937 blob_marks = {}
938 parsed_refs = {}
939 marks = None
940 parsed_tags = {}
941 filenodes = {}
942
943 repo = get_repo(url, alias)
944 prefix = 'refs/hg/%s' % alias
945
946 if not is_tmp:
947 fix_path(alias, peer or repo, url)
948
949 if not os.path.exists(dirname):
950 os.makedirs(dirname)
951
952 marks_path = os.path.join(dirname, 'marks-hg')
953 marks = Marks(marks_path)
954
955 parser = Parser(repo)
956 for line in parser:
957 if parser.check('capabilities'):
958 do_capabilities(parser)
959 elif parser.check('list'):
960 do_list(parser)
961 elif parser.check('import'):
962 do_import(parser)
963 elif parser.check('export'):
964 do_export(parser)
965 else:
966 die('unhandled command: %s' % line)
967 sys.stdout.flush()
968
969def bye():
970 if not marks:
971 return
972 if not is_tmp:
973 marks.store()
974 else:
975 shutil.rmtree(dirname)
976
977atexit.register(bye)
978sys.exit(main(sys.argv))