bringit 1.0.0
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 +7 -0
- data/lib/bringit.rb +96 -0
- data/lib/bringit/attributes.rb +129 -0
- data/lib/bringit/blame.rb +73 -0
- data/lib/bringit/blob.rb +175 -0
- data/lib/bringit/blob_snippet.rb +30 -0
- data/lib/bringit/branch.rb +7 -0
- data/lib/bringit/cloning.rb +79 -0
- data/lib/bringit/commit.rb +331 -0
- data/lib/bringit/commit_stats.rb +24 -0
- data/lib/bringit/committing.rb +334 -0
- data/lib/bringit/committing/merge.rb +125 -0
- data/lib/bringit/compare.rb +41 -0
- data/lib/bringit/diff.rb +320 -0
- data/lib/bringit/diff_collection.rb +127 -0
- data/lib/bringit/encoding_helper.rb +56 -0
- data/lib/bringit/hook.rb +87 -0
- data/lib/bringit/index.rb +128 -0
- data/lib/bringit/path_helper.rb +14 -0
- data/lib/bringit/popen.rb +34 -0
- data/lib/bringit/pulling.rb +43 -0
- data/lib/bringit/ref.rb +56 -0
- data/lib/bringit/repository.rb +1230 -0
- data/lib/bringit/rev_list.rb +40 -0
- data/lib/bringit/tag.rb +19 -0
- data/lib/bringit/tree.rb +104 -0
- data/lib/bringit/util.rb +16 -0
- data/lib/bringit/version.rb +5 -0
- data/lib/bringit/version_info.rb +54 -0
- data/lib/bringit/wrapper.rb +136 -0
- metadata +137 -0
@@ -0,0 +1,56 @@
|
|
1
|
+
module Bringit
|
2
|
+
module EncodingHelper
|
3
|
+
extend self
|
4
|
+
|
5
|
+
# This threshold is carefully tweaked to prevent usage of encodings detected
|
6
|
+
# by CharlockHolmes with low confidence. If CharlockHolmes confidence is low,
|
7
|
+
# we're better off sticking with utf8 encoding.
|
8
|
+
# Reason: git diff can return strings with invalid utf8 byte sequences if it
|
9
|
+
# truncates a diff in the middle of a multibyte character. In this case
|
10
|
+
# CharlockHolmes will try to guess the encoding and will likely suggest an
|
11
|
+
# obscure encoding with low confidence.
|
12
|
+
# There is a lot more info with this merge request:
|
13
|
+
# https://gitlab.com/gitlab-org/gitlab_git/merge_requests/77#note_4754193
|
14
|
+
ENCODING_CONFIDENCE_THRESHOLD = 40
|
15
|
+
|
16
|
+
def encode!(message)
|
17
|
+
return nil unless message.respond_to? :force_encoding
|
18
|
+
|
19
|
+
# if message is utf-8 encoding, just return it
|
20
|
+
message.force_encoding("UTF-8")
|
21
|
+
return message if message.valid_encoding?
|
22
|
+
|
23
|
+
# return message if message type is binary
|
24
|
+
detect = CharlockHolmes::EncodingDetector.detect(message)
|
25
|
+
return message.force_encoding("BINARY") if detect && detect[:type] == :binary
|
26
|
+
|
27
|
+
# force detected encoding if we have sufficient confidence.
|
28
|
+
if detect && detect[:encoding] && detect[:confidence] > ENCODING_CONFIDENCE_THRESHOLD
|
29
|
+
message.force_encoding(detect[:encoding])
|
30
|
+
end
|
31
|
+
|
32
|
+
# encode and clean the bad chars
|
33
|
+
message.replace clean(message)
|
34
|
+
rescue
|
35
|
+
encoding = detect ? detect[:encoding] : "unknown"
|
36
|
+
"--broken encoding: #{encoding}"
|
37
|
+
end
|
38
|
+
|
39
|
+
def encode_utf8(message)
|
40
|
+
detect = CharlockHolmes::EncodingDetector.detect(message)
|
41
|
+
if detect
|
42
|
+
CharlockHolmes::Converter.convert(message, detect[:encoding], 'UTF-8')
|
43
|
+
else
|
44
|
+
clean(message)
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
def clean(message)
|
51
|
+
message.encode("UTF-16BE", undef: :replace, invalid: :replace, replace: "")
|
52
|
+
.encode("UTF-8")
|
53
|
+
.gsub("\0".encode("UTF-8"), "")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
data/lib/bringit/hook.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require "bundler"
|
2
|
+
|
3
|
+
module Bringit
|
4
|
+
class Hook
|
5
|
+
GL_PROTOCOL = 'web'.freeze
|
6
|
+
attr_reader :name, :repo_path, :path
|
7
|
+
|
8
|
+
def initialize(name, repo_path)
|
9
|
+
@name = name
|
10
|
+
@repo_path = repo_path
|
11
|
+
@path = File.join(repo_path.strip, 'hooks', name)
|
12
|
+
end
|
13
|
+
|
14
|
+
def exists?
|
15
|
+
File.exist?(path)
|
16
|
+
end
|
17
|
+
|
18
|
+
def trigger(user_id, oldrev, newrev, ref)
|
19
|
+
return [true, nil] unless exists?
|
20
|
+
|
21
|
+
Bundler.with_clean_env do
|
22
|
+
case name
|
23
|
+
when "pre-receive", "post-receive"
|
24
|
+
call_receive_hook(user_id, oldrev, newrev, ref)
|
25
|
+
when "update"
|
26
|
+
call_update_hook(user_id, oldrev, newrev, ref)
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def call_receive_hook(user_id, oldrev, newrev, ref)
|
34
|
+
changes = [oldrev, newrev, ref].join(" ")
|
35
|
+
|
36
|
+
exit_status = false
|
37
|
+
exit_message = nil
|
38
|
+
|
39
|
+
vars = {
|
40
|
+
'USER_ID' => user_id,
|
41
|
+
'PWD' => repo_path,
|
42
|
+
'GL_PROTOCOL' => GL_PROTOCOL
|
43
|
+
}
|
44
|
+
|
45
|
+
options = {
|
46
|
+
chdir: repo_path
|
47
|
+
}
|
48
|
+
|
49
|
+
Open3.popen3(vars, path, options) do |stdin, stdout, stderr, wait_thr|
|
50
|
+
exit_status = true
|
51
|
+
stdin.sync = true
|
52
|
+
|
53
|
+
# in git, pre- and post- receive hooks may just exit without
|
54
|
+
# reading stdin. We catch the exception to avoid a broken pipe
|
55
|
+
# warning
|
56
|
+
begin
|
57
|
+
# inject all the changes as stdin to the hook
|
58
|
+
changes.lines do |line|
|
59
|
+
stdin.puts line
|
60
|
+
end
|
61
|
+
rescue Errno::EPIPE
|
62
|
+
end
|
63
|
+
|
64
|
+
stdin.close
|
65
|
+
|
66
|
+
unless wait_thr.value == 0
|
67
|
+
exit_status = false
|
68
|
+
exit_message = retrieve_error_message(stderr, stdout)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
[exit_status, exit_message]
|
73
|
+
end
|
74
|
+
|
75
|
+
def call_update_hook(user_id, oldrev, newrev, ref)
|
76
|
+
Dir.chdir(repo_path) do
|
77
|
+
stdout, stderr, status = Open3.capture3({ 'USER_ID' => user_id }, path, ref, oldrev, newrev)
|
78
|
+
[status.success?, stderr.presence || stdout]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def retrieve_error_message(stderr, stdout)
|
83
|
+
err_message = stderr.gets
|
84
|
+
err_message.blank? ? stdout.gets : err_message
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|
@@ -0,0 +1,128 @@
|
|
1
|
+
module Bringit
|
2
|
+
class Index
|
3
|
+
DEFAULT_MODE = 0o100644
|
4
|
+
|
5
|
+
attr_reader :repository, :raw_index
|
6
|
+
|
7
|
+
def initialize(repository)
|
8
|
+
@repository = repository
|
9
|
+
@raw_index = repository.rugged.index
|
10
|
+
end
|
11
|
+
|
12
|
+
delegate :read_tree, :get, to: :raw_index
|
13
|
+
|
14
|
+
def write_tree
|
15
|
+
raw_index.write_tree(repository.rugged)
|
16
|
+
end
|
17
|
+
|
18
|
+
def dir_exists?(path)
|
19
|
+
raw_index.find { |entry| entry[:path].start_with?("#{path}/") }
|
20
|
+
end
|
21
|
+
|
22
|
+
def create(options)
|
23
|
+
options = normalize_options(options)
|
24
|
+
|
25
|
+
file_entry = get(options[:file_path])
|
26
|
+
if file_entry
|
27
|
+
raise Bringit::Repository::InvalidBlobName.new("Filename already exists")
|
28
|
+
end
|
29
|
+
|
30
|
+
add_blob(options)
|
31
|
+
end
|
32
|
+
|
33
|
+
def create_dir(options)
|
34
|
+
options = normalize_options(options)
|
35
|
+
|
36
|
+
file_entry = get(options[:file_path])
|
37
|
+
if file_entry
|
38
|
+
raise Bringit::Repository::InvalidBlobName.new("Directory already exists as a file")
|
39
|
+
end
|
40
|
+
|
41
|
+
if dir_exists?(options[:file_path])
|
42
|
+
raise Bringit::Repository::InvalidBlobName.new("Directory already exists")
|
43
|
+
end
|
44
|
+
|
45
|
+
options = options.dup
|
46
|
+
options[:file_path] += '/.gitkeep'
|
47
|
+
options[:content] = ''
|
48
|
+
|
49
|
+
add_blob(options)
|
50
|
+
end
|
51
|
+
|
52
|
+
def update(options)
|
53
|
+
options = normalize_options(options)
|
54
|
+
|
55
|
+
file_entry = get(options[:file_path])
|
56
|
+
unless file_entry
|
57
|
+
raise Bringit::Repository::InvalidBlobName.new("File doesn't exist")
|
58
|
+
end
|
59
|
+
|
60
|
+
add_blob(options, mode: file_entry[:mode])
|
61
|
+
end
|
62
|
+
|
63
|
+
def move(options)
|
64
|
+
options = normalize_options(options)
|
65
|
+
|
66
|
+
file_entry = get(options[:previous_path])
|
67
|
+
unless file_entry
|
68
|
+
raise Bringit::Repository::InvalidBlobName.new("File doesn't exist")
|
69
|
+
end
|
70
|
+
|
71
|
+
if get(options[:file_path])
|
72
|
+
raise IndexError, "A file with this name already exists"
|
73
|
+
end
|
74
|
+
|
75
|
+
raw_index.remove(options[:previous_path])
|
76
|
+
|
77
|
+
add_blob(options, mode: file_entry[:mode])
|
78
|
+
end
|
79
|
+
|
80
|
+
def delete(options)
|
81
|
+
options = normalize_options(options)
|
82
|
+
|
83
|
+
file_entry = get(options[:file_path])
|
84
|
+
unless file_entry
|
85
|
+
raise Bringit::Repository::InvalidBlobName.new("File doesn't exist")
|
86
|
+
end
|
87
|
+
|
88
|
+
raw_index.remove(options[:file_path])
|
89
|
+
end
|
90
|
+
|
91
|
+
private
|
92
|
+
|
93
|
+
def normalize_options(options)
|
94
|
+
options = options.dup
|
95
|
+
options[:file_path] = normalize_path(options[:file_path]) if options[:file_path]
|
96
|
+
options[:previous_path] = normalize_path(options[:previous_path]) if options[:previous_path]
|
97
|
+
options
|
98
|
+
end
|
99
|
+
|
100
|
+
def normalize_path(path)
|
101
|
+
pathname = Bringit::PathHelper.normalize_path(path.dup)
|
102
|
+
|
103
|
+
if pathname.each_filename.include?('..')
|
104
|
+
raise Bringit::Repository::InvalidBlobName.new('Invalid path')
|
105
|
+
end
|
106
|
+
|
107
|
+
pathname.to_s
|
108
|
+
end
|
109
|
+
|
110
|
+
def add_blob(options, mode: nil)
|
111
|
+
content = options[:content]
|
112
|
+
content = Base64.decode64(content) if options[:encoding] == 'base64'
|
113
|
+
|
114
|
+
detect = CharlockHolmes::EncodingDetector.new.detect(content)
|
115
|
+
unless detect && detect[:type] == :binary
|
116
|
+
# When writing to the repo directly as we are doing here,
|
117
|
+
# the `core.autocrlf` config isn't taken into account.
|
118
|
+
content.gsub!("\r\n", "\n") if repository.autocrlf
|
119
|
+
end
|
120
|
+
|
121
|
+
oid = repository.rugged.write(content, :blob)
|
122
|
+
|
123
|
+
raw_index.add(path: options[:file_path], oid: oid, mode: mode || DEFAULT_MODE)
|
124
|
+
rescue Rugged::IndexError => e
|
125
|
+
raise Bringit::Repository::InvalidBlobName.new(e.message)
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Bringit
|
2
|
+
class PathHelper
|
3
|
+
class << self
|
4
|
+
def normalize_path(filename)
|
5
|
+
# Strip all leading slashes so that //foo -> foo
|
6
|
+
filename = filename.sub(/^\/*/, '')
|
7
|
+
|
8
|
+
# Expand relative paths (e.g. foo/../bar)
|
9
|
+
filename = Pathname.new(filename)
|
10
|
+
filename.relative_path_from(Pathname.new(''))
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'open3'
|
3
|
+
|
4
|
+
module Bringit
|
5
|
+
module Popen
|
6
|
+
extend self
|
7
|
+
|
8
|
+
def self.popen(cmd, path = nil, vars = {})
|
9
|
+
unless cmd.is_a?(Array)
|
10
|
+
raise 'System commands must be given as an array of strings'
|
11
|
+
end
|
12
|
+
|
13
|
+
path ||= Dir.pwd
|
14
|
+
vars = vars.dup
|
15
|
+
vars['PWD'] = path
|
16
|
+
options = {chdir: path}
|
17
|
+
|
18
|
+
FileUtils.mkdir_p(path) unless File.directory?(path)
|
19
|
+
|
20
|
+
cmd_output = ''
|
21
|
+
cmd_status = 0
|
22
|
+
Open3.popen3(vars, *cmd, options) do |stdin, stdout, stderr, wait_thr|
|
23
|
+
yield(stdin) if block_given?
|
24
|
+
stdin.close
|
25
|
+
|
26
|
+
cmd_output += stdout.read
|
27
|
+
cmd_output += stderr.read
|
28
|
+
cmd_status = wait_thr.value.exitstatus
|
29
|
+
end
|
30
|
+
|
31
|
+
[cmd_output, cmd_status]
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Bringit
|
4
|
+
# Methods for pulling
|
5
|
+
module Pulling
|
6
|
+
class Error < StandardError; end
|
7
|
+
|
8
|
+
def pull
|
9
|
+
if svn?
|
10
|
+
pull_svn
|
11
|
+
else
|
12
|
+
pull_git
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
protected
|
17
|
+
|
18
|
+
def svn?
|
19
|
+
path.join('svn').directory?
|
20
|
+
end
|
21
|
+
|
22
|
+
def pull_svn
|
23
|
+
_out_fetch, status_fetch = Popen.popen(%w(git svn fetch), path.to_s)
|
24
|
+
|
25
|
+
ref = svn_has_trunk? ? 'trunk' : 'git-svn'
|
26
|
+
cmd = %W(git update-ref refs/heads/master refs/remotes/#{ref})
|
27
|
+
_out_update, status_update = Popen.popen(cmd, path.to_s)
|
28
|
+
|
29
|
+
[status_fetch, status_update].all?(&:zero?)
|
30
|
+
end
|
31
|
+
|
32
|
+
def pull_git
|
33
|
+
_out, status = Popen.popen(%w(git fetch --all), path.to_s)
|
34
|
+
status.zero?
|
35
|
+
end
|
36
|
+
|
37
|
+
def svn_has_trunk?
|
38
|
+
out, _status =
|
39
|
+
Popen.popen(%w(git config svn-remote.svn.fetch), path.to_s)
|
40
|
+
out.start_with?('trunk:')
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
data/lib/bringit/ref.rb
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
module Bringit
|
2
|
+
class Ref
|
3
|
+
include Bringit::EncodingHelper
|
4
|
+
|
5
|
+
def self.name_valid?(name)
|
6
|
+
if name.start_with?('refs/heads/') || name.start_with?('refs/remotes/')
|
7
|
+
return false
|
8
|
+
end
|
9
|
+
|
10
|
+
Popen.popen(%W(git check-ref-format refs/#{name})).last == 0
|
11
|
+
end
|
12
|
+
|
13
|
+
|
14
|
+
# Branch or tag name
|
15
|
+
# without "refs/tags|heads" prefix
|
16
|
+
attr_reader :name
|
17
|
+
|
18
|
+
# Target sha.
|
19
|
+
# Usually it is commit sha but in case
|
20
|
+
# when tag reference on other tag it can be tag sha
|
21
|
+
attr_reader :target
|
22
|
+
|
23
|
+
# Dereferenced target
|
24
|
+
# Commit object to which the Ref points to
|
25
|
+
attr_reader :dereferenced_target
|
26
|
+
|
27
|
+
# Extract branch name from full ref path
|
28
|
+
#
|
29
|
+
# Ex.
|
30
|
+
# Ref.extract_branch_name('refs/heads/master') #=> 'master'
|
31
|
+
def self.extract_branch_name(str)
|
32
|
+
str.gsub(/\Arefs\/heads\//, '')
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.dereference_object(object)
|
36
|
+
object = object.target while object.is_a?(Rugged::Tag::Annotation)
|
37
|
+
|
38
|
+
object
|
39
|
+
end
|
40
|
+
|
41
|
+
def initialize(repository, name, target)
|
42
|
+
encode! name
|
43
|
+
@name = name.gsub(/\Arefs\/(tags|heads)\//, '')
|
44
|
+
@dereferenced_target = Bringit::Commit.find(repository, target)
|
45
|
+
@target = if target.respond_to?(:oid)
|
46
|
+
target.oid
|
47
|
+
elsif target.respond_to?(:name)
|
48
|
+
target.name
|
49
|
+
elsif target.is_a? String
|
50
|
+
target
|
51
|
+
else
|
52
|
+
nil
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
@@ -0,0 +1,1230 @@
|
|
1
|
+
# Bringit::Repository is a wrapper around native Rugged::Repository object
|
2
|
+
require 'tempfile'
|
3
|
+
require 'forwardable'
|
4
|
+
require "rubygems/package"
|
5
|
+
|
6
|
+
module Bringit
|
7
|
+
class Repository
|
8
|
+
include Bringit::Popen
|
9
|
+
|
10
|
+
SEARCH_CONTEXT_LINES = 3
|
11
|
+
|
12
|
+
NoRepository = Class.new(StandardError)
|
13
|
+
InvalidBlobName = Class.new(StandardError)
|
14
|
+
InvalidRef = Class.new(StandardError)
|
15
|
+
|
16
|
+
# Full path to repo
|
17
|
+
attr_reader :path
|
18
|
+
|
19
|
+
# Directory name of repo
|
20
|
+
attr_reader :name
|
21
|
+
|
22
|
+
# Rugged repo object
|
23
|
+
attr_reader :rugged
|
24
|
+
|
25
|
+
# 'path' must be the path to a _bare_ git repository, e.g.
|
26
|
+
# /path/to/my-repo.git
|
27
|
+
def initialize(path)
|
28
|
+
@path = path
|
29
|
+
@name = path.split("/").last
|
30
|
+
@attributes = Bringit::Attributes.new(path)
|
31
|
+
end
|
32
|
+
|
33
|
+
delegate :empty?,
|
34
|
+
:bare?,
|
35
|
+
to: :rugged
|
36
|
+
|
37
|
+
# Default branch in the repository
|
38
|
+
def root_ref
|
39
|
+
@root_ref ||= discover_default_branch
|
40
|
+
end
|
41
|
+
|
42
|
+
# Alias to old method for compatibility
|
43
|
+
def raw
|
44
|
+
rugged
|
45
|
+
end
|
46
|
+
|
47
|
+
def rugged
|
48
|
+
@rugged ||= Rugged::Repository.new(path)
|
49
|
+
rescue Rugged::RepositoryError, Rugged::OSError
|
50
|
+
raise NoRepository.new('no repository for such path')
|
51
|
+
end
|
52
|
+
|
53
|
+
# Returns an Array of branch names
|
54
|
+
# sorted by name ASC
|
55
|
+
def branch_names
|
56
|
+
branches.map(&:name)
|
57
|
+
end
|
58
|
+
|
59
|
+
# Returns an Array of Branches
|
60
|
+
def branches
|
61
|
+
rugged.branches.map do |rugged_ref|
|
62
|
+
begin
|
63
|
+
Bringit::Branch.new(self, rugged_ref.name, rugged_ref.target)
|
64
|
+
rescue Rugged::ReferenceError
|
65
|
+
# Omit invalid branch
|
66
|
+
end
|
67
|
+
end.compact.sort_by(&:name)
|
68
|
+
end
|
69
|
+
|
70
|
+
def reload_rugged
|
71
|
+
@rugged = nil
|
72
|
+
end
|
73
|
+
|
74
|
+
# Directly find a branch with a simple name (e.g. master)
|
75
|
+
#
|
76
|
+
# force_reload causes a new Rugged repository to be instantiated
|
77
|
+
#
|
78
|
+
# This is to work around a bug in libgit2 that causes in-memory refs to
|
79
|
+
# be stale/invalid when packed-refs is changed.
|
80
|
+
# See https://gitlab.com/gitlab-org/gitlab-ce/issues/15392#note_14538333
|
81
|
+
def find_branch(name, force_reload = false)
|
82
|
+
reload_rugged if force_reload
|
83
|
+
|
84
|
+
rugged_ref = rugged.branches[name]
|
85
|
+
Bringit::Branch.new(self, rugged_ref.name, rugged_ref.target) if rugged_ref
|
86
|
+
end
|
87
|
+
|
88
|
+
def local_branches
|
89
|
+
rugged.branches.each(:local).map do |branch|
|
90
|
+
Bringit::Branch.new(self, branch.name, branch.target)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
# Returns the number of valid branches
|
95
|
+
def branch_count
|
96
|
+
rugged.branches.count do |ref|
|
97
|
+
begin
|
98
|
+
ref.name && ref.target # ensures the branch is valid
|
99
|
+
|
100
|
+
true
|
101
|
+
rescue Rugged::ReferenceError
|
102
|
+
false
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
|
107
|
+
# Returns an Array of tag names
|
108
|
+
def tag_names
|
109
|
+
rugged.tags.map { |t| t.name }
|
110
|
+
end
|
111
|
+
|
112
|
+
# Returns an Array of Tags
|
113
|
+
def tags
|
114
|
+
rugged.references.each("refs/tags/*").map do |ref|
|
115
|
+
message = nil
|
116
|
+
|
117
|
+
if ref.target.is_a?(Rugged::Tag::Annotation)
|
118
|
+
tag_message = ref.target.message
|
119
|
+
|
120
|
+
if tag_message.respond_to?(:chomp)
|
121
|
+
message = tag_message.chomp
|
122
|
+
end
|
123
|
+
end
|
124
|
+
|
125
|
+
Bringit::Tag.new(self, ref.name, ref.target, message)
|
126
|
+
end.sort_by(&:name)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Returns true if the given tag exists
|
130
|
+
#
|
131
|
+
# name - The name of the tag as a String.
|
132
|
+
def tag_exists?(name)
|
133
|
+
!!rugged.tags[name]
|
134
|
+
end
|
135
|
+
|
136
|
+
# Returns true if the given branch exists
|
137
|
+
#
|
138
|
+
# name - The name of the branch as a String.
|
139
|
+
def branch_exists?(name)
|
140
|
+
rugged.branches.exists?(name)
|
141
|
+
|
142
|
+
# If the branch name is invalid (e.g. ".foo") Rugged will raise an error.
|
143
|
+
# Whatever code calls this method shouldn't have to deal with that so
|
144
|
+
# instead we just return `false` (which is true since a branch doesn't
|
145
|
+
# exist when it has an invalid name).
|
146
|
+
rescue Rugged::ReferenceError
|
147
|
+
false
|
148
|
+
end
|
149
|
+
|
150
|
+
# Returns an Array of branch and tag names
|
151
|
+
def ref_names
|
152
|
+
branch_names + tag_names
|
153
|
+
end
|
154
|
+
|
155
|
+
# Deprecated. Will be removed in 5.2
|
156
|
+
def heads
|
157
|
+
rugged.references.each("refs/heads/*").map do |head|
|
158
|
+
Bringit::Ref.new(self, head.name, head.target)
|
159
|
+
end.sort_by(&:name)
|
160
|
+
end
|
161
|
+
|
162
|
+
def has_commits?
|
163
|
+
!empty?
|
164
|
+
end
|
165
|
+
|
166
|
+
def repo_exists?
|
167
|
+
!!rugged
|
168
|
+
end
|
169
|
+
|
170
|
+
# Discovers the default branch based on the repository's available branches
|
171
|
+
#
|
172
|
+
# - If no branches are present, returns nil
|
173
|
+
# - If one branch is present, returns its name
|
174
|
+
# - If two or more branches are present, returns current HEAD or master or first branch
|
175
|
+
def discover_default_branch
|
176
|
+
names = branch_names
|
177
|
+
|
178
|
+
return if names.empty?
|
179
|
+
|
180
|
+
return names[0] if names.length == 1
|
181
|
+
|
182
|
+
if rugged_head
|
183
|
+
extracted_name = Ref.extract_branch_name(rugged_head.name)
|
184
|
+
|
185
|
+
return extracted_name if names.include?(extracted_name)
|
186
|
+
end
|
187
|
+
|
188
|
+
if names.include?('master')
|
189
|
+
'master'
|
190
|
+
else
|
191
|
+
names[0]
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
def rugged_head
|
196
|
+
rugged.head
|
197
|
+
rescue Rugged::ReferenceError
|
198
|
+
nil
|
199
|
+
end
|
200
|
+
|
201
|
+
def archive_prefix(ref, sha)
|
202
|
+
project_name = self.name.chomp('.git')
|
203
|
+
"#{project_name}-#{ref.tr('/', '-')}-#{sha}"
|
204
|
+
end
|
205
|
+
|
206
|
+
def archive_metadata(ref, storage_path, format = "tar.gz")
|
207
|
+
ref ||= root_ref
|
208
|
+
commit = Bringit::Commit.find(self, ref)
|
209
|
+
return {} if commit.nil?
|
210
|
+
|
211
|
+
prefix = archive_prefix(ref, commit.id)
|
212
|
+
|
213
|
+
{
|
214
|
+
'RepoPath' => path,
|
215
|
+
'ArchivePrefix' => prefix,
|
216
|
+
'ArchivePath' => archive_file_path(prefix, storage_path, format),
|
217
|
+
'CommitId' => commit.id,
|
218
|
+
}
|
219
|
+
end
|
220
|
+
|
221
|
+
def archive_file_path(name, storage_path, format = "tar.gz")
|
222
|
+
# Build file path
|
223
|
+
return nil unless name
|
224
|
+
|
225
|
+
extension =
|
226
|
+
case format
|
227
|
+
when "tar.bz2", "tbz", "tbz2", "tb2", "bz2"
|
228
|
+
"tar.bz2"
|
229
|
+
when "tar"
|
230
|
+
"tar"
|
231
|
+
when "zip"
|
232
|
+
"zip"
|
233
|
+
else
|
234
|
+
# everything else should fall back to tar.gz
|
235
|
+
"tar.gz"
|
236
|
+
end
|
237
|
+
|
238
|
+
file_name = "#{name}.#{extension}"
|
239
|
+
File.join(storage_path, self.name, file_name)
|
240
|
+
end
|
241
|
+
|
242
|
+
# Return repo size in megabytes
|
243
|
+
def size
|
244
|
+
size = Bringit::Popen.popen(%w(du -sk), path).first.strip.to_i
|
245
|
+
(size.to_f / 1024).round(2)
|
246
|
+
end
|
247
|
+
|
248
|
+
# Returns an array of BlobSnippets for files at the specified +ref+ that
|
249
|
+
# contain the +query+ string.
|
250
|
+
def search_files(query, ref = nil)
|
251
|
+
greps = []
|
252
|
+
ref ||= root_ref
|
253
|
+
|
254
|
+
populated_index(ref).each do |entry|
|
255
|
+
# Discard submodules
|
256
|
+
next if submodule?(entry)
|
257
|
+
|
258
|
+
blob = Bringit::Blob.raw(self, entry[:oid])
|
259
|
+
|
260
|
+
# Skip binary files
|
261
|
+
next if blob.data.encoding == Encoding::ASCII_8BIT
|
262
|
+
|
263
|
+
blob.load_all_data!
|
264
|
+
greps += build_greps(blob.data, query, ref, entry[:path])
|
265
|
+
end
|
266
|
+
|
267
|
+
greps
|
268
|
+
end
|
269
|
+
|
270
|
+
# Use the Rugged Walker API to build an array of commits.
|
271
|
+
#
|
272
|
+
# Usage.
|
273
|
+
# repo.log(
|
274
|
+
# ref: 'master',
|
275
|
+
# path: 'app/models',
|
276
|
+
# limit: 10,
|
277
|
+
# offset: 5,
|
278
|
+
# after: Time.new(2016, 4, 21, 14, 32, 10)
|
279
|
+
# )
|
280
|
+
#
|
281
|
+
def log(options)
|
282
|
+
default_options = {
|
283
|
+
limit: 10,
|
284
|
+
offset: 0,
|
285
|
+
path: nil,
|
286
|
+
follow: false,
|
287
|
+
skip_merges: false,
|
288
|
+
disable_walk: false,
|
289
|
+
after: nil,
|
290
|
+
before: nil,
|
291
|
+
unsafe_range: false,
|
292
|
+
}
|
293
|
+
|
294
|
+
options = default_options.merge(options)
|
295
|
+
options[:limit] ||= 0
|
296
|
+
options[:offset] ||= 0
|
297
|
+
actual_ref = options[:ref] || root_ref
|
298
|
+
|
299
|
+
if options[:unsafe_range]
|
300
|
+
log_by_shell(actual_ref, options)
|
301
|
+
else
|
302
|
+
begin
|
303
|
+
sha = sha_from_ref(actual_ref)
|
304
|
+
rescue Rugged::OdbError, Rugged::InvalidError, Rugged::ReferenceError
|
305
|
+
# Return an empty array if the ref wasn't found
|
306
|
+
return []
|
307
|
+
end
|
308
|
+
if log_using_shell?(options)
|
309
|
+
log_by_shell(sha, options)
|
310
|
+
else
|
311
|
+
log_by_walk(sha, options)
|
312
|
+
end
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
def log_using_shell?(options)
|
317
|
+
options[:path].present? ||
|
318
|
+
options[:disable_walk] ||
|
319
|
+
options[:skip_merges] ||
|
320
|
+
options[:after] ||
|
321
|
+
options[:before]
|
322
|
+
end
|
323
|
+
|
324
|
+
def log_by_walk(sha, options)
|
325
|
+
walk_options = {
|
326
|
+
show: sha,
|
327
|
+
sort: Rugged::SORT_TOPO,
|
328
|
+
limit: options[:limit],
|
329
|
+
offset: options[:offset]
|
330
|
+
}
|
331
|
+
commits = Rugged::Walker.walk(rugged, walk_options).to_a
|
332
|
+
if options[:only_commit_sha]
|
333
|
+
commits.map(&:oid)
|
334
|
+
else
|
335
|
+
commits
|
336
|
+
end
|
337
|
+
end
|
338
|
+
|
339
|
+
def log_by_shell(sha, options)
|
340
|
+
limit = options[:limit].to_i
|
341
|
+
offset = options[:offset].to_i
|
342
|
+
use_follow_flag = options[:follow] && options[:path].present?
|
343
|
+
|
344
|
+
# We will perform the offset in Ruby because --follow doesn't play well with --skip.
|
345
|
+
# See: https://gitlab.com/gitlab-org/gitlab-ce/issues/3574#note_3040520
|
346
|
+
offset_in_ruby = use_follow_flag && options[:offset].present?
|
347
|
+
limit += offset if offset_in_ruby
|
348
|
+
|
349
|
+
cmd = %W[git --git-dir=#{path} log]
|
350
|
+
cmd << "--max-count=#{limit}" if limit > 0
|
351
|
+
cmd << '--format=%H'
|
352
|
+
cmd << "--skip=#{offset}" unless offset_in_ruby
|
353
|
+
cmd << '--follow' if use_follow_flag
|
354
|
+
cmd << '--no-merges' if options[:skip_merges]
|
355
|
+
cmd << "--after=#{options[:after].iso8601}" if options[:after]
|
356
|
+
cmd << "--before=#{options[:before].iso8601}" if options[:before]
|
357
|
+
cmd << sha
|
358
|
+
if options[:path].present?
|
359
|
+
cmd += %W[-- #{options[:path].sub(%r{\A/*}, './')}]
|
360
|
+
end
|
361
|
+
|
362
|
+
raw_output = IO.popen(cmd) { |io| io.read }
|
363
|
+
lines = offset_in_ruby ? raw_output.lines.drop(offset) : raw_output.lines
|
364
|
+
|
365
|
+
if options[:only_commit_sha]
|
366
|
+
lines.map(&:strip)
|
367
|
+
else
|
368
|
+
lines.map! { |c| Rugged::Commit.new(rugged, c.strip) }
|
369
|
+
end
|
370
|
+
end
|
371
|
+
|
372
|
+
def count_commits(options)
|
373
|
+
cmd = %W[git --git-dir=#{path} rev-list]
|
374
|
+
cmd << "--after=#{options[:after].iso8601}" if options[:after]
|
375
|
+
cmd << "--before=#{options[:before].iso8601}" if options[:before]
|
376
|
+
cmd += %W[--count #{options[:ref]}]
|
377
|
+
cmd += %W[-- #{options[:path]}] if options[:path].present?
|
378
|
+
|
379
|
+
raw_output = IO.popen(cmd) { |io| io.read }
|
380
|
+
|
381
|
+
raw_output.to_i
|
382
|
+
end
|
383
|
+
|
384
|
+
def sha_from_ref(ref)
|
385
|
+
rev_parse_target(ref).oid
|
386
|
+
end
|
387
|
+
|
388
|
+
# Return the object that +revspec+ points to. If +revspec+ is an
|
389
|
+
# annotated tag, then return the tag's target instead.
|
390
|
+
def rev_parse_target(revspec)
|
391
|
+
obj = rugged.rev_parse(revspec)
|
392
|
+
Ref.dereference_object(obj)
|
393
|
+
end
|
394
|
+
|
395
|
+
# Return a collection of Rugged::Commits between the two revspec arguments.
|
396
|
+
# See http://git-scm.com/docs/git-rev-parse.html#_specifying_revisions for
|
397
|
+
# a detailed list of valid arguments.
|
398
|
+
def commits_between(from, to)
|
399
|
+
walker = Rugged::Walker.new(rugged)
|
400
|
+
walker.sorting(Rugged::SORT_TOPO | Rugged::SORT_REVERSE)
|
401
|
+
|
402
|
+
sha_from = sha_from_ref(from)
|
403
|
+
sha_to = sha_from_ref(to)
|
404
|
+
|
405
|
+
walker.push(sha_to)
|
406
|
+
walker.hide(sha_from)
|
407
|
+
|
408
|
+
commits = walker.to_a
|
409
|
+
walker.reset
|
410
|
+
|
411
|
+
commits
|
412
|
+
end
|
413
|
+
|
414
|
+
# Counts the amount of commits between `from` and `to`.
|
415
|
+
def count_commits_between(from, to)
|
416
|
+
commits_between(from, to).size
|
417
|
+
end
|
418
|
+
|
419
|
+
# Returns the SHA of the most recent common ancestor of +from+ and +to+
|
420
|
+
def merge_base_commit(from, to)
|
421
|
+
rugged.merge_base(from, to)
|
422
|
+
end
|
423
|
+
|
424
|
+
# Return an array of Diff objects that represent the diff
|
425
|
+
# between +from+ and +to+. See Diff::filter_diff_options for the allowed
|
426
|
+
# diff options. The +options+ hash can also include :break_rewrites to
|
427
|
+
# split larger rewrites into delete/add pairs.
|
428
|
+
def diff(from, to, options = {}, *paths)
|
429
|
+
Bringit::DiffCollection.new(diff_patches(from, to, options, *paths), options)
|
430
|
+
end
|
431
|
+
|
432
|
+
# Returns commits collection
|
433
|
+
#
|
434
|
+
# Ex.
|
435
|
+
# repo.find_commits(
|
436
|
+
# ref: 'master',
|
437
|
+
# max_count: 10,
|
438
|
+
# skip: 5,
|
439
|
+
# order: :date
|
440
|
+
# )
|
441
|
+
#
|
442
|
+
# +options+ is a Hash of optional arguments to git
|
443
|
+
# :ref is the ref from which to begin (SHA1 or name)
|
444
|
+
# :contains is the commit contained by the refs from which to begin (SHA1 or name)
|
445
|
+
# :max_count is the maximum number of commits to fetch
|
446
|
+
# :skip is the number of commits to skip
|
447
|
+
# :order is the commits order and allowed value is :date(default) or :topo
|
448
|
+
#
|
449
|
+
def find_commits(options = {})
|
450
|
+
actual_options = options.dup
|
451
|
+
|
452
|
+
allowed_options = [:ref, :max_count, :skip, :contains, :order]
|
453
|
+
|
454
|
+
actual_options.keep_if do |key|
|
455
|
+
allowed_options.include?(key)
|
456
|
+
end
|
457
|
+
|
458
|
+
default_options = { skip: 0 }
|
459
|
+
actual_options = default_options.merge(actual_options)
|
460
|
+
|
461
|
+
walker = Rugged::Walker.new(rugged)
|
462
|
+
|
463
|
+
if actual_options[:ref]
|
464
|
+
walker.push(rugged.rev_parse_oid(actual_options[:ref]))
|
465
|
+
elsif actual_options[:contains]
|
466
|
+
branches_contains(actual_options[:contains]).each do |branch|
|
467
|
+
walker.push(branch.target_id)
|
468
|
+
end
|
469
|
+
else
|
470
|
+
rugged.references.each("refs/heads/*") do |ref|
|
471
|
+
walker.push(ref.target_id)
|
472
|
+
end
|
473
|
+
end
|
474
|
+
|
475
|
+
if actual_options[:order] == :topo
|
476
|
+
walker.sorting(Rugged::SORT_TOPO)
|
477
|
+
else
|
478
|
+
walker.sorting(Rugged::SORT_DATE)
|
479
|
+
end
|
480
|
+
|
481
|
+
commits = []
|
482
|
+
offset = actual_options[:skip]
|
483
|
+
limit = actual_options[:max_count]
|
484
|
+
walker.each(offset: offset, limit: limit) do |commit|
|
485
|
+
bringit_commit = Bringit::Commit.decorate(commit, self)
|
486
|
+
commits.push(bringit_commit)
|
487
|
+
end
|
488
|
+
|
489
|
+
walker.reset
|
490
|
+
|
491
|
+
commits
|
492
|
+
rescue Rugged::OdbError
|
493
|
+
[]
|
494
|
+
end
|
495
|
+
|
496
|
+
# Returns branch names collection that contains the special commit(SHA1
|
497
|
+
# or name)
|
498
|
+
#
|
499
|
+
# Ex.
|
500
|
+
# repo.branch_names_contains('master')
|
501
|
+
#
|
502
|
+
def branch_names_contains(commit)
|
503
|
+
branches_contains(commit).map { |c| c.name }
|
504
|
+
end
|
505
|
+
|
506
|
+
# Returns branch collection that contains the special commit(SHA1 or name)
|
507
|
+
#
|
508
|
+
# Ex.
|
509
|
+
# repo.branch_names_contains('master')
|
510
|
+
#
|
511
|
+
def branches_contains(commit)
|
512
|
+
commit_obj = rugged.rev_parse(commit)
|
513
|
+
parent = commit_obj.parents.first unless commit_obj.parents.empty?
|
514
|
+
|
515
|
+
walker = Rugged::Walker.new(rugged)
|
516
|
+
|
517
|
+
rugged.branches.select do |branch|
|
518
|
+
walker.push(branch.target_id)
|
519
|
+
walker.hide(parent) if parent
|
520
|
+
result = walker.any? { |c| c.oid == commit_obj.oid }
|
521
|
+
walker.reset
|
522
|
+
|
523
|
+
result
|
524
|
+
end
|
525
|
+
end
|
526
|
+
|
527
|
+
# Get refs hash which key is SHA1
|
528
|
+
# and value is a Rugged::Reference
|
529
|
+
def refs_hash
|
530
|
+
# Initialize only when first call
|
531
|
+
if @refs_hash.nil?
|
532
|
+
@refs_hash = Hash.new { |h, k| h[k] = [] }
|
533
|
+
|
534
|
+
rugged.references.each do |r|
|
535
|
+
# Symbolic/remote references may not have an OID; skip over them
|
536
|
+
target_oid = r.target.try(:oid)
|
537
|
+
if target_oid
|
538
|
+
sha = rev_parse_target(target_oid).oid
|
539
|
+
@refs_hash[sha] << r
|
540
|
+
end
|
541
|
+
end
|
542
|
+
end
|
543
|
+
@refs_hash
|
544
|
+
end
|
545
|
+
|
546
|
+
# Lookup for rugged object by oid or ref name
|
547
|
+
def lookup(oid_or_ref_name)
|
548
|
+
rugged.rev_parse(oid_or_ref_name)
|
549
|
+
end
|
550
|
+
|
551
|
+
# Return hash with submodules info for this repository
|
552
|
+
#
|
553
|
+
# Ex.
|
554
|
+
# {
|
555
|
+
# "rack" => {
|
556
|
+
# "id" => "c67be4624545b4263184c4a0e8f887efd0a66320",
|
557
|
+
# "path" => "rack",
|
558
|
+
# "url" => "git://github.com/chneukirchen/rack.git"
|
559
|
+
# },
|
560
|
+
# "encoding" => {
|
561
|
+
# "id" => ....
|
562
|
+
# }
|
563
|
+
# }
|
564
|
+
#
|
565
|
+
def submodules(ref)
|
566
|
+
commit = rev_parse_target(ref)
|
567
|
+
return {} unless commit
|
568
|
+
|
569
|
+
begin
|
570
|
+
content = blob_content(commit, ".gitmodules")
|
571
|
+
rescue InvalidBlobName
|
572
|
+
return {}
|
573
|
+
end
|
574
|
+
|
575
|
+
parse_gitmodules(commit, content)
|
576
|
+
end
|
577
|
+
|
578
|
+
# Return total commits count accessible from passed ref
|
579
|
+
def commit_count(ref)
|
580
|
+
walker = Rugged::Walker.new(rugged)
|
581
|
+
walker.sorting(Rugged::SORT_TOPO | Rugged::SORT_REVERSE)
|
582
|
+
oid = rugged.rev_parse_oid(ref)
|
583
|
+
walker.push(oid)
|
584
|
+
walker.count
|
585
|
+
end
|
586
|
+
|
587
|
+
# Sets HEAD to the commit specified by +ref+; +ref+ can be a branch or
|
588
|
+
# tag name or a commit SHA. Valid +reset_type+ values are:
|
589
|
+
#
|
590
|
+
# [:soft]
|
591
|
+
# the head will be moved to the commit.
|
592
|
+
# [:mixed]
|
593
|
+
# will trigger a +:soft+ reset, plus the index will be replaced
|
594
|
+
# with the content of the commit tree.
|
595
|
+
# [:hard]
|
596
|
+
# will trigger a +:mixed+ reset and the working directory will be
|
597
|
+
# replaced with the content of the index. (Untracked and ignored files
|
598
|
+
# will be left alone)
|
599
|
+
delegate :reset, to: :rugged
|
600
|
+
|
601
|
+
# Mimic the `git clean` command and recursively delete untracked files.
|
602
|
+
# Valid keys that can be passed in the +options+ hash are:
|
603
|
+
#
|
604
|
+
# :d - Remove untracked directories
|
605
|
+
# :f - Remove untracked directories that are managed by a different
|
606
|
+
# repository
|
607
|
+
# :x - Remove ignored files
|
608
|
+
#
|
609
|
+
# The value in +options+ must evaluate to true for an option to take
|
610
|
+
# effect.
|
611
|
+
#
|
612
|
+
# Examples:
|
613
|
+
#
|
614
|
+
# repo.clean(d: true, f: true) # Enable the -d and -f options
|
615
|
+
#
|
616
|
+
# repo.clean(d: false, x: true) # -x is enabled, -d is not
|
617
|
+
def clean(options = {})
|
618
|
+
strategies = [:remove_untracked]
|
619
|
+
strategies.push(:force) if options[:f]
|
620
|
+
strategies.push(:remove_ignored) if options[:x]
|
621
|
+
|
622
|
+
# TODO: implement this method
|
623
|
+
end
|
624
|
+
|
625
|
+
# Check out the specified ref. Valid options are:
|
626
|
+
#
|
627
|
+
# :b - Create a new branch at +start_point+ and set HEAD to the new
|
628
|
+
# branch.
|
629
|
+
#
|
630
|
+
# * These options are passed to the Rugged::Repository#checkout method:
|
631
|
+
#
|
632
|
+
# :progress ::
|
633
|
+
# A callback that will be executed for checkout progress notifications.
|
634
|
+
# Up to 3 parameters are passed on each execution:
|
635
|
+
#
|
636
|
+
# - The path to the last updated file (or +nil+ on the very first
|
637
|
+
# invocation).
|
638
|
+
# - The number of completed checkout steps.
|
639
|
+
# - The number of total checkout steps to be performed.
|
640
|
+
#
|
641
|
+
# :notify ::
|
642
|
+
# A callback that will be executed for each checkout notification
|
643
|
+
# types specified with +:notify_flags+. Up to 5 parameters are passed
|
644
|
+
# on each execution:
|
645
|
+
#
|
646
|
+
# - An array containing the +:notify_flags+ that caused the callback
|
647
|
+
# execution.
|
648
|
+
# - The path of the current file.
|
649
|
+
# - A hash describing the baseline blob (or +nil+ if it does not
|
650
|
+
# exist).
|
651
|
+
# - A hash describing the target blob (or +nil+ if it does not exist).
|
652
|
+
# - A hash describing the workdir blob (or +nil+ if it does not
|
653
|
+
# exist).
|
654
|
+
#
|
655
|
+
# :strategy ::
|
656
|
+
# A single symbol or an array of symbols representing the strategies
|
657
|
+
# to use when performing the checkout. Possible values are:
|
658
|
+
#
|
659
|
+
# :none ::
|
660
|
+
# Perform a dry run (default).
|
661
|
+
#
|
662
|
+
# :safe ::
|
663
|
+
# Allow safe updates that cannot overwrite uncommitted data.
|
664
|
+
#
|
665
|
+
# :safe_create ::
|
666
|
+
# Allow safe updates plus creation of missing files.
|
667
|
+
#
|
668
|
+
# :force ::
|
669
|
+
# Allow all updates to force working directory to look like index.
|
670
|
+
#
|
671
|
+
# :allow_conflicts ::
|
672
|
+
# Allow checkout to make safe updates even if conflicts are found.
|
673
|
+
#
|
674
|
+
# :remove_untracked ::
|
675
|
+
# Remove untracked files not in index (that are not ignored).
|
676
|
+
#
|
677
|
+
# :remove_ignored ::
|
678
|
+
# Remove ignored files not in index.
|
679
|
+
#
|
680
|
+
# :update_only ::
|
681
|
+
# Only update existing files, don't create new ones.
|
682
|
+
#
|
683
|
+
# :dont_update_index ::
|
684
|
+
# Normally checkout updates index entries as it goes; this stops
|
685
|
+
# that.
|
686
|
+
#
|
687
|
+
# :no_refresh ::
|
688
|
+
# Don't refresh index/config/etc before doing checkout.
|
689
|
+
#
|
690
|
+
# :disable_pathspec_match ::
|
691
|
+
# Treat pathspec as simple list of exact match file paths.
|
692
|
+
#
|
693
|
+
# :skip_locked_directories ::
|
694
|
+
# Ignore directories in use, they will be left empty.
|
695
|
+
#
|
696
|
+
# :skip_unmerged ::
|
697
|
+
# Allow checkout to skip unmerged files (NOT IMPLEMENTED).
|
698
|
+
#
|
699
|
+
# :use_ours ::
|
700
|
+
# For unmerged files, checkout stage 2 from index (NOT IMPLEMENTED).
|
701
|
+
#
|
702
|
+
# :use_theirs ::
|
703
|
+
# For unmerged files, checkout stage 3 from index (NOT IMPLEMENTED).
|
704
|
+
#
|
705
|
+
# :update_submodules ::
|
706
|
+
# Recursively checkout submodules with same options (NOT
|
707
|
+
# IMPLEMENTED).
|
708
|
+
#
|
709
|
+
# :update_submodules_if_changed ::
|
710
|
+
# Recursively checkout submodules if HEAD moved in super repo (NOT
|
711
|
+
# IMPLEMENTED).
|
712
|
+
#
|
713
|
+
# :disable_filters ::
|
714
|
+
# If +true+, filters like CRLF line conversion will be disabled.
|
715
|
+
#
|
716
|
+
# :dir_mode ::
|
717
|
+
# Mode for newly created directories. Default: +0755+.
|
718
|
+
#
|
719
|
+
# :file_mode ::
|
720
|
+
# Mode for newly created files. Default: +0755+ or +0644+.
|
721
|
+
#
|
722
|
+
# :file_open_flags ::
|
723
|
+
# Mode for opening files. Default:
|
724
|
+
# <code>IO::CREAT | IO::TRUNC | IO::WRONLY</code>.
|
725
|
+
#
|
726
|
+
# :notify_flags ::
|
727
|
+
# A single symbol or an array of symbols representing the cases in
|
728
|
+
# which the +:notify+ callback should be invoked. Possible values are:
|
729
|
+
#
|
730
|
+
# :none ::
|
731
|
+
# Do not invoke the +:notify+ callback (default).
|
732
|
+
#
|
733
|
+
# :conflict ::
|
734
|
+
# Invoke the callback for conflicting paths.
|
735
|
+
#
|
736
|
+
# :dirty ::
|
737
|
+
# Invoke the callback for "dirty" files, i.e. those that do not need
|
738
|
+
# an update but no longer match the baseline.
|
739
|
+
#
|
740
|
+
# :updated ::
|
741
|
+
# Invoke the callback for any file that was changed.
|
742
|
+
#
|
743
|
+
# :untracked ::
|
744
|
+
# Invoke the callback for untracked files.
|
745
|
+
#
|
746
|
+
# :ignored ::
|
747
|
+
# Invoke the callback for ignored files.
|
748
|
+
#
|
749
|
+
# :all ::
|
750
|
+
# Invoke the callback for all these cases.
|
751
|
+
#
|
752
|
+
# :paths ::
|
753
|
+
# A glob string or an array of glob strings specifying which paths
|
754
|
+
# should be taken into account for the checkout operation. +nil+ will
|
755
|
+
# match all files. Default: +nil+.
|
756
|
+
#
|
757
|
+
# :baseline ::
|
758
|
+
# A Rugged::Tree that represents the current, expected contents of the
|
759
|
+
# workdir. Default: +HEAD+.
|
760
|
+
#
|
761
|
+
# :target_directory ::
|
762
|
+
# A path to an alternative workdir directory in which the checkout
|
763
|
+
# should be performed.
|
764
|
+
def checkout(ref, options = {}, start_point = "HEAD")
|
765
|
+
if options[:b]
|
766
|
+
rugged.branches.create(ref, start_point)
|
767
|
+
options.delete(:b)
|
768
|
+
end
|
769
|
+
default_options = { strategy: [:recreate_missing, :safe] }
|
770
|
+
rugged.checkout(ref, default_options.merge(options))
|
771
|
+
end
|
772
|
+
|
773
|
+
# Delete the specified branch from the repository
|
774
|
+
def delete_branch(branch_name)
|
775
|
+
rugged.branches.delete(branch_name)
|
776
|
+
end
|
777
|
+
|
778
|
+
# Create a new branch named **ref+ based on **stat_point+, HEAD by default
|
779
|
+
#
|
780
|
+
# Examples:
|
781
|
+
# create_branch("feature")
|
782
|
+
# create_branch("other-feature", "master")
|
783
|
+
def create_branch(ref, start_point = "HEAD")
|
784
|
+
rugged_ref = rugged.branches.create(ref, start_point)
|
785
|
+
Bringit::Branch.new(self, rugged_ref.name, rugged_ref.target)
|
786
|
+
rescue Rugged::ReferenceError => e
|
787
|
+
raise InvalidRef.new("Branch #{ref} already exists") if e.to_s =~ /'refs\/heads\/#{ref}'/
|
788
|
+
raise InvalidRef.new("Invalid reference #{start_point}")
|
789
|
+
end
|
790
|
+
|
791
|
+
# Return an array of this repository's remote names
|
792
|
+
def remote_names
|
793
|
+
rugged.remotes.each_name.to_a
|
794
|
+
end
|
795
|
+
|
796
|
+
# Delete the specified remote from this repository.
|
797
|
+
def remote_delete(remote_name)
|
798
|
+
rugged.remotes.delete(remote_name)
|
799
|
+
end
|
800
|
+
|
801
|
+
# Add a new remote to this repository. Returns a Rugged::Remote object
|
802
|
+
def remote_add(remote_name, url)
|
803
|
+
rugged.remotes.create(remote_name, url)
|
804
|
+
end
|
805
|
+
|
806
|
+
# Update the specified remote using the values in the +options+ hash
|
807
|
+
#
|
808
|
+
# Example
|
809
|
+
# repo.update_remote("origin", url: "path/to/repo")
|
810
|
+
def remote_update(remote_name, options = {})
|
811
|
+
# TODO: Implement other remote options
|
812
|
+
rugged.remotes.set_url(remote_name, options[:url]) if options[:url]
|
813
|
+
end
|
814
|
+
|
815
|
+
# Fetch the specified remote
|
816
|
+
def fetch(remote_name)
|
817
|
+
rugged.remotes[remote_name].fetch
|
818
|
+
end
|
819
|
+
|
820
|
+
# Push +*refspecs+ to the remote identified by +remote_name+.
|
821
|
+
def push(remote_name, *refspecs)
|
822
|
+
rugged.remotes[remote_name].push(refspecs)
|
823
|
+
end
|
824
|
+
|
825
|
+
# Merge the +source_name+ branch into the +target_name+ branch. This is
|
826
|
+
# equivalent to `git merge --no_ff +source_name+`, since a merge commit
|
827
|
+
# is always created.
|
828
|
+
def merge(source_name, target_name, options = {})
|
829
|
+
our_commit = rugged.branches[target_name].target
|
830
|
+
their_commit = rugged.branches[source_name].target
|
831
|
+
|
832
|
+
raise "Invalid merge target" if our_commit.nil?
|
833
|
+
raise "Invalid merge source" if their_commit.nil?
|
834
|
+
|
835
|
+
merge_index = rugged.merge_commits(our_commit, their_commit)
|
836
|
+
return false if merge_index.conflicts?
|
837
|
+
|
838
|
+
actual_options = options.merge(
|
839
|
+
parents: [our_commit, their_commit],
|
840
|
+
tree: merge_index.write_tree(rugged),
|
841
|
+
update_ref: "refs/heads/#{target_name}"
|
842
|
+
)
|
843
|
+
Rugged::Commit.create(rugged, actual_options)
|
844
|
+
end
|
845
|
+
|
846
|
+
def commits_since(from_date)
|
847
|
+
walker = Rugged::Walker.new(rugged)
|
848
|
+
walker.sorting(Rugged::SORT_DATE | Rugged::SORT_REVERSE)
|
849
|
+
|
850
|
+
rugged.references.each("refs/heads/*") do |ref|
|
851
|
+
walker.push(ref.target_id)
|
852
|
+
end
|
853
|
+
|
854
|
+
commits = []
|
855
|
+
walker.each do |commit|
|
856
|
+
break if commit.author[:time].to_date < from_date
|
857
|
+
commits.push(commit)
|
858
|
+
end
|
859
|
+
|
860
|
+
commits
|
861
|
+
end
|
862
|
+
|
863
|
+
AUTOCRLF_VALUES = {
|
864
|
+
"true" => true,
|
865
|
+
"false" => false,
|
866
|
+
"input" => :input
|
867
|
+
}.freeze
|
868
|
+
|
869
|
+
def autocrlf
|
870
|
+
AUTOCRLF_VALUES[rugged.config['core.autocrlf']]
|
871
|
+
end
|
872
|
+
|
873
|
+
def autocrlf=(value)
|
874
|
+
rugged.config['core.autocrlf'] = AUTOCRLF_VALUES.invert[value]
|
875
|
+
end
|
876
|
+
|
877
|
+
# Returns result like "git ls-files" , recursive and full file path
|
878
|
+
#
|
879
|
+
# Ex.
|
880
|
+
# repo.ls_files('master')
|
881
|
+
#
|
882
|
+
def ls_files(ref)
|
883
|
+
actual_ref = ref || root_ref
|
884
|
+
|
885
|
+
begin
|
886
|
+
sha_from_ref(actual_ref)
|
887
|
+
rescue Rugged::OdbError, Rugged::InvalidError, Rugged::ReferenceError
|
888
|
+
# Return an empty array if the ref wasn't found
|
889
|
+
return []
|
890
|
+
end
|
891
|
+
|
892
|
+
cmd = %W(git --git-dir=#{path} ls-tree)
|
893
|
+
cmd += %w(-r)
|
894
|
+
cmd += %w(--full-tree)
|
895
|
+
cmd += %w(--full-name)
|
896
|
+
cmd += %W(-- #{actual_ref})
|
897
|
+
|
898
|
+
raw_output = IO.popen(cmd, &:read).split("\n").map do |f|
|
899
|
+
stuff, path = f.split("\t")
|
900
|
+
_mode, type, _sha = stuff.split(" ")
|
901
|
+
path if type == "blob"
|
902
|
+
# Contain only blob type
|
903
|
+
end
|
904
|
+
|
905
|
+
raw_output.compact
|
906
|
+
end
|
907
|
+
|
908
|
+
def copy_gitattributes(ref)
|
909
|
+
begin
|
910
|
+
commit = lookup(ref)
|
911
|
+
rescue Rugged::ReferenceError
|
912
|
+
raise InvalidRef.new("Ref #{ref} is invalid")
|
913
|
+
end
|
914
|
+
|
915
|
+
# Create the paths
|
916
|
+
info_dir_path = File.join(path, 'info')
|
917
|
+
info_attributes_path = File.join(info_dir_path, 'attributes')
|
918
|
+
|
919
|
+
begin
|
920
|
+
# Retrieve the contents of the blob
|
921
|
+
gitattributes_content = blob_content(commit, '.gitattributes')
|
922
|
+
rescue InvalidBlobName
|
923
|
+
# No .gitattributes found. Should now remove any info/attributes and return
|
924
|
+
File.delete(info_attributes_path) if File.exist?(info_attributes_path)
|
925
|
+
return
|
926
|
+
end
|
927
|
+
|
928
|
+
# Create the info directory if needed
|
929
|
+
Dir.mkdir(info_dir_path) unless File.directory?(info_dir_path)
|
930
|
+
|
931
|
+
# Write the contents of the .gitattributes file to info/attributes
|
932
|
+
# Use binary mode to prevent Rails from converting ASCII-8BIT to UTF-8
|
933
|
+
File.open(info_attributes_path, "wb") do |file|
|
934
|
+
file.write(gitattributes_content)
|
935
|
+
end
|
936
|
+
end
|
937
|
+
|
938
|
+
# Checks if the blob should be diffable according to its attributes
|
939
|
+
def diffable?(blob)
|
940
|
+
attributes(blob.path).fetch('diff') { blob.text? }
|
941
|
+
end
|
942
|
+
|
943
|
+
# Returns the Git attributes for the given file path.
|
944
|
+
#
|
945
|
+
# See `Bringit::Attributes` for more information.
|
946
|
+
def attributes(path)
|
947
|
+
@attributes.attributes(path)
|
948
|
+
end
|
949
|
+
|
950
|
+
private
|
951
|
+
|
952
|
+
# Get the content of a blob for a given commit. If the blob is a commit
|
953
|
+
# (for submodules) then return the blob's OID.
|
954
|
+
def blob_content(commit, blob_name)
|
955
|
+
blob_entry = tree_entry(commit, blob_name)
|
956
|
+
|
957
|
+
unless blob_entry
|
958
|
+
raise InvalidBlobName.new("Invalid blob name: #{blob_name}")
|
959
|
+
end
|
960
|
+
|
961
|
+
case blob_entry[:type]
|
962
|
+
when :commit
|
963
|
+
blob_entry[:oid]
|
964
|
+
when :tree
|
965
|
+
raise InvalidBlobName.new("#{blob_name} is a tree, not a blob")
|
966
|
+
when :blob
|
967
|
+
rugged.lookup(blob_entry[:oid]).content
|
968
|
+
end
|
969
|
+
end
|
970
|
+
|
971
|
+
# Parses the contents of a .gitmodules file and returns a hash of
|
972
|
+
# submodule information.
|
973
|
+
def parse_gitmodules(commit, content)
|
974
|
+
results = {}
|
975
|
+
|
976
|
+
current = ""
|
977
|
+
content.split("\n").each do |txt|
|
978
|
+
if txt =~ /^\s*\[/
|
979
|
+
current = txt.match(/(?<=").*(?=")/)[0]
|
980
|
+
results[current] = {}
|
981
|
+
else
|
982
|
+
next unless results[current]
|
983
|
+
match_data = txt.match(/(\w+)\s*=\s*(.*)/)
|
984
|
+
next unless match_data
|
985
|
+
target = match_data[2].chomp
|
986
|
+
results[current][match_data[1]] = target
|
987
|
+
|
988
|
+
if match_data[1] == "path"
|
989
|
+
begin
|
990
|
+
results[current]["id"] = blob_content(commit, target)
|
991
|
+
rescue InvalidBlobName
|
992
|
+
results.delete(current)
|
993
|
+
end
|
994
|
+
end
|
995
|
+
end
|
996
|
+
end
|
997
|
+
|
998
|
+
results
|
999
|
+
end
|
1000
|
+
|
1001
|
+
# Returns true if +commit+ introduced changes to +path+, using commit
|
1002
|
+
# trees to make that determination. Uses the history simplification
|
1003
|
+
# rules that `git log` uses by default, where a commit is omitted if it
|
1004
|
+
# is TREESAME to any parent.
|
1005
|
+
#
|
1006
|
+
# If the +follow+ option is true and the file specified by +path+ was
|
1007
|
+
# renamed, then the path value is set to the old path.
|
1008
|
+
def commit_touches_path?(commit, path, follow, walker)
|
1009
|
+
entry = tree_entry(commit, path)
|
1010
|
+
|
1011
|
+
if commit.parents.empty?
|
1012
|
+
# This is the root commit, return true if it has +path+ in its tree
|
1013
|
+
return !entry.nil?
|
1014
|
+
end
|
1015
|
+
|
1016
|
+
num_treesame = 0
|
1017
|
+
commit.parents.each do |parent|
|
1018
|
+
parent_entry = tree_entry(parent, path)
|
1019
|
+
|
1020
|
+
# Only follow the first TREESAME parent for merge commits
|
1021
|
+
if num_treesame > 0
|
1022
|
+
walker.hide(parent)
|
1023
|
+
next
|
1024
|
+
end
|
1025
|
+
|
1026
|
+
if entry.nil? && parent_entry.nil?
|
1027
|
+
num_treesame += 1
|
1028
|
+
elsif entry && parent_entry && entry[:oid] == parent_entry[:oid]
|
1029
|
+
num_treesame += 1
|
1030
|
+
end
|
1031
|
+
end
|
1032
|
+
|
1033
|
+
case num_treesame
|
1034
|
+
when 0
|
1035
|
+
detect_rename(commit, commit.parents.first, path) if follow
|
1036
|
+
true
|
1037
|
+
else false
|
1038
|
+
end
|
1039
|
+
end
|
1040
|
+
|
1041
|
+
# Find the entry for +path+ in the tree for +commit+
|
1042
|
+
def tree_entry(commit, path)
|
1043
|
+
pathname = Pathname.new(path)
|
1044
|
+
first = true
|
1045
|
+
tmp_entry = nil
|
1046
|
+
|
1047
|
+
pathname.each_filename do |dir|
|
1048
|
+
if first
|
1049
|
+
tmp_entry = commit.tree[dir]
|
1050
|
+
first = false
|
1051
|
+
elsif tmp_entry.nil?
|
1052
|
+
return nil
|
1053
|
+
else
|
1054
|
+
tmp_entry = rugged.lookup(tmp_entry[:oid])
|
1055
|
+
return nil unless tmp_entry.type == :tree
|
1056
|
+
tmp_entry = tmp_entry[dir]
|
1057
|
+
end
|
1058
|
+
end
|
1059
|
+
|
1060
|
+
tmp_entry
|
1061
|
+
end
|
1062
|
+
|
1063
|
+
# Compare +commit+ and +parent+ for +path+. If +path+ is a file and was
|
1064
|
+
# renamed in +commit+, then set +path+ to the old filename.
|
1065
|
+
def detect_rename(commit, parent, path)
|
1066
|
+
diff = parent.diff(commit, paths: [path], disable_pathspec_match: true)
|
1067
|
+
|
1068
|
+
# If +path+ is a filename, not a directory, then we should only have
|
1069
|
+
# one delta. We don't need to follow renames for directories.
|
1070
|
+
return nil if diff.each_delta.count > 1
|
1071
|
+
|
1072
|
+
delta = diff.each_delta.first
|
1073
|
+
if delta.added?
|
1074
|
+
full_diff = parent.diff(commit)
|
1075
|
+
full_diff.find_similar!
|
1076
|
+
|
1077
|
+
full_diff.each_delta do |full_delta|
|
1078
|
+
if full_delta.renamed? && path == full_delta.new_file[:path]
|
1079
|
+
# Look for the old path in ancestors
|
1080
|
+
path.replace(full_delta.old_file[:path])
|
1081
|
+
end
|
1082
|
+
end
|
1083
|
+
end
|
1084
|
+
end
|
1085
|
+
|
1086
|
+
def archive_to_file(treeish = 'master', filename = 'archive.tar.gz', format = nil, compress_cmd = %w(gzip -n))
|
1087
|
+
git_archive_cmd = %W(git --git-dir=#{path} archive)
|
1088
|
+
|
1089
|
+
# Put files into a directory before archiving
|
1090
|
+
prefix = "#{archive_name(treeish)}/"
|
1091
|
+
git_archive_cmd << "--prefix=#{prefix}"
|
1092
|
+
|
1093
|
+
# Format defaults to tar
|
1094
|
+
git_archive_cmd << "--format=#{format}" if format
|
1095
|
+
|
1096
|
+
git_archive_cmd += %W(-- #{treeish})
|
1097
|
+
|
1098
|
+
open(filename, 'w') do |file|
|
1099
|
+
# Create a pipe to act as the '|' in 'git archive ... | gzip'
|
1100
|
+
pipe_rd, pipe_wr = IO.pipe
|
1101
|
+
|
1102
|
+
# Get the compression process ready to accept data from the read end
|
1103
|
+
# of the pipe
|
1104
|
+
compress_pid = spawn(*nice(compress_cmd), in: pipe_rd, out: file)
|
1105
|
+
# The read end belongs to the compression process now; we should
|
1106
|
+
# close our file descriptor for it.
|
1107
|
+
pipe_rd.close
|
1108
|
+
|
1109
|
+
# Start 'git archive' and tell it to write into the write end of the
|
1110
|
+
# pipe.
|
1111
|
+
git_archive_pid = spawn(*nice(git_archive_cmd), out: pipe_wr)
|
1112
|
+
# The write end belongs to 'git archive' now; close it.
|
1113
|
+
pipe_wr.close
|
1114
|
+
|
1115
|
+
# When 'git archive' and the compression process are finished, we are
|
1116
|
+
# done.
|
1117
|
+
Process.waitpid(git_archive_pid)
|
1118
|
+
raise "#{git_archive_cmd.join(' ')} failed" unless $?.success?
|
1119
|
+
Process.waitpid(compress_pid)
|
1120
|
+
raise "#{compress_cmd.join(' ')} failed" unless $?.success?
|
1121
|
+
end
|
1122
|
+
end
|
1123
|
+
|
1124
|
+
def nice(cmd)
|
1125
|
+
nice_cmd = %w(nice -n 20)
|
1126
|
+
unless unsupported_platform?
|
1127
|
+
nice_cmd += %w(ionice -c 2 -n 7)
|
1128
|
+
end
|
1129
|
+
nice_cmd + cmd
|
1130
|
+
end
|
1131
|
+
|
1132
|
+
def unsupported_platform?
|
1133
|
+
%w[darwin freebsd solaris].map { |platform| RUBY_PLATFORM.include?(platform) }.any?
|
1134
|
+
end
|
1135
|
+
|
1136
|
+
# Returns true if the index entry has the special file mode that denotes
|
1137
|
+
# a submodule.
|
1138
|
+
def submodule?(index_entry)
|
1139
|
+
index_entry[:mode] == 57344
|
1140
|
+
end
|
1141
|
+
|
1142
|
+
# Return a Rugged::Index that has read from the tree at +ref_name+
|
1143
|
+
def populated_index(ref_name)
|
1144
|
+
commit = rev_parse_target(ref_name)
|
1145
|
+
index = rugged.index
|
1146
|
+
index.read_tree(commit.tree)
|
1147
|
+
index
|
1148
|
+
end
|
1149
|
+
|
1150
|
+
# Return an array of BlobSnippets for lines in +file_contents+ that match
|
1151
|
+
# +query+
|
1152
|
+
def build_greps(file_contents, query, ref, filename)
|
1153
|
+
# The file_contents string is potentially huge so we make sure to loop
|
1154
|
+
# through it one line at a time. This gives Ruby the chance to GC lines
|
1155
|
+
# we are not interested in.
|
1156
|
+
#
|
1157
|
+
# We need to do a little extra work because we are not looking for just
|
1158
|
+
# the lines that matches the query, but also for the context
|
1159
|
+
# (surrounding lines). We will use Enumerable#each_cons to efficiently
|
1160
|
+
# loop through the lines while keeping surrounding lines on hand.
|
1161
|
+
#
|
1162
|
+
# First, we turn "foo\nbar\nbaz" into
|
1163
|
+
# [
|
1164
|
+
# [nil, -3], [nil, -2], [nil, -1],
|
1165
|
+
# ['foo', 0], ['bar', 1], ['baz', 3],
|
1166
|
+
# [nil, 4], [nil, 5], [nil, 6]
|
1167
|
+
# ]
|
1168
|
+
lines_with_index = Enumerator.new do |yielder|
|
1169
|
+
# Yield fake 'before' lines for the first line of file_contents
|
1170
|
+
(-SEARCH_CONTEXT_LINES..-1).each do |i|
|
1171
|
+
yielder.yield [nil, i]
|
1172
|
+
end
|
1173
|
+
|
1174
|
+
# Yield the actual file contents
|
1175
|
+
count = 0
|
1176
|
+
file_contents.each_line do |line|
|
1177
|
+
line.chomp!
|
1178
|
+
yielder.yield [line, count]
|
1179
|
+
count += 1
|
1180
|
+
end
|
1181
|
+
|
1182
|
+
# Yield fake 'after' lines for the last line of file_contents
|
1183
|
+
(count + 1..count + SEARCH_CONTEXT_LINES).each do |i|
|
1184
|
+
yielder.yield [nil, i]
|
1185
|
+
end
|
1186
|
+
end
|
1187
|
+
|
1188
|
+
greps = []
|
1189
|
+
|
1190
|
+
# Loop through consecutive blocks of lines with indexes
|
1191
|
+
lines_with_index.each_cons(2 * SEARCH_CONTEXT_LINES + 1) do |line_block|
|
1192
|
+
# Get the 'middle' line and index from the block
|
1193
|
+
line, _ = line_block[SEARCH_CONTEXT_LINES]
|
1194
|
+
|
1195
|
+
next unless line && line.match(/#{Regexp.escape(query)}/i)
|
1196
|
+
|
1197
|
+
# Yay, 'line' contains a match!
|
1198
|
+
# Get an array with just the context lines (no indexes)
|
1199
|
+
match_with_context = line_block.map(&:first)
|
1200
|
+
# Remove 'nil' lines in case we are close to the first or last line
|
1201
|
+
match_with_context.compact!
|
1202
|
+
|
1203
|
+
# Get the line number (1-indexed) of the first context line
|
1204
|
+
first_context_line_number = line_block[0][1] + 1
|
1205
|
+
|
1206
|
+
greps << Bringit::BlobSnippet.new(
|
1207
|
+
ref,
|
1208
|
+
match_with_context,
|
1209
|
+
first_context_line_number,
|
1210
|
+
filename
|
1211
|
+
)
|
1212
|
+
end
|
1213
|
+
|
1214
|
+
greps
|
1215
|
+
end
|
1216
|
+
|
1217
|
+
# Return the Rugged patches for the diff between +from+ and +to+.
|
1218
|
+
def diff_patches(from, to, options = {}, *paths)
|
1219
|
+
options ||= {}
|
1220
|
+
paths = paths.map { |p| p.sub(%r{\A/}, '') }
|
1221
|
+
break_rewrites = options[:break_rewrites]
|
1222
|
+
actual_options = Bringit::Diff.filter_diff_options(options.merge(paths: paths))
|
1223
|
+
|
1224
|
+
diff = rugged.diff(from, to, actual_options)
|
1225
|
+
return [] if diff.nil?
|
1226
|
+
diff.find_similar!(break_rewrites: break_rewrites)
|
1227
|
+
diff.each_patch
|
1228
|
+
end
|
1229
|
+
end
|
1230
|
+
end
|