Read about our upcoming Code of Conduct on this issue

This instance will be upgraded to Heptapod 0.28.1 on 2022-01-26 at 16:00 UTC+1 (a few minutes of down time)

commit.rb 4.05 KB
Newer Older
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
1
class Commit
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
2
  extend ActiveModel::Naming
3
4

  include ActiveModel::Conversion
5
  include Mentionable
6
  include Participable
7
8
  include Referable
  include StaticModel
9
10

  attr_mentionable :safe_message
11
  participant :author, :committer, :notes, :mentioned_users
Saito's avatar
Saito committed
12

13
14
  attr_accessor :project

15
  # Safe amount of changes (files and lines) in one commit to render
16
17
  # Used to prevent 500 error on huge commits by suppressing diff
  #
18
  # User can force display of diff above this size
19
20
  DIFF_SAFE_FILES  = 100 unless defined?(DIFF_SAFE_FILES)
  DIFF_SAFE_LINES  = 5000 unless defined?(DIFF_SAFE_LINES)
21

22
  # Commits above this size will not be rendered in HTML
23
24
  DIFF_HARD_LIMIT_FILES = 1000 unless defined?(DIFF_HARD_LIMIT_FILES)
  DIFF_HARD_LIMIT_LINES = 50000 unless defined?(DIFF_HARD_LIMIT_LINES)
25

26
  class << self
27
    def decorate(commits, project)
28
29
30
31
      commits.map do |commit|
        if commit.kind_of?(Commit)
          commit
        else
32
          self.new(commit, project)
33
34
        end
      end
35
    end
36

37
38
    # Calculate number of lines to render for diffs
    def diff_line_count(diffs)
39
      diffs.reduce(0) { |sum, d| sum + d.diff.lines.count }
40
    end
41

42
    # Truncate sha to 8 characters
43
    def truncate_sha(sha)
44
      sha[0..7]
45
    end
46
47
  end

48
  attr_accessor :raw
Nihad Abbasov's avatar
Nihad Abbasov committed
49

50
  def initialize(raw_commit, project)
51
52
    raise "Nil as raw commit passed" unless raw_commit

53
    @raw = raw_commit
54
    @project = project
55
  end
56

57
58
59
60
  def id
    @raw.id
  end

Robert Speicher's avatar
Robert Speicher committed
61
62
63
64
  def ==(other)
    (self.class === other) && (raw == other.raw)
  end

65
66
67
68
69
70
71
72
  def to_reference(from_project = nil)
    if cross_project_reference?(from_project)
      "#{project.to_reference}@#{id}"
    else
      id
    end
  end

73
74
75
76
77
  def diff_line_count
    @diff_line_count ||= Commit::diff_line_count(self.diffs)
    @diff_line_count
  end

78
79
80
81
82
83
84
85
86
87
88
89
  # Returns a string describing the commit for use in a link title
  #
  # Example
  #
  #   "Commit: Alex Denisov - Project git clone panel"
  def link_title
    "Commit: #{author_name} - #{title}"
  end

  # Returns the commits title.
  #
  # Usually, the commit title is the first line of the commit message.
90
91
  # In case this first line is longer than 100 characters, it is cut off
  # after 80 characters and ellipses (`&hellp;`) are appended.
92
93
94
95
96
  def title
    title = safe_message

    return no_commit_message if title.blank?

97
    title_end = title.index("\n")
98
    if (!title_end && title.length > 100) || (title_end && title_end > 100)
99
      title[0..79] << "…"
100
    else
101
      title.split("\n", 2).first
102
103
104
105
106
107
108
    end
  end

  # Returns the commits description
  #
  # cut off, ellipses (`&hellp;`) are prepended to the commit message.
  def description
109
    title_end = safe_message.index("\n")
110
111
    @description ||=
      if (!title_end && safe_message.length > 100) || (title_end && title_end > 100)
112
        "…" << safe_message[80..-1]
113
114
115
      else
        safe_message.split("\n", 2)[1].try(:chomp)
      end
116
  end
117

118
119
  def description?
    description.present?
120
121
  end

122
  def hook_attrs
Kirill Zaitsev's avatar
Kirill Zaitsev committed
123
124
125
126
127
128
129
130
131
132
133
134
135
136
    path_with_namespace = project.path_with_namespace

    {
      id: id,
      message: safe_message,
      timestamp: committed_date.xmlschema,
      url: "#{Gitlab.config.gitlab.url}/#{path_with_namespace}/commit/#{id}",
      author: {
        name: author_name,
        email: author_email
      }
    }
  end

137
138
  # Discover issues should be closed when this commit is pushed to a project's
  # default branch.
139
  def closes_issues(current_user = self.committer)
Douwe Maan's avatar
Douwe Maan committed
140
    Gitlab::ClosingIssueExtractor.new(project, current_user).closed_by_message(safe_message)
141
142
143
144
  end

  # Mentionable override.
  def gfm_reference
145
    "commit #{to_reference}"
146
147
  end

Douwe Maan's avatar
Douwe Maan committed
148
149
150
151
152
153
154
155
  def author
    User.find_for_commit(author_email, author_name)
  end

  def committer
    User.find_for_commit(committer_email, committer_name)
  end

156
  def notes
157
158
159
    project.notes.for_commit_id(self.id)
  end

160
161
  def method_missing(m, *args, &block)
    @raw.send(m, *args, &block)
162
  end
163
164
165
166
167
168

  def respond_to?(method)
    return true if @raw.respond_to?(method)

    super
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
169

170
171
172
173
174
  # Truncate sha to 8 characters
  def short_id
    @raw.short_id(7)
  end

Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
175
  def parents
176
    @parents ||= Commit.decorate(super, project)
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
177
  end
Dmitriy Zaporozhets's avatar
Dmitriy Zaporozhets committed
178
end