Class: GitHelpers::GitDiff

Inherits:
Object
  • Object
show all
Includes:
Enumerable
Defined in:
lib/git_helpers/diff.rb

Direct Known Subclasses

GitDiffDebug, GitDiffHighlight, GitFancyDiff

Constant Summary collapse

NoNewLine =
"\\ No newline at end of file\n"

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(diff, **opts) ⇒ GitDiff

Returns a new instance of GitDiff.



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# File 'lib/git_helpers/diff.rb', line 31

def initialize(diff,**opts)
	@diff=diff #Assume diff is a line iterator ['gitdiff'.each_line]
	@current=0
	@mode=:unknown
	@opts=opts
	@opts[:color]=@opts.fetch(:color,true)
	#modes: 
	#- unknown (temp mode)
	#- commit
	#- meta
	#- submodule_header
	#- submodule
	#- diff_header
	#- hunk
	@colors={meta: [:bold]}
end

Instance Attribute Details

#outputObject (readonly)

Returns the value of attribute output.



27
28
29
# File 'lib/git_helpers/diff.rb', line 27

def output
  @output
end

Class Method Details

.output(gdiff, **opts) ⇒ Object



18
19
20
21
22
23
24
25
# File 'lib/git_helpers/diff.rb', line 18

def self.output(gdiff, **opts)
	if gdiff.respond_to?(:each_line)
		enum=gdiff.each_line
	else
		enum=gdiff.each
	end
	self.new(enum, **opts).output
end

Instance Method Details

#change_mode(nmode) ⇒ Object



66
67
68
69
70
71
# File 'lib/git_helpers/diff.rb', line 66

def change_mode(nmode)
	@start_mode=true
	send :"end_#{@mode}" unless @mode==:unknown
	@mode=nmode
	send :"new_#{@mode}" unless @mode==:unknown
end

#detect_deleteObject



198
199
200
201
202
203
204
205
# File 'lib/git_helpers/diff.rb', line 198

def detect_delete
	if m=@line.match(/^deleted file mode\s+(.*)/)
		@file[:old_perm]=m[1]
		@file[:mode]=:delete
		return true
	end
	false
end

#detect_diff_headerObject



245
246
247
248
249
250
251
252
253
254
255
256
# File 'lib/git_helpers/diff.rb', line 245

def detect_diff_header
	if @start_mode
		if m=@line.chomp.match(/^diff\s--git\s(.*)\s(.*)/)
			@file[:old_name]=get_file_name(m[1])
			@file[:name]=get_file_name(m[2])
		elsif
			m=@line.match(/^diff\s--(?:cc|combined)\s(.*)/)
			@file[:name]=get_file_name(m[1])
		end
		true
	end
end

#detect_end_diff_headerObject



89
90
91
# File 'lib/git_helpers/diff.rb', line 89

def detect_end_diff_header
	@line =~ /^\+\+\+\s/
end

#detect_end_hunkObject



96
97
98
# File 'lib/git_helpers/diff.rb', line 96

def detect_end_hunk
	@hunk[:lines_seen].each_with_index.all? { |v,i| v==@hunk[:lines][i].first }
end

#detect_filenameObject



164
165
166
167
168
169
170
171
172
173
174
# File 'lib/git_helpers/diff.rb', line 164

def detect_filename
	if m=@line.match(/^---\s(.*)/)
		@file[:old_name]=get_file_name(m[1])
		return true
	end
	if m=@line.match(/^\+\+\+\s(.*)/)
		@file[:name]=get_file_name(m[1])
		return true
	end
	false
end

#detect_indexObject



188
189
190
191
192
193
194
195
196
# File 'lib/git_helpers/diff.rb', line 188

def detect_index
	if m=@line.match(/^index\s+(.*)\.\.(.*)/)
		@file[:oldhash]=m[1].split(',')
		@file[:hash],perm=m[2].split
		@file[:perm]||=perm
		return true
	end
	false
end

#detect_new_commitObject



317
318
319
# File 'lib/git_helpers/diff.rb', line 317

def detect_new_commit
	@line=~/^commit\b/
end

#detect_new_diff_headerObject



86
87
88
# File 'lib/git_helpers/diff.rb', line 86

def detect_new_diff_header
	@line =~ /^diff\s/
end

#detect_new_hunkObject



93
94
95
# File 'lib/git_helpers/diff.rb', line 93

def detect_new_hunk
	@line.match(/^@@+\s.*\s@@/)
end

#detect_new_submodule_headerObject



273
274
275
276
277
278
279
# File 'lib/git_helpers/diff.rb', line 273

def detect_new_submodule_header
	if m=@line.chomp.match(/^Submodule\s(.*)\s(.*)/)
		subname=m[1];
		return not(@submodule && @submodule[:name]==subname)
	end
	false
end

#detect_newfileObject



207
208
209
210
211
212
213
214
# File 'lib/git_helpers/diff.rb', line 207

