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