309 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Ruby
		
	
	
	
			
		
		
	
	
			309 lines
		
	
	
		
			7.7 KiB
		
	
	
	
		
			Ruby
		
	
	
	
# Gitlab::Git::Commit is a wrapper around native Rugged::Commit object
 | 
						|
module Gitlab
 | 
						|
  module Git
 | 
						|
    class Commit
 | 
						|
      include Gitlab::Git::EncodingHelper
 | 
						|
 | 
						|
      attr_accessor :raw_commit, :head, :refs
 | 
						|
 | 
						|
      SERIALIZE_KEYS = [
 | 
						|
        :id, :message, :parent_ids,
 | 
						|
        :authored_date, :author_name, :author_email,
 | 
						|
        :committed_date, :committer_name, :committer_email
 | 
						|
      ].freeze
 | 
						|
 | 
						|
      attr_accessor *SERIALIZE_KEYS # rubocop:disable Lint/AmbiguousOperator
 | 
						|
 | 
						|
      delegate :tree, to: :raw_commit
 | 
						|
 | 
						|
      def ==(other)
 | 
						|
        return false unless other.is_a?(Gitlab::Git::Commit)
 | 
						|
 | 
						|
        methods = [:message, :parent_ids, :authored_date, :author_name,
 | 
						|
                   :author_email, :committed_date, :committer_name,
 | 
						|
                   :committer_email]
 | 
						|
 | 
						|
        methods.all? do |method|
 | 
						|
          send(method) == other.send(method)
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      class << self
 | 
						|
        # Get commits collection
 | 
						|
        #
 | 
						|
        # Ex.
 | 
						|
        #   Commit.where(
 | 
						|
        #     repo: repo,
 | 
						|
        #     ref: 'master',
 | 
						|
        #     path: 'app/models',
 | 
						|
        #     limit: 10,
 | 
						|
        #     offset: 5,
 | 
						|
        #   )
 | 
						|
        #
 | 
						|
        def where(options)
 | 
						|
          repo = options.delete(:repo)
 | 
						|
          raise 'Gitlab::Git::Repository is required' unless repo.respond_to?(:log)
 | 
						|
 | 
						|
          repo.log(options).map { |c| decorate(c) }
 | 
						|
        end
 | 
						|
 | 
						|
        # Get single commit
 | 
						|
        #
 | 
						|
        # Ex.
 | 
						|
        #   Commit.find(repo, '29eda46b')
 | 
						|
        #
 | 
						|
        #   Commit.find(repo, 'master')
 | 
						|
        #
 | 
						|
        def find(repo, commit_id = "HEAD")
 | 
						|
          return decorate(commit_id) if commit_id.is_a?(Rugged::Commit)
 | 
						|
 | 
						|
          obj = if commit_id.is_a?(String)
 | 
						|
                  repo.rev_parse_target(commit_id)
 | 
						|
                else
 | 
						|
                  Gitlab::Git::Ref.dereference_object(commit_id)
 | 
						|
                end
 | 
						|
 | 
						|
          return nil unless obj.is_a?(Rugged::Commit)
 | 
						|
 | 
						|
          decorate(obj)
 | 
						|
        rescue Rugged::ReferenceError, Rugged::InvalidError, Rugged::ObjectError, Gitlab::Git::Repository::NoRepository
 | 
						|
          nil
 | 
						|
        end
 | 
						|
 | 
						|
        # Get last commit for HEAD
 | 
						|
        #
 | 
						|
        # Ex.
 | 
						|
        #   Commit.last(repo)
 | 
						|
        #
 | 
						|
        def last(repo)
 | 
						|
          find(repo)
 | 
						|
        end
 | 
						|
 | 
						|
        # Get last commit for specified path and ref
 | 
						|
        #
 | 
						|
        # Ex.
 | 
						|
        #   Commit.last_for_path(repo, '29eda46b', 'app/models')
 | 
						|
        #
 | 
						|
        #   Commit.last_for_path(repo, 'master', 'Gemfile')
 | 
						|
        #
 | 
						|
        def last_for_path(repo, ref, path = nil)
 | 
						|
          where(
 | 
						|
            repo: repo,
 | 
						|
            ref: ref,
 | 
						|
            path: path,
 | 
						|
            limit: 1
 | 
						|
          ).first
 | 
						|
        end
 | 
						|
 | 
						|
        # Get commits between two revspecs
 | 
						|
        # See also #repository.commits_between
 | 
						|
        #
 | 
						|
        # Ex.
 | 
						|
        #   Commit.between(repo, '29eda46b', 'master')
 | 
						|
        #
 | 
						|
        def between(repo, base, head)
 | 
						|
          repo.commits_between(base, head).map do |commit|
 | 
						|
            decorate(commit)
 | 
						|
          end
 | 
						|
        rescue Rugged::ReferenceError
 | 
						|
          []
 | 
						|
        end
 | 
						|
 | 
						|
        # Delegate Repository#find_commits
 | 
						|
        def find_all(repo, options = {})
 | 
						|
          repo.find_commits(options)
 | 
						|
        end
 | 
						|
 | 
						|
        def decorate(commit, ref = nil)
 | 
						|
          Gitlab::Git::Commit.new(commit, ref)
 | 
						|
        end
 | 
						|
 | 
						|
        # Returns a diff object for the changes introduced by +rugged_commit+.
 | 
						|
        # If +rugged_commit+ doesn't have a parent, then the diff is between
 | 
						|
        # this commit and an empty repo.  See Repository#diff for the keys
 | 
						|
        # allowed in the +options+ hash.
 | 
						|
        def diff_from_parent(rugged_commit, options = {})
 | 
						|
          options ||= {}
 | 
						|
          break_rewrites = options[:break_rewrites]
 | 
						|
          actual_options = Gitlab::Git::Diff.filter_diff_options(options)
 | 
						|
 | 
						|
          diff = if rugged_commit.parents.empty?
 | 
						|
                   rugged_commit.diff(actual_options.merge(reverse: true))
 | 
						|
                 else
 | 
						|
                   rugged_commit.parents[0].diff(rugged_commit, actual_options)
 | 
						|
                 end
 | 
						|
 | 
						|
          diff.find_similar!(break_rewrites: break_rewrites)
 | 
						|
          diff
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      def initialize(raw_commit, head = nil)
 | 
						|
        raise "Nil as raw commit passed" unless raw_commit
 | 
						|
 | 
						|
        if raw_commit.is_a?(Hash)
 | 
						|
          init_from_hash(raw_commit)
 | 
						|
        elsif raw_commit.is_a?(Rugged::Commit)
 | 
						|
          init_from_rugged(raw_commit)
 | 
						|
        else
 | 
						|
          raise "Invalid raw commit type: #{raw_commit.class}"
 | 
						|
        end
 | 
						|
 | 
						|
        @head = head
 | 
						|
      end
 | 
						|
 | 
						|
      def sha
 | 
						|
        id
 | 
						|
      end
 | 
						|
 | 
						|
      def short_id(length = 10)
 | 
						|
        id.to_s[0..length]
 | 
						|
      end
 | 
						|
 | 
						|
      def safe_message
 | 
						|
        @safe_message ||= message
 | 
						|
      end
 | 
						|
 | 
						|
      def created_at
 | 
						|
        committed_date
 | 
						|
      end
 | 
						|
 | 
						|
      # Was this commit committed by a different person than the original author?
 | 
						|
      def different_committer?
 | 
						|
        author_name != committer_name || author_email != committer_email
 | 
						|
      end
 | 
						|
 | 
						|
      def parent_id
 | 
						|
        parent_ids.first
 | 
						|
      end
 | 
						|
 | 
						|
      # Shows the diff between the commit's parent and the commit.
 | 
						|
      #
 | 
						|
      # Cuts out the header and stats from #to_patch and returns only the diff.
 | 
						|
      def to_diff(options = {})
 | 
						|
        diff_from_parent(options).patch
 | 
						|
      end
 | 
						|
 | 
						|
      # Returns a diff object for the changes from this commit's first parent.
 | 
						|
      # If there is no parent, then the diff is between this commit and an
 | 
						|
      # empty repo.  See Repository#diff for keys allowed in the +options+
 | 
						|
      # hash.
 | 
						|
      def diff_from_parent(options = {})
 | 
						|
        Commit.diff_from_parent(raw_commit, options)
 | 
						|
      end
 | 
						|
 | 
						|
      def has_zero_stats?
 | 
						|
        stats.total.zero?
 | 
						|
      rescue
 | 
						|
        true
 | 
						|
      end
 | 
						|
 | 
						|
      def no_commit_message
 | 
						|
        "--no commit message"
 | 
						|
      end
 | 
						|
 | 
						|
      def to_hash
 | 
						|
        serialize_keys.map.with_object({}) do |key, hash|
 | 
						|
          hash[key] = send(key)
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      def date
 | 
						|
        committed_date
 | 
						|
      end
 | 
						|
 | 
						|
      def diffs(options = {})
 | 
						|
        Gitlab::Git::DiffCollection.new(diff_from_parent(options), options)
 | 
						|
      end
 | 
						|
 | 
						|
      def parents
 | 
						|
        raw_commit.parents.map { |c| Gitlab::Git::Commit.new(c) }
 | 
						|
      end
 | 
						|
 | 
						|
      def stats
 | 
						|
        Gitlab::Git::CommitStats.new(self)
 | 
						|
      end
 | 
						|
 | 
						|
      def to_patch(options = {})
 | 
						|
        begin
 | 
						|
          raw_commit.to_mbox(options)
 | 
						|
        rescue Rugged::InvalidError => ex
 | 
						|
          if ex.message =~ /Commit \w+ is a merge commit/
 | 
						|
            'Patch format is not currently supported for merge commits.'
 | 
						|
          end
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      # Get a collection of Rugged::Reference objects for this commit.
 | 
						|
      #
 | 
						|
      # Ex.
 | 
						|
      #   commit.ref(repo)
 | 
						|
      #
 | 
						|
      def refs(repo)
 | 
						|
        repo.refs_hash[id]
 | 
						|
      end
 | 
						|
 | 
						|
      # Get ref names collection
 | 
						|
      #
 | 
						|
      # Ex.
 | 
						|
      #   commit.ref_names(repo)
 | 
						|
      #
 | 
						|
      def ref_names(repo)
 | 
						|
        refs(repo).map do |ref|
 | 
						|
          ref.name.sub(%r{^refs/(heads|remotes|tags)/}, "")
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      def message
 | 
						|
        encode! @message
 | 
						|
      end
 | 
						|
 | 
						|
      def author_name
 | 
						|
        encode! @author_name
 | 
						|
      end
 | 
						|
 | 
						|
      def author_email
 | 
						|
        encode! @author_email
 | 
						|
      end
 | 
						|
 | 
						|
      def committer_name
 | 
						|
        encode! @committer_name
 | 
						|
      end
 | 
						|
 | 
						|
      def committer_email
 | 
						|
        encode! @committer_email
 | 
						|
      end
 | 
						|
 | 
						|
      private
 | 
						|
 | 
						|
      def init_from_hash(hash)
 | 
						|
        raw_commit = hash.symbolize_keys
 | 
						|
 | 
						|
        serialize_keys.each do |key|
 | 
						|
          send("#{key}=", raw_commit[key])
 | 
						|
        end
 | 
						|
      end
 | 
						|
 | 
						|
      def init_from_rugged(commit)
 | 
						|
        author = commit.author
 | 
						|
        committer = commit.committer
 | 
						|
 | 
						|
        @raw_commit = commit
 | 
						|
        @id = commit.oid
 | 
						|
        @message = commit.message
 | 
						|
        @authored_date = author[:time]
 | 
						|
        @committed_date = committer[:time]
 | 
						|
        @author_name = author[:name]
 | 
						|
        @author_email = author[:email]
 | 
						|
        @committer_name = committer[:name]
 | 
						|
        @committer_email = committer[:email]
 | 
						|
        @parent_ids = commit.parents.map(&:oid)
 | 
						|
      end
 | 
						|
 | 
						|
      def serialize_keys
 | 
						|
        SERIALIZE_KEYS
 | 
						|
      end
 | 
						|
    end
 | 
						|
  end
 | 
						|
end
 |