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