bringit 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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