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