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