kevinjalbert/git_statistics

View on GitHub
lib/git_statistics/commits.rb

Summary

Maintainability
A
1 hr
Test Coverage
module GitStatistics
  class Commits < Hash
    attr_accessor :stats, :totals, :path, :fresh, :limit, :pretty

    def initialize(path, fresh, limit, pretty)
      super()
      @path = path
      @fresh = fresh
      @limit = limit
      @pretty = pretty
      clean
    end

    def clean
      # Ensure the path exists
      FileUtils.mkdir_p(path)

      # Remove all files within path if saving
      if fresh
        files_in_path.each do |file|
          File.delete(File.join(path, file))
        end
      end

      # Initilize/resets stats and totals
      @stats = Stats.new
      @totals = Hash.new { |h,k| h[k] = 0 }
      @totals[:languages] = {}
    end

    def files_in_path
      Dir.entries(path).reject { |file| %w(. ..).include?(file) }
    end

    def flush_commits(force = false)
      return unless size >= limit || force

      file_count = Utilities.number_of_matching_files(path, /\d+\.json/)
      save(File.join(path, "#{file_count}.json"), @pretty)
      clear
    end

    def author_top_n_type(type, top_n = 0)
      if @stats.empty? || !@stats.first[1].has_key?(type)
        nil
      else
        @stats.sort_by(type).take_top(top_n)
      end
    end

    def calculate_statistics(email, merge)
      # Identify authors and author type
      type = email ? :author_email : :author

      # Process the commits from file or memory
      files = Dir.entries(path) - ['.', '..']
      if files.size == 0
        process_commits(type, merge)
      else
        # Load commit file and extract the commits
        files.each do |file|
          next unless file =~ /\d+\.json/

          load(File.join(path, file))
          process_commits(type, merge)
          clear
        end
      end
    end

    def process_commits(type, merge)
      # Collect the stats from each commit
      each do |key, value|
        next if !merge && value[:merge]
        # If there are no changed files move to next commit
        next if value[:files].empty?

        # Acquire author (make if not seen before)
        author = @stats[value[type]]

        if author.nil?
          @stats[value[type]] = Hash.new(0)
          author = @stats[value[type]]
          author[:languages] = {}
        end

        # Collect language stats
        value[:files].each do |file|
          # Add to author's languages
          add_language_stats(author, file)

          # Add to repository's languages
          add_language_stats(@totals, file)
        end

        # Add commit stats to author
        add_commit_stats(author, value)

        # Add commit stats to repository
        add_commit_stats(@totals, value)

        # Save new changes back to stats
        @stats[value[type]] = author
        author = nil
      end
    end

    def add_language_stats(data, file)
      # Add stats to data's languages
      if data[:languages][file[:language].to_sym].nil?
        data[:languages][file[:language].to_sym] = Hash.new(0)
      end

      data[:languages][file[:language].to_sym][:additions] += file[:additions]
      data[:languages][file[:language].to_sym][:deletions] += file[:deletions]

      # Keep count of languages status (i.e., added, deleted) and keep keys consistent (i.e., added_files, deleted_files)
      unless file[:status].nil?
        data[:languages][file[:language].to_sym][(file[:status] + '_files').to_sym] += 1
      end

      data
    end

    def add_commit_stats(data, commit)
      # Add commit stats to author
      data[:merges] += 1 if commit[:merge]
      data[:commits] += 1
      data[:additions] += commit[:additions]
      data[:deletions] += commit[:deletions]
      data[:added_files] += commit[:added_files] unless commit[:added_files].nil?
      data[:deleted_files] += commit[:deleted_files] unless commit[:deleted_files].nil?
      data
    end

    def load(file)
      merge!(JSON.parse(File.read(file), symbolize_names: true))
    end

    def save(file, pretty)
      return if empty?

      # Ensure the path to the file exists
      FileUtils.mkdir_p(File.dirname(file))

      # Save file in a simple or pretty format
      File.open(file, 'w') do |f|
        json_content = pretty ? JSON.pretty_generate(self) : to_json
        f.write(json_content)
      end
    end
  end
end