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