librariesio/repo_miner

View on GitHub
lib/repo_miner/miners/dependencies.rb

Summary

Maintainability
C
1 day
Test Coverage
require 'bibliothecary'

module RepoMiner
  module Miners
    class Dependencies
      def analyse(commit)
        return if commit.merge?

        all_paths = blob_paths(commit.rugged_commit)

        added_paths = all_paths.select{|path| path[:status] == :added }.map{|path| path[:path] }
        modified_paths = all_paths.select{|path| path[:status] == :modified }.map{|path| path[:path] }
        removed_paths = all_paths.select{|path| path[:status] == :deleted }.map{|path| path[:path] }

        added_manifest_paths = Bibliothecary.identify_manifests(added_paths)
        modified_manifest_paths = Bibliothecary.identify_manifests(modified_paths)
        removed_manifest_paths = Bibliothecary.identify_manifests(removed_paths)

        # don't both analysing commits where no dependency files touched
        return nil if added_manifest_paths.empty? && modified_manifest_paths.empty? && removed_manifest_paths.empty?

        # Added manifest files
        added_manifests = []
        added_manifest_paths.each do |manifest_path|
          manifest = Bibliothecary.analyse_file(manifest_path, commit.content_after(manifest_path))

          new_manifest = manifest.first

          if new_manifest
            dependencies = new_manifest[:dependencies]
            added = dependencies.map{|d| d[:name] }

            added.map! do |dep_name|
              dep = dependencies.find{|d| d[:name] == dep_name }
              {
                name: dep_name,
                requirement: dep[:requirement],
                type: dep[:type]
              }
            end

            added_manifests << {
              path: manifest_path,
              platform: manifest[0][:platform],
              kind: manifest[0][:kind],
              added_dependencies: added,
              modified_dependencies: [],
              removed_dependencies: []
            }
          end
        end

        # Modified manifest files
        modified_manifests = []
        modified_manifest_paths.each do |manifest_path|
          before_manifest = Bibliothecary.analyse_file(manifest_path, commit.content_before(manifest_path))
          before_modified_manifest = before_manifest.first

          after_manifest = Bibliothecary.analyse_file(manifest_path, commit.content_after(manifest_path))
          after_modified_manifest = after_manifest.first

          if before_modified_manifest && after_modified_manifest
            before_dependencies = before_modified_manifest[:dependencies]
            after_dependencies = after_modified_manifest[:dependencies]

            added_dependency_names = after_dependencies.map{|d| d[:name] } - before_dependencies.map{|d| d[:name] }
            removed_dependency_names = before_dependencies.map{|d| d[:name] } - after_dependencies.map{|d| d[:name] }

            potentially_modified_dependency_names = after_dependencies.map{|d| d[:name] } - added_dependency_names - removed_dependency_names
            modified_dependency_names = potentially_modified_dependency_names.select do |name|
              after = after_dependencies.find{|d| d[:name] == name }
              before = before_dependencies.find{|d| d[:name] == name }
              (after[:requirement] != before[:requirement]) || (after[:type] != before[:type])
            end

            # added_dependencies
            added_dependencies = added_dependency_names.map do |dep_name|
              dep = after_dependencies.find{|d| d[:name] == dep_name }
              {
                name: dep_name,
                requirement: dep[:requirement],
                type: dep[:type]
              }
            end

            # modified_dependencies
            modified_dependencies = modified_dependency_names.map do |dep_name|
              after = after_dependencies.find{|d| d[:name] == dep_name }
              before = before_dependencies.find{|d| d[:name] == dep_name }
              dep_hash = {
                name: dep_name,
                requirement: after[:requirement],
                type: after[:type]
              }
              dep_hash[:previous_requirement] = before[:requirement] if after[:requirement] != before[:requirement]
              dep_hash[:previous_type] = before[:type] if after[:type] != before[:type]
              dep_hash
            end

            # removed_dependencies
            removed_dependencies = removed_dependency_names.map do |dep_name|
              dep = before_dependencies.find{|d| d[:name] == dep_name }
              {
                name: dep_name,
                requirement: dep[:requirement],
                type: dep[:type]
              }
            end

            modified_manifests << {
              path: manifest_path,
              platform: after_modified_manifest[:platform],
              kind: after_modified_manifest[:kind],
              added_dependencies: added_dependencies,
              modified_dependencies: modified_dependencies,
              removed_dependencies: removed_dependencies
            }
          end
        end

        # Removed manifest files
        removed_manifests = []
        removed_manifest_paths.each do |manifest_path|
          manifest = Bibliothecary.analyse_file(manifest_path, commit.content_before(manifest_path))

          removed_manifest = manifest.first

          if removed_manifest
            dependencies = removed_manifest[:dependencies]
            removed = dependencies.map{|d| d[:name] }

            removed.map! do |dep_name|
              dep = dependencies.find{|d| d[:name] == dep_name }
              {
                name: dep_name,
                requirement: dep[:requirement],
                type: dep[:type]
              }
            end

            removed_manifests << {
              path: manifest_path,
              platform: manifest[0][:platform],
              kind: manifest[0][:kind],
              added_dependencies: [],
              modified_dependencies: [],
              removed_dependencies: removed
            }
          end
        end

        data = {
          added_manifests: added_manifests,
          modified_manifests: modified_manifests,
          removed_manifests: removed_manifests
        }

        commit.add_data(:dependencies, data)
      end

      private

      def blob_paths(commit)
        paths = []

        if commit.parents.count == 0 # initial commit
          commit.tree.walk_blobs(:postorder) do |root, entry|
            paths << {
              status: :added,
              path: "#{root}#{entry[:name]}"
            }
          end
        else
          diffs = commit.parents[0].diff(commit)

          diffs.each_delta do |delta|
            paths << {
              status: delta.status,
              path: delta.new_file[:path]
            }
          end
        end
        paths
      end
    end
  end
end