summaryrefslogtreecommitdiff
path: root/app/models/commit.rb
blob: dd1f980187852f2fc8131e57c2a33e93442b8297 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
class Commit
  include ActiveModel::Conversion
  include StaticModel
  extend ActiveModel::Naming
  include Mentionable

  attr_mentionable :safe_message

  # Safe amount of changes (files and lines) in one commit to render
  # Used to prevent 500 error on huge commits by suppressing diff
  #
  # User can force display of diff above this size
  DIFF_SAFE_FILES  = 100
  DIFF_SAFE_LINES  = 5000
  # Commits above this size will not be rendered in HTML
  DIFF_HARD_LIMIT_FILES = 500
  DIFF_HARD_LIMIT_LINES = 10000

  def self.decorate(commits)
    commits.map { |c| self.new(c) }
  end

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

  def self.diff_suppress?(diffs, line_count = nil)
    # optimize - check file count first
    return true if diffs.size > DIFF_SAFE_FILES

    line_count ||= Commit::diff_line_count(diffs)
    line_count > DIFF_SAFE_LINES
  end

  def self.diff_force_suppress?(diffs, line_count = nil)
    # optimize - check file count first
    return true if diffs.size > DIFF_HARD_LIMIT_FILES

    line_count ||= Commit::diff_line_count(diffs)
    line_count > DIFF_HARD_LIMIT_LINES
  end

  attr_accessor :raw

  def initialize(raw_commit)
    raise "Nil as raw commit passed" unless raw_commit

    @raw = raw_commit
  end

  def id
    @raw.id
  end

  def diff_line_count
    @diff_line_count ||= Commit::diff_line_count(self.diffs)
    @diff_line_count
  end

  def diff_suppress?
    Commit::diff_suppress?(self.diffs, diff_line_count)
  end

  def diff_force_suppress?
    Commit::diff_force_suppress?(self.diffs, diff_line_count)
  end

  # 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.
  # In case this first line is longer than 100 characters, it is cut off
  # after 80 characters and ellipses (`&hellp;`) are appended.
  def title
    title = safe_message

    return no_commit_message if title.blank?

    title_end = title.index(/\n/)
    if (!title_end && title.length > 100) || (title_end && title_end > 100)
      title[0..79] << "&hellip;".html_safe
    else
      title.split(/\n/, 2).first
    end
  end

  # Returns the commits description
  #
  # cut off, ellipses (`&hellp;`) are prepended to the commit message.
  def description
    description = safe_message

    title_end = description.index(/\n/)
    if (!title_end && description.length > 100) || (title_end && title_end > 100)
      "&hellip;".html_safe << description[80..-1]
    else
      description.split(/\n/, 2)[1].try(:chomp)
    end
  end

  # Regular expression that identifies commit message clauses that trigger issue closing.
  def issue_closing_regex
    @issue_closing_regex ||= Regexp.new(Gitlab.config.gitlab.issue_closing_pattern)
  end

  # Discover issues should be closed when this commit is pushed to a project's
  # default branch.
  def closes_issues project
    md = issue_closing_regex.match(safe_message)
    if md
      extractor = Gitlab::ReferenceExtractor.new
      extractor.analyze(md[0])
      extractor.issues_for(project)
    else
      []
    end
  end

  # Mentionable override.
  def gfm_reference
    "commit #{sha[0..5]}"
  end

  def method_missing(m, *args, &block)
    @raw.send(m, *args, &block)
  end

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

    super
  end
end