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, discovery, util
16
17import re
18import sys
19import os
20import json
21import shutil
22import subprocess
23import urllib
24import atexit
25import urlparse, hashlib
26import time as ptime
27
28#
29# If you want to see Mercurial revisions as Git commit notes:
30# git config core.notesRef refs/notes/hg
31#
32# If you are not in hg-git-compat mode and want to disable the tracking of
33# named branches:
34# git config --global remote-hg.track-branches false
35#
36# If you want the equivalent of hg's clone/pull--insecure option:
37# git config --global remote-hg.insecure true
38#
39# If you want to switch to hg-git compatibility mode:
40# git config --global remote-hg.hg-git-compat true
41#
42# git:
43# Sensible defaults for git.
44# hg bookmarks are exported as git branches, hg branches are prefixed
45# with 'branches/', HEAD is a special case.
46#
47# hg:
48# Emulate hg-git.
49# Only hg bookmarks are exported as git branches.
50# Commits are modified to preserve hg information and allow bidirectionality.
51#
52
53NAME_RE = re.compile('^([^<>]+)')
54AUTHOR_RE = re.compile('^([^<>]+?)? ?<([^<>]*)>$')
55EMAIL_RE = re.compile('^([^<>]+[^ \\\t<>])?\\b(?:[ \\t<>]*?)\\b([^ \\t<>]+@[^ \\t<>]+)')
56AUTHOR_HG_RE = re.compile('^(.*?) ?<(.*?)(?:>(.+)?)?$')
57RAW_AUTHOR_RE = re.compile('^(\w+) (?:(.+)? )?<(.*)> (\d+) ([+-]\d+)')
58
59VERSION = 2
60
61def die(msg, *args):
62 sys.stderr.write('ERROR: %s\n' % (msg % args))
63 sys.exit(1)
64
65def warn(msg, *args):
66 sys.stderr.write('WARNING: %s\n' % (msg % args))
67
68def gitmode(flags):
69 return 'l' in flags and '120000' or 'x' in flags and '100755' or '100644'
70
71def gittz(tz):
72 return '%+03d%02d' % (-tz / 3600, -tz % 3600 / 60)
73
74def hgmode(mode):
75 m = { '100755': 'x', '120000': 'l' }
76 return m.get(mode, '')
77
78def hghex(n):
79 return node.hex(n)
80
81def hgbin(n):
82 return node.bin(n)
83
84def hgref(ref):
85 return ref.replace('___', ' ')
86
87def gitref(ref):
88 return ref.replace(' ', '___')
89
90def check_version(*check):
91 if not hg_version:
92 return True
93 return hg_version >= check
94
95def get_config(config):
96 cmd = ['git', 'config', '--get', config]
97 process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
98 output, _ = process.communicate()
99 return output
100
101def get_config_bool(config, default=False):
102 value = get_config(config).rstrip('\n')
103 if value == "true":
104 return True
105 elif value == "false":
106 return False
107 else:
108 return default
109
110class Marks:
111
112 def __init__(self, path, repo):
113 self.path = path
114 self.repo = repo
115 self.clear()
116 self.load()
117
118 if self.version < VERSION:
119 if self.version == 1:
120 self.upgrade_one()
121
122 # upgraded?
123 if self.version < VERSION:
124 self.clear()
125 self.version = VERSION
126
127 def clear(self):
128 self.tips = {}
129 self.marks = {}
130 self.rev_marks = {}
131 self.last_mark = 0
132 self.version = 0
133 self.last_note = 0
134
135 def load(self):
136 if not os.path.exists(self.path):
137 return
138
139 tmp = json.load(open(self.path))
140
141 self.tips = tmp['tips']
142 self.marks = tmp['marks']
143 self.last_mark = tmp['last-mark']
144 self.version = tmp.get('version', 1)
145 self.last_note = tmp.get('last-note', 0)
146
147 for rev, mark in self.marks.iteritems():
148 self.rev_marks[mark] = rev
149
150 def upgrade_one(self):
151 def get_id(rev):
152 return hghex(self.repo.changelog.node(int(rev)))
153 self.tips = dict((name, get_id(rev)) for name, rev in self.tips.iteritems())
154 self.marks = dict((get_id(rev), mark) for rev, mark in self.marks.iteritems())
155 self.rev_marks = dict((mark, get_id(rev)) for mark, rev in self.rev_marks.iteritems())
156 self.version = 2
157
158 def dict(self):
159 return { 'tips': self.tips, 'marks': self.marks, 'last-mark' : self.last_mark, 'version' : self.version, 'last-note' : self.last_note }
160
161 def store(self):
162 json.dump(self.dict(), open(self.path, 'w'))
163
164 def __str__(self):
165 return str(self.dict())
166
167 def from_rev(self, rev):
168 return self.marks[rev]
169
170 def to_rev(self, mark):
171 return str(self.rev_marks[mark])
172
173 def next_mark(self):
174 self.last_mark += 1
175 return self.last_mark
176
177 def get_mark(self, rev):
178 self.last_mark += 1
179 self.marks[rev] = self.last_mark
180 return self.last_mark
181
182 def new_mark(self, rev, mark):
183 self.marks[rev] = mark
184 self.rev_marks[mark] = rev
185 self.last_mark = mark
186
187 def is_marked(self, rev):
188 return rev in self.marks
189
190 def get_tip(self, branch):
191 return str(self.tips[branch])
192
193 def set_tip(self, branch, tip):
194 self.tips[branch] = tip
195
196class Parser:
197
198 def __init__(self, repo):
199 self.repo = repo
200 self.line = self.get_line()
201
202 def get_line(self):
203 return sys.stdin.readline().strip()
204
205 def __getitem__(self, i):
206 return self.line.split()[i]
207
208 def check(self, word):
209 return self.line.startswith(word)
210
211 def each_block(self, separator):
212 while self.line != separator:
213 yield self.line
214 self.line = self.get_line()
215
216 def __iter__(self):
217 return self.each_block('')
218
219 def next(self):
220 self.line = self.get_line()
221 if self.line == 'done':
222 self.line = None
223
224 def get_mark(self):
225 i = self.line.index(':') + 1
226 return int(self.line[i:])
227
228 def get_data(self):
229 if not self.check('data'):
230 return None
231 i = self.line.index(' ') + 1
232 size = int(self.line[i:])
233 return sys.stdin.read(size)
234
235 def get_author(self):
236 ex = None
237 m = RAW_AUTHOR_RE.match(self.line)
238 if not m:
239 return None
240 _, name, email, date, tz = m.groups()
241 if name and 'ext:' in name:
242 m = re.match('^(.+?) ext:\((.+)\)$', name)
243 if m:
244 name = m.group(1)
245 ex = urllib.unquote(m.group(2))
246
247 if email != bad_mail:
248 if name:
249 user = '%s <%s>' % (name, email)
250 else:
251 user = '<%s>' % (email)
252 else:
253 user = name
254
255 if ex:
256 user += ex
257
258 tz = int(tz)
259 tz = ((tz / 100) * 3600) + ((tz % 100) * 60)
260 return (user, int(date), -tz)
261
262def fix_file_path(path):
263 if not os.path.isabs(path):
264 return path
265 return os.path.relpath(path, '/')
266
267def export_files(files):
268 final = []
269 for f in files:
270 fid = node.hex(f.filenode())
271
272 if fid in filenodes:
273 mark = filenodes[fid]
274 else:
275 mark = marks.next_mark()
276 filenodes[fid] = mark
277 d = f.data()
278
279 print "blob"
280 print "mark :%u" % mark
281 print "data %d" % len(d)
282 print d
283
284 path = fix_file_path(f.path())
285 final.append((gitmode(f.flags()), mark, path))
286
287 return final
288
289def get_filechanges(repo, ctx, parent):
290 modified = set()
291 added = set()
292 removed = set()
293
294 # load earliest manifest first for caching reasons
295 prev = parent.manifest().copy()
296 cur = ctx.manifest()
297
298 for fn in cur:
299 if fn in prev:
300 if (cur.flags(fn) != prev.flags(fn) or cur[fn] != prev[fn]):
301 modified.add(fn)
302 del prev[fn]
303 else:
304 added.add(fn)
305 removed |= set(prev.keys())
306
307 return added | modified, removed
308
309def fixup_user_git(user):
310 name = mail = None
311 user = user.replace('"', '')
312 m = AUTHOR_RE.match(user)
313 if m:
314 name = m.group(1)
315 mail = m.group(2).strip()
316 else:
317 m = EMAIL_RE.match(user)
318 if m:
319 name = m.group(1)
320 mail = m.group(2)
321 else:
322 m = NAME_RE.match(user)
323 if m:
324 name = m.group(1).strip()
325 return (name, mail)
326
327def fixup_user_hg(user):
328 def sanitize(name):
329 # stole this from hg-git
330 return re.sub('[<>\n]', '?', name.lstrip('< ').rstrip('> '))
331
332 m = AUTHOR_HG_RE.match(user)
333 if m:
334 name = sanitize(m.group(1))
335 mail = sanitize(m.group(2))
336 ex = m.group(3)
337 if ex:
338 name += ' ext:(' + urllib.quote(ex) + ')'
339 else:
340 name = sanitize(user)
341 if '@' in user:
342 mail = name
343 else:
344 mail = None
345
346 return (name, mail)
347
348def fixup_user(user):
349 if mode == 'git':
350 name, mail = fixup_user_git(user)
351 else:
352 name, mail = fixup_user_hg(user)
353
354 if not name:
355 name = bad_name
356 if not mail:
357 mail = bad_mail
358
359 return '%s <%s>' % (name, mail)
360
361def updatebookmarks(repo, peer):
362 remotemarks = peer.listkeys('bookmarks')
363 localmarks = repo._bookmarks
364
365 if not remotemarks:
366 return
367
368 for k, v in remotemarks.iteritems():
369 localmarks[k] = hgbin(v)
370
371 if hasattr(localmarks, 'write'):
372 localmarks.write()
373 else:
374 bookmarks.write(repo)
375
376def get_repo(url, alias):
377 global peer
378
379 myui = ui.ui()
380 myui.setconfig('ui', 'interactive', 'off')
381 myui.fout = sys.stderr
382
383 if get_config_bool('remote-hg.insecure'):
384 myui.setconfig('web', 'cacerts', '')
385
386 extensions.loadall(myui)
387
388 if hg.islocal(url) and not os.environ.get('GIT_REMOTE_HG_TEST_REMOTE'):
389 repo = hg.repository(myui, url)
390 if not os.path.exists(dirname):
391 os.makedirs(dirname)
392 else:
393 shared_path = os.path.join(gitdir, 'hg')
394
395 # check and upgrade old organization
396 hg_path = os.path.join(shared_path, '.hg')
397 if os.path.exists(shared_path) and not os.path.exists(hg_path):
398 repos = os.listdir(shared_path)
399 for x in repos:
400 local_hg = os.path.join(shared_path, x, 'clone', '.hg')
401 if not os.path.exists(local_hg):
402 continue
403 if not os.path.exists(hg_path):
404 shutil.move(local_hg, hg_path)
405 shutil.rmtree(os.path.join(shared_path, x, 'clone'))
406
407 # setup shared repo (if not there)
408 try:
409 hg.peer(myui, {}, shared_path, create=True)
410 except error.RepoError:
411 pass
412
413 if not os.path.exists(dirname):
414 os.makedirs(dirname)
415
416 local_path = os.path.join(dirname, 'clone')
417 if not os.path.exists(local_path):
418 hg.share(myui, shared_path, local_path, update=False)
419
420 repo = hg.repository(myui, local_path)
421 try:
422 peer = hg.peer(myui, {}, url)
423 except:
424 die('Repository error')
425 repo.pull(peer, heads=None, force=True)
426
427 updatebookmarks(repo, peer)
428
429 return repo
430
431def rev_to_mark(rev):
432 return marks.from_rev(rev.hex())
433
434def mark_to_rev(mark):
435 return marks.to_rev(mark)
436
437def export_ref(repo, name, kind, head):
438 ename = '%s/%s' % (kind, name)
439 try:
440 tip = marks.get_tip(ename)
441 tip = repo[tip].rev()
442 except:
443 tip = 0
444
445 revs = xrange(tip, head.rev() + 1)
446 total = len(revs)
447
448 for rev in revs:
449
450 c = repo[rev]
451 node = c.node()
452
453 if marks.is_marked(c.hex()):
454 continue
455
456 (manifest, user, (time, tz), files, desc, extra) = repo.changelog.read(node)
457 rev_branch = extra['branch']
458
459 author = "%s %d %s" % (fixup_user(user), time, gittz(tz))
460 if 'committer' in extra:
461 user, time, tz = extra['committer'].rsplit(' ', 2)
462 committer = "%s %s %s" % (user, time, gittz(int(tz)))
463 else:
464 committer = author
465
466 parents = [repo[p] for p in repo.changelog.parentrevs(rev) if p >= 0]
467
468 if len(parents) == 0:
469 modified = c.manifest().keys()
470 removed = []
471 else:
472 modified, removed = get_filechanges(repo, c, parents[0])
473
474 desc += '\n'
475
476 if mode == 'hg':
477 extra_msg = ''
478
479 if rev_branch != 'default':
480 extra_msg += 'branch : %s\n' % rev_branch
481
482 renames = []
483 for f in c.files():
484 if f not in c.manifest():
485 continue
486 rename = c.filectx(f).renamed()
487 if rename:
488 renames.append((rename[0], f))
489
490 for e in renames:
491 extra_msg += "rename : %s => %s\n" % e
492
493 for key, value in extra.iteritems():
494 if key in ('author', 'committer', 'encoding', 'message', 'branch', 'hg-git'):
495 continue
496 else:
497 extra_msg += "extra : %s : %s\n" % (key, urllib.quote(value))
498
499 if extra_msg:
500 desc += '\n--HG--\n' + extra_msg
501
502 if len(parents) == 0 and rev:
503 print 'reset %s/%s' % (prefix, ename)
504
505 modified_final = export_files(c.filectx(f) for f in modified)
506
507 print "commit %s/%s" % (prefix, ename)
508 print "mark :%d" % (marks.get_mark(c.hex()))
509 print "author %s" % (author)
510 print "committer %s" % (committer)
511 print "data %d" % (len(desc))
512 print desc
513
514 if len(parents) > 0:
515 print "from :%s" % (rev_to_mark(parents[0]))
516 if len(parents) > 1:
517 print "merge :%s" % (rev_to_mark(parents[1]))
518
519 for f in removed:
520 print "D %s" % (fix_file_path(f))
521 for f in modified_final:
522 print "M %s :%u %s" % f
523 print
524
525 progress = (rev - tip)
526 if (progress % 100 == 0):
527 print "progress revision %d '%s' (%d/%d)" % (rev, name, progress, total)
528
529 # make sure the ref is updated
530 print "reset %s/%s" % (prefix, ename)
531 print "from :%u" % rev_to_mark(head)
532 print
533
534 pending_revs = set(revs) - notes
535 if pending_revs:
536 note_mark = marks.next_mark()
537 ref = "refs/notes/hg"
538
539 print "commit %s" % ref
540 print "mark :%d" % (note_mark)
541 print "committer remote-hg <> %s" % (ptime.strftime('%s %z'))
542 desc = "Notes for %s\n" % (name)
543 print "data %d" % (len(desc))
544 print desc
545 if marks.last_note:
546 print "from :%u" % marks.last_note
547
548 for rev in pending_revs:
549 notes.add(rev)
550 c = repo[rev]
551 print "N inline :%u" % rev_to_mark(c)
552 msg = c.hex()
553 print "data %d" % (len(msg))
554 print msg
555 print
556
557 marks.last_note = note_mark
558
559 marks.set_tip(ename, head.hex())
560
561def export_tag(repo, tag):
562 export_ref(repo, tag, 'tags', repo[hgref(tag)])
563
564def export_bookmark(repo, bmark):
565 head = bmarks[hgref(bmark)]
566 export_ref(repo, bmark, 'bookmarks', head)
567
568def export_branch(repo, branch):
569 tip = get_branch_tip(repo, branch)
570 head = repo[tip]
571 export_ref(repo, branch, 'branches', head)
572
573def export_head(repo):
574 export_ref(repo, g_head[0], 'bookmarks', g_head[1])
575
576def do_capabilities(parser):
577 print "import"
578 print "export"
579 print "refspec refs/heads/branches/*:%s/branches/*" % prefix
580 print "refspec refs/heads/*:%s/bookmarks/*" % prefix
581 print "refspec refs/tags/*:%s/tags/*" % prefix
582
583 path = os.path.join(dirname, 'marks-git')
584
585 if os.path.exists(path):
586 print "*import-marks %s" % path
587 print "*export-marks %s" % path
588 print "option"
589
590 print
591
592def branch_tip(branch):
593 return branches[branch][-1]
594
595def get_branch_tip(repo, branch):
596 heads = branches.get(hgref(branch), None)
597 if not heads:
598 return None
599
600 # verify there's only one head
601 if (len(heads) > 1):
602 warn("Branch '%s' has more than one head, consider merging" % branch)
603 return branch_tip(hgref(branch))
604
605 return heads[0]
606
607def list_head(repo, cur):
608 global g_head, fake_bmark
609
610 if 'default' not in branches:
611 # empty repo
612 return
613
614 node = repo[branch_tip('default')]
615 head = 'master' if not 'master' in bmarks else 'default'
616 fake_bmark = head
617 bmarks[head] = node
618
619 head = gitref(head)
620 print "@refs/heads/%s HEAD" % head
621 g_head = (head, node)
622
623def do_list(parser):
624 repo = parser.repo
625 for bmark, node in bookmarks.listbookmarks(repo).iteritems():
626 bmarks[bmark] = repo[node]
627
628 cur = repo.dirstate.branch()
629 orig = peer if peer else repo
630
631 for branch, heads in orig.branchmap().iteritems():
632 # only open heads
633 heads = [h for h in heads if 'close' not in repo.changelog.read(h)[5]]
634 if heads:
635 branches[branch] = heads
636
637 list_head(repo, cur)
638
639 if track_branches:
640 for branch in branches:
641 print "? refs/heads/branches/%s" % gitref(branch)
642
643 for bmark in bmarks:
644 print "? refs/heads/%s" % gitref(bmark)
645
646 for tag, node in repo.tagslist():
647 if tag == 'tip':
648 continue
649 print "? refs/tags/%s" % gitref(tag)
650
651 print
652
653def do_import(parser):
654 repo = parser.repo
655
656 path = os.path.join(dirname, 'marks-git')
657
658 print "feature done"
659 if os.path.exists(path):
660 print "feature import-marks=%s" % path
661 print "feature export-marks=%s" % path
662 print "feature force"
663 sys.stdout.flush()
664
665 tmp = encoding.encoding
666 encoding.encoding = 'utf-8'
667
668 # lets get all the import lines
669 while parser.check('import'):
670 ref = parser[1]
671
672 if (ref == 'HEAD'):
673 export_head(repo)
674 elif ref.startswith('refs/heads/branches/'):
675 branch = ref[len('refs/heads/branches/'):]
676 export_branch(repo, branch)
677 elif ref.startswith('refs/heads/'):
678 bmark = ref[len('refs/heads/'):]
679 export_bookmark(repo, bmark)
680 elif ref.startswith('refs/tags/'):
681 tag = ref[len('refs/tags/'):]
682 export_tag(repo, tag)
683
684 parser.next()
685
686 encoding.encoding = tmp
687
688 print 'done'
689
690def parse_blob(parser):
691 parser.next()
692 mark = parser.get_mark()
693 parser.next()
694 data = parser.get_data()
695 blob_marks[mark] = data
696 parser.next()
697
698def get_merge_files(repo, p1, p2, files):
699 for e in repo[p1].files():
700 if e not in files:
701 if e not in repo[p1].manifest():
702 continue
703 f = { 'ctx' : repo[p1][e] }
704 files[e] = f
705
706def c_style_unescape(string):
707 if string[0] == string[-1] == '"':
708 return string.decode('string-escape')[1:-1]
709 return string
710
711def parse_commit(parser):
712 from_mark = merge_mark = None
713
714 ref = parser[1]
715 parser.next()
716
717 commit_mark = parser.get_mark()
718 parser.next()
719 author = parser.get_author()
720 parser.next()
721 committer = parser.get_author()
722 parser.next()
723 data = parser.get_data()
724 parser.next()
725 if parser.check('from'):
726 from_mark = parser.get_mark()
727 parser.next()
728 if parser.check('merge'):
729 merge_mark = parser.get_mark()
730 parser.next()
731 if parser.check('merge'):
732 die('octopus merges are not supported yet')
733
734 # fast-export adds an extra newline
735 if data[-1] == '\n':
736 data = data[:-1]
737
738 files = {}
739
740 for line in parser:
741 if parser.check('M'):
742 t, m, mark_ref, path = line.split(' ', 3)
743 mark = int(mark_ref[1:])
744 f = { 'mode' : hgmode(m), 'data' : blob_marks[mark] }
745 elif parser.check('D'):
746 t, path = line.split(' ', 1)
747 f = { 'deleted' : True }
748 else:
749 die('Unknown file command: %s' % line)
750 path = c_style_unescape(path).decode('utf-8')
751 files[path] = f
752
753 # only export the commits if we are on an internal proxy repo
754 if dry_run and not peer:
755 parsed_refs[ref] = None
756 return
757
758 def getfilectx(repo, memctx, f):
759 of = files[f]
760 if 'deleted' in of:
761 raise IOError
762 if 'ctx' in of:
763 return of['ctx']
764 is_exec = of['mode'] == 'x'
765 is_link = of['mode'] == 'l'
766 rename = of.get('rename', None)
767 return context.memfilectx(f, of['data'],
768 is_link, is_exec, rename)
769
770 repo = parser.repo
771
772 user, date, tz = author
773 extra = {}
774
775 if committer != author:
776 extra['committer'] = "%s %u %u" % committer
777
778 if from_mark:
779 p1 = mark_to_rev(from_mark)
780 else:
781 p1 = '0' * 40
782
783 if merge_mark:
784 p2 = mark_to_rev(merge_mark)
785 else:
786 p2 = '0' * 40
787
788 #
789 # If files changed from any of the parents, hg wants to know, but in git if
790 # nothing changed from the first parent, nothing changed.
791 #
792 if merge_mark:
793 get_merge_files(repo, p1, p2, files)
794
795 # Check if the ref is supposed to be a named branch
796 if ref.startswith('refs/heads/branches/'):
797 branch = ref[len('refs/heads/branches/'):]
798 extra['branch'] = hgref(branch)
799
800 if mode == 'hg':
801 i = data.find('\n--HG--\n')
802 if i >= 0:
803 tmp = data[i + len('\n--HG--\n'):].strip()
804 for k, v in [e.split(' : ', 1) for e in tmp.split('\n')]:
805 if k == 'rename':
806 old, new = v.split(' => ', 1)
807 files[new]['rename'] = old
808 elif k == 'branch':
809 extra[k] = v
810 elif k == 'extra':
811 ek, ev = v.split(' : ', 1)
812 extra[ek] = urllib.unquote(ev)
813 data = data[:i]
814
815 ctx = context.memctx(repo, (p1, p2), data,
816 files.keys(), getfilectx,
817 user, (date, tz), extra)
818
819 tmp = encoding.encoding
820 encoding.encoding = 'utf-8'
821
822 node = hghex(repo.commitctx(ctx))
823
824 encoding.encoding = tmp
825
826 parsed_refs[ref] = node
827 marks.new_mark(node, commit_mark)
828
829def parse_reset(parser):
830 ref = parser[1]
831 parser.next()
832 # ugh
833 if parser.check('commit'):
834 parse_commit(parser)
835 return
836 if not parser.check('from'):
837 return
838 from_mark = parser.get_mark()
839 parser.next()
840
841 try:
842 rev = mark_to_rev(from_mark)
843 except KeyError:
844 rev = None
845 parsed_refs[ref] = rev
846
847def parse_tag(parser):
848 name = parser[1]
849 parser.next()
850 from_mark = parser.get_mark()
851 parser.next()
852 tagger = parser.get_author()
853 parser.next()
854 data = parser.get_data()
855 parser.next()
856
857 parsed_tags[name] = (tagger, data)
858
859def write_tag(repo, tag, node, msg, author):
860 branch = repo[node].branch()
861 tip = branch_tip(branch)
862 tip = repo[tip]
863
864 def getfilectx(repo, memctx, f):
865 try:
866 fctx = tip.filectx(f)
867 data = fctx.data()
868 except error.ManifestLookupError:
869 data = ""
870 content = data + "%s %s\n" % (node, tag)
871 return context.memfilectx(f, content, False, False, None)
872
873 p1 = tip.hex()
874 p2 = '0' * 40
875 if author:
876 user, date, tz = author
877 date_tz = (date, tz)
878 else:
879 cmd = ['git', 'var', 'GIT_COMMITTER_IDENT']
880 process = subprocess.Popen(cmd, stdout=subprocess.PIPE)
881 output, _ = process.communicate()
882 m = re.match('^.* <.*>', output)
883 if m:
884 user = m.group(0)
885 else:
886 user = repo.ui.username()
887 date_tz = None
888
889 ctx = context.memctx(repo, (p1, p2), msg,
890 ['.hgtags'], getfilectx,
891 user, date_tz, {'branch' : branch})
892
893 tmp = encoding.encoding
894 encoding.encoding = 'utf-8'
895
896 tagnode = repo.commitctx(ctx)
897
898 encoding.encoding = tmp
899
900 return (tagnode, branch)
901
902def checkheads_bmark(repo, ref, ctx):
903 bmark = ref[len('refs/heads/'):]
904 if not bmark in bmarks:
905 # new bmark
906 return True
907
908 ctx_old = bmarks[bmark]
909 ctx_new = ctx
910 if not repo.changelog.descendant(ctx_old.rev(), ctx_new.rev()):
911 if force_push:
912 print "ok %s forced update" % ref
913 else:
914 print "error %s non-fast forward" % ref
915 return False
916
917 return True
918
919def checkheads(repo, remote, p_revs):
920
921 remotemap = remote.branchmap()
922 if not remotemap:
923 # empty repo
924 return True
925
926 new = {}
927 ret = True
928
929 for node, ref in p_revs.iteritems():
930 ctx = repo[node]
931 branch = ctx.branch()
932 if not branch in remotemap:
933 # new branch
934 continue
935 if not ref.startswith('refs/heads/branches'):
936 if ref.startswith('refs/heads/'):
937 if not checkheads_bmark(repo, ref, ctx):
938 ret = False
939
940 # only check branches
941 continue
942 new.setdefault(branch, []).append(ctx.rev())
943
944 for branch, heads in new.iteritems():
945 old = [repo.changelog.rev(x) for x in remotemap[branch]]
946 for rev in heads:
947 if check_version(2, 3):
948 ancestors = repo.changelog.ancestors([rev], stoprev=min(old))
949 else:
950 ancestors = repo.changelog.ancestors(rev)
951 found = False
952
953 for x in old:
954 if x in ancestors:
955 found = True
956 break
957
958 if found:
959 continue
960
961 node = repo.changelog.node(rev)
962 ref = p_revs[node]
963 if force_push:
964 print "ok %s forced update" % ref
965 else:
966 print "error %s non-fast forward" % ref
967 ret = False
968
969 return ret
970
971def push_unsafe(repo, remote, parsed_refs, p_revs):
972
973 force = force_push
974
975 fci = discovery.findcommonincoming
976 commoninc = fci(repo, remote, force=force)
977 common, _, remoteheads = commoninc
978
979 if not checkheads(repo, remote, p_revs):
980 return None
981
982 cg = repo.getbundle('push', heads=list(p_revs), common=common)
983
984 unbundle = remote.capable('unbundle')
985 if unbundle:
986 if force:
987 remoteheads = ['force']
988 return remote.unbundle(cg, remoteheads, 'push')
989 else:
990 return remote.addchangegroup(cg, 'push', repo.url())
991
992def push(repo, remote, parsed_refs, p_revs):
993 if hasattr(remote, 'canpush') and not remote.canpush():
994 print "error cannot push"
995
996 if not p_revs:
997 # nothing to push
998 return
999
1000 lock = None
1001 unbundle = remote.capable('unbundle')
1002 if not unbundle:
1003 lock = remote.lock()
1004 try:
1005 ret = push_unsafe(repo, remote, parsed_refs, p_revs)
1006 finally:
1007 if lock is not None:
1008 lock.release()
1009
1010 return ret
1011
1012def check_tip(ref, kind, name, heads):
1013 try:
1014 ename = '%s/%s' % (kind, name)
1015 tip = marks.get_tip(ename)
1016 except KeyError:
1017 return True
1018 else:
1019 return tip in heads
1020
1021def do_export(parser):
1022 p_bmarks = []
1023 p_revs = {}
1024
1025 parser.next()
1026
1027 for line in parser.each_block('done'):
1028 if parser.check('blob'):
1029 parse_blob(parser)
1030 elif parser.check('commit'):
1031 parse_commit(parser)
1032 elif parser.check('reset'):
1033 parse_reset(parser)
1034 elif parser.check('tag'):
1035 parse_tag(parser)
1036 elif parser.check('feature'):
1037 pass
1038 else:
1039 die('unhandled export command: %s' % line)
1040
1041 need_fetch = False
1042
1043 for ref, node in parsed_refs.iteritems():
1044 bnode = hgbin(node) if node else None
1045 if ref.startswith('refs/heads/branches'):
1046 branch = ref[len('refs/heads/branches/'):]
1047 if branch in branches and bnode in branches[branch]:
1048 # up to date
1049 continue
1050
1051 if peer:
1052 remotemap = peer.branchmap()
1053 if remotemap and branch in remotemap:
1054 heads = [hghex(e) for e in remotemap[branch]]
1055 if not check_tip(ref, 'branches', branch, heads):
1056 print "error %s fetch first" % ref
1057 need_fetch = True
1058 continue
1059
1060 p_revs[bnode] = ref
1061 print "ok %s" % ref
1062 elif ref.startswith('refs/heads/'):
1063 bmark = ref[len('refs/heads/'):]
1064 new = node
1065 old = bmarks[bmark].hex() if bmark in bmarks else ''
1066
1067 if old == new:
1068 continue
1069
1070 print "ok %s" % ref
1071 if bmark != fake_bmark and \
1072 not (bmark == 'master' and bmark not in parser.repo._bookmarks):
1073 p_bmarks.append((ref, bmark, old, new))
1074
1075 if peer:
1076 remote_old = peer.listkeys('bookmarks').get(bmark)
1077 if remote_old:
1078 if not check_tip(ref, 'bookmarks', bmark, remote_old):
1079 print "error %s fetch first" % ref
1080 need_fetch = True
1081 continue
1082
1083 p_revs[bnode] = ref
1084 elif ref.startswith('refs/tags/'):
1085 if dry_run:
1086 print "ok %s" % ref
1087 continue
1088 tag = ref[len('refs/tags/'):]
1089 tag = hgref(tag)
1090 author, msg = parsed_tags.get(tag, (None, None))
1091 if mode == 'git':
1092 if not msg:
1093 msg = 'Added tag %s for changeset %s' % (tag, node[:12])
1094 tagnode, branch = write_tag(parser.repo, tag, node, msg, author)
1095 p_revs[tagnode] = 'refs/heads/branches/' + gitref(branch)
1096 else:
1097 fp = parser.repo.opener('localtags', 'a')
1098 fp.write('%s %s\n' % (node, tag))
1099 fp.close()
1100 p_revs[bnode] = ref
1101 print "ok %s" % ref
1102 else:
1103 # transport-helper/fast-export bugs
1104 continue
1105
1106 if need_fetch:
1107 print
1108 return
1109
1110 if dry_run:
1111 if peer and not force_push:
1112 checkheads(parser.repo, peer, p_revs)
1113 print
1114 return
1115
1116 if peer:
1117 if not push(parser.repo, peer, parsed_refs, p_revs):
1118 # do not update bookmarks
1119 print
1120 return
1121
1122 # update remote bookmarks
1123 remote_bmarks = peer.listkeys('bookmarks')
1124 for ref, bmark, old, new in p_bmarks:
1125 if force_push:
1126 old = remote_bmarks.get(bmark, '')
1127 if not peer.pushkey('bookmarks', bmark, old, new):
1128 print "error %s" % ref
1129 else:
1130 # update local bookmarks
1131 for ref, bmark, old, new in p_bmarks:
1132 if not bookmarks.pushbookmark(parser.repo, bmark, old, new):
1133 print "error %s" % ref
1134
1135 print
1136
1137def do_option(parser):
1138 global dry_run, force_push
1139 _, key, value = parser.line.split(' ')
1140 if key == 'dry-run':
1141 dry_run = (value == 'true')
1142 print 'ok'
1143 elif key == 'force':
1144 force_push = (value == 'true')
1145 print 'ok'
1146 else:
1147 print 'unsupported'
1148
1149def fix_path(alias, repo, orig_url):
1150 url = urlparse.urlparse(orig_url, 'file')
1151 if url.scheme != 'file' or os.path.isabs(os.path.expanduser(url.path)):
1152 return
1153 abs_url = urlparse.urljoin("%s/" % os.getcwd(), orig_url)
1154 cmd = ['git', 'config', 'remote.%s.url' % alias, "hg::%s" % abs_url]
1155 subprocess.call(cmd)
1156
1157def main(args):
1158 global prefix, gitdir, dirname, branches, bmarks
1159 global marks, blob_marks, parsed_refs
1160 global peer, mode, bad_mail, bad_name
1161 global track_branches, force_push, is_tmp
1162 global parsed_tags
1163 global filenodes
1164 global fake_bmark, hg_version
1165 global dry_run
1166 global notes, alias
1167
1168 alias = args[1]
1169 url = args[2]
1170 peer = None
1171
1172 hg_git_compat = get_config_bool('remote-hg.hg-git-compat')
1173 track_branches = get_config_bool('remote-hg.track-branches', True)
1174 force_push = False
1175
1176 if hg_git_compat:
1177 mode = 'hg'
1178 bad_mail = 'none@none'
1179 bad_name = ''
1180 else:
1181 mode = 'git'
1182 bad_mail = 'unknown'
1183 bad_name = 'Unknown'
1184
1185 if alias[4:] == url:
1186 is_tmp = True
1187 alias = hashlib.sha1(alias).hexdigest()
1188 else:
1189 is_tmp = False
1190
1191 gitdir = os.environ['GIT_DIR']
1192 dirname = os.path.join(gitdir, 'hg', alias)
1193 branches = {}
1194 bmarks = {}
1195 blob_marks = {}
1196 parsed_refs = {}
1197 marks = None
1198 parsed_tags = {}
1199 filenodes = {}
1200 fake_bmark = None
1201 try:
1202 hg_version = tuple(int(e) for e in util.version().split('.'))
1203 except:
1204 hg_version = None
1205 dry_run = False
1206 notes = set()
1207
1208 repo = get_repo(url, alias)
1209 prefix = 'refs/hg/%s' % alias
1210
1211 if not is_tmp:
1212 fix_path(alias, peer or repo, url)
1213
1214 marks_path = os.path.join(dirname, 'marks-hg')
1215 marks = Marks(marks_path, repo)
1216
1217 if sys.platform == 'win32':
1218 import msvcrt
1219 msvcrt.setmode(sys.stdout.fileno(), os.O_BINARY)
1220
1221 parser = Parser(repo)
1222 for line in parser:
1223 if parser.check('capabilities'):
1224 do_capabilities(parser)
1225 elif parser.check('list'):
1226 do_list(parser)
1227 elif parser.check('import'):
1228 do_import(parser)
1229 elif parser.check('export'):
1230 do_export(parser)
1231 elif parser.check('option'):
1232 do_option(parser)
1233 else:
1234 die('unhandled command: %s' % line)
1235 sys.stdout.flush()
1236
1237def bye():
1238 if not marks:
1239 return
1240 if not is_tmp:
1241 marks.store()
1242 else:
1243 shutil.rmtree(dirname)
1244
1245atexit.register(bye)
1246sys.exit(main(sys.argv))