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