rosette-proj/rosette-core

View on GitHub
lib/rosette/core/commands/git/diff_command.rb

Summary

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

module Rosette
  module Core
    module Commands

      # Detects phrase changes between two git refs or commit ids. Identifies phrases
      # that have been added, removed, or changed. The refs used in the comparison
      # are referred to as a "head" and a "diff point". In Rosette/git parlance, a "head"
      # generally means a git ref that is currently being modified, i.e. a branch. A
      # "diff point" is some common ref to compare "head" to, often the repository's
      # "master" branch.
      #
      # Perhaps an easier way to visualize these concepts is through a git command-line
      # example. Imagine you're developing a feature to add widget support to your app.
      # You start work by switching to a new git branch (i.e. +git+ +checkout+ +-b+ +add_widgets+).
      # After adding a few files, changing some others, and deleting old code, you want
      # to see a complete set of all your changes. To do this, you run +git+ +diff+. At
      # this point, your "head" is your branch "add_widgets" and your "diff point" is
      # "master" (or whichever branch you were on when you ran +git+ +checkout+). You
      # could also have run +git+ +diff+ +master+ or +git+ +diff+ +master+ +HEAD+ to
      # get the same result. You can think of {DiffCommand} like a +git+ +diff+ command
      # of the form +git+ +diff+ +<diff+ +point>+ +<head>+.
      #
      # @see Rosette::Core::DiffFinder
      #
      # @example
      #   cmd = DiffCommand.new(configuration)
      #     .set_repo_name('my_repo')
      #     .set_head_ref('my_branch')
      #     .set_diff_point_ref('master')
      #     .set_paths(['config/locales/en.yml'])
      #
      #   cmd.execute
      #   # {
      #   #   added: [
      #   #     { key: 'Foo', ... },
      #   #   ],
      #   #   removed: [
      #   #     { key: 'I got deleted', ... }
      #   #   ],
      #   #   modified: [
      #   #     { key: 'New value', old_key: 'Old value', ... }
      #   #   ]
      #   # }
      #
      # @!attribute [r] head_commit_str
      #   @return [String] the raw head ref or commit id as set via {#set_head_ref}
      #     or {#set_head_commit_id}.
      #
      # @!attribute [r] diff_point_commit_str
      #   @return [String] the raw diff point ref or commit id as set via
      #     {#set_diff_point_ref} or {#set_diff_point_commit_id}.
      #
      # @!attribute [r] paths
      #   @return [Array] the list of paths to include in the diff. The diff will not
      #     contain phrases that were added/removed/modified in files that are not
      #     contained within this list.
      class DiffCommand < DiffBaseCommand
        attr_reader :head_commit_str, :diff_point_commit_str, :paths

        include WithRepoName

        validate :head_commit_str, type: :commit
        validate :diff_point_commit_str, type: :commit

        # Set the head commit id. Calling this method after {#set_head_ref} will
        # overwrite the head ref value. In other words, it's generally a good idea
        # to only call one of {#set_head_commit_id} or {#set_head_ref} but not both.
        #
        # @param [String] head_commit_id The head commit id.
        # @return [self]
        def set_head_commit_id(head_commit_id)
          @head_commit_str = head_commit_id
          self
        end

        # Set the head ref (i.e. a branch name). Calling this method after
        # {#set_head_commit_id} will overwrite the head commit id value. In other
        # words, it's generally a good idea to only call one of {#set_head_commit_id}
        # or {#set_head_ref} but not both.
        #
        # @param [String] head_ref The head ref.
        # @return [self]
        def set_head_ref(head_ref)
          @head_commit_str = head_ref
          self
        end

        # Set the diff point commit id. Calling this method after {#set_head_ref}
        # will overwrite the head ref value. In other words, it's generally a good
        # idea to only call one of {#set_head_commit_id} or {#set_head_ref} but
        # not both.
        #
        # @param [String] diff_point_commit_id The diff point commit id.
        # @return [self]
        def set_diff_point_commit_id(diff_point_commit_id)
          @diff_point_commit_str = diff_point_commit_id
          self
        end

        # Set the diff point ref (i.e. master). Calling this method after
        # {#set_diff_point_commit_id} will overwrite the diff point commit id value.
        # In other words, it's generally a good idea to only call one of
        # {#set_diff_point_commit_id} or {#set_diff_point_ref} but not both.
        #
        # @param [String] diff_point_ref The diff point ref.
        # @return [self]
        def set_diff_point_ref(diff_point_ref)
          @diff_point_commit_str = diff_point_ref
          self
        end

        # Set the list of paths to consider when computing the diff. Any paths not
        # in this list will not appear in the diff.
        #
        # @param [Array] paths The list of paths.
        # @return [self]
        def set_paths(paths)
          @paths = paths
          self
        end

        # Resolves the given head git ref or commit id and returns the corresponding
        # commit id. If {#set_head_ref} was used to set a git ref (i.e. branch name),
        # this method looks up and returns the corresponding commit id. If
        # {#set_head_commit_id} was used to set a commit id, then that commit id is
        # validated and returned.
        #
        # @return [String] The commit id set via either {#set_head_ref} or
        #   {#set_head_commit_id}.
        #
        # @raise [Java::OrgEclipseJgitErrors::MissingObjectException, Java::JavaLang::IllegalArgumentException]
        #   If either the commit id doesn't exist or the ref can't be found.
        def head_commit_id
          @head_commit_id ||= get_repo(repo_name)
            .repo.get_rev_commit(head_commit_str)
            .getId
            .name
        end

        # Resolves the given diff point git ref or commit id and returns the corresponding
        # commit id. If {#set_diff_point_ref} was used to set a git ref (i.e. branch name),
        # this method looks up and returns the corresponding commit id. If
        # {#set_diff_point_commit_id} was used to set a commit id, then that commit id is
        # validated and returned.
        #
        # @return [String] The commit id set via either {#set_diff_point_ref} or
        #   {#set_diff_point_commit_id}.
        #
        # @raise [Java::OrgEclipseJgitErrors::MissingObjectException, Java::JavaLang::IllegalArgumentException]
        #   If either the commit id doesn't exist or the ref can't be found.
        def diff_point_commit_id
          @diff_point_commit_id ||= get_repo(repo_name)
            .repo.get_rev_commit(diff_point_commit_str)
            .getId
            .name
        end

        # Computes the diff.
        # @return [Hash] a hash of differences, grouped by added/removed/modified keys. Each
        #   value is an array of phrases. For added and removed phrases, the phrase hashes
        #   will contain normal phrase attributes. For changed phrases, the phrase hashes
        #   will contain normal phrase attributes plus a special +old_key+ attribute that
        #   contains the previous key of the phrase. See the example above for a visual
        #   representation of the diff hash.
        def execute
          diff_between(head_commit_id, Array(parent_commit_id))
        end

        protected

        def diff_point_exists?
          commit_exists?(repo_name, diff_point_commit_id)
        end

        def parent_commit_id
          parent_commit_id = if strict? || diff_point_exists?
            diff_point_commit_id
          else
            get_closest_processed_parent(diff_point_commit_id)
          end
        end
      end

    end
  end
end