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
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
|
# frozen_string_literal: true
module Network
class Graph
attr_reader :days, :commits, :map, :notes, :repo
def self.max_count
@max_count ||= 650
end
def initialize(project, ref, commit, filter_ref)
@project = project
@ref = ref
@commit = commit
@filter_ref = filter_ref
@repo = project.repository
@commits = collect_commits
@days = index_commits
@notes = collect_notes
end
protected
def collect_notes
h = Hash.new(0)
@project
.notes
.where('noteable_type = ?', 'Commit')
.group('notes.commit_id')
.select('notes.commit_id, count(notes.id) as note_count')
.each do |item|
h[item.commit_id] = item.note_count.to_i
end
h
end
# Get commits from repository
#
def collect_commits
# https://gitlab.com/gitlab-org/gitlab-ce/issues/58013
Gitlab::GitalyClient.allow_n_plus_1_calls do
find_commits(count_to_display_commit_in_center).map do |commit|
# Decorate with app/model/network/commit.rb
Network::Commit.new(commit)
end
end
end
# Method is adding time and space on the
# list of commits. As well as returns date list
# correlated with time set on commits.
#
# @return [Array<TimeDate>] list of commit dates correlated with time on commits
def index_commits
days = []
@map = {}
@reserved = {}
@commits.each_with_index do |c, i|
c.time = i
days[i] = c.committed_date
@map[c.id] = c
@reserved[i] = []
end
commits_sort_by_ref.each do |commit|
place_chain(commit)
end
# find parent spaces for not overlap lines
@commits.each do |c|
c.parent_spaces.concat(find_free_parent_spaces(c))
end
days
end
# Skip count that the target commit is displayed in center.
def count_to_display_commit_in_center
offset = -1
skip = 0
while offset == -1
tmp_commits = find_commits(skip)
if tmp_commits.present?
index = tmp_commits.index do |c|
c.id == @commit.id
end
if index
# Find the target commit
offset = index + skip
else
skip += self.class.max_count
end
else
# Can't find the target commit in the repo.
offset = 0
end
end
if self.class.max_count / 2 < offset
# get max index that commit is displayed in the center.
offset - self.class.max_count / 2
else
0
end
end
def find_commits(skip = 0)
opts = {
max_count: self.class.max_count,
skip: skip,
order: :date
}
opts[:ref] = @commit.id if @filter_ref
Gitlab::Git::Commit.find_all(@repo.raw_repository, opts)
end
def commits_sort_by_ref
@commits.sort do |a, b|
if include_ref?(a)
-1
elsif include_ref?(b)
1
else
b.committed_date <=> a.committed_date
end
end
end
def include_ref?(commit)
commit.ref_names(@repo).include?(@ref)
end
def find_free_parent_spaces(commit)
spaces = []
commit.parents(@map).each do |parent|
range = commit.time..parent.time
space = if commit.space >= parent.space
find_free_parent_space(range, parent.space, -1, commit.space)
else
find_free_parent_space(range, commit.space, -1, parent.space)
end
mark_reserved(range, space)
spaces << space
end
spaces
end
def find_free_parent_space(range, space_base, space_step, space_default)
if overlap?(range, space_default)
find_free_space(range, space_step, space_base, space_default)
else
space_default
end
end
def overlap?(range, overlap_space)
range.each do |i|
if i != range.first &&
i != range.last &&
@commits[i].spaces.include?(overlap_space)
return true
end
end
false
end
# Add space mark on commit and its parents
#
# @param [::Commit] the commit object.
def place_chain(commit, parent_time = nil)
leaves = take_left_leaves(commit)
if leaves.empty?
return
end
time_range = leaves.first.time..leaves.last.time
space_base = get_space_base(leaves)
space = find_free_space(time_range, 2, space_base)
leaves.each do |l|
l.spaces << space
end
# and mark it as reserved
min_time =
if parent_time.nil?
leaves.first.time
else
parent_time + 1
end
max_time = leaves.last.time
leaves.last.parents(@map).each do |parent|
if max_time < parent.time
max_time = parent.time
end
end
mark_reserved(min_time..max_time, space)
# Visit branching chains
leaves.each do |l|
parents = l.parents(@map).select {|p| p.space.zero?}
parents.each do |p|
place_chain(p, l.time)
end
end
end
def get_space_base(leaves)
space_base = 1
parents = leaves.last.parents(@map)
if parents.present?
if parents.first.space > 0
space_base = parents.first.space
end
end
space_base
end
def mark_reserved(time_range, space)
time_range.each do |day|
@reserved[day].push(space)
end
end
def find_free_space(time_range, space_step, space_base = 1, space_default = nil)
space_default ||= space_base
reserved = []
time_range.each do |day|
reserved.push(*@reserved[day])
end
reserved.uniq!
space = space_default
while reserved.include?(space)
space += space_step
if space < space_base
space_step *= -1
space = space_base + space_step
end
end
space
end
# Takes most left subtree branch of commits
# which don't have space mark yet.
#
# @param [::Commit] the commit object.
#
# @return [Array<Network::Commit>] list of branch commits
def take_left_leaves(raw_commit)
commit = @map[raw_commit.id]
leaves = []
leaves.push(commit) if commit.space.zero?
loop do
return leaves if commit.parents(@map).count.zero?
commit = commit.parents(@map).first
return leaves unless commit.space.zero?
leaves.push(commit)
end
end
end
end
|