88# Just copy to your ~/bin, or anywhere in your $PATH.
99# Then you can clone with:
1010# 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/".
1114
12- from mercurial import hg , ui , bookmarks , context , util , encoding
15+ from mercurial import hg , ui , bookmarks , context , util , encoding , node , error
1316
1417import re
1518import sys
@@ -18,11 +21,22 @@ import json
1821import shutil
1922import subprocess
2023import urllib
24+ import atexit
2125
2226#
2327# If you want to switch to hg-git compatibility mode:
2428# git config --global remote-hg.hg-git-compat true
2529#
30+ # If you are not in hg-git-compat mode and want to disable the tracking of
31+ # named branches:
32+ # git config --global remote-hg.track-branches false
33+ #
34+ # If you don't want to force pushes (and thus risk creating new remote heads):
35+ # git config --global remote-hg.force-push false
36+ #
37+ # If you want the equivalent of hg's clone/pull--insecure option:
38+ # git config remote-hg.insecure true
39+ #
2640# git:
2741# Sensible defaults for git.
2842# hg bookmarks are exported as git branches, hg branches are prefixed
@@ -56,6 +70,9 @@ def hgmode(mode):
5670 m = { '100755' : 'x' , '120000' : 'l' }
5771 return m .get (mode , '' )
5872
73+ def hghex (node ):
74+ return hg .node .hex (node )
75+
5976def get_config (config ):
6077 cmd = ['git' , 'config' , '--get' , config ]
6178 process = subprocess .Popen (cmd , stdout = subprocess .PIPE )
@@ -188,9 +205,15 @@ class Parser:
188205 tz = ((tz / 100 ) * 3600 ) + ((tz % 100 ) * 60 )
189206 return (user , int (date ), - tz )
190207
208+ def fix_file_path (path ):
209+ if not os .path .isabs (path ):
210+ return path
211+ return os .path .relpath (path , '/' )
212+
191213def export_file (fc ):
192214 d = fc .data ()
193- print "M %s inline %s" % (gitmode (fc .flags ()), fc .path ())
215+ path = fix_file_path (fc .path ())
216+ print "M %s inline %s" % (gitmode (fc .flags ()), path )
194217 print "data %d" % len (d )
195218 print d
196219
@@ -267,17 +290,30 @@ def get_repo(url, alias):
267290
268291 myui = ui .ui ()
269292 myui .setconfig ('ui' , 'interactive' , 'off' )
293+ myui .fout = sys .stderr
294+
295+ try :
296+ if get_config ('remote-hg.insecure' ) == 'true\n ' :
297+ myui .setconfig ('web' , 'cacerts' , '' )
298+ except subprocess .CalledProcessError :
299+ pass
270300
271301 if hg .islocal (url ):
272302 repo = hg .repository (myui , url )
273303 else :
274304 local_path = os .path .join (dirname , 'clone' )
275305 if not os .path .exists (local_path ):
276- peer , dstpeer = hg .clone (myui , {}, url , local_path , update = False , pull = True )
306+ try :
307+ peer , dstpeer = hg .clone (myui , {}, url , local_path , update = True , pull = True )
308+ except :
309+ die ('Repository error' )
277310 repo = dstpeer .local ()
278311 else :
279312 repo = hg .repository (myui , local_path )
280- peer = hg .peer (myui , {}, url )
313+ try :
314+ peer = hg .peer (myui , {}, url )
315+ except :
316+ die ('Repository error' )
281317 repo .pull (peer , heads = None , force = True )
282318
283319 return repo
@@ -372,7 +408,7 @@ def export_ref(repo, name, kind, head):
372408 for f in modified :
373409 export_file (c .filectx (f ))
374410 for f in removed :
375- print "D %s" % (f )
411+ print "D %s" % (fix_file_path ( f ) )
376412 print
377413
378414 count += 1
@@ -532,7 +568,6 @@ def parse_blob(parser):
532568 data = parser .get_data ()
533569 blob_marks [mark ] = data
534570 parser .next ()
535- return
536571
537572def get_merge_files (repo , p1 , p2 , files ):
538573 for e in repo [p1 ].files ():
@@ -543,7 +578,7 @@ def get_merge_files(repo, p1, p2, files):
543578 files [e ] = f
544579
545580def parse_commit (parser ):
546- global marks , blob_marks , bmarks , parsed_refs
581+ global marks , blob_marks , parsed_refs
547582 global mode
548583
549584 from_mark = merge_mark = None
@@ -576,7 +611,7 @@ def parse_commit(parser):
576611 mark = int (mark_ref [1 :])
577612 f = { 'mode' : hgmode (m ), 'data' : blob_marks [mark ] }
578613 elif parser .check ('D' ):
579- t , path = line .split (' ' )
614+ t , path = line .split (' ' , 1 )
580615 f = { 'deleted' : True }
581616 else :
582617 die ('Unknown file command: %s' % line )
@@ -619,11 +654,15 @@ def parse_commit(parser):
619654 if merge_mark :
620655 get_merge_files (repo , p1 , p2 , files )
621656
657+ # Check if the ref is supposed to be a named branch
658+ if ref .startswith ('refs/heads/branches/' ):
659+ extra ['branch' ] = ref [len ('refs/heads/branches/' ):]
660+
622661 if mode == 'hg' :
623662 i = data .find ('\n --HG--\n ' )
624663 if i >= 0 :
625664 tmp = data [i + len ('\n --HG--\n ' ):].strip ()
626- for k , v in [e .split (' : ' ) for e in tmp .split ('\n ' )]:
665+ for k , v in [e .split (' : ' , 1 ) for e in tmp .split ('\n ' )]:
627666 if k == 'rename' :
628667 old , new = v .split (' => ' , 1 )
629668 files [new ]['rename' ] = old
@@ -648,10 +687,11 @@ def parse_commit(parser):
648687 rev = repo [node ].rev ()
649688
650689 parsed_refs [ref ] = node
651-
652690 marks .new_mark (rev , commit_mark )
653691
654692def parse_reset (parser ):
693+ global parsed_refs
694+
655695 ref = parser [1 ]
656696 parser .next ()
657697 # ugh
@@ -681,6 +721,8 @@ def parse_tag(parser):
681721def do_export (parser ):
682722 global parsed_refs , bmarks , peer
683723
724+ p_bmarks = []
725+
684726 parser .next ()
685727
686728 for line in parser .each_block ('done' ):
@@ -699,28 +741,55 @@ def do_export(parser):
699741
700742 for ref , node in parsed_refs .iteritems ():
701743 if ref .startswith ('refs/heads/branches' ):
702- pass
744+ print "ok %s" % ref
703745 elif ref .startswith ('refs/heads/' ):
704746 bmark = ref [len ('refs/heads/' ):]
705- if bmark in bmarks :
706- old = bmarks [bmark ].hex ()
707- else :
708- old = ''
709- if not bookmarks .pushbookmark (parser .repo , bmark , old , node ):
710- continue
747+ p_bmarks .append ((bmark , node ))
748+ continue
711749 elif ref .startswith ('refs/tags/' ):
712750 tag = ref [len ('refs/tags/' ):]
713- parser .repo .tag ([tag ], node , None , True , None , {})
751+ if mode == 'git' :
752+ msg = 'Added tag %s for changeset %s' % (tag , hghex (node [:6 ]));
753+ parser .repo .tag ([tag ], node , msg , False , None , {})
754+ else :
755+ parser .repo .tag ([tag ], node , None , True , None , {})
756+ print "ok %s" % ref
714757 else :
715758 # transport-helper/fast-export bugs
716759 continue
760+
761+ if peer :
762+ parser .repo .push (peer , force = force_push )
763+
764+ # handle bookmarks
765+ for bmark , node in p_bmarks :
766+ ref = 'refs/heads/' + bmark
767+ new = hghex (node )
768+
769+ if bmark in bmarks :
770+ old = bmarks [bmark ].hex ()
771+ else :
772+ old = ''
773+
774+ if bmark == 'master' and 'master' not in parser .repo ._bookmarks :
775+ # fake bookmark
776+ pass
777+ elif bookmarks .pushbookmark (parser .repo , bmark , old , new ):
778+ # updated locally
779+ pass
780+ else :
781+ print "error %s" % ref
782+ continue
783+
784+ if peer :
785+ if not peer .pushkey ('bookmarks' , bmark , old , new ):
786+ print "error %s" % ref
787+ continue
788+
717789 print "ok %s" % ref
718790
719791 print
720792
721- if peer :
722- parser .repo .push (peer , force = False )
723-
724793def fix_path (alias , repo , orig_url ):
725794 repo_url = util .url (repo .url ())
726795 url = util .url (orig_url )
@@ -733,20 +802,24 @@ def main(args):
733802 global prefix , dirname , branches , bmarks
734803 global marks , blob_marks , parsed_refs
735804 global peer , mode , bad_mail , bad_name
736- global track_branches
805+ global track_branches , force_push , is_tmp
737806
738807 alias = args [1 ]
739808 url = args [2 ]
740809 peer = None
741810
742811 hg_git_compat = False
743812 track_branches = True
813+ force_push = True
814+
744815 try :
745816 if get_config ('remote-hg.hg-git-compat' ) == 'true\n ' :
746817 hg_git_compat = True
747818 track_branches = False
748819 if get_config ('remote-hg.track-branches' ) == 'false\n ' :
749820 track_branches = False
821+ if get_config ('remote-hg.force-push' ) == 'false\n ' :
822+ force_push = False
750823 except subprocess .CalledProcessError :
751824 pass
752825
@@ -771,6 +844,7 @@ def main(args):
771844 bmarks = {}
772845 blob_marks = {}
773846 parsed_refs = {}
847+ marks = None
774848
775849 repo = get_repo (url , alias )
776850 prefix = 'refs/hg/%s' % alias
@@ -798,9 +872,13 @@ def main(args):
798872 die ('unhandled command: %s' % line )
799873 sys .stdout .flush ()
800874
875+ def bye ():
876+ if not marks :
877+ return
801878 if not is_tmp :
802879 marks .store ()
803880 else :
804881 shutil .rmtree (dirname )
805882
883+ atexit .register (bye )
806884sys .exit (main (sys .argv ))
0 commit comments