class GitHelpers::GitDiff

Constants

NoNewLine

Attributes

output[R]

Public Class Methods

new(diff,**opts) click to toggle source
# 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
output(gdiff, **opts) click to toggle source
# 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

Public Instance Methods

change_mode(nmode) click to toggle source
# 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_delete() click to toggle source
# 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_header() click to toggle source
# 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_header() click to toggle source
# File lib/git_helpers/diff.rb, line 89
def detect_end_diff_header
        @line =~ /^\+\+\+\s/
end
detect_end_hunk() click to toggle source
# 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_filename() click to toggle source
# 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_index() click to toggle source
# 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_commit() click to toggle source
# File lib/git_helpers/diff.rb, line 317
def detect_new_commit
        @line=~/^commit\b/
end
detect_new_diff_header() click to toggle source
# File lib/git_helpers/diff.rb, line 86
def detect_new_diff_header
        @line =~ /^diff\s/
end
detect_new_hunk() click to toggle source
# File lib/git_helpers/diff.rb, line 93
def detect_new_hunk
        @line.match(/^@@+\s.*\s@@/)
end
detect_new_submodule_header() click to toggle source
# 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_newfile() click to toggle source
# 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_perm() click to toggle source
# 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_copy() click to toggle source
# 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) click to toggle source
# File lib/git_helpers/diff.rb, line 387
def each(&b)
        parse.each(&b)
end
end_commit() click to toggle source
# File lib/git_helpers/diff.rb, line 74
def end_commit; end
end_diff_header() click to toggle source
# File lib/git_helpers/diff.rb, line 84
def end_diff_header; end
end_hunk() click to toggle source
# File lib/git_helpers/diff.rb, line 78
def end_hunk; end
end_meta() click to toggle source
# File lib/git_helpers/diff.rb, line 76
def end_meta; end
end_submodule() click to toggle source
# File lib/git_helpers/diff.rb, line 82
def end_submodule; end
end_submodule_header() click to toggle source
# File lib/git_helpers/diff.rb, line 80
def end_submodule_header; end
get_file_name(file) click to toggle source
# 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_commit() click to toggle source
# 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_header() click to toggle source
# 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_hunk() click to toggle source
# 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_line() click to toggle source
# File lib/git_helpers/diff.rb, line 335
def handle_line
end
handle_meta() click to toggle source
# File lib/git_helpers/diff.rb, line 100
def handle_meta
        handle_line
end
handle_submodule() click to toggle source
# 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_header() click to toggle source
# 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_commit() click to toggle source
# File lib/git_helpers/diff.rb, line 73
def new_commit; @commit={}; end
new_diff_header() click to toggle source
# File lib/git_helpers/diff.rb, line 83
def new_diff_header; @file={mode: :modify} end
new_hunk() click to toggle source
# File lib/git_helpers/diff.rb, line 77
def new_hunk; end
new_meta() click to toggle source
# File lib/git_helpers/diff.rb, line 75
def new_meta; end
new_submodule() click to toggle source
# File lib/git_helpers/diff.rb, line 81
def new_submodule; end
new_submodule_header() click to toggle source
# File lib/git_helpers/diff.rb, line 79
def new_submodule_header; @submodule={}; end
next_mode(nmode) click to toggle source
# File lib/git_helpers/diff.rb, line 58
def next_mode(nmode)
        @next_mode=nmode
end
output_line(l) click to toggle source
# File lib/git_helpers/diff.rb, line 48
def output_line(l)
        @output << l.chomp+"\n"
end
output_lines(lines) click to toggle source
# File lib/git_helpers/diff.rb, line 51
def output_lines(lines)
        lines.each {|l| output_line l}
end
parse() { || ... } click to toggle source
# 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_header() click to toggle source
# 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_line() click to toggle source
# 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) click to toggle source
# 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) click to toggle source
# File lib/git_helpers/diff.rb, line 330
def reparse(nmode)
        change_mode(nmode)
        parse_line
end
submodule_line() click to toggle source
# File lib/git_helpers/diff.rb, line 306
def submodule_line
        @line=~/^  [><] /
end
update_mode() click to toggle source
# File lib/git_helpers/diff.rb, line 61
def update_mode
        @start_mode=false
        @next_mode && change_mode(@next_mode)
        @next_mode=nil
end