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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: be4c0173cdf310733872d3c191685be11d84ad75
|
4
|
+
data.tar.gz: '0096d31422d89baaa73df9475dac864c6166b91e'
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 2fb3f7d165f9ad42a5ca4880034d0dc613bd78c9ff5020e1c5bbc9439d3e12670475862b37f8dc9831f96d3fc7bae4c3cde6194756351dc6e3e0ffe63c3d0a81
|
7
|
+
data.tar.gz: '08ba4d74431dee2c8a077b26dba6667d19704daf415d8fe06db5f1ae8f0009f3b8e28956310e744bba68d8d29e4e493aea96e5ac37d6f8569ccc9f78983f935e'
|
data/lib/bringit.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# Libraries
|
2
|
+
require 'ostruct'
|
3
|
+
require 'fileutils'
|
4
|
+
require 'linguist'
|
5
|
+
require 'active_support/core_ext/hash/compact'
|
6
|
+
require 'active_support/core_ext/hash/keys'
|
7
|
+
require 'active_support/core_ext/hash/slice'
|
8
|
+
require 'active_support/core_ext/object/blank'
|
9
|
+
require 'active_support/core_ext/object/try'
|
10
|
+
require 'active_support/core_ext/module/delegation'
|
11
|
+
require 'rugged'
|
12
|
+
require "charlock_holmes"
|
13
|
+
|
14
|
+
# Bringit
|
15
|
+
require_relative "bringit/popen"
|
16
|
+
require_relative 'bringit/encoding_helper'
|
17
|
+
require_relative 'bringit/path_helper'
|
18
|
+
require_relative "bringit/blame"
|
19
|
+
require_relative "bringit/blob"
|
20
|
+
require_relative "bringit/commit"
|
21
|
+
require_relative "bringit/commit_stats"
|
22
|
+
require_relative "bringit/compare"
|
23
|
+
require_relative "bringit/diff"
|
24
|
+
require_relative "bringit/diff_collection"
|
25
|
+
require_relative "bringit/hook"
|
26
|
+
require_relative "bringit/index"
|
27
|
+
require_relative "bringit/rev_list"
|
28
|
+
require_relative "bringit/repository"
|
29
|
+
require_relative "bringit/tree"
|
30
|
+
require_relative "bringit/blob_snippet"
|
31
|
+
require_relative "bringit/ref"
|
32
|
+
require_relative "bringit/branch"
|
33
|
+
require_relative "bringit/tag"
|
34
|
+
require_relative "bringit/util"
|
35
|
+
require_relative "bringit/attributes"
|
36
|
+
require_relative "bringit/version_info"
|
37
|
+
require_relative "bringit/committing"
|
38
|
+
require_relative "bringit/cloning"
|
39
|
+
require_relative "bringit/pulling"
|
40
|
+
require_relative "bringit/wrapper"
|
41
|
+
|
42
|
+
module Bringit
|
43
|
+
BLANK_SHA = ('0' * 40).freeze
|
44
|
+
TAG_REF_PREFIX = "refs/tags/".freeze
|
45
|
+
BRANCH_REF_PREFIX = "refs/heads/".freeze
|
46
|
+
|
47
|
+
class << self
|
48
|
+
def ref_name(ref)
|
49
|
+
ref.sub(/\Arefs\/(tags|heads)\//, '')
|
50
|
+
end
|
51
|
+
|
52
|
+
def branch_name(ref)
|
53
|
+
ref = ref.to_s
|
54
|
+
if self.branch_ref?(ref)
|
55
|
+
self.ref_name(ref)
|
56
|
+
else
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def committer_hash(email:, name:)
|
62
|
+
return if email.nil? || name.nil?
|
63
|
+
|
64
|
+
{
|
65
|
+
email: email,
|
66
|
+
name: name,
|
67
|
+
time: Time.now
|
68
|
+
}
|
69
|
+
end
|
70
|
+
|
71
|
+
def tag_name(ref)
|
72
|
+
ref = ref.to_s
|
73
|
+
if self.tag_ref?(ref)
|
74
|
+
self.ref_name(ref)
|
75
|
+
else
|
76
|
+
nil
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def tag_ref?(ref)
|
81
|
+
ref.start_with?(TAG_REF_PREFIX)
|
82
|
+
end
|
83
|
+
|
84
|
+
def branch_ref?(ref)
|
85
|
+
ref.start_with?(BRANCH_REF_PREFIX)
|
86
|
+
end
|
87
|
+
|
88
|
+
def blank_ref?(ref)
|
89
|
+
ref == BLANK_SHA
|
90
|
+
end
|
91
|
+
|
92
|
+
def version
|
93
|
+
Bringit::VersionInfo.parse(Bringit::Popen.popen(%W(git --version)).first)
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,129 @@
|
|
1
|
+
module Bringit
|
2
|
+
# Class for parsing Git attribute files and extracting the attributes for
|
3
|
+
# file patterns.
|
4
|
+
#
|
5
|
+
# Unlike Rugged this parser only needs a single IO call (a call to `open`),
|
6
|
+
# vastly reducing the time spent in extracting attributes.
|
7
|
+
#
|
8
|
+
# This class _only_ supports parsing the attributes file located at
|
9
|
+
# `$GIT_DIR/info/attributes` as GitLab doesn't use any other files
|
10
|
+
# (`.gitattributes` is copied to this particular path).
|
11
|
+
#
|
12
|
+
# Basic usage:
|
13
|
+
#
|
14
|
+
# attributes = Bringit::Attributes.new(some_repo.path)
|
15
|
+
#
|
16
|
+
# attributes.attributes('README.md') # => { "eol" => "lf }
|
17
|
+
class Attributes
|
18
|
+
# path - The path to the Git repository.
|
19
|
+
def initialize(path)
|
20
|
+
@path = File.expand_path(path)
|
21
|
+
@patterns = nil
|
22
|
+
end
|
23
|
+
|
24
|
+
# Returns all the Git attributes for the given path.
|
25
|
+
#
|
26
|
+
# path - A path to a file for which to get the attributes.
|
27
|
+
#
|
28
|
+
# Returns a Hash.
|
29
|
+
def attributes(path)
|
30
|
+
full_path = File.join(@path, path)
|
31
|
+
|
32
|
+
patterns.each do |pattern, attrs|
|
33
|
+
return attrs if File.fnmatch?(pattern, full_path)
|
34
|
+
end
|
35
|
+
|
36
|
+
{}
|
37
|
+
end
|
38
|
+
|
39
|
+
# Returns a Hash containing the file patterns and their attributes.
|
40
|
+
def patterns
|
41
|
+
@patterns ||= parse_file
|
42
|
+
end
|
43
|
+
|
44
|
+
# Parses an attribute string.
|
45
|
+
#
|
46
|
+
# These strings can be in the following formats:
|
47
|
+
#
|
48
|
+
# text # => { "text" => true }
|
49
|
+
# -text # => { "text" => false }
|
50
|
+
# key=value # => { "key" => "value" }
|
51
|
+
#
|
52
|
+
# string - The string to parse.
|
53
|
+
#
|
54
|
+
# Returns a Hash containing the attributes and their values.
|
55
|
+
def parse_attributes(string)
|
56
|
+
values = {}
|
57
|
+
dash = '-'
|
58
|
+
equal = '='
|
59
|
+
binary = 'binary'
|
60
|
+
|
61
|
+
string.split(/\s+/).each do |chunk|
|
62
|
+
# Data such as "foo = bar" should be treated as "foo" and "bar" being
|
63
|
+
# separate boolean attributes.
|
64
|
+
next if chunk == equal
|
65
|
+
|
66
|
+
key = chunk
|
67
|
+
|
68
|
+
# Input: "-foo"
|
69
|
+
if chunk.start_with?(dash)
|
70
|
+
key = chunk.byteslice(1, chunk.length - 1)
|
71
|
+
value = false
|
72
|
+
|
73
|
+
# Input: "foo=bar"
|
74
|
+
elsif chunk.include?(equal)
|
75
|
+
key, value = chunk.split(equal, 2)
|
76
|
+
|
77
|
+
# Input: "foo"
|
78
|
+
else
|
79
|
+
value = true
|
80
|
+
end
|
81
|
+
|
82
|
+
values[key] = value
|
83
|
+
|
84
|
+
# When the "binary" option is set the "diff" option should be set to
|
85
|
+
# the inverse. If "diff" is later set it should overwrite the
|
86
|
+
# automatically set value.
|
87
|
+
values['diff'] = false if key == binary && value
|
88
|
+
end
|
89
|
+
|
90
|
+
values
|
91
|
+
end
|
92
|
+
|
93
|
+
# Iterates over every line in the attributes file.
|
94
|
+
def each_line
|
95
|
+
full_path = File.join(@path, 'info/attributes')
|
96
|
+
|
97
|
+
return unless File.exist?(full_path)
|
98
|
+
|
99
|
+
File.open(full_path, 'r') do |handle|
|
100
|
+
handle.each_line do |line|
|
101
|
+
break unless line.valid_encoding?
|
102
|
+
|
103
|
+
yield line.strip
|
104
|
+
end
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
# Parses the Git attributes file.
|
111
|
+
def parse_file
|
112
|
+
pairs = []
|
113
|
+
comment = '#'
|
114
|
+
|
115
|
+
each_line do |line|
|
116
|
+
next if line.start_with?(comment) || line.empty?
|
117
|
+
|
118
|
+
pattern, attrs = line.split(/\s+/, 2)
|
119
|
+
|
120
|
+
parsed = attrs ? parse_attributes(attrs) : {}
|
121
|
+
|
122
|
+
pairs << [File.join(@path, pattern), parsed]
|
123
|
+
end
|
124
|
+
|
125
|
+
# Newer entries take precedence over older entries.
|
126
|
+
pairs.reverse.to_h
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
module Bringit
|
2
|
+
class Blame
|
3
|
+
include Bringit::EncodingHelper
|
4
|
+
|
5
|
+
attr_reader :lines, :blames
|
6
|
+
|
7
|
+
def initialize(repository, sha, path)
|
8
|
+
@repo = repository
|
9
|
+
@sha = sha
|
10
|
+
@path = path
|
11
|
+
@lines = []
|
12
|
+
@blames = load_blame
|
13
|
+
end
|
14
|
+
|
15
|
+
def each
|
16
|
+
@blames.each do |blame|
|
17
|
+
yield(
|
18
|
+
Bringit::Commit.new(blame.commit, @repo),
|
19
|
+
blame.line
|
20
|
+
)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
private
|
25
|
+
|
26
|
+
def load_blame
|
27
|
+
cmd = %W(git --git-dir=#{@repo.path} blame -p #{@sha} -- #{@path})
|
28
|
+
# Read in binary mode to ensure ASCII-8BIT
|
29
|
+
raw_output = IO.popen(cmd, 'rb') {|io| io.read }
|
30
|
+
output = encode_utf8(raw_output)
|
31
|
+
process_raw_blame output
|
32
|
+
end
|
33
|
+
|
34
|
+
def process_raw_blame(output)
|
35
|
+
lines, final = [], []
|
36
|
+
info, commits = {}, {}
|
37
|
+
|
38
|
+
# process the output
|
39
|
+
output.split("\n").each do |line|
|
40
|
+
if line[0, 1] == "\t"
|
41
|
+
lines << line[1, line.size]
|
42
|
+
elsif m = /^(\w{40}) (\d+) (\d+)/.match(line)
|
43
|
+
commit_id, old_lineno, lineno = m[1], m[2].to_i, m[3].to_i
|
44
|
+
commits[commit_id] = nil unless commits.key?(commit_id)
|
45
|
+
info[lineno] = [commit_id, old_lineno]
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# load all commits in single call
|
50
|
+
commits.keys.each do |key|
|
51
|
+
commits[key] = @repo.lookup(key)
|
52
|
+
end
|
53
|
+
|
54
|
+
# get it together
|
55
|
+
info.sort.each do |lineno, (commit_id, old_lineno)|
|
56
|
+
commit = commits[commit_id]
|
57
|
+
final << BlameLine.new(lineno, old_lineno, commit, lines[lineno - 1])
|
58
|
+
end
|
59
|
+
|
60
|
+
@lines = final
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
class BlameLine
|
65
|
+
attr_accessor :lineno, :oldlineno, :commit, :line
|
66
|
+
def initialize(lineno, oldlineno, commit, line)
|
67
|
+
@lineno = lineno
|
68
|
+
@oldlineno = oldlineno
|
69
|
+
@commit = commit
|
70
|
+
@line = line
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
data/lib/bringit/blob.rb
ADDED
@@ -0,0 +1,175 @@
|
|
1
|
+
module Bringit
|
2
|
+
class Blob
|
3
|
+
include Linguist::BlobHelper
|
4
|
+
include Bringit::EncodingHelper
|
5
|
+
|
6
|
+
# This number is the maximum amount of data that we want to display to
|
7
|
+
# the user. We load as much as we can for encoding detection
|
8
|
+
# (Linguist) and LFS pointer parsing. All other cases where we need full
|
9
|
+
# blob data should use load_all_data!.
|
10
|
+
MAX_DATA_DISPLAY_SIZE = 10485760
|
11
|
+
|
12
|
+
attr_reader :repository
|
13
|
+
attr_accessor :name, :path, :size, :data, :mode, :id, :commit_id, :loaded_size, :binary
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def find(repository, sha, path)
|
17
|
+
path = path&.sub(%r{\A/*}, '')
|
18
|
+
commit = repository.lookup(sha)
|
19
|
+
root_tree = commit.tree
|
20
|
+
|
21
|
+
blob_entry = find_entry_by_path(repository, root_tree.oid, path)
|
22
|
+
|
23
|
+
return nil unless blob_entry
|
24
|
+
|
25
|
+
if blob_entry[:type] == :commit
|
26
|
+
submodule_blob(repository, blob_entry, path, sha)
|
27
|
+
else
|
28
|
+
blob = repository.lookup(blob_entry[:oid])
|
29
|
+
|
30
|
+
if blob
|
31
|
+
new(repository,
|
32
|
+
id: blob.oid,
|
33
|
+
name: blob_entry[:name],
|
34
|
+
size: blob.size,
|
35
|
+
data: blob.content(MAX_DATA_DISPLAY_SIZE),
|
36
|
+
mode: blob_entry[:filemode].to_s(8),
|
37
|
+
path: Bringit::PathHelper.normalize_path(path).to_s,
|
38
|
+
commit_id: sha,
|
39
|
+
binary: blob.binary?
|
40
|
+
)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
def raw(repository, sha)
|
46
|
+
blob = repository.lookup(sha)
|
47
|
+
|
48
|
+
new(repository,
|
49
|
+
id: blob.oid,
|
50
|
+
size: blob.size,
|
51
|
+
data: blob.content(MAX_DATA_DISPLAY_SIZE),
|
52
|
+
binary: blob.binary?
|
53
|
+
)
|
54
|
+
end
|
55
|
+
|
56
|
+
# Recursive search of blob id by path
|
57
|
+
#
|
58
|
+
# Ex.
|
59
|
+
# blog/ # oid: 1a
|
60
|
+
# app/ # oid: 2a
|
61
|
+
# models/ # oid: 3a
|
62
|
+
# file.rb # oid: 4a
|
63
|
+
#
|
64
|
+
#
|
65
|
+
# Blob.find_entry_by_path(repo, '1a', 'app/file.rb') # => '4a'
|
66
|
+
#
|
67
|
+
def find_entry_by_path(repository, root_id, path)
|
68
|
+
root_tree = repository.lookup(root_id)
|
69
|
+
# Strip leading slashes
|
70
|
+
path = path.sub(%r{\A/*}, '')
|
71
|
+
path_arr = path.split('/')
|
72
|
+
|
73
|
+
entry = root_tree.find do |entry|
|
74
|
+
entry[:name] == path_arr[0]
|
75
|
+
end
|
76
|
+
|
77
|
+
return nil unless entry
|
78
|
+
|
79
|
+
if path_arr.size > 1
|
80
|
+
return nil unless entry[:type] == :tree
|
81
|
+
path_arr.shift
|
82
|
+
find_entry_by_path(repository, entry[:oid], path_arr.join('/'))
|
83
|
+
else
|
84
|
+
[:blob, :commit].include?(entry[:type]) ? entry : nil
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
def submodule_blob(repository, blob_entry, path, sha)
|
89
|
+
path = path&.sub(%r{\A/*}, '')
|
90
|
+
new(repository,
|
91
|
+
id: blob_entry[:oid],
|
92
|
+
name: blob_entry[:name],
|
93
|
+
data: '',
|
94
|
+
path: path,
|
95
|
+
commit_id: sha,
|
96
|
+
)
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
100
|
+
def initialize(repository, options)
|
101
|
+
@repository = repository
|
102
|
+
%w(id name path size data mode commit_id binary).each do |key|
|
103
|
+
self.send("#{key}=", options[key.to_sym])
|
104
|
+
end
|
105
|
+
|
106
|
+
@loaded_all_data = false
|
107
|
+
# Retain the actual size before it is encoded
|
108
|
+
@loaded_size = @data.bytesize if @data
|
109
|
+
end
|
110
|
+
|
111
|
+
def binary?
|
112
|
+
@binary.nil? ? super : @binary == true
|
113
|
+
end
|
114
|
+
|
115
|
+
def empty?
|
116
|
+
!data || data == ''
|
117
|
+
end
|
118
|
+
|
119
|
+
def data
|
120
|
+
encode! @data
|
121
|
+
end
|
122
|
+
|
123
|
+
# Load all blob data (not just the first MAX_DATA_DISPLAY_SIZE bytes) into
|
124
|
+
# memory as a Ruby string.
|
125
|
+
def load_all_data!
|
126
|
+
return if @data == '' # don't mess with submodule blobs
|
127
|
+
return @data if @loaded_all_data
|
128
|
+
|
129
|
+
@loaded_all_data = true
|
130
|
+
@data = repository.lookup(id).content
|
131
|
+
@loaded_size = @data.bytesize
|
132
|
+
end
|
133
|
+
|
134
|
+
def name
|
135
|
+
encode! @name
|
136
|
+
end
|
137
|
+
|
138
|
+
# Valid LFS object pointer is a text file consisting of
|
139
|
+
# version
|
140
|
+
# oid
|
141
|
+
# size
|
142
|
+
# see https://github.com/github/git-lfs/blob/v1.1.0/docs/spec.md#the-pointer
|
143
|
+
def lfs_pointer?
|
144
|
+
has_lfs_version_key? && lfs_oid.present? && lfs_size.present?
|
145
|
+
end
|
146
|
+
|
147
|
+
def lfs_oid
|
148
|
+
if has_lfs_version_key?
|
149
|
+
oid = data.match(/(?<=sha256:)([0-9a-f]{64})/)
|
150
|
+
return oid[1] if oid
|
151
|
+
end
|
152
|
+
|
153
|
+
nil
|
154
|
+
end
|
155
|
+
|
156
|
+
def lfs_size
|
157
|
+
if has_lfs_version_key?
|
158
|
+
size = data.match(/(?<=size )([0-9]+)/)
|
159
|
+
return size[1] if size
|
160
|
+
end
|
161
|
+
|
162
|
+
nil
|
163
|
+
end
|
164
|
+
|
165
|
+
def truncated?
|
166
|
+
size && (size > loaded_size)
|
167
|
+
end
|
168
|
+
|
169
|
+
private
|
170
|
+
|
171
|
+
def has_lfs_version_key?
|
172
|
+
!empty? && text? && data.start_with?("version https://git-lfs.github.com/spec")
|
173
|
+
end
|
174
|
+
end
|
175
|
+
end
|