def detect_newfile
	if m=@line.match(/^new file mode\s+(.*)/)
		@file[:new_perm]=m[1]
		@file[:mode]=:new
		return true
	end
	false
end

#detect_permObject



176
177
178
179
180
181
182
183
184
185
186
# File 'lib/git_helpers/diff.rb', line 176

def detect_perm
	if m=@line.match(/^old mode\s+(.*)/)
		@file[:old_perm]=m[1]
		return true
	end
	if m=@line.match(/^new mode\s+(.*)/)
		@file[:new_perm]=m[1]
		return true
	end
	false
end

#detect_rename_copyObject



216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
# File 'lib/git_helpers/diff.rb', line 216

def detect_rename_copy
	if m=@line.match(/^similarity index\s+(.*)/)
		@file[:similarity]=m[1]
		return true
	end
	if m=@line.match(/^dissimilarity index\s+(.*)/)
		@file[:mode]=:rewrite
		@file[:dissimilarity]=m[1]
		return true
	end
	#if we have a rename with 100% similarity, there won't be any hunks so
	#we need to detect the filenames there
	if m=@line.match(/^(?:rename|copy) from\s+(.*)/)
		@file[:old_name]=m[1]
	end
	if m=@line.match(/^(?:rename|copy) to\s+(.*)/)
		@file[:name]=m[1]
	end
	if m=@line.match(/^rename\s+(.*)/)
		@file[:mode]=:rename
		return true
	end
	if m=@line.match(/^copy\s+(.*)/)
		@file[:mode]=:copy
		return true
	end
	false
end

#each(&b) ⇒ Object



387
388
389
# File 'lib/git_helpers/diff.rb', line 387

def each(&b)
	parse.each(&b)
end

#end_commitObject



74
# File 'lib/git_helpers/diff.rb', line 74

def end_commit; end

#end_diff_headerObject



84
# File 'lib/git_helpers/diff.rb', line 84

def end_diff_header; end

#end_hunkObject



78
# File 'lib/git_helpers/diff.rb', line 78

def end_hunk; end

#end_metaObject



76
# File 'lib/git_helpers/diff.rb', line 76

def end_meta; end

#end_submoduleObject



82
# File 'lib/git_helpers/diff.rb', line 82

def end_submodule; end

#end_submodule_headerObject



80
# File 'lib/git_helpers/diff.rb', line 80

def end_submodule_header; end

#get_file_name(file) ⇒ Object



159
160
161
162
# File 'lib/git_helpers/diff.rb', line 159

