commit_range.rb 4.2 KB
Newer Older
R
Robert Speicher 已提交
1 2 3 4
# CommitRange makes it easier to work with commit ranges
#
# Examples:
#
5
#   range = CommitRange.new('f3f85602...e86e1013', project)
6
#   range.exclude_start?  # => false
R
Robert Speicher 已提交
7
#   range.reference_title # => "Commits f3f85602 through e86e1013"
8
#   range.to_s            # => "f3f85602...e86e1013"
R
Robert Speicher 已提交
9
#
10
#   range = CommitRange.new('f3f856029bc5f966c5a7ee24cf7efefdd20e6019..e86e1013709735be5bb767e2b228930c543f25ae', project)
11 12 13 14
#   range.exclude_start?  # => true
#   range.reference_title # => "Commits f3f85602^ through e86e1013"
#   range.to_param        # => {from: "f3f856029bc5f966c5a7ee24cf7efefdd20e6019^", to: "e86e1013709735be5bb767e2b228930c543f25ae"}
#   range.to_s            # => "f3f85602..e86e1013"
R
Robert Speicher 已提交
15
#
D
Douwe Maan 已提交
16
#   # Assuming the specified project has a repository containing both commits:
R
Robert Speicher 已提交
17 18 19 20
#   range.valid_commits? # => true
#
class CommitRange
  include ActiveModel::Conversion
21
  include Referable
R
Robert Speicher 已提交
22

23
  attr_reader :commit_from, :notation, :commit_to
24
  attr_reader :ref_from, :ref_to
R
Robert Speicher 已提交
25 26 27 28

  # Optional Project model
  attr_accessor :project

29
  # The beginning and ending refs can be named or SHAs, and
30
  # the range notation can be double- or triple-dot.
31 32 33 34 35 36
  REF_PATTERN = /[0-9a-zA-Z][0-9a-zA-Z_.-]*[0-9a-zA-Z\^]/
  PATTERN = /#{REF_PATTERN}\.{2,3}#{REF_PATTERN}/

  # In text references, the beginning and ending refs can only be SHAs
  # between 6 and 40 hex characters.
  STRICT_PATTERN = /\h{6,40}\.{2,3}\h{6,40}/
R
Robert Speicher 已提交
37

38 39 40 41 42 43 44 45 46
  def self.reference_prefix
    '@'
  end

  # Pattern used to extract commit range references from text
  #
  # This pattern supports cross-project references.
  def self.reference_pattern
    %r{
47 48 49
      #{link_reference_pattern} |
      (?:
        (?:#{Project.reference_pattern}#{reference_prefix})?
50
        (?<commit_range>#{STRICT_PATTERN})
51
      )
52 53 54
    }x
  end

55 56 57 58
  def self.link_reference_pattern
    super("compare", /(?<commit_range>#{PATTERN})/)
  end

R
Robert Speicher 已提交
59 60 61 62 63 64
  # Initialize a CommitRange
  #
  # range_string - The String commit range.
  # project      - An optional Project model.
  #
  # Raises ArgumentError if `range_string` does not match `PATTERN`.
65 66 67
  def initialize(range_string, project)
    @project = project

R
Robert Speicher 已提交
68 69
    range_string.strip!

D
Douwe Maan 已提交
70
    unless range_string =~ /\A#{PATTERN}\z/
R
Robert Speicher 已提交
71 72 73
      raise ArgumentError, "invalid CommitRange string format: #{range_string}"
    end

74
    @ref_from, @notation, @ref_to = range_string.split(/(\.{2,3})/, 2)
R
Robert Speicher 已提交
75

76
    if project.valid_repo?
77 78 79 80 81 82 83
      @commit_from = project.commit(@ref_from)
      @commit_to   = project.commit(@ref_to)
    end

    if valid_commits?
      @ref_from = Commit.truncate_sha(sha_from) if sha_from.start_with?(@ref_from)
      @ref_to   = Commit.truncate_sha(sha_to)   if sha_to.start_with?(@ref_to)
84
    end
R
Robert Speicher 已提交
85 86 87 88 89 90
  end

  def inspect
    %(#<#{self.class}:#{object_id} #{to_s}>)
  end

91
  def to_s
92
    sha_from + notation + sha_to
R
Robert Speicher 已提交
93 94
  end

95 96
  alias_method :id, :to_s

97
  def to_reference(from_project = nil)
98 99 100 101 102 103 104 105
    if cross_project_reference?(from_project)
      reference = project.to_reference + self.class.reference_prefix + self.id
    else
      self.id
    end
  end

  def reference_link_text(from_project = nil)
106
    reference = ref_from + notation + ref_to
107

108
    if cross_project_reference?(from_project)
109
      reference = project.to_reference + self.class.reference_prefix + reference
110
    end
111 112

    reference
113 114
  end

R
Robert Speicher 已提交
115 116
  # Returns a String for use in a link's title attribute
  def reference_title
117
    "Commits #{sha_start} through #{sha_to}"
R
Robert Speicher 已提交
118 119 120 121 122 123
  end

  # Return a Hash of parameters for passing to a URL helper
  #
  # See `namespace_project_compare_url`
  def to_param
124
    { from: sha_start, to: sha_to }
R
Robert Speicher 已提交
125 126
  end

127
  def exclude_start?
128
    @notation == '..'
R
Robert Speicher 已提交
129 130 131 132
  end

  # Check if both the starting and ending commit IDs exist in a project's
  # repository
133 134
  def valid_commits?
    commit_start.present? && commit_end.present?
R
Robert Speicher 已提交
135 136 137 138 139 140
  end

  def persisted?
    true
  end

141 142 143 144
  def sha_from
    return nil unless @commit_from

    @commit_from.id
R
Robert Speicher 已提交
145
  end
R
Robert Speicher 已提交
146

147 148 149 150 151 152 153 154 155 156
  def sha_to
    return nil unless @commit_to

    @commit_to.id
  end

  def sha_start
    return nil unless sha_from

    exclude_start? ? sha_from + '^' : sha_from
R
Robert Speicher 已提交
157 158
  end

159 160
  def commit_start
    return nil unless sha_start
R
Robert Speicher 已提交
161

162 163 164 165 166
    if exclude_start?
      @commit_start ||= project.commit(sha_start)
    else
      commit_from
    end
R
Robert Speicher 已提交
167
  end
168 169 170

  alias_method :sha_end, :sha_to
  alias_method :commit_end, :commit_to
R
Robert Speicher 已提交
171
end