rosette-proj/rosette-core

View on GitHub
lib/rosette/core/git/diff_finder.rb

Summary

Maintainability
A
0 mins
Test Coverage
# encoding: UTF-8

java_import 'org.eclipse.jgit.util.io.NullOutputStream'
java_import 'org.eclipse.jgit.diff.DiffFormatter'
java_import 'org.eclipse.jgit.treewalk.CanonicalTreeParser'
java_import 'org.eclipse.jgit.treewalk.EmptyTreeIterator'
java_import 'org.eclipse.jgit.treewalk.filter.PathFilterGroup'

module Rosette
  module Core

    # Used to compute diffs between two git refs. Can also read file
    # contents from diff entries.
    #
    # @!attribute [r] jgit_repo
    #   @return [Java::OrgEclipseJgitStorageFile::FileRepository] the git
    #     repository.
    # @!attribute [r] rev_walker
    #   @return [Java::OrgEclipseJgitRevwalk::RevWalk] the RevWalk instance
    #     to use.
    class DiffFinder
      attr_reader :jgit_repo, :rev_walker

      # Creates a new diff finder instance.
      #
      # @param [Java::OrgEclipseJgitStorageFile::FileRepository] jgit_repo
      #   The git repository.
      # @param [Java::OrgEclipseJgitRevwalk::RevWalk] rev_walker The RevWalk
      #   instance to use.
      def initialize(jgit_repo, rev_walker)
        @jgit_repo = jgit_repo
        @rev_walker = rev_walker
      end

      # Computes a diff between two revs.
      #
      # @param [Java::OrgEclipseJgitRevwalk::RevCommit] rev_parents The first
      #   commit or commits to use in the diff (the parent, i.e. the commit that
      #   occurred earlier in time).
      # @param [Java::OrgEclipseJgitRevwalk::RevCommit] rev_child The second
      #   commit to use in the diff (the child of the parent, i.e. the commit
      #   that occurred later in time).
      # @param [Array<String>] paths The paths to include in the diff. If given
      #   an empty array, this method will return a diff for all paths.
      # @return [Hash<String, Java::OrgEclipseJgitDiff::DiffEntry>] A hash of
      #   commit ids to diff entries for the diff between +rev_parents+ and
      #   +rev_child+. There will be one diff entry for each file that changed.
      def diff(rev_parents, rev_child, paths = [])
        rev_parents = Array(rev_parents)
        diff_formatter.setPathFilter(construct_filter(Array(paths)))

        rev_parents.each_with_object({}) do |rev_parent, ret|
          ret[rev_parent.getId.name] = diff_formatter.scan(
            rev_walker.parseCommit(rev_parent.getId).getTree,
            rev_child.getTree
          )
        end
      end

      # Computes a diff between a rev and its parent.
      #
      # @param [Java::OrgEclipseJgitRevwalk::RevCommit] rev The rev to use.
      # @return [Hash<String, Java::OrgEclipseJgitDiff::DiffEntry>] A hash of
      #   commit ids to diff entries for the diff between +rev+ and its parents.
      #   There will be one diff entry for each file that changed.
      def diff_with_parents(rev)
        if rev.getParentCount > 0
          rev.getParentCount.times.each_with_object({}) do |i, ret|
            parent = rev.getParent(i)

            ret[parent.getId.name] = diff_formatter.scan(
              rev_walker.parseCommit(parent.getId).getTree,
              rev.getTree
            )
          end
        else
          {
            rev.getId.name => diff_formatter.scan(
              EmptyTreeIterator.new,
              CanonicalTreeParser.new(
                nil, rev_walker.getObjectReader, rev.getTree
              )
            )
          }
        end
      end

      # Reads the "new" contents of a diff entry. Diff entries contain a
      # reference to both the new and old files. The "new" contents means
      # the contents of the changed file, not the original.
      #
      # @param [Java::OrgEclipseJgitDiff::DiffEntry] entry The diff entry
      #   to read from.
      # @param [Encoding] encoding The encoding to expect the contents
      #   to be in.
      # @return [String] The file contents, encoded in +encoding+.
      def read_new_entry(entry, encoding = Encoding::UTF_8)
        Java::JavaLang::String.new(
          object_reader.open(entry.newId.toObjectId).getBytes, encoding.to_s
        )
      end

      private

      def object_reader
        @object_reader ||= jgit_repo.newObjectReader
      end

      def construct_filter(paths)
        paths = fix_paths(paths)
        PathFilterGroup.createFromStrings(paths) if paths.size > 0
      end

      def fix_paths(paths)
        # paths can't begin with a dot or dot slash (jgit limitation)
        paths.map do |path|
          path.gsub(/\A(\.(?:\/|\z))/, '')
        end.select do |path|
          !path.strip.empty?
        end
      end

      def diff_formatter
        @diff_formatter ||= DiffFormatter.new(NullOutputStream::INSTANCE).tap do |formatter|
          formatter.setRepository(jgit_repo)
        end
      end
    end

  end
end