hg-fast-export.py: Rewrite merge logic
authorRocco Rutte <pdmef@gmx.net>
Mon, 22 Oct 2007 08:06:58 +0000 (10:06 +0200)
committerRocco Rutte <pdmef@gmx.net>
Mon, 22 Oct 2007 08:06:58 +0000 (10:06 +0200)
Merges were completely broken as they ended up with twice the same
parent in git. Still everything besides gitk seemed to work.

This because of 1) the incorrect assumption that a commit's parent is
the commit exported right before it and 2) some confusion with markes
being saved/loaded/used since git-fast-import doesn't allow for a ":0"
mark to map hg revision 1:1 to marks.

The merge "algorithm" now works like this:

1) If the commit's higher parent (highest hg rev), is not the last
commit exported for a particular branch, we issue a "from" command to
place it on top of it.

2) If the commit's lower parent exists, we issue a "merge" for it.

This is much simpler and seems to produce correct merges in git. And
while I'm at it, make output less confusing by prepending the target
branch name to each line.

The "twice the same parent" bug was discovered by Michele Ballabio on
the git list.

Signed-off-by: Rocco Rutte <pdmef@gmx.net>
hg-fast-export.py

index 91caf7d..c85e84e 100755 (executable)
@@ -22,6 +22,8 @@ def gitmode(x):
   return x and '100755' or '100644'
 
 def wr(msg=''):
+  if msg == None:
+    msg = ''
   print msg
   #map(lambda x: sys.stderr.write('\t[%s]\n' % x),msg.split('\n'))
 
@@ -37,7 +39,7 @@ def get_parent_mark(parent,marks):
   """Get the mark for some parent.
   If we saw it in the current session, return :%d syntax and
   otherwise the SHA1 from the cache."""
-  return marks.get(str(parent+1),':%d' % (parent+1))
+  return marks.get(str(parent),':%d' % (parent+1))
 
 def mismatch(f1,f2):
   """See if two revisions of a file are not equal."""
@@ -147,6 +149,10 @@ def export_commit(ui,repo,revision,marks,heads,last,max,count,authors,sob):
   wr(desc)
   wr()
 
+  pidx1, pidx2 = 0, 1
+  if parents[0] < parents[1]:
+    pidx1, pidx2 = 1, 0
+
   src=heads.get(branch,'')
   link=''
   if src!='':
@@ -154,34 +160,27 @@ def export_commit(ui,repo,revision,marks,heads,last,max,count,authors,sob):
     # and kill reference so we won't init it again
     wr('from %s' % src)
     heads[branch]=''
-    sys.stderr.write('Initializing branch [%s] to parent [%s]\n' %
+    sys.stderr.write('%s: Initializing to parent [%s]\n' %
         (branch,src))
     link=src # avoid making a merge commit for incremental import
   elif link=='' and not heads.has_key(branch) and revision>0:
     # newly created branch and not the first one: connect to parent
     tmp=get_parent_mark(parents[0],marks)
     wr('from %s' % tmp)
-    sys.stderr.write('Link new branch [%s] to parent [%s]\n' %
+    sys.stderr.write('%s: Link new branch to parent [%s]\n' %
         (branch,tmp))
     link=tmp # avoid making a merge commit for branch fork
-
-  if parents:
-    l=last.get(branch,revision)
-    for p in parents:
-      # 1) as this commit implicitely is the child of the most recent
-      #    commit of this branch, ignore this parent
-      # 2) ignore nonexistent parents
-      # 3) merge otherwise
-      if p==l or p==revision or p<0:
-        continue
-      tmp=get_parent_mark(p,marks)
-      # if we fork off a branch, don't merge with our parent via 'merge'
-      # as we have 'from' already above
-      if tmp==link:
-        continue
-      sys.stderr.write('Merging branch [%s] with parent [%s] from [r%d]\n' %
-          (branch,tmp,p))
-      wr('merge %s' % tmp)
+  elif last.get(branch,revision) != parents[pidx1] and parents[pidx1] > 0 and revision > 0:
+    pm=get_parent_mark(parents[pidx1],marks)
+    sys.stderr.write('%s: Placing commit [r%d] in branch [%s] on top of [r%d]\n' %
+        (branch,revision,branch,parents[pidx1]));
+    wr('from %s' % pm)
+
+  if parents[pidx2] > 0:
+    pm=get_parent_mark(parents[pidx2],marks)
+    sys.stderr.write('%s: Merging with parent [%s] from [r%d]\n' %
+        (branch,pm,parents[pidx2]))
+    wr('merge %s' % pm)
 
   last[branch]=revision
   heads[branch]=''
@@ -210,8 +209,8 @@ def export_commit(ui,repo,revision,marks,heads,last,max,count,authors,sob):
     added,changed,removed=f[1],f[0],f[2]
     type='simple delta'
 
-  sys.stderr.write('Exporting %s revision %d/%d with %d/%d/%d added/changed/removed files\n' %
-      (type,revision+1,max,len(added),len(changed),len(removed)))
+  sys.stderr.write('%s: Exporting %s revision %d/%d with %d/%d/%d added/changed/removed files\n' %
+      (branch,type,revision+1,max,len(added),len(changed),len(removed)))
 
   map(lambda r: wr('D %s' % r),removed)
   export_file_contents(ctx,man,added+changed)
@@ -288,10 +287,13 @@ def verify_heads(ui,repo,cache,force):
 
   return True
 
+def mangle_mark(mark):
+  return str(int(mark)-1)
+
 def hg2git(repourl,m,marksfile,headsfile,tipfile,authors={},sob=False,force=False):
   _max=int(m)
 
-  marks_cache=load_cache(marksfile)
+  marks_cache=load_cache(marksfile,mangle_mark)
   heads_cache=load_cache(headsfile)
   state_cache=load_cache(tipfile)