def get_file_name(file)
	#remove prefix (todo handle the no-prefix option)
	file.gsub(/^[abciow12]\//,'')
end

#handle_commitObject



321
322
323
324
325
326
327
328
# File 'lib/git_helpers/diff.rb', line 321

def handle_commit
	if m=@line.match(/^(\w+):\s(.*)/)
		@commit[m[1]]=m[2]
		handle_line
	else
		@start_mode ? handle_line : reparse(:unknown)
	end
end

#handle_diff_headerObject



258
259
260
261
262
263
264
265
266
267
268
269
270
271
# File 'lib/git_helpers/diff.rb', line 258

def handle_diff_header
	if detect_diff_header
	elsif detect_filename
	elsif detect_perm
	elsif detect_index
	elsif detect_delete
	elsif detect_newfile
	elsif detect_rename_copy
	else
		return reparse(:unknown)
	end
	next_mode(:unknown) if detect_end_diff_header
	handle_line
end

#handle_hunkObject



128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
# File 'lib/git_helpers/diff.rb', line 128

def handle_hunk
	if @start_mode
		parse_hunk_header
	else
		#'The 'No new line at end of file' is sort of part of the hunk, but
		#is not considerer in the hunkheader
		unless @line == NoNewLine
			#we need to wait for a NoNewLine to be sure we are at the end of the hunk
			return reparse(:unknown) if detect_end_hunk
			linemodes=@line[0...@hunk[:n]-1]
			newline=true
			#the line is on the new file unless there is a '-' somewhere
			if linemodes=~/-/
				newline=false
			else
				@hunk[:lines_seen][0]+=1
			end
			(1...@hunk[:n]).each do |i|
				linemode=linemodes[i-1]
				case linemode
				when '-'
					@hunk[:lines_seen][i]+=1
				when ' '
					@hunk[:lines_seen][i]+=1 if newline
				end
			end
		end
	end
	handle_line
end

#handle_lineObject



335
336
# File 'lib/git_helpers/diff.rb', line 335

def handle_line
end

#handle_metaObject



100
101
102
# File 'lib/git_helpers/diff.rb', line 100

def handle_meta
	handle_line
end

#handle_submoduleObject



310
311
312
313
314
315
# File 'lib/git_helpers/diff.rb', line 310

def handle_submodule
	#we have lines indicating new commits
	#they always end by a new line except when followed by another submodule
	return reparse(:unknown) if !submodule_line
	handle_line
end

#handle_submodule_headerObject



281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
# File 'lib/git_helpers/diff.rb', line 281

def handle_submodule_header
	if m=@line.chomp.match(/^Submodule\s(\S*)\s(.*)/)
		subname=m[1]
		if @submodule[:name]
			#we may be dealing with a new submodule
			#require 'pry'; binding.pry
			return reparse(:submodule_header) if subname != @submodule[:name]
		else
			@submodule[:name]=m[1]
		end
		subinfo=m[2]
		if subinfo == "contains untracked content"
			@submodule[:untracked]=true
		elsif subinfo == "contains modified content"
			@submodule[:modified]=true
		else
			(@submodule[:info]||="") << subinfo
			next_mode(:submodule) if subinfo =~ /^.......\.\.\.?........*:$/
		end
		handle_line
	else
		return reparse(:unknown)
	end
end

#new_commitObject



73
# File 'lib/git_helpers/diff.rb', line 73

def new_commit; @commit={}; end

#new_diff_headerObject



83
# File 'lib/git_helpers/diff.rb', line 83

def new_diff_header; @file={mode: :modify} end

#new_hunkObject



77
# File 'lib/git_helpers/diff.rb', line 77

def new_hunk; end

#new_metaObject



75
# File 'lib/git_helpers/diff.rb', line 75

def new_meta; end

#new_submoduleObject



81
# File 'lib/git_helpers/diff.rb', line 81

def new_submodule; end

#new_submodule_headerObject



79
# File 'lib/git_helpers/diff.rb', line 79

def new_submodule_header; @submodule={}; end

#next_mode(nmode) ⇒ Object



58
59
60
# File 'lib/git_helpers/diff.rb', line 58

def next_mode(nmode)
	@next_mode=nmode
end

#output_line(l) ⇒ Object



48
49
50
# File 'lib/git_helpers/diff.rb', line 48

def output_line(l)
	@output << l.chomp+"\n"
end

#output_lines(lines) ⇒ Object



51
52
53
# File 'lib/git_helpers/diff.rb', line 51

def output_lines(lines)
	lines.each {|l| output_line l}
end

#parseObject



375
376
377
378
379
380
381
382
383
384
385
# File 'lib/git_helpers/diff.rb', line 375

def parse
	Enumerator.new do |y|
		@output=y
		@diff.each do |line|
			prepare_new_line(line)
			parse_line
			yield if block_given?
		end
		change_mode(:unknown) #to trigger the last end_* hook
	end
end

#parse_hunk_headerObject



104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/git_helpers/diff.rb', line 104

def parse_hunk_header
	m=@line.match(/^@@+\s(.*)\s@@\s*(.*)/)
	hunks=m[1]
	@hunk={lines: []}
	@hunk[:header]=m[2]
	filenumber=0
	hunks.split.each do |hunk|
		hunkmode=hunk[0]
		hunk=hunk[1..-1]
		line,length=hunk.split(',').map(&:to_i)
		#handle hunks of the form @@ -1 +0,0 @@
		length,line=line,length unless length
		case hunkmode
		when '-'
			filenumber+=1
			@hunk[:lines][filenumber]=[length,line]
		when '+'
			@hunk[:lines][0]=[length,line]
		end
	end
	@hunk[:n]=@hunk[:lines].length
	@hunk[:lines_seen]=Array.new(@hunk[:n],0)
end

#parse_lineObject



339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
# File 'lib/git_helpers/diff.rb', line 339

def parse_line
	case @mode
	when :unknown, :meta
		if detect_new_hunk
			return reparse(:hunk)
		elsif detect_new_diff_header
			return reparse(:diff_header)
		elsif detect_new_submodule_header
			return reparse(:submodule_header)
		elsif detect_new_commit
			return reparse(:commit)
		else
			change_mode(:meta) if @mode==:unknown
			handle_meta
		end
	when :commit
		handle_commit
	when :submodule_header
		handle_submodule_header
	when :submodule
		handle_submodule
	when :diff_header
		handle_diff_header
		#=> mode=unknown if we detect we are not a diff header anymore
	when :hunk
		handle_hunk
		#=> mode=unknown at end of hunk
	end
end

#prepare_new_line(line) ⇒ Object



369
370
371
372
373
# File 'lib/git_helpers/diff.rb', line 369

def prepare_new_line(line)
	@orig_line=line
	@line=@orig_line.uncolor
	update_mode
end

#reparse(nmode) ⇒ Object



330
331
332
333
# File 'lib/git_helpers/diff.rb', line 330

def reparse(nmode)
	change_mode(nmode)
	parse_line
end

#submodule_lineObject



306
307
308
# File 'lib/git_helpers/diff.rb', line 306

def submodule_line
	@line=~/^  [><] /
end

#update_modeObject



61
62
63
64
65
# File 'lib/git_helpers/diff.rb', line 61

def update_mode
	@start_mode=false
	@next_mode && change_mode(@next_mode)
	@next_mode=nil
end