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