@ -55,6 +55,7 @@ import java.util.List;
import org.eclipse.jgit.JGitText ;
import org.eclipse.jgit.JGitText ;
import org.eclipse.jgit.diff.DiffEntry.ChangeType ;
import org.eclipse.jgit.diff.DiffEntry.ChangeType ;
import org.eclipse.jgit.lib.AbbreviatedObjectId ;
import org.eclipse.jgit.lib.AbbreviatedObjectId ;
import org.eclipse.jgit.lib.Constants ;
import org.eclipse.jgit.lib.FileMode ;
import org.eclipse.jgit.lib.FileMode ;
import org.eclipse.jgit.lib.NullProgressMonitor ;
import org.eclipse.jgit.lib.NullProgressMonitor ;
import org.eclipse.jgit.lib.ObjectReader ;
import org.eclipse.jgit.lib.ObjectReader ;
@ -99,7 +100,7 @@ public class RenameDetector {
}
}
} ;
} ;
private final List < DiffEntry > entries = new ArrayList < DiffEntry > ( ) ;
private List < DiffEntry > entries = new ArrayList < DiffEntry > ( ) ;
private List < DiffEntry > deleted = new ArrayList < DiffEntry > ( ) ;
private List < DiffEntry > deleted = new ArrayList < DiffEntry > ( ) ;
@ -112,6 +113,13 @@ public class RenameDetector {
/** Similarity score required to pair an add/delete as a rename. */
/** Similarity score required to pair an add/delete as a rename. */
private int renameScore = 60 ;
private int renameScore = 60 ;
/ * *
* Similarity score required to keep modified file pairs together . Any
* modified file pairs with a similarity score below this will be broken
* apart .
* /
private int breakScore = - 1 ;
/** Limit in the number of files to consider for renames. */
/** Limit in the number of files to consider for renames. */
private int renameLimit ;
private int renameLimit ;
@ -159,6 +167,29 @@ public class RenameDetector {
renameScore = score ;
renameScore = score ;
}
}
/ * *
* @return the similarity score required to keep modified file pairs
* together . Any modify pairs that score below this will be broken
* apart into separate add / deletes . Values less than or equal to
* zero indicate that no modifies will be broken apart . Values over
* 100 cause all modify pairs to be broken .
* /
public int getBreakScore ( ) {
return breakScore ;
}
/ * *
* @param breakScore
* the similarity score required to keep modified file pairs
* together . Any modify pairs that score below this will be
* broken apart into separate add / deletes . Values less than or
* equal to zero indicate that no modifies will be broken apart .
* Values over 100 cause all modify pairs to be broken .
* /
public void setBreakScore ( int breakScore ) {
this . breakScore = breakScore ;
}
/** @return limit on number of paths to perform inexact rename detection. */
/** @return limit on number of paths to perform inexact rename detection. */
public int getRenameLimit ( ) {
public int getRenameLimit ( ) {
return renameLimit ;
return renameLimit ;
@ -219,10 +250,13 @@ public class RenameDetector {
break ;
break ;
case MODIFY :
case MODIFY :
if ( sameType ( entry . getOldMode ( ) , entry . getNewMode ( ) ) )
if ( sameType ( entry . getOldMode ( ) , entry . getNewMode ( ) ) ) {
entries . add ( entry ) ;
entries . add ( entry ) ;
else
} else {
entries . addAll ( DiffEntry . breakModify ( entry ) ) ;
List < DiffEntry > tmp = DiffEntry . breakModify ( entry ) ;
deleted . add ( tmp . get ( 0 ) ) ;
added . add ( tmp . get ( 1 ) ) ;
}
break ;
break ;
case COPY :
case COPY :
@ -275,8 +309,15 @@ public class RenameDetector {
if ( pm = = null )
if ( pm = = null )
pm = NullProgressMonitor . INSTANCE ;
pm = NullProgressMonitor . INSTANCE ;
findExactRenames ( pm ) ;
ObjectReader reader = repo . newObjectReader ( ) ;
findContentRenames ( pm ) ;
try {
breakModifies ( reader , pm ) ;
findExactRenames ( pm ) ;
findContentRenames ( reader , pm ) ;
rejoinModifies ( pm ) ;
} finally {
reader . release ( ) ;
}
entries . addAll ( added ) ;
entries . addAll ( added ) ;
added = null ;
added = null ;
@ -289,25 +330,96 @@ public class RenameDetector {
return Collections . unmodifiableList ( entries ) ;
return Collections . unmodifiableList ( entries ) ;
}
}
private void findContentRenames ( ProgressMonitor pm ) throws IOException {
private void breakModifies ( ObjectReader reader , ProgressMonitor pm )
throws IOException {
if ( breakScore < = 0 )
return ;
ArrayList < DiffEntry > newEntries = new ArrayList < DiffEntry > ( entries . size ( ) ) ;
pm . beginTask ( JGitText . get ( ) . renamesBreakingModifies , entries . size ( ) ) ;
for ( int i = 0 ; i < entries . size ( ) ; i + + ) {
DiffEntry e = entries . get ( i ) ;
if ( e . getChangeType ( ) = = ChangeType . MODIFY ) {
int score = calculateModifyScore ( reader , e ) ;
if ( score < breakScore ) {
List < DiffEntry > tmp = DiffEntry . breakModify ( e ) ;
DiffEntry del = tmp . get ( 0 ) ;
del . score = score ;
deleted . add ( del ) ;
added . add ( tmp . get ( 1 ) ) ;
} else {
newEntries . add ( e ) ;
}
} else {
newEntries . add ( e ) ;
}
pm . update ( 1 ) ;
}
entries = newEntries ;
}
private void rejoinModifies ( ProgressMonitor pm ) {
HashMap < String , DiffEntry > nameMap = new HashMap < String , DiffEntry > ( ) ;
ArrayList < DiffEntry > newAdded = new ArrayList < DiffEntry > ( added . size ( ) ) ;
pm . beginTask ( JGitText . get ( ) . renamesRejoiningModifies , added . size ( )
+ deleted . size ( ) ) ;
for ( DiffEntry src : deleted ) {
nameMap . put ( src . oldName , src ) ;
pm . update ( 1 ) ;
}
for ( DiffEntry dst : added ) {
DiffEntry src = nameMap . remove ( dst . newName ) ;
if ( src ! = null ) {
if ( sameType ( src . oldMode , dst . newMode ) ) {
entries . add ( DiffEntry . pair ( ChangeType . MODIFY , src , dst ,
src . score ) ) ;
} else {
nameMap . put ( src . oldName , src ) ;
newAdded . add ( dst ) ;
}
} else {
newAdded . add ( dst ) ;
}
pm . update ( 1 ) ;
}
added = newAdded ;
deleted = new ArrayList < DiffEntry > ( nameMap . values ( ) ) ;
}
private int calculateModifyScore ( ObjectReader reader , DiffEntry d )
throws IOException {
SimilarityIndex src = new SimilarityIndex ( ) ;
src . hash ( reader . open ( d . oldId . toObjectId ( ) , Constants . OBJ_BLOB ) ) ;
src . sort ( ) ;
SimilarityIndex dst = new SimilarityIndex ( ) ;
dst . hash ( reader . open ( d . newId . toObjectId ( ) , Constants . OBJ_BLOB ) ) ;
dst . sort ( ) ;
return src . score ( dst , 100 ) ;
}
private void findContentRenames ( ObjectReader reader , ProgressMonitor pm )
throws IOException {
int cnt = Math . max ( added . size ( ) , deleted . size ( ) ) ;
int cnt = Math . max ( added . size ( ) , deleted . size ( ) ) ;
if ( cnt = = 0 )
if ( cnt = = 0 )
return ;
return ;
if ( getRenameLimit ( ) = = 0 | | cnt < = getRenameLimit ( ) ) {
if ( getRenameLimit ( ) = = 0 | | cnt < = getRenameLimit ( ) ) {
ObjectReader reader = repo . newObjectReader ( ) ;
SimilarityRenameDetector d ;
try {
SimilarityRenameDetector d ;
d = new SimilarityRenameDetector ( reader , deleted , added ) ;
d . setRenameScore ( getRenameScore ( ) ) ;
d = new SimilarityRenameDetector ( reader , deleted , added ) ;
d . compute ( pm ) ;
d . setRenameScore ( getRenameScore ( ) ) ;
deleted = d . getLeftOverSources ( ) ;
d . compute ( pm ) ;
added = d . getLeftOverDestinations ( ) ;
deleted = d . getLeftOverSources ( ) ;
entries . addAll ( d . getMatches ( ) ) ;
added = d . getLeftOverDestinations ( ) ;
entries . addAll ( d . getMatches ( ) ) ;
} finally {
reader . release ( ) ;
}
} else {
} else {
overRenameLimit = true ;
overRenameLimit = true ;
}
}