hg export: Support tag movement
authorJason S. McMullan <jason.mcmullan@netronome.com>
Thu, 11 Dec 2008 14:05:05 +0000 (09:05 -0500)
committerJason S. McMullan <jason.mcmullan@netronome.com>
Thu, 11 Dec 2008 14:05:05 +0000 (09:05 -0500)
HG tag movement is now supported with this patch.

This patch creates a .git/hg2git-mapping file, which maps
HG revision numbers to HG hashes. Combined with the
.git/hg2git-marks file, which maps HG revisions to GIT hashes,
we can now reprocess all tags at the end of each hg export
operation.

hg-fast-export.py
hg-fast-export.sh
hg-reset.py
hg-reset.sh

index ff32dbc..dd9f179 100755 (executable)
@@ -117,6 +117,10 @@ def export_file_contents(ctx,manifest,files):
   count=0
   max=len(files)
   for file in files:
+    # Skip .hgtags files. They only get us in trouble.
+    if file == ".hgtags":
+      sys.stderr.write('Skip %s\n' % (file))
+      continue
     d=ctx.filectx(file).data()
     wr('M %s inline %s' % (gitmode(manifest.flags(file)),file))
     wr('data %d' % len(d)) # had some trouble with size()
@@ -153,7 +157,7 @@ def sanitize_name(name,what="branch"):
     sys.stderr.write('Warning: sanitized %s [%s] to [%s]\n' % (what,name,n))
   return n
 
-def export_commit(ui,repo,revision,marks,heads,last,max,count,authors,sob,brmap):
+def export_commit(ui,repo,revision,marks,mapping,heads,last,max,count,authors,sob,brmap):
   def get_branchname(name):
     if brmap.has_key(name):
       return brmap[name]
@@ -246,17 +250,20 @@ def export_commit(ui,repo,revision,marks,heads,last,max,count,authors,sob,brmap)
 
   return checkpoint(count)
 
-def export_tags(ui,repo,marks_cache,start,end,count,authors):
+def export_tags(ui,repo,marks_cache,mapping_cache,count,authors):
   l=repo.tagslist()
   for tag,node in l:
     tag=sanitize_name(tag,"tag")
     # ignore latest revision
     if tag=='tip': continue
-    rev=repo.changelog.rev(node)
-    # ignore those tags not in our import range
-    if rev<start or rev>=end: continue
+    # ignore tags to nodes that are missing (ie, 'in the future')
+    if node.encode('hex_codec') not in mapping_cache:
+      sys.stderr.write('Tag %s refers to unseen node %s\n' % (tag, node.encode('hex_codec')))
+      continue
+
+    rev=int(mapping_cache[node.encode('hex_codec')])
 
-    ref=get_parent_mark(rev,marks_cache)
+    ref=marks_cache.get(str(rev),':%d' % (rev))
     if ref==None:
       sys.stderr.write('Failed to find reference for creating tag'
           ' %s at r%d\n' % (tag,rev))
@@ -319,10 +326,11 @@ def verify_heads(ui,repo,cache,force):
 def mangle_mark(mark):
   return str(int(mark)-1)
 
-def hg2git(repourl,m,marksfile,headsfile,tipfile,authors={},sob=False,force=False):
+def hg2git(repourl,m,marksfile,mappingfile,headsfile,tipfile,authors={},sob=False,force=False):
   _max=int(m)
 
   marks_cache=load_cache(marksfile,mangle_mark)
+  mapping_cache=load_cache(mappingfile)
   heads_cache=load_cache(headsfile)
   state_cache=load_cache(tipfile)
 
