@ -45,6 +45,7 @@ package org.eclipse.jgit.diff;
import java.io.IOException ;
import java.io.IOException ;
import java.util.ArrayList ;
import java.util.ArrayList ;
import java.util.Arrays ;
import java.util.Collection ;
import java.util.Collection ;
import java.util.Collections ;
import java.util.Collections ;
import java.util.Comparator ;
import java.util.Comparator ;
@ -312,70 +313,131 @@ public class RenameDetector {
return ;
return ;
pm . beginTask ( JGitText . get ( ) . renamesFindingExact , //
pm . beginTask ( JGitText . get ( ) . renamesFindingExact , //
added . size ( ) + deleted . size ( ) ) ;
added . size ( ) + added . size ( ) + deleted . size ( )
+ added . size ( ) * deleted . size ( ) ) ;
HashMap < AbbreviatedObjectId , Object > map = new HashMap < AbbreviatedObjectId , Object > ( ) ;
HashMap < AbbreviatedObjectId , Object > deletedMap = populateMap ( deleted , pm ) ;
for ( DiffEntry del : deleted ) {
HashMap < AbbreviatedObjectId , Object > addedMap = populateMap ( added , pm ) ;
Object old = map . put ( del . oldId , del ) ;
if ( old instanceof DiffEntry ) {
ArrayList < DiffEntry > list = new ArrayList < DiffEntry > ( 2 ) ;
list . add ( ( DiffEntry ) old ) ;
list . add ( del ) ;
map . put ( del . oldId , list ) ;
} else if ( old ! = null ) {
ArrayList < DiffEntry > uniqueAdds = new ArrayList < DiffEntry > ( added . size ( ) ) ;
// Must be a list of DiffEntries
ArrayList < List < DiffEntry > > nonUniqueAdds = new ArrayList < List < DiffEntry > > ( ) ;
( ( List ) old ) . add ( del ) ;
map . put ( del . oldId , old ) ;
for ( Object o : addedMap . values ( ) ) {
}
if ( o instanceof DiffEntry )
pm . update ( 1 ) ;
uniqueAdds . add ( ( DiffEntry ) o ) ;
else
nonUniqueAdds . add ( ( List < DiffEntry > ) o ) ;
}
}
ArrayList < DiffEntry > left = new ArrayList < DiffEntry > ( added . size ( ) ) ;
ArrayList < DiffEntry > left = new ArrayList < DiffEntry > ( added . size ( ) ) ;
for ( DiffEntry dst : added ) {
Object del = map . get ( dst . newId ) ;
for ( DiffEntry a : uniqueAdds ) {
Object del = deletedMap . get ( a . newId ) ;
if ( del instanceof DiffEntry ) {
if ( del instanceof DiffEntry ) {
// We have one add to one delete: pair them if they are the same
// type
DiffEntry e = ( DiffEntry ) del ;
DiffEntry e = ( DiffEntry ) del ;
if ( sameType ( e . oldMode , dst . newMode ) ) {
if ( sameType ( e . oldMode , a . newMode ) ) {
if ( e . changeType = = ChangeType . DELETE ) {
e . changeType = ChangeType . RENAME ;
e . changeType = ChangeType . RENAME ;
entries . add ( exactRename ( e , a ) ) ;
entries . add ( exactRename ( e , dst ) ) ;
} else {
entries . add ( exactCopy ( e , dst ) ) ;
}
} else {
} else {
left . add ( dst ) ;
left . add ( a ) ;
}
}
} else if ( del ! = null ) {
} else if ( del ! = null ) {
// We have one add to many deletes: find the delete with the
// same type and closest name to the add, then pair them
List < DiffEntry > list = ( List < DiffEntry > ) del ;
List < DiffEntry > list = ( List < DiffEntry > ) del ;
DiffEntry best = null ;
DiffEntry best = bestPathMatch ( a , list ) ;
for ( DiffEntry e : list ) {
if ( best ! = null ) {
if ( sameType ( e . oldMode , dst . newMode ) ) {
best . changeType = ChangeType . RENAME ;
best = e ;
entries . add ( exactRename ( best , a ) ) ;
break ;
} else {
}
left . add ( a ) ;
}
}
} else {
left . add ( a ) ;
}
pm . update ( 1 ) ;
}
for ( List < DiffEntry > adds : nonUniqueAdds ) {
Object o = deletedMap . get ( adds . get ( 0 ) . newId ) ;
if ( o instanceof DiffEntry ) {
// We have many adds to one delete: find the add with the same
// type and closest name to the delete, then pair them. Mark the
// rest as copies of the delete.
DiffEntry d = ( DiffEntry ) o ;
DiffEntry best = bestPathMatch ( d , adds ) ;
if ( best ! = null ) {
if ( best ! = null ) {
if ( best . changeType = = ChangeType . DELETE ) {
d . changeType = ChangeType . RENAME ;
best . changeType = ChangeType . RENAME ;
entries . add ( exactRename ( d , best ) ) ;
entries . add ( exactRename ( best , dst ) ) ;
for ( DiffEntry a : adds ) {
} else {
if ( a ! = best ) {
entries . add ( exactCopy ( best , dst ) ) ;
if ( sameType ( d . oldMode , a . newMode ) ) {
entries . add ( exactCopy ( d , a ) ) ;
} else {
left . add ( a ) ;
}
}
}
}
} else {
} else {
left . add ( dst ) ;
left . addAll ( ad ds) ;
}
}
} else {
} else {
left . add ( dst ) ;
// We have many adds to many deletes: score all the adds against
// all the deletes by path name, take the best matches, pair
// them as renames, then call the rest copies
List < DiffEntry > dels = ( List < DiffEntry > ) o ;
long [ ] matrix = new long [ dels . size ( ) * adds . size ( ) ] ;
int mNext = 0 ;
for ( int addIdx = 0 ; addIdx < adds . size ( ) ; addIdx + + ) {
String addedName = adds . get ( addIdx ) . newName ;
for ( int delIdx = 0 ; delIdx < dels . size ( ) ; delIdx + + ) {
String deletedName = dels . get ( delIdx ) . oldName ;
int score = SimilarityRenameDetector . nameScore ( addedName , deletedName ) ;
matrix [ mNext ] = SimilarityRenameDetector . encode ( score , addIdx , delIdx ) ;
mNext + + ;
}
}
Arrays . sort ( matrix ) ;
for ( - - mNext ; mNext > = 0 ; mNext - - ) {
long ent = matrix [ mNext ] ;
int delIdx = SimilarityRenameDetector . srcFile ( ent ) ;
int addIdx = SimilarityRenameDetector . dstFile ( ent ) ;
DiffEntry d = dels . get ( delIdx ) ;
DiffEntry a = adds . get ( addIdx ) ;
if ( a = = null ) {
pm . update ( 1 ) ;
continue ; // was already matched earlier
}
ChangeType type ;
if ( d . changeType = = ChangeType . DELETE ) {
// First use of this source file. Tag it as a rename so we
// later know it is already been used as a rename, other
// matches (if any) will claim themselves as copies instead.
//
d . changeType = ChangeType . RENAME ;
type = ChangeType . RENAME ;
} else {
type = ChangeType . COPY ;
}
entries . add ( DiffEntry . pair ( type , d , a , 100 ) ) ;
adds . set ( addIdx , null ) ; // Claim the destination was matched.
pm . update ( 1 ) ;
}
}
}
pm . update ( 1 ) ;
}
}
added = left ;
added = left ;
deleted = new ArrayList < DiffEntry > ( map . size ( ) ) ;
deleted = new ArrayList < DiffEntry > ( deletedM ap. size ( ) ) ;
for ( Object o : map . values ( ) ) {
for ( Object o : deletedM ap. values ( ) ) {
if ( o instanceof DiffEntry ) {
if ( o instanceof DiffEntry ) {
DiffEntry e = ( DiffEntry ) o ;
DiffEntry e = ( DiffEntry ) o ;
if ( e . changeType = = ChangeType . DELETE )
if ( e . changeType = = ChangeType . DELETE )
@ -391,6 +453,69 @@ public class RenameDetector {
pm . endTask ( ) ;
pm . endTask ( ) ;
}
}
/ * *
* Find the best match by file path for a given DiffEntry from a list of
* DiffEntrys . The returned DiffEntry will be of the same type as < src > . If
* no DiffEntry can be found that has the same type , this method will return
* null .
*
* @param src
* the DiffEntry to try to find a match for
* @param list
* a list of DiffEntrys to search through
* @return the DiffEntry from < list > who ' s file path best matches < src >
* /
private static DiffEntry bestPathMatch ( DiffEntry src , List < DiffEntry > list ) {
DiffEntry best = null ;
int score = - 1 ;
for ( DiffEntry d : list ) {
if ( sameType ( mode ( d ) , mode ( src ) ) ) {
int tmp = SimilarityRenameDetector
. nameScore ( path ( d ) , path ( src ) ) ;
if ( tmp > score ) {
best = d ;
score = tmp ;
}
}
}
return best ;
}
@SuppressWarnings ( "unchecked" )
private HashMap < AbbreviatedObjectId , Object > populateMap (
List < DiffEntry > diffEntries , ProgressMonitor pm ) {
HashMap < AbbreviatedObjectId , Object > map = new HashMap < AbbreviatedObjectId , Object > ( ) ;
for ( DiffEntry de : diffEntries ) {
Object old = map . put ( id ( de ) , de ) ;
if ( old instanceof DiffEntry ) {
ArrayList < DiffEntry > list = new ArrayList < DiffEntry > ( 2 ) ;
list . add ( ( DiffEntry ) old ) ;
list . add ( de ) ;
map . put ( id ( de ) , list ) ;
} else if ( old ! = null ) {
// Must be a list of DiffEntries
( ( List < DiffEntry > ) old ) . add ( de ) ;
map . put ( id ( de ) , old ) ;
}
pm . update ( 1 ) ;
}
return map ;
}
private static String path ( DiffEntry de ) {
return de . changeType = = ChangeType . DELETE ? de . oldName : de . newName ;
}
private static FileMode mode ( DiffEntry de ) {
return de . changeType = = ChangeType . DELETE ? de . oldMode : de . newMode ;
}
private static AbbreviatedObjectId id ( DiffEntry de ) {
return de . changeType = = ChangeType . DELETE ? de . oldId : de . newId ;
}
static boolean sameType ( FileMode a , FileMode b ) {
static boolean sameType ( FileMode a , FileMode b ) {
// Files have to be of the same type in order to rename them.
// Files have to be of the same type in order to rename them.
// We would never want to rename a file to a gitlink, or a
// We would never want to rename a file to a gitlink, or a