gitlab_git 6.3.0 → 7.0.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/VERSION +1 -1
- data/lib/gitlab_git.rb +2 -7
- data/lib/gitlab_git/blob_snippet.rb +1 -1
- data/lib/gitlab_git/commit.rb +46 -27
- data/lib/gitlab_git/commit_stats.rb +26 -0
- data/lib/gitlab_git/diff.rb +26 -14
- data/lib/gitlab_git/git_stats.rb +53 -8
- data/lib/gitlab_git/repository.rb +647 -105
- data/lib/gitlab_git/stats.rb +21 -16
- metadata +17 -16
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: badf0b7ecbe40208622e3e555055ab825f98ad6e
|
4
|
+
data.tar.gz: f85a1a6a30411397118fe910b8f0479799ddfcf2
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4f12a58cdb7e060e72096836c8cff6ab7ca1f56870c69b09cb9af578198e27c1a39234e3c21cff566a84569dd8d02e93a89e3bb6b475443f83ab6c007bc77e49
|
7
|
+
data.tar.gz: 5cc43362edf2f18cb84f6090a46e380153d5ac5b44c94e440b556c9b55ac0a0c39d8c84f11f734840d0a6559ccbab22ccdf88e17d315f627201014ad9a7489a8
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
|
1
|
+
7.0.0.rc1
|
data/lib/gitlab_git.rb
CHANGED
@@ -1,18 +1,12 @@
|
|
1
1
|
# Libraries
|
2
2
|
require 'ostruct'
|
3
3
|
require 'fileutils'
|
4
|
-
require 'grit'
|
5
4
|
require 'linguist'
|
6
5
|
require 'active_support/core_ext/hash/keys'
|
7
6
|
require 'active_support/core_ext/object/try'
|
8
|
-
require 'grit'
|
9
|
-
require 'grit_ext'
|
10
7
|
require 'rugged'
|
11
8
|
require "charlock_holmes"
|
12
|
-
|
13
|
-
Grit::Blob.class_eval do
|
14
|
-
include Linguist::BlobHelper
|
15
|
-
end
|
9
|
+
require "zip"
|
16
10
|
|
17
11
|
# Gitlab::Git
|
18
12
|
require_relative "gitlab_git/popen"
|
@@ -20,6 +14,7 @@ require_relative 'gitlab_git/encoding_helper'
|
|
20
14
|
require_relative "gitlab_git/blame"
|
21
15
|
require_relative "gitlab_git/blob"
|
22
16
|
require_relative "gitlab_git/commit"
|
17
|
+
require_relative "gitlab_git/commit_stats"
|
23
18
|
require_relative "gitlab_git/compare"
|
24
19
|
require_relative "gitlab_git/diff"
|
25
20
|
require_relative "gitlab_git/repository"
|
data/lib/gitlab_git/commit.rb
CHANGED
@@ -1,6 +1,4 @@
|
|
1
|
-
# Gitlab::Git::Commit is a wrapper around native
|
2
|
-
# We dont want to use grit objects inside app/
|
3
|
-
# It helps us easily migrate to rugged in future
|
1
|
+
# Gitlab::Git::Commit is a wrapper around native Rugged::Commit object
|
4
2
|
module Gitlab
|
5
3
|
module Git
|
6
4
|
class Commit
|
@@ -13,6 +11,18 @@ module Gitlab
|
|
13
11
|
]
|
14
12
|
attr_accessor *SERIALIZE_KEYS
|
15
13
|
|
14
|
+
def ==(other)
|
15
|
+
return false unless other.is_a?(Gitlab::Git::Commit)
|
16
|
+
|
17
|
+
methods = [:message, :parent_ids, :authored_date, :author_name,
|
18
|
+
:author_email, :committed_date, :committer_name,
|
19
|
+
:committer_email]
|
20
|
+
|
21
|
+
methods.all? do |method|
|
22
|
+
send(method) == other.send(method)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
16
26
|
class << self
|
17
27
|
# Get commits collection
|
18
28
|
#
|
@@ -39,9 +49,17 @@ module Gitlab
|
|
39
49
|
#
|
40
50
|
# Commit.find(repo, 'master')
|
41
51
|
#
|
42
|
-
def find(repo, commit_id =
|
43
|
-
|
52
|
+
def find(repo, commit_id = "HEAD")
|
53
|
+
return decorate(commit_id) if commit_id.is_a?(Rugged::Commit)
|
54
|
+
|
55
|
+
obj = repo.rugged.rev_parse(commit_id)
|
56
|
+
commit = case obj
|
57
|
+
when Rugged::Tag::Annotation then obj.target
|
58
|
+
when Rugged::Commit then obj
|
59
|
+
end
|
44
60
|
decorate(commit) if commit
|
61
|
+
rescue Rugged::ReferenceError, Rugged::ObjectError
|
62
|
+
nil
|
45
63
|
end
|
46
64
|
|
47
65
|
# Get last commit for HEAD
|
@@ -50,7 +68,7 @@ module Gitlab
|
|
50
68
|
# Commit.last(repo)
|
51
69
|
#
|
52
70
|
def last(repo)
|
53
|
-
find(repo
|
71
|
+
find(repo)
|
54
72
|
end
|
55
73
|
|
56
74
|
# Get last commit for specified path and ref
|
@@ -78,6 +96,8 @@ module Gitlab
|
|
78
96
|
repo.commits_between(base, head).map do |commit|
|
79
97
|
decorate(commit)
|
80
98
|
end
|
99
|
+
rescue Rugged::ReferenceError
|
100
|
+
[]
|
81
101
|
end
|
82
102
|
|
83
103
|
# Delegate Repository#find_commits
|
@@ -98,7 +118,7 @@ module Gitlab
|
|
98
118
|
elsif raw_commit.is_a?(Rugged::Commit)
|
99
119
|
init_from_rugged(raw_commit)
|
100
120
|
else
|
101
|
-
|
121
|
+
raise "Invalid raw commit type: #{raw_commit.class}"
|
102
122
|
end
|
103
123
|
|
104
124
|
@head = head
|
@@ -133,7 +153,6 @@ module Gitlab
|
|
133
153
|
#
|
134
154
|
# Cuts out the header and stats from #to_patch and returns only the diff.
|
135
155
|
def to_diff
|
136
|
-
# see Grit::Commit#show
|
137
156
|
patch = to_patch
|
138
157
|
|
139
158
|
# discard lines before the diff
|
@@ -146,6 +165,17 @@ module Gitlab
|
|
146
165
|
lines.join("\n")
|
147
166
|
end
|
148
167
|
|
168
|
+
# Returns a diff object for the changes from this commit's first parent.
|
169
|
+
# If there is no parent, then the diff is between this commit and an
|
170
|
+
# empty repo.
|
171
|
+
def diff_from_parent
|
172
|
+
if raw_commit.parents.empty?
|
173
|
+
raw_commit.diff(reverse: true)
|
174
|
+
else
|
175
|
+
raw_commit.parents[0].diff(raw_commit)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
|
149
179
|
def has_zero_stats?
|
150
180
|
stats.total.zero?
|
151
181
|
rescue
|
@@ -167,11 +197,11 @@ module Gitlab
|
|
167
197
|
end
|
168
198
|
|
169
199
|
def diffs
|
170
|
-
|
200
|
+
diff_from_parent.map { |diff| Gitlab::Git::Diff.new(diff) }
|
171
201
|
end
|
172
202
|
|
173
203
|
def parents
|
174
|
-
raw_commit.parents
|
204
|
+
raw_commit.parents.map { |c| Gitlab::Git::Commit.new(c) }
|
175
205
|
end
|
176
206
|
|
177
207
|
def tree
|
@@ -179,14 +209,14 @@ module Gitlab
|
|
179
209
|
end
|
180
210
|
|
181
211
|
def stats
|
182
|
-
|
212
|
+
Gitlab::Git::CommitStats.new(self)
|
183
213
|
end
|
184
214
|
|
185
215
|
def to_patch
|
186
|
-
raw_commit.
|
216
|
+
raw_commit.to_mbox
|
187
217
|
end
|
188
218
|
|
189
|
-
# Get
|
219
|
+
# Get a collection of Rugged::Reference objects for this commit.
|
190
220
|
#
|
191
221
|
# Ex.
|
192
222
|
# commit.ref(repo)
|
@@ -201,24 +231,13 @@ module Gitlab
|
|
201
231
|
# commit.ref_names(repo)
|
202
232
|
#
|
203
233
|
def ref_names(repo)
|
204
|
-
refs(repo).map
|
234
|
+
refs(repo).map do |ref|
|
235
|
+
ref.name.sub(%r{^refs/(heads|remotes|tags)/}, "")
|
236
|
+
end
|
205
237
|
end
|
206
238
|
|
207
239
|
private
|
208
240
|
|
209
|
-
def init_from_grit(grit)
|
210
|
-
@raw_commit = grit
|
211
|
-
@id = grit.id
|
212
|
-
@message = grit.message
|
213
|
-
@authored_date = grit.authored_date
|
214
|
-
@committed_date = grit.committed_date
|
215
|
-
@author_name = grit.author.name
|
216
|
-
@author_email = grit.author.email
|
217
|
-
@committer_name = grit.committer.name
|
218
|
-
@committer_email = grit.committer.email
|
219
|
-
@parent_ids = grit.parents.map(&:id)
|
220
|
-
end
|
221
|
-
|
222
241
|
def init_from_hash(hash)
|
223
242
|
raw_commit = hash.symbolize_keys
|
224
243
|
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# Gitlab::Git::CommitStats counts the additions, deletions, and total changes
|
2
|
+
# in a commit.
|
3
|
+
module Gitlab
|
4
|
+
module Git
|
5
|
+
class CommitStats
|
6
|
+
attr_reader :id, :additions, :deletions, :total
|
7
|
+
|
8
|
+
# Instantiate a CommitStats object
|
9
|
+
def initialize(commit)
|
10
|
+
@id = commit.id
|
11
|
+
@additions = 0
|
12
|
+
@deletions = 0
|
13
|
+
@total = 0
|
14
|
+
|
15
|
+
diff = commit.diff_from_parent
|
16
|
+
|
17
|
+
diff.each_patch do |p|
|
18
|
+
# TODO: Use the new Rugged convenience methods when they're released
|
19
|
+
@additions += p.stat[0]
|
20
|
+
@deletions += p.stat[1]
|
21
|
+
@total += p.changes
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
data/lib/gitlab_git/diff.rb
CHANGED
@@ -1,10 +1,9 @@
|
|
1
|
-
# Gitlab::Git::Diff is a wrapper around native
|
2
|
-
# We dont want to use grit objects inside app/
|
3
|
-
# It helps us easily migrate to rugged in future
|
1
|
+
# Gitlab::Git::Diff is a wrapper around native Rugged::Diff object
|
4
2
|
module Gitlab
|
5
3
|
module Git
|
6
4
|
class Diff
|
7
5
|
class TimeoutError < StandardError; end
|
6
|
+
include EncodingHelper
|
8
7
|
|
9
8
|
attr_accessor :raw_diff
|
10
9
|
|
@@ -21,11 +20,7 @@ module Gitlab
|
|
21
20
|
# From the git documentation: "git diff A...B" is equivalent to "git diff $(git-merge-base A B) B"
|
22
21
|
common_commit = repo.merge_base_commit(head, base)
|
23
22
|
|
24
|
-
repo.diff(common_commit, head, *paths)
|
25
|
-
Gitlab::Git::Diff.new(diff)
|
26
|
-
end
|
27
|
-
rescue Grit::Git::GitTimeout
|
28
|
-
raise TimeoutError.new("Diff.between exited with timeout")
|
23
|
+
repo.diff(common_commit, head, *paths)
|
29
24
|
end
|
30
25
|
end
|
31
26
|
|
@@ -34,8 +29,10 @@ module Gitlab
|
|
34
29
|
|
35
30
|
if raw_diff.is_a?(Hash)
|
36
31
|
init_from_hash(raw_diff)
|
32
|
+
elsif raw_diff.is_a?(Rugged::Patch)
|
33
|
+
init_from_rugged(raw_diff)
|
37
34
|
else
|
38
|
-
|
35
|
+
raise "Invalid raw diff type: #{raw_diff.class}"
|
39
36
|
end
|
40
37
|
end
|
41
38
|
|
@@ -57,12 +54,19 @@ module Gitlab
|
|
57
54
|
|
58
55
|
private
|
59
56
|
|
60
|
-
def
|
61
|
-
@raw_diff =
|
57
|
+
def init_from_rugged(rugged)
|
58
|
+
@raw_diff = rugged
|
62
59
|
|
63
|
-
|
64
|
-
|
65
|
-
|
60
|
+
@diff = encode!(strip_diff_headers(rugged.to_s))
|
61
|
+
|
62
|
+
d = rugged.delta
|
63
|
+
@new_path = encode!(d.new_file[:path])
|
64
|
+
@old_path = encode!(d.old_file[:path])
|
65
|
+
@a_mode = d.old_file[:mode].to_s(8)
|
66
|
+
@b_mode = d.new_file[:mode].to_s(8)
|
67
|
+
@new_file = d.added?
|
68
|
+
@renamed_file = d.renamed?
|
69
|
+
@deleted_file = d.deleted?
|
66
70
|
end
|
67
71
|
|
68
72
|
def init_from_hash(hash)
|
@@ -72,6 +76,14 @@ module Gitlab
|
|
72
76
|
send(:"#{key}=", raw_diff[key.to_sym])
|
73
77
|
end
|
74
78
|
end
|
79
|
+
|
80
|
+
# Strip out the information at the beginning of the patch's text to match
|
81
|
+
# Grit's output
|
82
|
+
def strip_diff_headers(diff_text)
|
83
|
+
lines = diff_text.split("\n")
|
84
|
+
lines.shift until lines.empty? || lines.first.match("^(---|Binary)")
|
85
|
+
lines.join("\n")
|
86
|
+
end
|
75
87
|
end
|
76
88
|
end
|
77
89
|
end
|
data/lib/gitlab_git/git_stats.rb
CHANGED
@@ -9,22 +9,67 @@ module Gitlab
|
|
9
9
|
@repo, @ref, @timeout = repo, ref, timeout
|
10
10
|
end
|
11
11
|
|
12
|
+
# Returns a string of log information; equivalent to running 'git log`
|
13
|
+
# with these options:
|
14
|
+
#
|
15
|
+
# -6000
|
16
|
+
# --format=%aN%x0a%aE%x0a%cd
|
17
|
+
# --date=short
|
18
|
+
# --shortstat
|
19
|
+
# --no-merges
|
20
|
+
# --diff-filter=ACDM
|
12
21
|
def log
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
22
|
+
commit_strings = []
|
23
|
+
walker = Rugged::Walker.new(repo.rugged)
|
24
|
+
walker.push(repo.lookup(ref))
|
25
|
+
walker.each(limit: 6000) do |commit|
|
26
|
+
# Skip merge commits
|
27
|
+
next if commit.parents.length > 1
|
28
|
+
|
29
|
+
g_commit = Gitlab::Git::Commit.new(commit)
|
30
|
+
|
31
|
+
commit_strings << [
|
32
|
+
g_commit.author_name,
|
33
|
+
g_commit.author_email,
|
34
|
+
g_commit.committed_date.strftime("%Y-%m-%d"),
|
35
|
+
"",
|
36
|
+
status_string(g_commit)
|
37
|
+
].join("\n")
|
18
38
|
end
|
19
39
|
|
20
|
-
|
21
|
-
rescue Grit::Git::GitTimeout
|
22
|
-
nil
|
40
|
+
commit_strings.join("\n")
|
23
41
|
end
|
24
42
|
|
25
43
|
def parsed_log
|
26
44
|
LogParser.parse_log(log)
|
27
45
|
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
# Returns a string describing the files changed, additions and deletions
|
50
|
+
# for +commit+
|
51
|
+
def status_string(commit)
|
52
|
+
stats = commit.stats
|
53
|
+
|
54
|
+
status = "#{num_files_changed(commit)} files changed"
|
55
|
+
status << ", #{stats.additions} insertions" if stats.additions > 0
|
56
|
+
status << ", #{stats.deletions} deletions" if stats.deletions > 0
|
57
|
+
status
|
58
|
+
end
|
59
|
+
|
60
|
+
# Returns the number of files that were either added, copied, deleted, or
|
61
|
+
# modified by +commit+
|
62
|
+
def num_files_changed(commit)
|
63
|
+
count = 0
|
64
|
+
|
65
|
+
diff = commit.diff_from_parent
|
66
|
+
diff.find_similar!
|
67
|
+
diff.each_delta do |d|
|
68
|
+
count += 1 if d.added? || d.copied? || d.deleted? || d.modified?
|
69
|
+
end
|
70
|
+
|
71
|
+
count
|
72
|
+
end
|
28
73
|
end
|
29
74
|
end
|
30
75
|
end
|
@@ -1,8 +1,7 @@
|
|
1
|
-
# Gitlab::Git::Repository is a wrapper around native
|
2
|
-
# We dont want to use grit objects inside app/
|
3
|
-
# It helps us easily migrate to rugged in future
|
1
|
+
# Gitlab::Git::Repository is a wrapper around native Rugged::Repository object
|
4
2
|
require_relative 'encoding_helper'
|
5
3
|
require 'tempfile'
|
4
|
+
require "rubygems/package"
|
6
5
|
|
7
6
|
module Gitlab
|
8
7
|
module Git
|
@@ -20,9 +19,6 @@ module Gitlab
|
|
20
19
|
# Directory name of repo
|
21
20
|
attr_reader :name
|
22
21
|
|
23
|
-
# Grit repo object
|
24
|
-
attr_reader :grit
|
25
|
-
|
26
22
|
# Rugged repo object
|
27
23
|
attr_reader :rugged
|
28
24
|
|
@@ -32,15 +28,9 @@ module Gitlab
|
|
32
28
|
@root_ref = discover_default_branch
|
33
29
|
end
|
34
30
|
|
35
|
-
def grit
|
36
|
-
@grit ||= Grit::Repo.new(path)
|
37
|
-
rescue Grit::NoSuchPathError
|
38
|
-
raise NoRepository.new('no repository for such path')
|
39
|
-
end
|
40
|
-
|
41
31
|
# Alias to old method for compatibility
|
42
32
|
def raw
|
43
|
-
|
33
|
+
rugged
|
44
34
|
end
|
45
35
|
|
46
36
|
def rugged
|
@@ -69,18 +59,8 @@ module Gitlab
|
|
69
59
|
|
70
60
|
# Returns an Array of Tags
|
71
61
|
def tags
|
72
|
-
rugged.refs.
|
73
|
-
ref.name
|
74
|
-
end.map do |rugged_ref|
|
75
|
-
target = rugged_ref.target
|
76
|
-
message = nil
|
77
|
-
if rugged_ref.target.is_a?(Rugged::Tag::Annotation) &&
|
78
|
-
rugged_ref.target.target.is_a?(Rugged::Commit)
|
79
|
-
unless rugged_ref.target.target.message == rugged_ref.target.message
|
80
|
-
message = rugged_ref.target.message.chomp
|
81
|
-
end
|
82
|
-
end
|
83
|
-
Tag.new(rugged_ref.name, target, message)
|
62
|
+
rugged.references.each("refs/tags/*").map do |ref|
|
63
|
+
Tag.new(ref.name, ref.target, ref.target.message.chomp)
|
84
64
|
end.sort_by(&:name)
|
85
65
|
end
|
86
66
|
|
@@ -91,7 +71,9 @@ module Gitlab
|
|
91
71
|
|
92
72
|
# Deprecated. Will be removed in 5.2
|
93
73
|
def heads
|
94
|
-
|
74
|
+
rugged.references.each("refs/heads/*").map do |head|
|
75
|
+
Gitlab::Git::Ref.new(head.name, head.target)
|
76
|
+
end.sort_by(&:name)
|
95
77
|
end
|
96
78
|
|
97
79
|
def has_commits?
|
@@ -137,12 +119,11 @@ module Gitlab
|
|
137
119
|
# app_root/tmp/repositories/project_name/project_name-commit-id.tag.gz
|
138
120
|
#
|
139
121
|
def archive_repo(ref, storage_path, format = "tar.gz")
|
140
|
-
ref
|
122
|
+
ref ||= root_ref
|
141
123
|
commit = Gitlab::Git::Commit.find(self, ref)
|
142
124
|
return nil unless commit
|
143
125
|
|
144
126
|
extension = nil
|
145
|
-
git_archive_format = nil
|
146
127
|
pipe_cmd = nil
|
147
128
|
|
148
129
|
case format
|
@@ -154,12 +135,10 @@ module Gitlab
|
|
154
135
|
pipe_cmd = %W(cat)
|
155
136
|
when "zip"
|
156
137
|
extension = ".zip"
|
157
|
-
git_archive_format = "zip"
|
158
138
|
pipe_cmd = %W(cat)
|
159
139
|
else
|
160
140
|
# everything else should fall back to tar.gz
|
161
141
|
extension = ".tar.gz"
|
162
|
-
git_archive_format = nil
|
163
142
|
pipe_cmd = %W(gzip -n)
|
164
143
|
end
|
165
144
|
|
@@ -167,23 +146,8 @@ module Gitlab
|
|
167
146
|
file_name = self.name.gsub("\.git", "") + "-" + commit.id.to_s + extension
|
168
147
|
file_path = File.join(storage_path, self.name, file_name)
|
169
148
|
|
170
|
-
# Put files into a directory before archiving
|
171
|
-
prefix = File.basename(self.name) + "/"
|
172
|
-
|
173
149
|
# Create file if not exists
|
174
|
-
unless File.
|
175
|
-
# create archive in temp file
|
176
|
-
tmp_file = Tempfile.new('gitlab-archive-repo', storage_path)
|
177
|
-
self.grit.archive_to_file(ref, prefix, tmp_file.path, git_archive_format, pipe_cmd)
|
178
|
-
|
179
|
-
# move temp file to persisted location
|
180
|
-
FileUtils.mkdir_p File.dirname(file_path)
|
181
|
-
FileUtils.move(tmp_file.path, file_path)
|
182
|
-
|
183
|
-
# delte temp file
|
184
|
-
tmp_file.close
|
185
|
-
tmp_file.unlink
|
186
|
-
end
|
150
|
+
create_archive(ref, pipe_cmd, file_path) unless File.exist?(file_path)
|
187
151
|
|
188
152
|
file_path
|
189
153
|
end
|
@@ -194,19 +158,24 @@ module Gitlab
|
|
194
158
|
(size.to_f / 1024).round(2)
|
195
159
|
end
|
196
160
|
|
161
|
+
# Returns an array of BlobSnippets for files at the specified +ref+ that
|
162
|
+
# contain the +query+ string.
|
197
163
|
def search_files(query, ref = nil)
|
198
|
-
|
199
|
-
|
200
|
-
end
|
164
|
+
greps = []
|
165
|
+
ref ||= root_ref
|
201
166
|
|
202
|
-
|
167
|
+
populated_index(ref).each do |entry|
|
168
|
+
# Discard submodules
|
169
|
+
next if submodule?(entry)
|
203
170
|
|
204
|
-
|
205
|
-
|
171
|
+
content = rugged.lookup(entry[:oid]).content
|
172
|
+
greps += build_greps(content, query, ref, entry[:path])
|
206
173
|
end
|
174
|
+
|
175
|
+
greps
|
207
176
|
end
|
208
177
|
|
209
|
-
#
|
178
|
+
# Use the Rugged Walker API to build an array of commits.
|
210
179
|
#
|
211
180
|
# Usage.
|
212
181
|
# repo.log(
|
@@ -226,28 +195,61 @@ module Gitlab
|
|
226
195
|
}
|
227
196
|
|
228
197
|
options = default_options.merge(options)
|
198
|
+
options[:limit] ||= 0
|
199
|
+
options[:offset] ||= 0
|
200
|
+
actual_ref = options[:ref] || root_ref
|
201
|
+
sha = sha_from_ref(actual_ref)
|
202
|
+
|
203
|
+
build_log(sha, options)
|
204
|
+
rescue Rugged::OdbError, Rugged::InvalidError, Rugged::ReferenceError
|
205
|
+
# Return an empty array if the ref wasn't found
|
206
|
+
[]
|
207
|
+
end
|
229
208
|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
)
|
209
|
+
def sha_from_ref(ref)
|
210
|
+
sha = rugged.rev_parse_oid(ref)
|
211
|
+
object = rugged.lookup(sha)
|
212
|
+
|
213
|
+
if object.kind_of?(Rugged::Commit)
|
214
|
+
sha
|
215
|
+
elsif object.respond_to?(:target)
|
216
|
+
sha_from_ref(object.target.oid)
|
217
|
+
end
|
237
218
|
end
|
238
219
|
|
239
|
-
#
|
220
|
+
# Return a collection of Rugged::Commits between the two SHA arguments.
|
240
221
|
#
|
241
222
|
def commits_between(from, to)
|
242
|
-
|
223
|
+
walker = Rugged::Walker.new(rugged)
|
224
|
+
walker.push(to)
|
225
|
+
walker.hide(from)
|
226
|
+
commits = walker.to_a
|
227
|
+
walker.reset
|
228
|
+
|
229
|
+
commits.reverse
|
243
230
|
end
|
244
231
|
|
232
|
+
# Returns the SHA of the most recent common ancestor of +from+ and +to+
|
245
233
|
def merge_base_commit(from, to)
|
246
|
-
|
234
|
+
rugged.merge_base(from, to)
|
247
235
|
end
|
248
236
|
|
237
|
+
# Return an array of Diff objects that represent the diff
|
238
|
+
# between +from+ and +to+.
|
249
239
|
def diff(from, to, *paths)
|
250
|
-
|
240
|
+
rugged.diff(from, to, paths: paths).patches.map do |p|
|
241
|
+
Gitlab::Git::Diff.new(p)
|
242
|
+
end
|
243
|
+
end
|
244
|
+
|
245
|
+
# Return the diff between +from+ and +to+ in a single patch string.
|
246
|
+
def diff_text(from, to, *paths)
|
247
|
+
# NOTE: It would be simpler to use the Rugged::Diff#patch method, but
|
248
|
+
# that formats the diff text differently than Rugged::Patch#to_s for
|
249
|
+
# changes to binary files.
|
250
|
+
rugged.diff(from, to, paths: paths).patches.map do |p|
|
251
|
+
p.to_s
|
252
|
+
end.join("\n")
|
251
253
|
end
|
252
254
|
|
253
255
|
# Returns commits collection
|
@@ -272,81 +274,92 @@ module Gitlab
|
|
272
274
|
|
273
275
|
allowed_options = [:ref, :max_count, :skip, :contains, :order]
|
274
276
|
|
275
|
-
actual_options.keep_if do |key
|
277
|
+
actual_options.keep_if do |key|
|
276
278
|
allowed_options.include?(key)
|
277
279
|
end
|
278
280
|
|
279
|
-
default_options = {
|
280
|
-
|
281
|
+
default_options = { skip: 0 }
|
281
282
|
actual_options = default_options.merge(actual_options)
|
282
283
|
|
283
|
-
|
284
|
+
walker = Rugged::Walker.new(rugged)
|
284
285
|
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
286
|
+
if actual_options[:ref]
|
287
|
+
walker.push(rugged.rev_parse_oid(actual_options[:ref]))
|
288
|
+
elsif actual_options[:contains]
|
289
|
+
branches_contains(actual_options[:contains]).each do |branch|
|
290
|
+
walker.push(branch.target_id)
|
291
|
+
end
|
292
|
+
else
|
293
|
+
rugged.references.each("refs/heads/*") do |ref|
|
294
|
+
walker.push(ref.target_id)
|
295
|
+
end
|
290
296
|
end
|
291
297
|
|
292
|
-
|
293
|
-
|
294
|
-
containing_commit = actual_options.delete(:contains)
|
298
|
+
walker.sorting(Rugged::SORT_TOPO) if actual_options[:order] == :topo
|
295
299
|
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
else
|
303
|
-
actual_options[:all] = true
|
300
|
+
commits = []
|
301
|
+
offset = actual_options[:skip]
|
302
|
+
limit = actual_options[:max_count]
|
303
|
+
walker.each(offset: offset, limit: limit) do |commit|
|
304
|
+
gitlab_commit = Gitlab::Git::Commit.decorate(commit)
|
305
|
+
commits.push(gitlab_commit)
|
304
306
|
end
|
305
307
|
|
306
|
-
|
308
|
+
walker.reset
|
307
309
|
|
308
|
-
|
309
|
-
|
310
|
-
end
|
311
|
-
rescue Grit::GitRuby::Repository::NoSuchShaFound
|
310
|
+
commits
|
311
|
+
rescue Rugged::OdbError
|
312
312
|
[]
|
313
313
|
end
|
314
314
|
|
315
|
-
# Returns branch names collection that contains the special commit(SHA1
|
315
|
+
# Returns branch names collection that contains the special commit(SHA1
|
316
|
+
# or name)
|
316
317
|
#
|
317
318
|
# Ex.
|
318
319
|
# repo.branch_names_contains('master')
|
319
320
|
#
|
320
321
|
def branch_names_contains(commit)
|
321
|
-
|
322
|
+
branches_contains(commit).map { |c| c.target_id }
|
323
|
+
end
|
322
324
|
|
323
|
-
|
324
|
-
|
325
|
+
# Returns branch collection that contains the special commit(SHA1 or name)
|
326
|
+
#
|
327
|
+
# Ex.
|
328
|
+
# repo.branch_names_contains('master')
|
329
|
+
#
|
330
|
+
def branches_contains(commit)
|
331
|
+
sha = rugged.rev_parse_oid(commit)
|
332
|
+
|
333
|
+
walker = Rugged::Walker.new(rugged)
|
325
334
|
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
335
|
+
rugged.branches.select do |branch|
|
336
|
+
walker.push(branch.target_id)
|
337
|
+
result = walker.any? { |c| c.oid == sha }
|
338
|
+
walker.reset
|
339
|
+
|
340
|
+
result
|
341
|
+
end
|
331
342
|
end
|
332
343
|
|
333
344
|
# Get refs hash which key is SHA1
|
334
|
-
# and value is
|
345
|
+
# and value is a Rugged::Reference
|
335
346
|
def refs_hash
|
336
347
|
# Initialize only when first call
|
337
348
|
if @refs_hash.nil?
|
338
349
|
@refs_hash = Hash.new { |h, k| h[k] = [] }
|
339
350
|
|
340
|
-
|
341
|
-
|
351
|
+
rugged.references.each do |r|
|
352
|
+
sha = rev_parse_target(r.target.oid).oid
|
353
|
+
|
354
|
+
@refs_hash[sha] << r
|
342
355
|
end
|
343
356
|
end
|
344
357
|
@refs_hash
|
345
358
|
end
|
346
359
|
|
347
|
-
# Lookup for rugged object by oid
|
348
|
-
def lookup(
|
349
|
-
rugged.
|
360
|
+
# Lookup for rugged object by oid or ref name
|
361
|
+
def lookup(oid_or_ref_name)
|
362
|
+
rugged.rev_parse(oid_or_ref_name)
|
350
363
|
end
|
351
364
|
|
352
365
|
# Return hash with submodules info for this repository
|
@@ -364,7 +377,10 @@ module Gitlab
|
|
364
377
|
# }
|
365
378
|
#
|
366
379
|
def submodules(ref)
|
367
|
-
|
380
|
+
tree = rugged.lookup(rugged.rev_parse_oid(ref)).tree
|
381
|
+
|
382
|
+
content = blob_content(tree, ".gitmodules")
|
383
|
+
parse_gitmodules(tree, content)
|
368
384
|
end
|
369
385
|
|
370
386
|
# Return total commits count accessible from passed ref
|
@@ -374,6 +390,532 @@ module Gitlab
|
|
374
390
|
walker.push(ref)
|
375
391
|
walker.count
|
376
392
|
end
|
393
|
+
|
394
|
+
# Sets HEAD to the commit specified by +ref+; +ref+ can be a branch or
|
395
|
+
# tag name or a commit SHA. Valid +reset_type+ values are:
|
396
|
+
#
|
397
|
+
# [:soft]
|
398
|
+
# the head will be moved to the commit.
|
399
|
+
# [:mixed]
|
400
|
+
# will trigger a +:soft+ reset, plus the index will be replaced
|
401
|
+
# with the content of the commit tree.
|
402
|
+
# [:hard]
|
403
|
+
# will trigger a +:mixed+ reset and the working directory will be
|
404
|
+
# replaced with the content of the index. (Untracked and ignored files
|
405
|
+
# will be left alone)
|
406
|
+
def reset(ref, reset_type)
|
407
|
+
rugged.reset(ref, reset_type)
|
408
|
+
end
|
409
|
+
|
410
|
+
# Mimic the `git clean` command and recursively delete untracked files.
|
411
|
+
# Valid keys that can be passed in the +options+ hash are:
|
412
|
+
#
|
413
|
+
# :d - Remove untracked directories
|
414
|
+
# :f - Remove untracked directories that are managed by a different
|
415
|
+
# repository
|
416
|
+
# :x - Remove ignored files
|
417
|
+
#
|
418
|
+
# The value in +options+ must evaluate to true for an option to take
|
419
|
+
# effect.
|
420
|
+
#
|
421
|
+
# Examples:
|
422
|
+
#
|
423
|
+
# repo.clean(d: true, f: true) # Enable the -d and -f options
|
424
|
+
#
|
425
|
+
# repo.clean(d: false, x: true) # -x is enabled, -d is not
|
426
|
+
def clean(options = {})
|
427
|
+
strategies = [:remove_untracked]
|
428
|
+
strategies.push(:force) if options[:f]
|
429
|
+
strategies.push(:remove_ignored) if options[:x]
|
430
|
+
|
431
|
+
# TODO: implement this method
|
432
|
+
end
|
433
|
+
|
434
|
+
# Check out the specified ref. Valid options are:
|
435
|
+
#
|
436
|
+
# :b - Create a new branch at +start_point+ and set HEAD to the new
|
437
|
+
# branch.
|
438
|
+
#
|
439
|
+
# * These options are passed to the Rugged::Repository#checkout method:
|
440
|
+
#
|
441
|
+
# :progress ::
|
442
|
+
# A callback that will be executed for checkout progress notifications.
|
443
|
+
# Up to 3 parameters are passed on each execution:
|
444
|
+
#
|
445
|
+
# - The path to the last updated file (or +nil+ on the very first
|
446
|
+
# invocation).
|
447
|
+
# - The number of completed checkout steps.
|
448
|
+
# - The number of total checkout steps to be performed.
|
449
|
+
#
|
450
|
+
# :notify ::
|
451
|
+
# A callback that will be executed for each checkout notification
|
452
|
+
# types specified with +:notify_flags+. Up to 5 parameters are passed
|
453
|
+
# on each execution:
|
454
|
+
#
|
455
|
+
# - An array containing the +:notify_flags+ that caused the callback
|
456
|
+
# execution.
|
457
|
+
# - The path of the current file.
|
458
|
+
# - A hash describing the baseline blob (or +nil+ if it does not
|
459
|
+
# exist).
|
460
|
+
# - A hash describing the target blob (or +nil+ if it does not exist).
|
461
|
+
# - A hash describing the workdir blob (or +nil+ if it does not
|
462
|
+
# exist).
|
463
|
+
#
|
464
|
+
# :strategy ::
|
465
|
+
# A single symbol or an array of symbols representing the strategies
|
466
|
+
# to use when performing the checkout. Possible values are:
|
467
|
+
#
|
468
|
+
# :none ::
|
469
|
+
# Perform a dry run (default).
|
470
|
+
#
|
471
|
+
# :safe ::
|
472
|
+
# Allow safe updates that cannot overwrite uncommitted data.
|
473
|
+
#
|
474
|
+
# :safe_create ::
|
475
|
+
# Allow safe updates plus creation of missing files.
|
476
|
+
#
|
477
|
+
# :force ::
|
478
|
+
# Allow all updates to force working directory to look like index.
|
479
|
+
#
|
480
|
+
# :allow_conflicts ::
|
481
|
+
# Allow checkout to make safe updates even if conflicts are found.
|
482
|
+
#
|
483
|
+
# :remove_untracked ::
|
484
|
+
# Remove untracked files not in index (that are not ignored).
|
485
|
+
#
|
486
|
+
# :remove_ignored ::
|
487
|
+
# Remove ignored files not in index.
|
488
|
+
#
|
489
|
+
# :update_only ::
|
490
|
+
# Only update existing files, don't create new ones.
|
491
|
+
#
|
492
|
+
# :dont_update_index ::
|
493
|
+
# Normally checkout updates index entries as it goes; this stops
|
494
|
+
# that.
|
495
|
+
#
|
496
|
+
# :no_refresh ::
|
497
|
+
# Don't refresh index/config/etc before doing checkout.
|
498
|
+
#
|
499
|
+
# :disable_pathspec_match ::
|
500
|
+
# Treat pathspec as simple list of exact match file paths.
|
501
|
+
#
|
502
|
+
# :skip_locked_directories ::
|
503
|
+
# Ignore directories in use, they will be left empty.
|
504
|
+
#
|
505
|
+
# :skip_unmerged ::
|
506
|
+
# Allow checkout to skip unmerged files (NOT IMPLEMENTED).
|
507
|
+
#
|
508
|
+
# :use_ours ::
|
509
|
+
# For unmerged files, checkout stage 2 from index (NOT IMPLEMENTED).
|
510
|
+
#
|
511
|
+
# :use_theirs ::
|
512
|
+
# For unmerged files, checkout stage 3 from index (NOT IMPLEMENTED).
|
513
|
+
#
|
514
|
+
# :update_submodules ::
|
515
|
+
# Recursively checkout submodules with same options (NOT
|
516
|
+
# IMPLEMENTED).
|
517
|
+
#
|
518
|
+
# :update_submodules_if_changed ::
|
519
|
+
# Recursively checkout submodules if HEAD moved in super repo (NOT
|
520
|
+
# IMPLEMENTED).
|
521
|
+
#
|
522
|
+
# :disable_filters ::
|
523
|
+
# If +true+, filters like CRLF line conversion will be disabled.
|
524
|
+
#
|
525
|
+
# :dir_mode ::
|
526
|
+
# Mode for newly created directories. Default: +0755+.
|
527
|
+
#
|
528
|
+
# :file_mode ::
|
529
|
+
# Mode for newly created files. Default: +0755+ or +0644+.
|
530
|
+
#
|
531
|
+
# :file_open_flags ::
|
532
|
+
# Mode for opening files. Default:
|
533
|
+
# <code>IO::CREAT | IO::TRUNC | IO::WRONLY</code>.
|
534
|
+
#
|
535
|
+
# :notify_flags ::
|
536
|
+
# A single symbol or an array of symbols representing the cases in
|
537
|
+
# which the +:notify+ callback should be invoked. Possible values are:
|
538
|
+
#
|
539
|
+
# :none ::
|
540
|
+
# Do not invoke the +:notify+ callback (default).
|
541
|
+
#
|
542
|
+
# :conflict ::
|
543
|
+
# Invoke the callback for conflicting paths.
|
544
|
+
#
|
545
|
+
# :dirty ::
|
546
|
+
# Invoke the callback for "dirty" files, i.e. those that do not need
|
547
|
+
# an update but no longer match the baseline.
|
548
|
+
#
|
549
|
+
# :updated ::
|
550
|
+
# Invoke the callback for any file that was changed.
|
551
|
+
#
|
552
|
+
# :untracked ::
|
553
|
+
# Invoke the callback for untracked files.
|
554
|
+
#
|
555
|
+
# :ignored ::
|
556
|
+
# Invoke the callback for ignored files.
|
557
|
+
#
|
558
|
+
# :all ::
|
559
|
+
# Invoke the callback for all these cases.
|
560
|
+
#
|
561
|
+
# :paths ::
|
562
|
+
# A glob string or an array of glob strings specifying which paths
|
563
|
+
# should be taken into account for the checkout operation. +nil+ will
|
564
|
+
# match all files. Default: +nil+.
|
565
|
+
#
|
566
|
+
# :baseline ::
|
567
|
+
# A Rugged::Tree that represents the current, expected contents of the
|
568
|
+
# workdir. Default: +HEAD+.
|
569
|
+
#
|
570
|
+
# :target_directory ::
|
571
|
+
# A path to an alternative workdir directory in which the checkout
|
572
|
+
# should be performed.
|
573
|
+
def checkout(ref, options = {}, start_point = "HEAD")
|
574
|
+
if options[:b]
|
575
|
+
rugged.branches.create(ref, start_point)
|
576
|
+
options.delete(:b)
|
577
|
+
end
|
578
|
+
default_options = { strategy: :safe_create }
|
579
|
+
rugged.checkout(ref, default_options.merge(options))
|
580
|
+
end
|
581
|
+
|
582
|
+
# Delete the specified branch from the repository
|
583
|
+
def delete_branch(branch_name)
|
584
|
+
rugged.branches.delete(branch_name)
|
585
|
+
end
|
586
|
+
|
587
|
+
# Return an array of this repository's remote names
|
588
|
+
def remote_names
|
589
|
+
rugged.remotes.each_name.to_a
|
590
|
+
end
|
591
|
+
|
592
|
+
# Delete the specified remote from this repository.
|
593
|
+
def remote_delete(remote_name)
|
594
|
+
rugged.remotes.delete(remote_name)
|
595
|
+
end
|
596
|
+
|
597
|
+
# Add a new remote to this repository. Returns a Rugged::Remote object
|
598
|
+
def remote_add(remote_name, url)
|
599
|
+
rugged.remotes.create(remote_name, url)
|
600
|
+
end
|
601
|
+
|
602
|
+
# Update the specified remote using the values in the +options+ hash
|
603
|
+
#
|
604
|
+
# Example
|
605
|
+
# repo.update_remote("origin", url: "path/to/repo")
|
606
|
+
def remote_update(remote_name, options = {})
|
607
|
+
# TODO: Implement other remote options
|
608
|
+
remote = rugged.remotes[remote_name]
|
609
|
+
remote.url = options[:url] if options[:url]
|
610
|
+
remote.save
|
611
|
+
end
|
612
|
+
|
613
|
+
# Fetch the specified remote
|
614
|
+
def fetch(remote_name)
|
615
|
+
rugged.remotes[remote_name].fetch
|
616
|
+
end
|
617
|
+
|
618
|
+
# Push +*refspecs+ to the remote identified by +remote_name+.
|
619
|
+
def push(remote_name, *refspecs)
|
620
|
+
rugged.remotes[remote_name].push(refspecs)
|
621
|
+
end
|
622
|
+
|
623
|
+
# Return a String containing the mbox-formatted diff between +from+ and
|
624
|
+
# +to+
|
625
|
+
def format_patch(from, to)
|
626
|
+
rugged.diff(from, to).patch
|
627
|
+
from_sha = rugged.rev_parse_oid(from)
|
628
|
+
to_sha = rugged.rev_parse_oid(to)
|
629
|
+
commits_between(from_sha, to_sha).map do |commit|
|
630
|
+
commit.to_mbox
|
631
|
+
end.join("\n")
|
632
|
+
end
|
633
|
+
|
634
|
+
# Merge the +source_name+ branch into the +target_name+ branch. This is
|
635
|
+
# equivalent to `git merge --no_ff +source_name+`, since a merge commit
|
636
|
+
# is always created.
|
637
|
+
def merge(source_name, target_name, options = {})
|
638
|
+
our_commit = rugged.branches[target_name].target
|
639
|
+
their_commit = rugged.branches[source_name].target
|
640
|
+
|
641
|
+
raise "Invalid merge target" if our_commit.nil?
|
642
|
+
raise "Invalid merge source" if their_commit.nil?
|
643
|
+
|
644
|
+
merge_index = rugged.merge_commits(our_commit, their_commit)
|
645
|
+
return false if merge_index.conflicts?
|
646
|
+
|
647
|
+
actual_options = options.merge(
|
648
|
+
parents: [our_commit, their_commit],
|
649
|
+
tree: merge_index.write_tree(rugged),
|
650
|
+
update_ref: "refs/heads/#{target_name}"
|
651
|
+
)
|
652
|
+
Rugged::Commit.create(rugged, actual_options)
|
653
|
+
end
|
654
|
+
|
655
|
+
def commits_since(from_date)
|
656
|
+
walker = Rugged::Walker.new(rugged)
|
657
|
+
walker.sorting(Rugged::SORT_DATE | Rugged::SORT_REVERSE)
|
658
|
+
|
659
|
+
rugged.references.each("refs/heads/*") do |ref|
|
660
|
+
walker.push(ref.target_id)
|
661
|
+
end
|
662
|
+
|
663
|
+
commits = []
|
664
|
+
walker.each do |commit|
|
665
|
+
break if commit.author[:time].to_date < from_date
|
666
|
+
commits.push(commit)
|
667
|
+
end
|
668
|
+
|
669
|
+
commits
|
670
|
+
end
|
671
|
+
|
672
|
+
private
|
673
|
+
|
674
|
+
# Return the object that +revspec+ points to. If +revspec+ is an
|
675
|
+
# annotated tag, then return the tag's target instead.
|
676
|
+
def rev_parse_target(revspec)
|
677
|
+
obj = rugged.rev_parse(revspec)
|
678
|
+
if obj.is_a?(Rugged::Tag::Annotation)
|
679
|
+
obj.target
|
680
|
+
else
|
681
|
+
obj
|
682
|
+
end
|
683
|
+
end
|
684
|
+
|
685
|
+
# Get the content of a blob for a given tree. If the blob is a commit
|
686
|
+
# (for submodules) then return the blob's OID.
|
687
|
+
def blob_content(tree, blob_name)
|
688
|
+
blob_hash = tree.detect { |b| b[:name] == blob_name }
|
689
|
+
|
690
|
+
if blob_hash[:type] == :commit
|
691
|
+
blob_hash[:oid]
|
692
|
+
else
|
693
|
+
rugged.lookup(blob_hash[:oid]).content
|
694
|
+
end
|
695
|
+
end
|
696
|
+
|
697
|
+
# Parses the contents of a .gitmodules file and returns a hash of
|
698
|
+
# submodule information.
|
699
|
+
def parse_gitmodules(tree, content)
|
700
|
+
results = {}
|
701
|
+
|
702
|
+
current = ""
|
703
|
+
content.split("\n").each do |txt|
|
704
|
+
if txt.match(/^\[/)
|
705
|
+
current = txt.match(/(?<=").*(?=")/)[0]
|
706
|
+
results[current] = {}
|
707
|
+
else
|
708
|
+
match_data = txt.match(/(\w+) = (.*)/)
|
709
|
+
results[current][match_data[1]] = match_data[2]
|
710
|
+
|
711
|
+
if match_data[1] == "path"
|
712
|
+
results[current]["id"] = blob_content(tree, match_data[2])
|
713
|
+
end
|
714
|
+
end
|
715
|
+
end
|
716
|
+
|
717
|
+
results
|
718
|
+
end
|
719
|
+
|
720
|
+
# Return an array of log commits, given an SHA hash and a hash of
|
721
|
+
# options.
|
722
|
+
def build_log(sha, options)
|
723
|
+
# Instantiate a Walker and add the SHA hash
|
724
|
+
walker = Rugged::Walker.new(rugged)
|
725
|
+
walker.push(sha)
|
726
|
+
|
727
|
+
commits = []
|
728
|
+
skipped = 0
|
729
|
+
current_path = options[:path]
|
730
|
+
|
731
|
+
walker.each do |c|
|
732
|
+
break if options[:limit].to_i > 0 &&
|
733
|
+
commits.length >= options[:limit].to_i
|
734
|
+
|
735
|
+
if !current_path ||
|
736
|
+
commit_touches_path?(c, current_path, options[:follow])
|
737
|
+
|
738
|
+
# This is a commit we care about, unless we haven't skipped enough
|
739
|
+
# yet
|
740
|
+
skipped += 1
|
741
|
+
commits.push(c) if skipped > options[:offset]
|
742
|
+
end
|
743
|
+
end
|
744
|
+
|
745
|
+
walker.reset
|
746
|
+
|
747
|
+
commits
|
748
|
+
end
|
749
|
+
|
750
|
+
# Returns true if the given commit affects the given path. If the
|
751
|
+
# +follow+ option is true and the file specified by +path+ was renamed,
|
752
|
+
# then the path value is set to the old path.
|
753
|
+
def commit_touches_path?(commit, path, follow)
|
754
|
+
if commit.parents.empty?
|
755
|
+
diff = commit.diff
|
756
|
+
else
|
757
|
+
diff = commit.parents[0].diff(commit)
|
758
|
+
diff.find_similar! if follow
|
759
|
+
end
|
760
|
+
|
761
|
+
# Check the commit's deltas to see if it touches the :path
|
762
|
+
# argument
|
763
|
+
diff.each_delta do |d|
|
764
|
+
if path_matches?(path, d.old_file[:path], d.new_file[:path])
|
765
|
+
if should_follow?(follow, d, path)
|
766
|
+
# Look for the old path in ancestors
|
767
|
+
path.replace(d.old_file[:path])
|
768
|
+
end
|
769
|
+
|
770
|
+
return true
|
771
|
+
end
|
772
|
+
end
|
773
|
+
|
774
|
+
false
|
775
|
+
end
|
776
|
+
|
777
|
+
# Used by the #commit_touches_path method to determine whether the
|
778
|
+
# specified file has been renamed and should be followed in ancestor
|
779
|
+
# commits. Returns true if +follow_option+ is true, the file is renamed
|
780
|
+
# in this commit, and the new file's path matches the path option.
|
781
|
+
def should_follow?(follow_option, delta, path)
|
782
|
+
follow_option && delta.renamed? && path == delta.new_file[:path]
|
783
|
+
end
|
784
|
+
|
785
|
+
# Returns true if any of the strings in +*paths+ begins with the
|
786
|
+
# +path_to_match+ argument
|
787
|
+
def path_matches?(path_to_match, *paths)
|
788
|
+
paths.any? do |p|
|
789
|
+
p.match(/^#{Regexp.escape(path_to_match)}/)
|
790
|
+
end
|
791
|
+
end
|
792
|
+
|
793
|
+
# Create an archive with the repository's files
|
794
|
+
def create_archive(ref_name, pipe_cmd, file_path)
|
795
|
+
# Put files into a prefix directory in the archive
|
796
|
+
prefix = File.basename(name)
|
797
|
+
extension = Pathname.new(file_path).extname
|
798
|
+
|
799
|
+
if extension == ".zip"
|
800
|
+
create_zip_archive(ref_name, file_path, prefix)
|
801
|
+
else
|
802
|
+
# Create a tarfile in memory
|
803
|
+
tarfile = tar_string_io(ref_name, prefix)
|
804
|
+
|
805
|
+
if extension == ".tar"
|
806
|
+
File.new(file_path, "wb").write(tarfile.read)
|
807
|
+
else
|
808
|
+
compress_tar(tarfile, file_path, pipe_cmd)
|
809
|
+
end
|
810
|
+
end
|
811
|
+
end
|
812
|
+
|
813
|
+
# Return a StringIO with the contents of the repo's tar file
|
814
|
+
def tar_string_io(ref_name, prefix)
|
815
|
+
tarfile = StringIO.new
|
816
|
+
Gem::Package::TarWriter.new(tarfile) do |tar|
|
817
|
+
tar.mkdir(prefix, 33261)
|
818
|
+
|
819
|
+
populated_index(ref_name).each do |entry|
|
820
|
+
add_archive_entry(tar, prefix, entry)
|
821
|
+
end
|
822
|
+
end
|
823
|
+
|
824
|
+
tarfile.rewind
|
825
|
+
tarfile
|
826
|
+
end
|
827
|
+
|
828
|
+
# Create a zip file with the contents of the repo
|
829
|
+
def create_zip_archive(ref_name, archive_path, prefix)
|
830
|
+
Zip::File.open(archive_path, Zip::File::CREATE) do |zipfile|
|
831
|
+
populated_index(ref_name).each do |entry|
|
832
|
+
add_archive_entry(zipfile, prefix, entry)
|
833
|
+
end
|
834
|
+
end
|
835
|
+
end
|
836
|
+
|
837
|
+
# Add a file or directory from the index to the given tar or zip file
|
838
|
+
def add_archive_entry(archive, prefix, entry)
|
839
|
+
prefixed_path = File.join(prefix, entry[:path])
|
840
|
+
content = rugged.lookup(entry[:oid]).content unless submodule?(entry)
|
841
|
+
|
842
|
+
# Create a file in the archive for each index entry
|
843
|
+
if archive.is_a?(Zip::File)
|
844
|
+
unless submodule?(entry)
|
845
|
+
archive.get_output_stream(prefixed_path) do |os|
|
846
|
+
os.write(content)
|
847
|
+
end
|
848
|
+
end
|
849
|
+
else
|
850
|
+
if submodule?(entry)
|
851
|
+
# Create directories for submodules
|
852
|
+
archive.mkdir(prefixed_path, 33261)
|
853
|
+
else
|
854
|
+
# Write the blob contents to the file
|
855
|
+
archive.add_file(prefixed_path, entry[:mode]) do |tf|
|
856
|
+
tf.write(content)
|
857
|
+
end
|
858
|
+
end
|
859
|
+
end
|
860
|
+
end
|
861
|
+
|
862
|
+
# Returns true if the index entry has the special file mode that denotes
|
863
|
+
# a submodule.
|
864
|
+
def submodule?(index_entry)
|
865
|
+
index_entry[:mode] == 57344
|
866
|
+
end
|
867
|
+
|
868
|
+
# Send the +tar_string+ StringIO to +pipe_cmd+ for bzip2 or gzip
|
869
|
+
# compression.
|
870
|
+
def compress_tar(tar_string, file_path, pipe_cmd)
|
871
|
+
# Write the in-memory tarfile to a pipe
|
872
|
+
rd_pipe, rw_pipe = IO.pipe
|
873
|
+
tar_pid = fork do
|
874
|
+
rd_pipe.close
|
875
|
+
rw_pipe.write(tar_string.read)
|
876
|
+
rw_pipe.close
|
877
|
+
end
|
878
|
+
|
879
|
+
# Use the other end of the pipe to compress with bzip2 or gzip
|
880
|
+
FileUtils.mkdir_p(Pathname.new(file_path).dirname)
|
881
|
+
archive_file = File.new(file_path, "wb")
|
882
|
+
rw_pipe.close
|
883
|
+
compress_pid = spawn(*pipe_cmd, in: rd_pipe, out: archive_file)
|
884
|
+
rd_pipe.close
|
885
|
+
|
886
|
+
Process.waitpid(tar_pid)
|
887
|
+
Process.waitpid(compress_pid)
|
888
|
+
|
889
|
+
archive_file.close
|
890
|
+
tar_string.close
|
891
|
+
end
|
892
|
+
|
893
|
+
# Return a Rugged::Index that has read from the tree at +ref_name+
|
894
|
+
def populated_index(ref_name)
|
895
|
+
tree = rugged.lookup(rugged.rev_parse_oid(ref_name)).tree
|
896
|
+
index = rugged.index
|
897
|
+
index.read_tree(tree)
|
898
|
+
index
|
899
|
+
end
|
900
|
+
|
901
|
+
# Return an array of BlobSnippets for lines in +file_contents+ that match
|
902
|
+
# +query+
|
903
|
+
def build_greps(file_contents, query, ref, filename)
|
904
|
+
greps = []
|
905
|
+
|
906
|
+
file_contents.split("\n").each_with_index do |line, i|
|
907
|
+
next unless line.match(/#{Regexp.escape(query)}/i)
|
908
|
+
|
909
|
+
greps << Gitlab::Git::BlobSnippet.new(
|
910
|
+
ref,
|
911
|
+
file_contents.split("\n")[i - 3..i + 3],
|
912
|
+
i - 2,
|
913
|
+
filename
|
914
|
+
)
|
915
|
+
end
|
916
|
+
|
917
|
+
greps
|
918
|
+
end
|
377
919
|
end
|
378
920
|
end
|
379
921
|
end
|