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,41 @@
1
+ module Bringit
2
+ class Compare
3
+ attr_reader :head, :base, :straight
4
+
5
+ def initialize(repository, base, head, straight = false)
6
+ @repository = repository
7
+ @straight = straight
8
+
9
+ unless base && head
10
+ @commits = []
11
+ return
12
+ end
13
+
14
+ @base = Bringit::Commit.find(repository, base.try(:strip))
15
+ @head = Bringit::Commit.find(repository, head.try(:strip))
16
+
17
+ @commits = [] unless @base && @head
18
+ @commits = [] if same
19
+ end
20
+
21
+ def same
22
+ @base && @head && @base.id == @head.id
23
+ end
24
+
25
+ def commits
26
+ return @commits if defined?(@commits)
27
+
28
+ @commits = Bringit::Commit.between(@repository, @base.id, @head.id)
29
+ end
30
+
31
+ def diffs(options = {})
32
+ unless @head && @base
33
+ return Bringit::DiffCollection.new([])
34
+ end
35
+
36
+ paths = options.delete(:paths) || []
37
+ options[:straight] = @straight
38
+ Bringit::Diff.between(@repository, @head.id, @base.id, options, *paths)
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,320 @@
1
+ # Bringit::Diff is a wrapper around native Rugged::Diff object
2
+ module Bringit
3
+ class Diff
4
+ TimeoutError = Class.new(StandardError)
5
+ include Bringit::EncodingHelper
6
+
7
+ # Diff properties
8
+ attr_accessor :old_path, :new_path, :a_mode, :b_mode, :diff
9
+
10
+ # Stats properties
11
+ attr_accessor :new_file, :renamed_file, :deleted_file
12
+
13
+ attr_accessor :too_large
14
+
15
+ # The maximum size of a diff to display.
16
+ DIFF_SIZE_LIMIT = 102400 # 100 KB
17
+
18
+ # The maximum size before a diff is collapsed.
19
+ DIFF_COLLAPSE_LIMIT = 10240 # 10 KB
20
+
21
+ class << self
22
+ def between(repo, head, base, options = {}, *paths)
23
+ straight = options.delete(:straight) || false
24
+
25
+ common_commit = if straight
26
+ base
27
+ else
28
+ # Only show what is new in the source branch
29
+ # compared to the target branch, not the other way
30
+ # around. The linex below with merge_base is
31
+ # equivalent to diff with three dots (git diff
32
+ # branch1...branch2) From the git documentation:
33
+ # "git diff A...B" is equivalent to "git diff
34
+ # $(git-merge-base A B) B"
35
+ repo.merge_base_commit(head, base)
36
+ end
37
+
38
+ options ||= {}
39
+ actual_options = filter_diff_options(options)
40
+ repo.diff(common_commit, head, actual_options, *paths)
41
+ end
42
+
43
+ # Return a copy of the +options+ hash containing only keys that can be
44
+ # passed to Rugged. Allowed options are:
45
+ #
46
+ # :max_size ::
47
+ # An integer specifying the maximum byte size of a file before a it
48
+ # will be treated as binary. The default value is 512MB.
49
+ #
50
+ # :context_lines ::
51
+ # The number of unchanged lines that define the boundary of a hunk
52
+ # (and to display before and after the actual changes). The default is
53
+ # 3.
54
+ #
55
+ # :interhunk_lines ::
56
+ # The maximum number of unchanged lines between hunk boundaries before
57
+ # the hunks will be merged into a one. The default is 0.
58
+ #
59
+ # :old_prefix ::
60
+ # The virtual "directory" to prefix to old filenames in hunk headers.
61
+ # The default is "a".
62
+ #
63
+ # :new_prefix ::
64
+ # The virtual "directory" to prefix to new filenames in hunk headers.
65
+ # The default is "b".
66
+ #
67
+ # :reverse ::
68
+ # If true, the sides of the diff will be reversed.
69
+ #
70
+ # :force_text ::
71
+ # If true, all files will be treated as text, disabling binary
72
+ # attributes & detection.
73
+ #
74
+ # :ignore_whitespace ::
75
+ # If true, all whitespace will be ignored.
76
+ #
77
+ # :ignore_whitespace_change ::
78
+ # If true, changes in amount of whitespace will be ignored.
79
+ #
80
+ # :ignore_whitespace_eol ::
81
+ # If true, whitespace at end of line will be ignored.
82
+ #
83
+ # :ignore_submodules ::
84
+ # if true, submodules will be excluded from the diff completely.
85
+ #
86
+ # :patience ::
87
+ # If true, the "patience diff" algorithm will be used (currenlty
88
+ # unimplemented).
89
+ #
90
+ # :include_ignored ::
91
+ # If true, ignored files will be included in the diff.
92
+ #
93
+ # :include_untracked ::
94
+ # If true, untracked files will be included in the diff.
95
+ #
96
+ # :include_unmodified ::
97
+ # If true, unmodified files will be included in the diff.
98
+ #
99
+ # :recurse_untracked_dirs ::
100
+ # Even if +:include_untracked+ is true, untracked directories will
101
+ # only be marked with a single entry in the diff. If this flag is set
102
+ # to true, all files under ignored directories will be included in the
103
+ # diff, too.
104
+ #
105
+ # :disable_pathspec_match ::
106
+ # If true, the given +*paths+ will be applied as exact matches,
107
+ # instead of as fnmatch patterns.
108
+ #
109
+ # :deltas_are_icase ::
110
+ # If true, filename comparisons will be made with case-insensitivity.
111
+ #
112
+ # :include_untracked_content ::
113
+ # if true, untracked content will be contained in the the diff patch
114
+ # text.
115
+ #
116
+ # :skip_binary_check ::
117
+ # If true, diff deltas will be generated without spending time on
118
+ # binary detection. This is useful to improve performance in cases
119
+ # where the actual file content difference is not needed.
120
+ #
121
+ # :include_typechange ::
122
+ # If true, type changes for files will not be interpreted as deletion
123
+ # of the "old file" and addition of the "new file", but will generate
124
+ # typechange records.
125
+ #
126
+ # :include_typechange_trees ::
127
+ # Even if +:include_typechange+ is true, blob -> tree changes will
128
+ # still usually be handled as a deletion of the blob. If this flag is
129
+ # set to true, blob -> tree changes will be marked as typechanges.
130
+ #
131
+ # :ignore_filemode ::
132
+ # If true, file mode changes will be ignored.
133
+ #
134
+ # :recurse_ignored_dirs ::
135
+ # Even if +:include_ignored+ is true, ignored directories will only be
136
+ # marked with a single entry in the diff. If this flag is set to true,
137
+ # all files under ignored directories will be included in the diff,
138
+ # too.
139
+ def filter_diff_options(options, default_options = {})
140
+ allowed_options = [:max_size, :context_lines, :interhunk_lines,
141
+ :old_prefix, :new_prefix, :reverse, :force_text,
142
+ :ignore_whitespace, :ignore_whitespace_change,
143
+ :ignore_whitespace_eol, :ignore_submodules,
144
+ :patience, :include_ignored, :include_untracked,
145
+ :include_unmodified, :recurse_untracked_dirs,
146
+ :disable_pathspec_match, :deltas_are_icase,
147
+ :include_untracked_content, :skip_binary_check,
148
+ :include_typechange, :include_typechange_trees,
149
+ :ignore_filemode, :recurse_ignored_dirs, :paths,
150
+ :max_files, :max_lines, :all_diffs, :no_collapse]
151
+
152
+ if default_options
153
+ actual_defaults = default_options.dup
154
+ actual_defaults.keep_if do |key|
155
+ allowed_options.include?(key)
156
+ end
157
+ else
158
+ actual_defaults = {}
159
+ end
160
+
161
+ if options
162
+ filtered_opts = options.dup
163
+ filtered_opts.keep_if do |key|
164
+ allowed_options.include?(key)
165
+ end
166
+ filtered_opts = actual_defaults.merge(filtered_opts)
167
+ else
168
+ filtered_opts = actual_defaults
169
+ end
170
+
171
+ filtered_opts
172
+ end
173
+ end
174
+
175
+ def initialize(raw_diff, collapse: false)
176
+ case raw_diff
177
+ when Hash
178
+ init_from_hash(raw_diff, collapse: collapse)
179
+ when Rugged::Patch, Rugged::Diff::Delta
180
+ init_from_rugged(raw_diff, collapse: collapse)
181
+ when nil
182
+ raise "Nil as raw diff passed"
183
+ else
184
+ raise "Invalid raw diff type: #{raw_diff.class}"
185
+ end
186
+ end
187
+
188
+ def serialize_keys
189
+ @serialize_keys ||= %i(diff new_path old_path a_mode b_mode new_file renamed_file deleted_file too_large)
190
+ end
191
+
192
+ def to_hash
193
+ hash = {}
194
+
195
+ keys = serialize_keys
196
+
197
+ keys.each do |key|
198
+ hash[key] = send(key)
199
+ end
200
+
201
+ hash
202
+ end
203
+
204
+ def submodule?
205
+ a_mode == '160000' || b_mode == '160000'
206
+ end
207
+
208
+ def line_count
209
+ @line_count ||= Util.count_lines(@diff)
210
+ end
211
+
212
+ def too_large?
213
+ if @too_large.nil?
214
+ @too_large = @diff.bytesize >= DIFF_SIZE_LIMIT
215
+ else
216
+ @too_large
217
+ end
218
+ end
219
+
220
+ def collapsible?
221
+ @diff.bytesize >= DIFF_COLLAPSE_LIMIT
222
+ end
223
+
224
+ def prune_large_diff!
225
+ @diff = ''
226
+ @line_count = 0
227
+ @too_large = true
228
+ end
229
+
230
+ def collapsed?
231
+ return @collapsed if defined?(@collapsed)
232
+ false
233
+ end
234
+
235
+ def prune_collapsed_diff!
236
+ @diff = ''
237
+ @line_count = 0
238
+ @collapsed = true
239
+ end
240
+
241
+ private
242
+
243
+ def init_from_rugged(rugged, collapse: false)
244
+ if rugged.is_a?(Rugged::Patch)
245
+ init_from_rugged_patch(rugged, collapse: collapse)
246
+ d = rugged.delta
247
+ else
248
+ d = rugged
249
+ end
250
+
251
+ @new_path = encode!(d.new_file[:path])
252
+ @old_path = encode!(d.old_file[:path])
253
+ @a_mode = d.old_file[:mode].to_s(8)
254
+ @b_mode = d.new_file[:mode].to_s(8)
255
+ @new_file = d.added?
256
+ @renamed_file = d.renamed?
257
+ @deleted_file = d.deleted?
258
+ end
259
+
260
+ def init_from_rugged_patch(patch, collapse: false)
261
+ # Don't bother initializing diffs that are too large. If a diff is
262
+ # binary we're not going to display anything so we skip the size check.
263
+ return if !patch.delta.binary? && prune_large_patch(patch, collapse)
264
+
265
+ @diff = encode!(strip_diff_headers(patch.to_s))
266
+ end
267
+
268
+ def init_from_hash(hash, collapse: false)
269
+ raw_diff = hash.symbolize_keys
270
+
271
+ serialize_keys.each do |key|
272
+ send(:"#{key}=", raw_diff[key.to_sym])
273
+ end
274
+
275
+ prune_large_diff! if too_large?
276
+ prune_collapsed_diff! if collapse && collapsible?
277
+ end
278
+
279
+ # If the patch surpasses any of the diff limits it calls the appropiate
280
+ # prune method and returns true. Otherwise returns false.
281
+ def prune_large_patch(patch, collapse)
282
+ size = 0
283
+
284
+ patch.each_hunk do |hunk|
285
+ hunk.each_line do |line|
286
+ size += line.content.bytesize
287
+
288
+ if size >= DIFF_SIZE_LIMIT
289
+ prune_large_diff!
290
+ return true
291
+ end
292
+ end
293
+ end
294
+
295
+ if collapse && size >= DIFF_COLLAPSE_LIMIT
296
+ prune_collapsed_diff!
297
+ return true
298
+ end
299
+
300
+ false
301
+ end
302
+
303
+ # Strip out the information at the beginning of the patch's text to match
304
+ # Grit's output
305
+ def strip_diff_headers(diff_text)
306
+ # Delete everything up to the first line that starts with '---' or
307
+ # 'Binary'
308
+ diff_text.sub!(/\A.*?^(---|Binary)/m, '\1')
309
+
310
+ if diff_text.start_with?('---', 'Binary')
311
+ diff_text
312
+ else
313
+ # If the diff_text did not contain a line starting with '---' or
314
+ # 'Binary', return the empty string. No idea why; we are just
315
+ # preserving behavior from before the refactor.
316
+ ''
317
+ end
318
+ end
319
+ end
320
+ end
@@ -0,0 +1,127 @@
1
+ module Bringit
2
+ class DiffCollection
3
+ include Enumerable
4
+
5
+ DEFAULT_LIMITS = { max_files: 100, max_lines: 5000 }.freeze
6
+
7
+ def initialize(iterator, options = {})
8
+ @iterator = iterator
9
+ @max_files = options.fetch(:max_files, DEFAULT_LIMITS[:max_files])
10
+ @max_lines = options.fetch(:max_lines, DEFAULT_LIMITS[:max_lines])
11
+ @max_bytes = @max_files * 5120 # Average 5 KB per file
12
+ @safe_max_files = [@max_files, DEFAULT_LIMITS[:max_files]].min
13
+ @safe_max_lines = [@max_lines, DEFAULT_LIMITS[:max_lines]].min
14
+ @safe_max_bytes = @safe_max_files * 5120 # Average 5 KB per file
15
+ @all_diffs = !!options.fetch(:all_diffs, false)
16
+ @no_collapse = !!options.fetch(:no_collapse, true)
17
+ @deltas_only = !!options.fetch(:deltas_only, false)
18
+
19
+ @line_count = 0
20
+ @byte_count = 0
21
+ @overflow = false
22
+ @array = Array.new
23
+ end
24
+
25
+ def each(&block)
26
+ if @populated
27
+ # @iterator.each is slower than just iterating the array in place
28
+ @array.each(&block)
29
+ elsif @deltas_only
30
+ each_delta(&block)
31
+ else
32
+ each_patch(&block)
33
+ end
34
+ end
35
+
36
+ def empty?
37
+ !@iterator.any?
38
+ end
39
+
40
+ def overflow?
41
+ populate!
42
+ !!@overflow
43
+ end
44
+
45
+ def size
46
+ @size ||= count # forces a loop using each method
47
+ end
48
+
49
+ def real_size
50
+ populate!
51
+
52
+ if @overflow
53
+ "#{size}+"
54
+ else
55
+ size.to_s
56
+ end
57
+ end
58
+
59
+ def decorate!
60
+ collection = each_with_index do |element, i|
61
+ @array[i] = yield(element)
62
+ end
63
+ @populated = true
64
+ collection
65
+ end
66
+
67
+ private
68
+
69
+ def populate!
70
+ return if @populated
71
+
72
+ each { nil } # force a loop through all diffs
73
+ @populated = true
74
+ nil
75
+ end
76
+
77
+ def over_safe_limits?(files)
78
+ files >= @safe_max_files || @line_count > @safe_max_lines || @byte_count >= @safe_max_bytes
79
+ end
80
+
81
+ def each_delta
82
+ @iterator.each_delta.with_index do |delta, i|
83
+ diff = Bringit::Diff.new(delta)
84
+
85
+ yield @array[i] = diff
86
+ end
87
+ end
88
+
89
+ def each_patch
90
+ @iterator.each_with_index do |raw, i|
91
+ # First yield cached Diff instances from @array
92
+ if @array[i]
93
+ yield @array[i]
94
+ next
95
+ end
96
+
97
+ # We have exhausted @array, time to create new Diff instances or stop.
98
+ break if @overflow
99
+
100
+ if !@all_diffs && i >= @max_files
101
+ @overflow = true
102
+ break
103
+ end
104
+
105
+ collapse = !@all_diffs && !@no_collapse
106
+
107
+ diff = Bringit::Diff.new(raw, collapse: collapse)
108
+
109
+ if collapse && over_safe_limits?(i)
110
+ diff.prune_collapsed_diff!
111
+ end
112
+
113
+ @line_count += diff.line_count
114
+ @byte_count += diff.diff.bytesize
115
+
116
+ if !@all_diffs && (@line_count >= @max_lines || @byte_count >= @max_bytes)
117
+ # This last Diff instance pushes us over the lines limit. We stop and
118
+ # discard it.
119
+ @overflow = true
120
+ break
121
+ end
122
+
123
+ yield @array[i] = diff
124
+ end
125
+ end
126
+ end
127
+ end