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