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