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