1212# For remote repositories a local clone is stored in
1313# "$GIT_DIR/hg/origin/clone/.hg/".
1414
15- from mercurial import hg , ui , bookmarks , context , util , encoding , node , error
15+ from mercurial import hg , ui , bookmarks , context , util , encoding , node , error , extensions
1616
1717import re
1818import sys
@@ -22,6 +22,7 @@ import shutil
2222import subprocess
2323import urllib
2424import atexit
25+ import urlparse
2526
2627#
2728# If you want to switch to hg-git compatibility mode:
@@ -50,6 +51,7 @@ import atexit
5051
5152NAME_RE = re .compile ('^([^<>]+)' )
5253AUTHOR_RE = re .compile ('^([^<>]+?)? ?<([^<>]*)>$' )
54+ EMAIL_RE = re .compile ('^([^<>]+[^ \\ \t <>])?\\ b(?:[ \\ t<>]*?)\\ b([^ \\ t<>]+@[^ \\ t<>]+)' )
5355AUTHOR_HG_RE = re .compile ('^(.*?) ?<(.*?)(?:>(.+)?)?$' )
5456RAW_AUTHOR_RE = re .compile ('^(\w+) (?:(.+)? )?<(.*)> (\d+) ([+-]\d+)' )
5557
@@ -73,6 +75,12 @@ def hgmode(mode):
7375def hghex (node ):
7476 return hg .node .hex (node )
7577
78+ def hgref (ref ):
79+ return ref .replace ('___' , ' ' )
80+
81+ def gitref (ref ):
82+ return ref .replace (' ' , '___' )
83+
7684def get_config (config ):
7785 cmd = ['git' , 'config' , '--get' , config ]
7886 process = subprocess .Popen (cmd , stdout = subprocess .PIPE )
@@ -118,6 +126,10 @@ class Marks:
118126 def to_rev (self , mark ):
119127 return self .rev_marks [mark ]
120128
129+ def next_mark (self ):
130+ self .last_mark += 1
131+ return self .last_mark
132+
121133 def get_mark (self , rev ):
122134 self .last_mark += 1
123135 self .marks [str (rev )] = self .last_mark
@@ -129,7 +141,7 @@ class Marks:
129141 self .last_mark = mark
130142
131143 def is_marked (self , rev ):
132- return self . marks . has_key ( str (rev ))
144+ return str (rev ) in self . marks
133145
134146 def get_tip (self , branch ):
135147 return self .tips .get (branch , 0 )
@@ -210,20 +222,38 @@ def fix_file_path(path):
210222 return path
211223 return os .path .relpath (path , '/' )
212224
213- def export_file (fc ):
214- d = fc .data ()
215- path = fix_file_path (fc .path ())
216- print "M %s inline %s" % (gitmode (fc .flags ()), path )
217- print "data %d" % len (d )
218- print d
225+ def export_files (files ):
226+ global marks , filenodes
227+
228+ final = []
229+ for f in files :
230+ fid = node .hex (f .filenode ())
231+
232+ if fid in filenodes :
233+ mark = filenodes [fid ]
234+ else :
235+ mark = marks .next_mark ()
236+ filenodes [fid ] = mark
237+ d = f .data ()
238+
239+ print "blob"
240+ print "mark :%u" % mark
241+ print "data %d" % len (d )
242+ print d
243+
244+ path = fix_file_path (f .path ())
245+ final .append ((gitmode (f .flags ()), mark , path ))
246+
247+ return final
219248
220249def get_filechanges (repo , ctx , parent ):
221250 modified = set ()
222251 added = set ()
223252 removed = set ()
224253
225- cur = ctx . manifest ()
254+ # load earliest manifest first for caching reasons
226255 prev = repo [parent ].manifest ().copy ()
256+ cur = ctx .manifest ()
227257
228258 for fn in cur :
229259 if fn in prev :
@@ -244,9 +274,14 @@ def fixup_user_git(user):
244274 name = m .group (1 )
245275 mail = m .group (2 ).strip ()
246276 else :
247- m = NAME_RE .match (user )
277+ m = EMAIL_RE .match (user )
248278 if m :
249- name = m .group (1 ).strip ()
279+ name = m .group (1 )
280+ mail = m .group (2 )
281+ else :
282+ m = NAME_RE .match (user )
283+ if m :
284+ name = m .group (1 ).strip ()
250285 return (name , mail )
251286
252287def fixup_user_hg (user ):
@@ -298,6 +333,12 @@ def get_repo(url, alias):
298333 except subprocess .CalledProcessError :
299334 pass
300335
336+ try :
337+ mod = extensions .load (myui , 'hgext.schemes' , None )
338+ mod .extsetup (myui )
339+ except ImportError :
340+ pass
341+
301342 if hg .islocal (url ):
302343 repo = hg .repository (myui , url )
303344 else :
@@ -393,6 +434,8 @@ def export_ref(repo, name, kind, head):
393434 if len (parents ) == 0 and rev :
394435 print 'reset %s/%s' % (prefix , ename )
395436
437+ modified_final = export_files (c .filectx (f ) for f in modified )
438+
396439 print "commit %s/%s" % (prefix , ename )
397440 print "mark :%d" % (marks .get_mark (rev ))
398441 print "author %s" % (author )
@@ -405,8 +448,8 @@ def export_ref(repo, name, kind, head):
405448 if len (parents ) > 1 :
406449 print "merge :%s" % (rev_to_mark (parents [1 ]))
407450
408- for f in modified :
409- export_file ( c . filectx ( f ))
451+ for f in modified_final :
452+ print "M %s :%u %s" % f
410453 for f in removed :
411454 print "D %s" % (fix_file_path (f ))
412455 print
@@ -424,10 +467,10 @@ def export_ref(repo, name, kind, head):
424467 marks .set_tip (ename , rev )
425468
426469def export_tag (repo , tag ):
427- export_ref (repo , tag , 'tags' , repo [tag ])
470+ export_ref (repo , tag , 'tags' , repo [hgref ( tag ) ])
428471
429472def export_bookmark (repo , bmark ):
430- head = bmarks [bmark ]
473+ head = bmarks [hgref ( bmark ) ]
431474 export_ref (repo , bmark , 'bookmarks' , head )
432475
433476def export_branch (repo , branch ):
@@ -456,19 +499,24 @@ def do_capabilities(parser):
456499
457500 print
458501
502+ def branch_tip (repo , branch ):
503+ # older versions of mercurial don't have this
504+ if hasattr (repo , 'branchtip' ):
505+ return repo .branchtip (branch )
506+ else :
507+ return repo .branchtags ()[branch ]
508+
459509def get_branch_tip (repo , branch ):
460510 global branches
461511
462- heads = branches .get (branch , None )
512+ heads = branches .get (hgref ( branch ) , None )
463513 if not heads :
464514 return None
465515
466516 # verify there's only one head
467517 if (len (heads ) > 1 ):
468518 warn ("Branch '%s' has more than one head, consider merging" % branch )
469- # older versions of mercurial don't have this
470- if hasattr (repo , "branchtip" ):
471- return repo .branchtip (branch )
519+ return branch_tip (repo , hgref (branch ))
472520
473521 return heads [0 ]
474522
@@ -490,6 +538,7 @@ def list_head(repo, cur):
490538 head = 'master'
491539 bmarks [head ] = node
492540
541+ head = gitref (head )
493542 print "@refs/heads/%s HEAD" % head
494543 g_head = (head , node )
495544
@@ -511,15 +560,15 @@ def do_list(parser):
511560 branches [branch ] = heads
512561
513562 for branch in branches :
514- print "? refs/heads/branches/%s" % branch
563+ print "? refs/heads/branches/%s" % gitref ( branch )
515564
516565 for bmark in bmarks :
517- print "? refs/heads/%s" % bmark
566+ print "? refs/heads/%s" % gitref ( bmark )
518567
519568 for tag , node in repo .tagslist ():
520569 if tag == 'tip' :
521570 continue
522- print "? refs/tags/%s" % tag
571+ print "? refs/tags/%s" % gitref ( tag )
523572
524573 print
525574
@@ -603,6 +652,10 @@ def parse_commit(parser):
603652 if parser .check ('merge' ):
604653 die ('octopus merges are not supported yet' )
605654
655+ # fast-export adds an extra newline
656+ if data [- 1 ] == '\n ' :
657+ data = data [:- 1 ]
658+
606659 files = {}
607660
608661 for line in parser :
@@ -656,7 +709,8 @@ def parse_commit(parser):
656709
657710 # Check if the ref is supposed to be a named branch
658711 if ref .startswith ('refs/heads/branches/' ):
659- extra ['branch' ] = ref [len ('refs/heads/branches/' ):]
712+ branch = ref [len ('refs/heads/branches/' ):]
713+ extra ['branch' ] = hgref (branch )
660714
661715 if mode == 'hg' :
662716 i = data .find ('\n --HG--\n ' )
@@ -716,7 +770,40 @@ def parse_tag(parser):
716770 data = parser .get_data ()
717771 parser .next ()
718772
719- # nothing to do
773+ parsed_tags [name ] = (tagger , data )
774+
775+ def write_tag (repo , tag , node , msg , author ):
776+ branch = repo [node ].branch ()
777+ tip = branch_tip (repo , branch )
778+ tip = repo [tip ]
779+
780+ def getfilectx (repo , memctx , f ):
781+ try :
782+ fctx = tip .filectx (f )
783+ data = fctx .data ()
784+ except error .ManifestLookupError :
785+ data = ""
786+ content = data + "%s %s\n " % (hghex (node ), tag )
787+ return context .memfilectx (f , content , False , False , None )
788+
789+ p1 = tip .hex ()
790+ p2 = '\0 ' * 20
791+ if not author :
792+ author = (None , 0 , 0 )
793+ user , date , tz = author
794+
795+ ctx = context .memctx (repo , (p1 , p2 ), msg ,
796+ ['.hgtags' ], getfilectx ,
797+ user , (date , tz ), {'branch' : branch })
798+
799+ tmp = encoding .encoding
800+ encoding .encoding = 'utf-8'
801+
802+ tagnode = repo .commitctx (ctx )
803+
804+ encoding .encoding = tmp
805+
806+ return tagnode
720807
721808def do_export (parser ):
722809 global parsed_refs , bmarks , peer
@@ -741,18 +828,27 @@ def do_export(parser):
741828
742829 for ref , node in parsed_refs .iteritems ():
743830 if ref .startswith ('refs/heads/branches' ):
831+ branch = ref [len ('refs/heads/branches/' ):]
832+ if branch in branches and node in branches [branch ]:
833+ # up to date
834+ continue
744835 print "ok %s" % ref
745836 elif ref .startswith ('refs/heads/' ):
746837 bmark = ref [len ('refs/heads/' ):]
747838 p_bmarks .append ((bmark , node ))
748839 continue
749840 elif ref .startswith ('refs/tags/' ):
750841 tag = ref [len ('refs/tags/' ):]
842+ tag = hgref (tag )
843+ author , msg = parsed_tags .get (tag , (None , None ))
751844 if mode == 'git' :
752- msg = 'Added tag %s for changeset %s' % (tag , hghex (node [:6 ]));
753- parser .repo .tag ([tag ], node , msg , False , None , {})
845+ if not msg :
846+ msg = 'Added tag %s for changeset %s' % (tag , hghex (node [:6 ]));
847+ write_tag (parser .repo , tag , node , msg , author )
754848 else :
755- parser .repo .tag ([tag ], node , None , True , None , {})
849+ fp = parser .repo .opener ('localtags' , 'a' )
850+ fp .write ('%s %s\n ' % (hghex (node ), tag ))
851+ fp .close ()
756852 print "ok %s" % ref
757853 else :
758854 # transport-helper/fast-export bugs
@@ -771,6 +867,9 @@ def do_export(parser):
771867 else :
772868 old = ''
773869
870+ if old == new :
871+ continue
872+
774873 if bmark == 'master' and 'master' not in parser .repo ._bookmarks :
775874 # fake bookmark
776875 pass
@@ -782,6 +881,8 @@ def do_export(parser):
782881 continue
783882
784883 if peer :
884+ rb = peer .listkeys ('bookmarks' )
885+ old = rb .get (bmark , '' )
785886 if not peer .pushkey ('bookmarks' , bmark , old , new ):
786887 print "error %s" % ref
787888 continue
@@ -791,18 +892,20 @@ def do_export(parser):
791892 print
792893
793894def fix_path (alias , repo , orig_url ):
794- repo_url = util .url (repo .url ())
795- url = util .url (orig_url )
796- if str (url ) == str (repo_url ):
895+ url = urlparse .urlparse (orig_url , 'file' )
896+ if url .scheme != 'file' or os .path .isabs (url .path ):
797897 return
798- cmd = ['git' , 'config' , 'remote.%s.url' % alias , "hg::%s" % repo_url ]
898+ abs_url = urlparse .urljoin ("%s/" % os .getcwd (), orig_url )
899+ cmd = ['git' , 'config' , 'remote.%s.url' % alias , "hg::%s" % abs_url ]
799900 subprocess .call (cmd )
800901
801902def main (args ):
802903 global prefix , dirname , branches , bmarks
803904 global marks , blob_marks , parsed_refs
804905 global peer , mode , bad_mail , bad_name
805906 global track_branches , force_push , is_tmp
907+ global parsed_tags
908+ global filenodes
806909
807910 alias = args [1 ]
808911 url = args [2 ]
@@ -845,6 +948,8 @@ def main(args):
845948 blob_marks = {}
846949 parsed_refs = {}
847950 marks = None
951+ parsed_tags = {}
952+ filenodes = {}
848953
849954 repo = get_repo (url , alias )
850955 prefix = 'refs/hg/%s' % alias
0 commit comments