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