-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathgithub.rb
211 lines (176 loc) · 6.62 KB
/
github.rb
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
require File.expand_path('../../../../lib/redmine/scm/adapters/github_adapter', __FILE__)
class Repository::Github < Repository
def self.scm_adapter_class
Redmine::Scm::Adapters::GithubAdapter
end
def self.scm_name
'GitHub'
end
def supports_directory_revisions?
true
end
def supports_revision_graph?
true
end
def supports_annotate?
true
end
def repo_log_encoding
'UTF-8'
end
# Returns the readable identifier for the given git changeset
def self.format_changeset_identifier(changeset)
changeset.revision[0, 8]
end
def branches
scm.branches
end
# リモートリポジトリの最新状況を取得し、changesetsに反映する
def fetch_changesets(options = {})
opts = options.merge({
last_committed_date: extra_info&.send(:[], "last_committed_date"),
last_committed_id: extra_info&.send(:[], "last_committed_id"),
all: true
})
revisions = scm.revisions('', nil, nil, opts)
revisions_copy = revisions.clone # revisions will change
return if revisions.blank?
save_revisions!(revisions, revisions_copy)
merge_extra_info({
last_committed_date: (revisions.last || revisions_copy.last)&.time&.utc&.strftime("%FT%TZ"),
last_committed_id: (revisions.last || revisions_copy.last)&.scmid
}.compact.stringify_keys)
save(validate: false)
end
# revisionオブジェクトの配列を引数に受け取る
# changesetsテーブルに保存する(すでに保存済みのrevisionはスキップ)
def save_revisions!(revisions, revisions_copy)
limit = 100
offset = 0
while offset < revisions_copy.size
scmids = revisions_copy.slice(offset, limit).map(&:scmid)
# Subtract revisions that redmine already knows about
recent_revisions = changesets.where(scmid: scmids).pluck(:scmid)
revisions.reject!{|r| recent_revisions.include?(r.scmid)}
offset += limit
end
return if revisions.blank?
scm.get_filechanges_and_append_to(revisions)
transaction do
revisions.each do |rev|
# There is no search in the db for this revision, because above we ensured,
# that it's not in the db.
changeset = Changeset.create!(
repository: self,
revision: rev.identifier,
scmid: rev.scmid,
committer: rev.author,
committed_on: rev.time,
comments: rev.message,
)
unless changeset.new_record?
rev.paths.each { |change| changeset.create_change(change) }
end
end
revisions.each do |rev|
changeset = changesets.find_by(revision: rev.identifier)
changeset.parents = (rev.parents || []).map{|rp| find_changeset_by_name(rp) }.compact
changeset.save!
end
end
end
private :save_revisions!
# nameにコミットのSHA1ハッシュを受け取る
# nameにリビジョン名が一致する、もしくはscmidに先頭一致するchangeset一件を返す
def find_changeset_by_name(name)
if name.present?
changesets.find_by(revision: name.to_s) ||
changesets.where('scmid LIKE ?', "#{name}%").first
end
end
# pathにファイルパス、identifierにコミットのshaを受け取る
# scmから引数に該当するエントリを取得し配列で返す
# pathが空(ルート)でidentifierがdefault_branchの場合は、ファイルセットの参照時キャッシュを使用する
def scm_entries(path=nil, identifier=nil)
is_using_cache = using_root_fileset_cache?(path, identifier)
if is_using_cache
sha = scm.revision_to_sha(identifier)
changeset = find_changeset_by_name(sha)
end
# Load from cache
if changeset.present?
entries = GithubAdapterRootFileset.where(repository_id: self.id, changeset_id: changeset.id).map { |fileset|
latest_changeset = find_changeset_by_name(fileset.latest_commitid)
Redmine::Scm::Adapters::Entry.new(
name: fileset.path,
path: fileset.path,
kind: fileset.size.blank? ? 'dir': 'file',
size: fileset.size,
lastrev: Redmine::Scm::Adapters::Revision.new(
identifier: latest_changeset.identifier,
author: latest_changeset.committer,
time: latest_changeset.committed_on
),
)
}
end
# Not found in cache, get entries from SCM
if entries.blank?
entries = scm.entries(path, identifier, :report_last_commit => report_last_commit)
# Save as cache
if changeset.present?
GithubAdapterRootFileset.where(repository_id: self.id, revision: identifier).delete_all
entries.each do |entry|
GithubAdapterRootFileset.create!(
repository_id: self.id,
revision: identifier,
changeset_id: changeset.id,
path: entry.path,
size: entry.size,
latest_commitid: entry.lastrev.identifier
)
end
end
end
entries
end
# pathにファイルパス、revにコミットのshaもしくはブランチ名を受け取る
# rev時点のpath以下のファイルに該当するchengesetsを取得し配列で返す
# revがデフォルトブランチ以外の場合、未反映のrevisionを保存しchangesetsテーブルに保存する
def latest_changesets(path, rev, limit = 10)
revisions = scm.revisions(path, nil, rev, :limit => limit, :all => false)
return [] if revisions.nil? || revisions.empty?
if rev != default_branch
# Branch that is not default doesn't be synced automatically. so, save it here.
save_revisions!(revisions.dup, revisions.dup)
end
changesets.where(:scmid => revisions.map {|c| c.scmid}).to_a
end
# このリポジトリが削除された場合のクリーンアップ処理
# 紐づくGithubAdapterRootFilesetを削除する
def clear_changesets
super
GithubAdapterRootFileset.where(repository_id: self.id).delete_all
end
def default_branch
scm.default_branch
end
def properties(path, rev)
end
# scm_entries内でキャッシュを使用するかどうかの判定
# pathがルートで、identifierがデフォルトブランチ(またはデフォルトブランチのSHA1ハッシュ)の場合、true を返す
def using_root_fileset_cache?(path, identifier)
return false if path.present?
return false if identifier.blank?
example = GithubAdapterRootFileset.where(repository_id: self.id).first
if example.blank?
return identifier == default_branch
end
if example.revision == identifier
true
else
identifier == default_branch
end
end
private :using_root_fileset_cache?
end