Read about our upcoming Code of Conduct on this issue

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

21
  attr_reader :commit_from, :notation, :commit_to
22
  attr_reader :ref_from, :ref_to
Robert Speicher's avatar
Robert Speicher committed
23

24
  # The Project model
Robert Speicher's avatar
Robert Speicher committed
25
26
  attr_accessor :project

27
  # The beginning and ending refs can be named or SHAs, and
28
  # the range notation can be double- or triple-dot.
29
30
31
32
  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
33
34
  # between 7 and 40 hex characters.
  STRICT_PATTERN = /\h{7,40}\.{2,3}\h{7,40}/
Robert Speicher's avatar
Robert Speicher committed
35

36
37
38
39
40
41
42
43
  def self.reference_prefix
    '@'
  end

  # Pattern used to extract commit range references from text
  #
  # This pattern supports cross-project references.
  def self.reference_pattern
44
    @reference_pattern ||= %r{
45
46
      (?:#{Project.reference_pattern}#{reference_prefix})?
      (?<commit_range>#{STRICT_PATTERN})
47
48
49
    }x
  end

50
  def self.link_reference_pattern
51
    @link_reference_pattern ||= super("compare", /(?<commit_range>#{PATTERN})/)
52
53
  end

Robert Speicher's avatar
Robert Speicher committed
54
55
56
  # Initialize a CommitRange
  #
  # range_string - The String commit range.
57
  # project      - The Project model.
Robert Speicher's avatar
Robert Speicher committed
58
59
  #
  # Raises ArgumentError if `range_string` does not match `PATTERN`.
60
61
62
  def initialize(range_string, project)
    @project = project

63
    range_string = range_string.strip
Robert Speicher's avatar
Robert Speicher committed
64

Douwe Maan's avatar
Douwe Maan committed
65
    unless range_string =~ /\A#{PATTERN}\z/
Robert Speicher's avatar
Robert Speicher committed
66
67
68
      raise ArgumentError, "invalid CommitRange string format: #{range_string}"
    end

69
    @ref_from, @notation, @ref_to = range_string.split(/(\.{2,3})/, 2)
Robert Speicher's avatar
Robert Speicher committed
70

71
    if project.valid_repo?
72
73
74
75
76
77
78
      @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)
79
    end
Robert Speicher's avatar
Robert Speicher committed
80
81
82
  end

  def inspect
83
    %(#<#{self.class}:#{object_id} #{self}>)
Robert Speicher's avatar
Robert Speicher committed
84
85
  end

86
  def to_s
87
    sha_from + notation + sha_to
Robert Speicher's avatar
Robert Speicher committed
88
89
  end

90
91
  alias_method :id, :to_s

92
  def to_reference(from_project = nil)
93
94
95
96
    project_reference = project.to_reference(from_project)

    if project_reference.present?
      project_reference + self.class.reference_prefix + self.id
97
98
99
100
101
102
    else
      self.id
    end
  end

  def reference_link_text(from_project = nil)
103
104
    project_reference = project.to_reference(from_project)
    reference         = ref_from + notation + ref_to
105

106
107
108
109
    if project_reference.present?
      project_reference + self.class.reference_prefix + reference
    else
      reference
110
111
112
    end
  end

Robert Speicher's avatar
Robert Speicher committed
113
114
115
116
  # Return a Hash of parameters for passing to a URL helper
  #
  # See `namespace_project_compare_url`
  def to_param
117
    { from: sha_start, to: sha_to }
Robert Speicher's avatar
Robert Speicher committed
118
119
  end

120
  def exclude_start?
121
    @notation == '..'
Robert Speicher's avatar
Robert Speicher committed
122
123
124
125
  end

  # Check if both the starting and ending commit IDs exist in a project's
  # repository
126
127
  def valid_commits?
    commit_start.present? && commit_end.present?
Robert Speicher's avatar
Robert Speicher committed
128
129
130
131
132
133
  end

  def persisted?
    true
  end

134
135
136
137
  def sha_from
    return nil unless @commit_from

    @commit_from.id
Robert Speicher's avatar
Robert Speicher committed
138
  end
Robert Speicher's avatar
Robert Speicher committed
139

140
141
142
143
144
145
146
147
148
149
  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
Robert Speicher's avatar
Robert Speicher committed
150
151
  end

152
153
  def commit_start
    return nil unless sha_start
Robert Speicher's avatar
Robert Speicher committed
154

155
156
157
158
159
    if exclude_start?
      @commit_start ||= project.commit(sha_start)
    else
      commit_from
    end
Robert Speicher's avatar
Robert Speicher committed
160
  end
161
162
163

  alias_method :sha_end, :sha_to
  alias_method :commit_end, :commit_to
Robert Speicher's avatar
Robert Speicher committed
164
end