@@ -341,19 +349,25 @@ def hg2git(repourl,m,marksfile,headsfile,tipfile,authors={},sob=False,force=Fals
   if _max<0 or max>tip:
     max=tip
 
+  for rev in range(0,max):
+       (revnode,_,_,_,_,_,_,_)=get_changeset(ui,repo,rev,authors)
+       mapping_cache[revnode.encode('hex_codec')] = str(rev)
+
+
   c=0
   last={}
   brmap={}
   for rev in range(min,max):
-    c=export_commit(ui,repo,rev,marks_cache,heads_cache,last,max,c,authors,sob,brmap)
-
-  c=export_tags(ui,repo,marks_cache,min,max,c,authors)
-
-  sys.stderr.write('Issued %d commands\n' % c)
+    c=export_commit(ui,repo,rev,marks_cache,mapping_cache,heads_cache,last,max,c,authors,sob,brmap)
 
   state_cache['tip']=max
   state_cache['repo']=repourl
   save_cache(tipfile,state_cache)
+  save_cache(mappingfile,mapping_cache)
+
+  c=export_tags(ui,repo,marks_cache,mapping_cache,c,authors)
+
+  sys.stderr.write('Issued %d commands\n' % c)
 
   return 0
 
@@ -367,6 +381,8 @@ if __name__=='__main__':
 
   parser.add_option("-m","--max",type="int",dest="max",
       help="Maximum hg revision to import")
+  parser.add_option("--mapping",dest="mappingfile",
+      help="File to read last run's hg-to-git SHA1 mapping")
   parser.add_option("--marks",dest="marksfile",
       help="File to read git-fast-import's marks from")
   parser.add_option("--heads",dest="headsfile",
@@ -390,6 +406,7 @@ if __name__=='__main__':
   if options.max!=None: m=options.max
 
   if options.marksfile==None: bail(parser,'--marks')
+  if options.mappingfile==None: bail(parser,'--mapping')
   if options.headsfile==None: bail(parser,'--heads')
   if options.statusfile==None: bail(parser,'--status')
   if options.repourl==None: bail(parser,'--repo')
@@ -401,5 +418,5 @@ if __name__=='__main__':
   if options.default_branch!=None:
     set_default_branch(options.default_branch)
 
-  sys.exit(hg2git(options.repourl,m,options.marksfile,options.headsfile,
+  sys.exit(hg2git(options.repourl,m,options.marksfile,options.mappingfile,options.headsfile,
     options.statusfile,authors=a,sob=options.sob,force=options.force))
index 0f6b170..5cfc575 100755 (executable)
@@ -6,6 +6,7 @@
 ROOT="`dirname $0`"
 REPO=""
 PFX="hg2git"
+SFX_MAPPING="mapping"
 SFX_MARKS="marks"
 SFX_HEADS="heads"
 SFX_STATE="state"
@@ -67,11 +68,11 @@ fi
 GIT_DIR="$GIT_DIR" $PYTHON "$ROOT/hg-fast-export.py" \
   --repo "$REPO" \
   --marks "$GIT_DIR/$PFX-$SFX_MARKS" \
+  --mapping "$GIT_DIR/$PFX-$SFX_MAPPING" \
   --heads "$GIT_DIR/$PFX-$SFX_HEADS" \
   --status "$GIT_DIR/$PFX-$SFX_STATE" \
   "$@" \
-| git fast-import $QUIET --export-marks="$GIT_DIR/$PFX-$SFX_MARKS.tmp" \
-|| die 'Git fast-import failed'
+| git fast-import $QUIET --export-marks="$GIT_DIR/$PFX-$SFX_MARKS.tmp" 
 
 # move recent marks cache out of the way...
 if [ -f "$GIT_DIR/$PFX-$SFX_MARKS" ] ; then
index cc361f0..73eac02 100755 (executable)
@@ -35,7 +35,7 @@ def heads(ui,repo,start=None,stop=None,max=None):
 
   return [(repo.changelog.node(r),str(r)) for r in heads]
 
-def get_branches(ui,repo,heads_cache,marks_cache,max):
+def get_branches(ui,repo,heads_cache,marks_cache,mapping_cache,max):
   h=heads(ui,repo,max=max)
   stale=dict.fromkeys(heads_cache)
   changed=[]
@@ -53,12 +53,12 @@ def get_branches(ui,repo,heads_cache,marks_cache,max):
   unchanged.sort()
   return stale,changed,unchanged
 
-def get_tags(ui,repo,marks_cache,max):
+def get_tags(ui,repo,marks_cache,mapping_cache,max):
   l=repo.tagslist()
   good,bad=[],[]
   for tag,node in l:
     if tag=='tip': continue
-    rev=repo.changelog.rev(node)
+    rev=int(mapping_cache[node.encode('hex_codec')])
     cache_sha1=marks_cache.get(str(int(rev)+1))
     _,_,user,(_,_),_,desc,branch,_=get_changeset(ui,repo,rev)
     if int(rev)>int(max):
@@ -110,8 +110,8 @@ if __name__=='__main__':
 
   ui,repo=setup_repo(options.repourl)
 
-  stale,changed,unchanged=get_branches(ui,repo,heads_cache,marks_cache,options.revision+1)
-  good,bad=get_tags(ui,repo,marks_cache,options.revision+1)
+  stale,changed,unchanged=get_branches(ui,repo,heads_cache,marks_cache,mapping_cache,options.revision+1)
+  good,bad=get_tags(ui,repo,marks_cache,mapping_cache,options.revision+1)
 
   print "Possibly stale branches:"
   map(lambda b: sys.stdout.write('\t%s\n' % b),stale.keys())
index 8d1730c..1823738 100755 (executable)
@@ -7,6 +7,7 @@ ROOT="`dirname $0`"
 REPO=""
 PFX="hg2git"
 SFX_MARKS="marks"
+SFX_MAPPING="mapping"
 SFX_HEADS="heads"
 SFX_STATE="state"
 QUIET=""
@@ -58,6 +59,7 @@ fi
 GIT_DIR="$GIT_DIR" $PYTHON "$ROOT/hg-reset.py" \
   --repo "$REPO" \
   --marks "$GIT_DIR/$PFX-$SFX_MARKS" \
+  --mapping "$GIT_DIR/$PFX-$SFX_MAPPING" \
   --heads "$GIT_DIR/$PFX-$SFX_HEADS" \
   --status "$GIT_DIR/$PFX-$SFX_STATE" \
   "$@"