@@ -796,43 +796,80 @@ def build_log(sha, options)
796796 # +follow+ option is true and the file specified by +path+ was renamed,
797797 # then the path value is set to the old path.
798798 def commit_touches_path? ( commit , path , follow )
799- if commit . parents . empty?
800- diff = commit . diff
799+ if follow
800+ touches_path_diff? ( commit , path )
801801 else
802- diff = commit . parents [ 0 ] . diff ( commit )
803- diff . find_similar! if follow
802+ touches_path_tree? ( commit , path )
804803 end
804+ end
805805
806- # Check the commit's deltas to see if it touches the :path
807- # argument
808- diff . each_delta do |d |
809- if path_matches? ( path , d . old_file [ :path ] , d . new_file [ :path ] )
810- if should_follow? ( follow , d , path )
811- # Look for the old path in ancestors
812- path . replace ( d . old_file [ :path ] )
813- end
806+ # Returns true if +commit+ introduced changes to +path+, using commit
807+ # trees to make that determination.
808+ def touches_path_tree? ( commit , path )
809+ parent = commit . parents [ 0 ]
810+ entry = tree_entry ( commit , path )
814811
815- return true
816- end
812+ if parent . nil?
813+ # This is the root commit, return true if it has +path+ in its tree
814+ return entry != nil
817815 end
818816
819- false
817+ parent_entry = tree_entry ( parent , path )
818+
819+ if entry . nil? && parent_entry . nil?
820+ false
821+ elsif entry . nil? || parent_entry . nil?
822+ true
823+ else
824+ entry [ :oid ] != parent_entry [ :oid ]
825+ end
820826 end
821827
822- # Used by the #commit_touches_path method to determine whether the
823- # specified file has been renamed and should be followed in ancestor
824- # commits. Returns true if +follow_option+ is true, the file is renamed
825- # in this commit, and the new file's path matches the path option.
826- def should_follow? ( follow_option , delta , path )
827- follow_option && delta . renamed? && path == delta . new_file [ :path ]
828+ # Find the entry for +path+ in the tree for +commit+
829+ def tree_entry ( commit , path )
830+ pathname = Pathname . new ( path )
831+ tmp_entry = nil
832+
833+ pathname . each_filename do |dir |
834+ if tmp_entry . nil?
835+ tmp_entry = commit . tree [ dir ]
836+ else
837+ tmp_entry = rugged . lookup ( tmp_entry [ :oid ] ) [ dir ]
838+ end
839+ end
840+
841+ tmp_entry
828842 end
829843
830- # Returns true if any of the strings in +*paths+ begins with the
831- # +path_to_match+ argument
832- def path_matches? ( path_to_match , *paths )
833- paths . any? do |p |
834- p . match ( /^#{ Regexp . escape ( path_to_match ) } / )
844+ # Returns true if +commit+ introduced changes to +path+, using
845+ # Rugged::Diff objects to make that determination. This is slower than
846+ # comparing commit trees, but lets us use Rugged::Diff#find_similar to
847+ # detect file renames.
848+ def touches_path_diff? ( commit , path )
849+ diff = commit . diff ( reverse : true , paths : [ path ] ,
850+ disable_pathspec_match : true )
851+
852+ return false if diff . deltas . empty?
853+
854+ # If +path+ is a filename, not a directory, then we should only have
855+ # one delta. We don't need to follow renames for directories.
856+ return true if diff . deltas . length > 1
857+
858+ # Detect renames
859+ delta = diff . deltas . first
860+ if delta . added?
861+ full_diff = commit . diff ( reverse : true )
862+ full_diff . find_similar!
863+
864+ full_diff . each_delta do |full_delta |
865+ if full_delta . renamed? && path == full_delta . new_file [ :path ]
866+ # Look for the old path in ancestors
867+ path . replace ( full_delta . old_file [ :path ] )
868+ end
869+ end
835870 end
871+
872+ true
836873 end
837874
838875 def archive_to_file ( treeish = 'master' , prefix = nil , filename = 'archive.tar.gz' , format = nil , compress_cmd = %W( gzip ) )
0 commit comments