gitlab_git 6.3.0 → 7.0.0.rc1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|