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