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