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