summaryrefslogtreecommitdiff
path: root/app/models/commit.rb
blob: 32d942a9e47dca555afff7d38ad1e47e555acb5e (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
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
class Commit
  include ActiveModel::Conversion
  include StaticModel
  extend ActiveModel::Naming

  # Safe amount of files with diffs in one commit to render
  # Used to prevent 500 error on huge commits by suppressing diff
  #
  DIFF_SAFE_SIZE = 100

  attr_accessor :commit, :head, :refs

  delegate  :message, :authored_date, :committed_date, :parents, :sha,
            :date, :committer, :author, :diffs, :tree, :id,
            :to_patch, to: :commit

  class << self
    def find_or_first(repo, commit_id = nil, root_ref)
      commit = if commit_id
                 repo.commit(commit_id)
               else
                 repo.commits(root_ref).first
               end

      Commit.new(commit) if commit
    end

    def fresh_commits(repo, n = 10)
      commits = repo.heads.map do |h|
        repo.commits(h.name, n).map { |c| Commit.new(c, h) }
      end.flatten.uniq { |c| c.id }

      commits.sort! do |x, y|
        y.committed_date <=> x.committed_date
      end

      commits[0...n]
    end

    def commits_with_refs(repo, n = 20)
      commits = repo.branches.map { |ref| Commit.new(ref.commit, ref) }

      commits.sort! do |x, y|
        y.committed_date <=> x.committed_date
      end

      commits[0..n]
    end

    def commits_since(repo, date)
      commits = repo.heads.map do |h|
        repo.log(h.name, nil, since: date).each { |c| Commit.new(c, h) }
      end.flatten.uniq { |c| c.id }

      commits.sort! do |x, y|
        y.committed_date <=> x.committed_date
      end

      commits
    end

    def commits(repo, ref, path = nil, limit = nil, offset = nil)
      if path
        repo.log(ref, path, max_count: limit, skip: offset)
      elsif limit && offset
        repo.commits(ref, limit, offset)
      else
        repo.commits(ref)
      end.map{ |c| Commit.new(c) }
    end

    def commits_between(repo, from, to)
      repo.commits_between(from, to).map { |c| Commit.new(c) }
    end

    def compare(project, from, to)
      result = {
        commits: [],
        diffs: [],
        commit: nil,
        same: false
      }

      return result unless from && to

      first = project.repository.commit(to.try(:strip))
      last = project.repository.commit(from.try(:strip))

      if first && last
        result[:same] = (first.id == last.id)
        result[:commits] = project.repo.commits_between(last.id, first.id).map {|c| Commit.new(c)}
        result[:diffs] = project.repo.diff(last.id, first.id) rescue []
        result[:commit] = Commit.new(first)
      end

      result
    end
  end

  def initialize(raw_commit, head = nil)
    @commit = raw_commit
    @head = head
  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

  def author_email
    author.email
  end

  def author_name
    author.name
  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 committer_name
    committer.name
  end

  def committer_email
    committer.email
  end

  def prev_commit
    parents.try :first
  end

  def prev_commit_id
    prev_commit.try :id
  end

  def parents_count
    parents && parents.count || 0
  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
    # see Grit::Commit#show
    patch = to_patch

    # discard lines before the diff
    lines = patch.split("\n")
    while !lines.first.start_with?("diff --git") do
      lines.shift
    end
    lines.pop if lines.last =~ /^[\d.]+$/ # Git version
    lines.pop if lines.last == "-- "      # end of diff
    lines.join("\n")
  end
end