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.
@@ -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'
@@ -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
@@ -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