git_helpers 0.1.0 → 0.2
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.
- checksums.yaml +4 -4
- data/ChangeLog.md +99 -2
- data/LICENSE.txt +1 -1
- data/Rakefile +7 -10
- data/bin/diff-fancy.rb +1 -0
- data/bin/gitstatus.old.rb +349 -0
- data/bin/gitstatus.rb +74 -305
- data/lib/git_helpers.rb +24 -330
- data/lib/git_helpers/branch.rb +216 -0
- data/lib/git_helpers/branch_infos.rb +178 -0
- data/lib/git_helpers/diff.rb +701 -0
- data/lib/git_helpers/extra_helpers.rb +24 -220
- data/lib/git_helpers/git_dir.rb +176 -0
- data/lib/git_helpers/raw_helpers.rb +105 -0
- data/lib/git_helpers/stats.rb +183 -0
- data/lib/git_helpers/status.rb +404 -0
- data/lib/git_helpers/submodules.rb +32 -0
- data/lib/git_helpers/version.rb +1 -1
- metadata +13 -3
- data/bin/diff-fancy.rb +0 -699
@@ -0,0 +1,216 @@
|
|
1
|
+
module GitHelpers
|
2
|
+
GitBranchError = Class.new(Exception)
|
3
|
+
class GitBranch
|
4
|
+
attr_accessor :gitdir
|
5
|
+
attr_accessor :branch
|
6
|
+
attr_writer :infos
|
7
|
+
|
8
|
+
def initialize(branch="HEAD", dir: ".")
|
9
|
+
@gitdir=dir.is_a?(GitDir) ? dir : GitDir.new(dir)
|
10
|
+
@branch=branch
|
11
|
+
end
|
12
|
+
|
13
|
+
def new_branch(name)
|
14
|
+
self.class.new(name, dir: @gitdir)
|
15
|
+
end
|
16
|
+
|
17
|
+
def to_s
|
18
|
+
@branch.to_s
|
19
|
+
end
|
20
|
+
|
21
|
+
def nil?
|
22
|
+
@branch.nil?
|
23
|
+
end
|
24
|
+
|
25
|
+
def shellescape
|
26
|
+
@branch.shellescape
|
27
|
+
end
|
28
|
+
|
29
|
+
def reset!
|
30
|
+
@infos=nil
|
31
|
+
end
|
32
|
+
|
33
|
+
def run(*args,**kw, &b)
|
34
|
+
@gitdir.run(*args,**kw, &b)
|
35
|
+
end
|
36
|
+
def run_simple(*args,**kw,&b)
|
37
|
+
@gitdir.run_simple(*args,**kw, &b)
|
38
|
+
end
|
39
|
+
def run_success(*args,**kw,&b)
|
40
|
+
@gitdir.run_success(*args,**kw, &b)
|
41
|
+
end
|
42
|
+
|
43
|
+
def checkout
|
44
|
+
branch=@branch
|
45
|
+
branch&.delete_prefix!('refs/heads/') #git checkout refs/heads/master check out in a detached head
|
46
|
+
SH.sh! "git checkout #{branch}"
|
47
|
+
end
|
48
|
+
def checkout_detached
|
49
|
+
SH.sh! "git checkout #{@branch}~0"
|
50
|
+
end
|
51
|
+
|
52
|
+
def infos(*args, name: :default, detached_name: :detached_default)
|
53
|
+
@infos=infos!(*args) unless @infos
|
54
|
+
@infos.merge({name: self.name(method: name, detached_method: detached_name)})
|
55
|
+
end
|
56
|
+
|
57
|
+
def infos!(detached: true)
|
58
|
+
raise GitBranchError.new("Nil Branch #{self}") if nil?
|
59
|
+
infos=branch_infos
|
60
|
+
|
61
|
+
if infos.nil?
|
62
|
+
if !detached #error out
|
63
|
+
raise GitBranchError.new("Detached Branch #{self}")
|
64
|
+
else
|
65
|
+
infos={detached: true}
|
66
|
+
return infos
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
type=infos[:type]
|
71
|
+
infos[:detached]=false
|
72
|
+
if type == :local
|
73
|
+
rebase=gitdir.get_config("branch.#{infos["refname:short"]}.rebase")
|
74
|
+
rebase = false if rebase.empty?
|
75
|
+
rebase = true if rebase == "true"
|
76
|
+
infos[:rebase]=rebase
|
77
|
+
end
|
78
|
+
infos
|
79
|
+
end
|
80
|
+
|
81
|
+
def format_infos(**opts)
|
82
|
+
@gitdir.format_branch_infos([infos], **opts)
|
83
|
+
end
|
84
|
+
|
85
|
+
def name(method: :default, detached_method: [:detached_default, :short], shorten: true, highlight_detached: ':', expand_head: true)
|
86
|
+
l=lambda { |ev| run_simple(ev, chomp: true, error: :quiet) }
|
87
|
+
methods=[*method]
|
88
|
+
detached_methods=[*detached_method]
|
89
|
+
# we first test each method, then each detached_methods
|
90
|
+
method=methods.shift
|
91
|
+
if method.nil?
|
92
|
+
if !detached_methods.empty?
|
93
|
+
describe=self.name(method: detached_methods, detached_method: [], shorten: shorten, highlight_detached: highlight_detached, expand_head: expand_head)
|
94
|
+
describe="#{highlight_detached}#{describe}" unless describe.nil? or describe.empty?
|
95
|
+
return describe
|
96
|
+
else
|
97
|
+
return nil
|
98
|
+
end
|
99
|
+
end
|
100
|
+
method="name" if method == :default
|
101
|
+
#method="branch-fb" if method == :detached_default
|
102
|
+
#method="short" if method == :detached_default
|
103
|
+
method="match" if method == :detached_default
|
104
|
+
method="branch-fb" if method == :detached_infos
|
105
|
+
describe=
|
106
|
+
case method.to_s
|
107
|
+
when "sha1"
|
108
|
+
l.call "git rev-parse #{@branch.shellescape}"
|
109
|
+
when "short"
|
110
|
+
l.call "git rev-parse --short #{@branch.shellescape}"
|
111
|
+
when "symbolic-ref"
|
112
|
+
l.call "git symbolic-ref -q --short #{@branch.shellescape}"
|
113
|
+
when "describe"
|
114
|
+
l.call "git describe #{@branch.shellescape}"
|
115
|
+
when "contains"
|
116
|
+
l.call "git describe --contains #{@branch.shellescape}"
|
117
|
+
when "tags"
|
118
|
+
l.call "git describe --tags #{@branch.shellescape}"
|
119
|
+
when "match"
|
120
|
+
l.call "git describe --all --exact-match #{@branch.shellescape}"
|
121
|
+
when "topic"
|
122
|
+
l.call "git describe --all #{@branch.shellescape}"
|
123
|
+
when "branch"
|
124
|
+
l.call "git describe --contains --all #{@branch.shellescape}"
|
125
|
+
when "topic-fb" #try --all, then --contains all
|
126
|
+
d=l.call "git describe --all #{@branch.shellescape}"
|
127
|
+
d=l.call "git describe --contains --all #{@branch.shellescape}" if d.nil? or d.empty?
|
128
|
+
d
|
129
|
+
when "branch-fb" #try --contains all, then --all
|
130
|
+
d=l.call "git describe --contains --all #{@branch.shellescape}"
|
131
|
+
d=l.call "git describe --all #{@branch.shellescape}" if d.nil? or d.empty?
|
132
|
+
d
|
133
|
+
when "magic"
|
134
|
+
d1=l.call "git describe --contains --all #{@branch.shellescape}"
|
135
|
+
d2=l.call "git describe --all #{@branch.shellescape}"
|
136
|
+
d= d1.length < d2.length ? d1 : d2
|
137
|
+
d=d1 if d2.empty?
|
138
|
+
d=d2 if d1.empty?
|
139
|
+
d
|
140
|
+
when "name"
|
141
|
+
# note: the newer options `git branch --show-current` seems to be
|
142
|
+
# the same as this one
|
143
|
+
l.call "git rev-parse --abbrev-ref #{@branch.shellescape}"
|
144
|
+
when "full_name"
|
145
|
+
l.call "git rev-parse --symbolic-full-name #{@branch.shellescape}"
|
146
|
+
when "symbolic"
|
147
|
+
l.call "git rev-parse --symbolic #{@branch.shellescape}"
|
148
|
+
when Proc
|
149
|
+
method.call(@branch)
|
150
|
+
else
|
151
|
+
l.call method unless method.nil? or method.empty?
|
152
|
+
end
|
153
|
+
if describe.nil? or describe.empty? or describe == "HEAD" && expand_head
|
154
|
+
describe=self.name(method: methods, detached_method: detached_method, shorten: shorten, highlight_detached: highlight_detached, expand_head: expand_head)
|
155
|
+
end
|
156
|
+
if shorten
|
157
|
+
describe&.delete_prefix!("refs/")
|
158
|
+
describe&.delete_prefix!("heads/")
|
159
|
+
end
|
160
|
+
return describe
|
161
|
+
end
|
162
|
+
|
163
|
+
def full_name(method: :full_name, detached_method: nil, shorten: false, **opts)
|
164
|
+
name(method: method, detached_method: detached_method, shorten: shorten, **opts)
|
165
|
+
end
|
166
|
+
|
167
|
+
|
168
|
+
def rebase?
|
169
|
+
infos[:rebase]
|
170
|
+
end
|
171
|
+
def remote
|
172
|
+
infos["upstream:remotename"]
|
173
|
+
end
|
174
|
+
def push_remote
|
175
|
+
infos["push:remotename"]
|
176
|
+
end
|
177
|
+
def upstream(short: true, warn: true)
|
178
|
+
# up=%x/git rev-parse --abbrev-ref #{@branch.shellescape}@{u}/.chomp!
|
179
|
+
br= short ? infos["upstream:short"] : infos["upstream"]
|
180
|
+
if br&.empty?
|
181
|
+
br=nil
|
182
|
+
warn "Warning: Branch #{self} has no upstream" if warn
|
183
|
+
end
|
184
|
+
new_branch(br)
|
185
|
+
end
|
186
|
+
def push(short: true)
|
187
|
+
# pu=%x/git rev-parse --abbrev-ref #{@branch.shellescape}@{push}/.chomp!
|
188
|
+
br= short ? infos["push:short"] : infos["push"]
|
189
|
+
br=nil if br.empty?
|
190
|
+
new_branch(br)
|
191
|
+
end
|
192
|
+
def hash
|
193
|
+
infos["objectname"]
|
194
|
+
end
|
195
|
+
|
196
|
+
def ==(other)
|
197
|
+
@branch == other.branch && @gitdir=other.gitdir
|
198
|
+
end
|
199
|
+
|
200
|
+
#return upstream + push if push !=upstream
|
201
|
+
def related
|
202
|
+
up=upstream
|
203
|
+
pu=push
|
204
|
+
pu=new_branch(nil) if up==pu
|
205
|
+
return up, pu
|
206
|
+
end
|
207
|
+
|
208
|
+
def branch_infos
|
209
|
+
@gitdir.branch_infos(branch).values.first
|
210
|
+
end
|
211
|
+
|
212
|
+
def ahead_behind(br)
|
213
|
+
@gitdir.ahead_behind(@branch,br)
|
214
|
+
end
|
215
|
+
end
|
216
|
+
end
|
@@ -0,0 +1,178 @@
|
|
1
|
+
module GitHelpers
|
2
|
+
# more infos on branches
|
3
|
+
module GitBranchInfos
|
4
|
+
def ahead_behind(br1, br2)
|
5
|
+
with_dir do
|
6
|
+
out=run_simple("git rev-list --left-right --count #{br1.shellescape}...#{br2.shellescape}", error: :quiet)
|
7
|
+
out.match(/(\d+)\s+(\d+)/) do |m|
|
8
|
+
return m[1].to_i, m[2].to_i #br1 is ahead by m[1], behind by m[2] from br2
|
9
|
+
end
|
10
|
+
return 0, 0
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
14
|
+
def branch_infos(*branches, local: false, remote: false, tags: false, merged: nil, no_merged: nil)
|
15
|
+
query = []
|
16
|
+
query << "--merged=#{merged.shellescape}" if merged
|
17
|
+
query << "--no_merged=#{no_merged.shellescape}" if no_merged
|
18
|
+
query += branches.map {|b| name_branch(b)}
|
19
|
+
query << 'refs/heads' if local
|
20
|
+
query << 'refs/remotes' if remote
|
21
|
+
query << 'refs/tags' if tags
|
22
|
+
r={}
|
23
|
+
format=%w(refname refname:short objecttype objectsize objectname upstream upstream:short upstream:track upstream:remotename upstream:remoteref push push:short push:track push:remotename push:remoteref HEAD symref)
|
24
|
+
#Note push:remoteref is buggy (empty if no push refspec specified)
|
25
|
+
#and push:track is upstream:track (cf my patch to the git mailing
|
26
|
+
#list to correct that)
|
27
|
+
out=run_simple("git for-each-ref --format '#{format.map {|f| "%(#{f})"}.join(';')}' #{query.shelljoin}", chomp: :lines)
|
28
|
+
out.each do |l|
|
29
|
+
infos=l.split(';')
|
30
|
+
full_name=infos[0]
|
31
|
+
infos=Hash[format.zip(infos)]
|
32
|
+
|
33
|
+
infos[:name]=infos["refname:short"]
|
34
|
+
infos[:head]=!(infos["HEAD"].empty? or infos["HEAD"]==" ")
|
35
|
+
|
36
|
+
type=if full_name.start_with?("refs/heads/")
|
37
|
+
:local
|
38
|
+
elsif full_name.start_with?("refs/remotes/")
|
39
|
+
:remote
|
40
|
+
elsif full_name.start_with?("refs/tags/")
|
41
|
+
:tags
|
42
|
+
end
|
43
|
+
name = case type
|
44
|
+
when :local
|
45
|
+
full_name.delete_prefix("refs/heads/")
|
46
|
+
when :remote
|
47
|
+
full_name.delete_prefix("refs/remotes/")
|
48
|
+
when :tags
|
49
|
+
full_name.delete_prefix("refs/tags/")
|
50
|
+
end
|
51
|
+
infos[:type]=type
|
52
|
+
infos[:name]=name
|
53
|
+
|
54
|
+
infos[:upstream_ahead]=0
|
55
|
+
infos[:upstream_behind]=0
|
56
|
+
infos[:push_ahead]=0
|
57
|
+
infos[:push_behind]=0
|
58
|
+
track=infos["upstream:track"]
|
59
|
+
track.match(/ahead (\d+)/) do |m|
|
60
|
+
infos[:upstream_ahead]=m[1].to_i
|
61
|
+
end
|
62
|
+
track.match(/behind (\d+)/) do |m|
|
63
|
+
infos[:upstream_behind]=m[1].to_i
|
64
|
+
end
|
65
|
+
|
66
|
+
## git has a bug for push:track
|
67
|
+
# ptrack=infos["push:track"]
|
68
|
+
# ptrack.match(/ahead (\d+)/) do |m|
|
69
|
+
# infos[:push_ahead]=m[1].to_i
|
70
|
+
# end
|
71
|
+
# ptrack.match(/behind (\d+)/) do |m|
|
72
|
+
# infos[:push_behind]=m[1].to_i
|
73
|
+
# end
|
74
|
+
unless infos["push"].empty?
|
75
|
+
ahead, behind=ahead_behind(infos["refname"], infos["push"])
|
76
|
+
infos[:push_ahead]=ahead
|
77
|
+
infos[:push_behind]=behind
|
78
|
+
end
|
79
|
+
|
80
|
+
origin = infos["upstream:remotename"]
|
81
|
+
unless origin.empty?
|
82
|
+
upstream_short=infos["upstream:short"]
|
83
|
+
infos["upstream:name"]=upstream_short.delete_prefix(origin+"/")
|
84
|
+
end
|
85
|
+
pushorigin = infos["push:remotename"]
|
86
|
+
unless pushorigin.empty?
|
87
|
+
push_short=infos["push:short"]
|
88
|
+
if push_short.empty?
|
89
|
+
infos["push:name"]=infos["refname:short"]
|
90
|
+
else
|
91
|
+
infos["push:name"]= push_short.delete_prefix(pushorigin+"/")
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
r[full_name]=infos
|
96
|
+
end
|
97
|
+
r
|
98
|
+
end
|
99
|
+
|
100
|
+
def format_branch_infos(infos, compare: nil, merged: nil, cherry: false, log: false)
|
101
|
+
# warning, here we pass the info values, ie infos should be a list
|
102
|
+
infos.each do |i|
|
103
|
+
name=i["refname:short"]
|
104
|
+
upstream=i["upstream:short"]
|
105
|
+
push=i["push:short"]
|
106
|
+
color=:magenta
|
107
|
+
if merged
|
108
|
+
color=:red #not merged
|
109
|
+
[*merged].each do |br|
|
110
|
+
ahead, _behind=ahead_behind(i["refname"], br)
|
111
|
+
if ahead==0
|
112
|
+
color=:magenta
|
113
|
+
break
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
117
|
+
r="#{i["HEAD"]}#{name.color(color)}"
|
118
|
+
if compare
|
119
|
+
ahead, behind=ahead_behind(i["refname"], compare)
|
120
|
+
r << "↑#{ahead}" unless ahead==0
|
121
|
+
r << "↓#{behind}" unless behind==0
|
122
|
+
end
|
123
|
+
unless upstream.empty?
|
124
|
+
r << " @{u}"
|
125
|
+
r << "=@{push}" if push==upstream
|
126
|
+
r << "=#{upstream.color(:yellow)}"
|
127
|
+
r << "↑#{i[:upstream_ahead]}" unless i[:upstream_ahead]==0
|
128
|
+
r << "↓#{i[:upstream_behind]}" unless i[:upstream_behind]==0
|
129
|
+
end
|
130
|
+
unless push.empty? or push == upstream
|
131
|
+
r << " @{push}=#{push.color(:yellow)}"
|
132
|
+
r << "↑#{i[:push_ahead]}" unless i[:push_ahead]==0
|
133
|
+
r << "↓#{i[:push_behind]}" unless i[:push_behind]==0
|
134
|
+
end
|
135
|
+
if log
|
136
|
+
log_options=case log
|
137
|
+
when Hash
|
138
|
+
log.map {|k,v| "--#{k}=#{v.shellescape}"}.join(' ')
|
139
|
+
when String
|
140
|
+
log
|
141
|
+
else
|
142
|
+
""
|
143
|
+
end
|
144
|
+
r << " → "+run_simple("git -c color.ui=always log --date=human --oneline --no-walk #{log_options} #{name}")
|
145
|
+
end
|
146
|
+
puts r
|
147
|
+
if cherry #todo: add push cherry?
|
148
|
+
if upstream and i[:upstream_ahead] != 0 || i[:upstream_behind] != 0
|
149
|
+
ch=run_simple("git -c color.ui=always log --left-right --topo-order --oneline #{name}...#{upstream}")
|
150
|
+
ch.each_line do |l|
|
151
|
+
puts " #{l}"
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def name_branch(branch='HEAD',**args)
|
159
|
+
self.branch(branch).full_name(**args)
|
160
|
+
end
|
161
|
+
def name(branch='HEAD',**args)
|
162
|
+
self.branch(branch).name(**args)
|
163
|
+
end
|
164
|
+
|
165
|
+
#return all local upstreams of branches, recursively
|
166
|
+
def recursive_upstream(*branches, local: true)
|
167
|
+
require 'tsort'
|
168
|
+
each_node=lambda do |&b| branches.each(&b) end
|
169
|
+
each_child=lambda do |br, &b|
|
170
|
+
upstream=branch(br).upstream(short: false)
|
171
|
+
upstreams=[]
|
172
|
+
upstreams << upstream.to_s unless upstream.nil? or local && upstream.to_s.start_with?("refs/remotes/")
|
173
|
+
upstreams.each(&b)
|
174
|
+
end
|
175
|
+
TSort.tsort(each_node, each_child)
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
@@ -0,0 +1,701 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Inspired by diff-so-fancy; wrapper around diff-highlight
|
3
|
+
# https://github.com/stevemao/diff-so-fancy
|
4
|
+
# [commit: 0ea7c129420c57ec0384a704325e27c41f8f450d,
|
5
|
+
# last commit checked: 3adf0114da99643ec53a16253a3d6f42390e4c19 (2017-04-04)]
|
6
|
+
#TODO: use git-config
|
7
|
+
#TODO: work with 'git log -p --graph'
|
8
|
+
|
9
|
+
require "simplecolor"
|
10
|
+
SimpleColor.mix_in_string
|
11
|
+
begin
|
12
|
+
require "shell_helpers"
|
13
|
+
rescue LoadError
|
14
|
+
end
|
15
|
+
|
16
|
+
module GitHelpers
|
17
|
+
class GitDiff
|
18
|
+
def self.output(gdiff, **opts)
|
19
|
+
if gdiff.respond_to?(:each_line)
|
20
|
+
enum=gdiff.each_line
|
21
|
+
else
|
22
|
+
enum=gdiff.each
|
23
|
+
end
|
24
|
+
self.new(enum, **opts).output
|
25
|
+
end
|
26
|
+
|
27
|
+
attr_reader :output
|
28
|
+
include Enumerable
|
29
|
+
NoNewLine="\\n"
|
30
|
+
|
31
|
+
def initialize(diff,**opts)
|
32
|
+
@diff=diff #Assume diff is a line iterator ['gitdiff'.each_line]
|
33
|
+
@current=0
|
34
|
+
@mode=:unknown
|
35
|
+
@opts=opts
|
36
|
+
@opts[:color]=@opts.fetch(:color,true)
|
37
|
+
#modes:
|
38
|
+
#- unknown (temp mode)
|
39
|
+
#- commit
|
40
|
+
#- meta
|
41
|
+
#- submodule_header
|
42
|
+
#- submodule
|
43
|
+
#- diff_header
|
44
|
+
#- hunk
|
45
|
+
@colors={meta: [:bold]}
|
46
|
+
end
|
47
|
+
|
48
|
+
def output_line(l)
|
49
|
+
@output << l.chomp+"\n"
|
50
|
+
end
|
51
|
+
def output_lines(lines)
|
52
|
+
lines.each {|l| output_line l}
|
53
|
+
end
|
54
|
+
def output
|
55
|
+
each {|l| puts l}
|
56
|
+
end
|
57
|
+
|
58
|
+
def next_mode(nmode)
|
59
|
+
@next_mode=nmode
|
60
|
+
end
|
61
|
+
def update_mode
|
62
|
+
@start_mode=false
|
63
|
+
@next_mode && change_mode(@next_mode)
|
64
|
+
@next_mode=nil
|
65
|
+
end
|
66
|
+
def change_mode(nmode)
|
67
|
+
@start_mode=true
|
68
|
+
send :"end_#{@mode}" unless @mode==:unknown
|
69
|
+
@mode=nmode
|
70
|
+
send :"new_#{@mode}" unless @mode==:unknown
|
71
|
+
end
|
72
|
+
|
73
|
+
def new_commit; @commit={}; end
|
74
|
+
def end_commit; end
|
75
|
+
def new_meta; end
|
76
|
+
def end_meta; end
|
77
|
+
def new_hunk; end
|
78
|
+
def end_hunk; end
|
79
|
+
def new_submodule_header; @submodule={}; end
|
80
|
+
def end_submodule_header; end
|
81
|
+
def new_submodule; end
|
82
|
+
def end_submodule; end
|
83
|
+
def new_diff_header; @file={mode: :modify} end
|
84
|
+
def end_diff_header; end
|
85
|
+
|
86
|
+
def detect_new_diff_header
|
87
|
+
@line =~ /^diff\s/
|
88
|
+
end
|
89
|
+
def detect_end_diff_header
|
90
|
+
@line =~ /^\+\+\+\s/
|
91
|
+
end
|
92
|
+
|
93
|
+
def detect_new_hunk
|
94
|
+
@line.match(/^@@+\s.*\s@@/)
|
95
|
+
end
|
96
|
+
def detect_end_hunk
|
97
|
+
@hunk[:lines_seen].each_with_index.all? { |v,i| v==@hunk[:lines][i].first }
|
98
|
+
end
|
99
|
+
|
100
|
+
def handle_meta
|
101
|
+
handle_line
|
102
|
+
end
|
103
|
+
|
104
|
+
def parse_hunk_header
|
105
|
+
m=@line.match(/^@@+\s(.*)\s@@\s*(.*)/)
|
106
|
+
hunks=m[1]
|
107
|
+
@hunk={lines: []}
|
108
|
+
@hunk[:header]=m[2]
|
109
|
+
filenumber=0
|
110
|
+
hunks.split.each do |hunk|
|
111
|
+
hunkmode=hunk[0]
|
112
|
+
hunk=hunk[1..-1]
|
113
|
+
line,length=hunk.split(',').map(&:to_i)
|
114
|
+
#handle hunks of the form @@ -1 +0,0 @@
|
115
|
+
length,line=line,length unless length
|
116
|
+
case hunkmode
|
117
|
+
when '-'
|
118
|
+
filenumber+=1
|
119
|
+
@hunk[:lines][filenumber]=[length,line]
|
120
|
+
when '+'
|
121
|
+
@hunk[:lines][0]=[length,line]
|
122
|
+
end
|
123
|
+
end
|
124
|
+
@hunk[:n]=@hunk[:lines].length
|
125
|
+
@hunk[:lines_seen]=Array.new(@hunk[:n],0)
|
126
|
+
end
|
127
|
+
|
128
|
+
def handle_hunk
|
129
|
+
if @start_mode
|
130
|
+
parse_hunk_header
|
131
|
+
else
|
132
|
+
#'The 'No new line at end of file' is sort of part of the hunk, but
|
133
|
+
#is not considerer in the hunkheader
|
134
|
+
unless @line == NoNewLine
|
135
|
+
#we need to wait for a NoNewLine to be sure we are at the end of the hunk
|
136
|
+
return reparse(:unknown) if detect_end_hunk
|
137
|
+
linemodes=@line[0...@hunk[:n]-1]
|
138
|
+
newline=true
|
139
|
+
#the line is on the new file unless there is a '-' somewhere
|
140
|
+
if linemodes=~/-/
|
141
|
+
newline=false
|
142
|
+
else
|
143
|
+
@hunk[:lines_seen][0]+=1
|
144
|
+
end
|
145
|
+
(1...@hunk[:n]).each do |i|
|
146
|
+
linemode=linemodes[i-1]
|
147
|
+
case linemode
|
148
|
+
when '-'
|
149
|
+
@hunk[:lines_seen][i]+=1
|
150
|
+
when ' '
|
151
|
+
@hunk[:lines_seen][i]+=1 if newline
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
handle_line
|
157
|
+
end
|
158
|
+
|
159
|
+
def get_file_name(file)
|
160
|
+
#remove prefix (todo handle the no-prefix option)
|
161
|
+
file.gsub(/^[abciow12]\//,'')
|
162
|
+
end
|
163
|
+
|
164
|
+
def detect_filename
|
165
|
+
if m=@line.match(/^---\s(.*)/)
|
166
|
+
@file[:old_name]=get_file_name(m[1])
|
167
|
+
return true
|
168
|
+
end
|
169
|
+
if m=@line.match(/^\+\+\+\s(.*)/)
|
170
|
+
@file[:name]=get_file_name(m[1])
|
171
|
+
return true
|
172
|
+
end
|
173
|
+
false
|
174
|
+
end
|
175
|
+
|
176
|
+
def detect_perm
|
177
|
+
if m=@line.match(/^old mode\s+(.*)/)
|
178
|
+
@file[:old_perm]=m[1]
|
179
|
+
return true
|
180
|
+
end
|
181
|
+
if m=@line.match(/^new mode\s+(.*)/)
|
182
|
+
@file[:new_perm]=m[1]
|
183
|
+
return true
|
184
|
+
end
|
185
|
+
false
|
186
|
+
end
|
187
|
+
|
188
|
+
def detect_index
|
189
|
+
if m=@line.match(/^index\s+(.*)\.\.(.*)/)
|
190
|
+
@file[:oldhash]=m[1].split(',')
|
191
|
+
@file[:hash],perm=m[2].split
|
192
|
+
@file[:perm]||=perm
|
193
|
+
return true
|
194
|
+
end
|
195
|
+
false
|
196
|
+
end
|
197
|
+
|
198
|
+
def detect_delete
|
199
|
+
if m=@line.match(/^deleted file mode\s+(.*)/)
|
200
|
+
@file[:old_perm]=m[1]
|
201
|
+
@file[:mode]=:delete
|
202
|
+
return true
|
203
|
+
end
|
204
|
+
false
|
205
|
+
end
|
206
|
+
|
207
|
+
def detect_newfile
|
208
|
+
if m=@line.match(/^new file mode\s+(.*)/)
|
209
|
+
@file[:new_perm]=m[1]
|
210
|
+
@file[:mode]=:new
|
211
|
+
return true
|
212
|
+
end
|
213
|
+
false
|
214
|
+
end
|
215
|
+
|
216
|
+
def detect_rename_copy
|
217
|
+
if m=@line.match(/^similarity index\s+(.*)/)
|
218
|
+
@file[:similarity]=m[1]
|
219
|
+
return true
|
220
|
+
end
|
221
|
+
if m=@line.match(/^dissimilarity index\s+(.*)/)
|
222
|
+
@file[:mode]=:rewrite
|
223
|
+
@file[:dissimilarity]=m[1]
|
224
|
+
return true
|
225
|
+
end
|
226
|
+
#if we have a rename with 100% similarity, there won't be any hunks so
|
227
|
+
#we need to detect the filenames there
|
228
|
+
if m=@line.match(/^(?:rename|copy) from\s+(.*)/)
|
229
|
+
@file[:old_name]=m[1]
|
230
|
+
end
|
231
|
+
if m=@line.match(/^(?:rename|copy) to\s+(.*)/)
|
232
|
+
@file[:name]=m[1]
|
233
|
+
end
|
234
|
+
if m=@line.match(/^rename\s+(.*)/)
|
235
|
+
@file[:mode]=:rename
|
236
|
+
return true
|
237
|
+
end
|
238
|
+
if m=@line.match(/^copy\s+(.*)/)
|
239
|
+
@file[:mode]=:copy
|
240
|
+
return true
|
241
|
+
end
|
242
|
+
false
|
243
|
+
end
|
244
|
+
|
245
|
+
def detect_diff_header
|
246
|
+
if @start_mode
|
247
|
+
if m=@line.chomp.match(/^diff\s--git\s(.*)\s(.*)/)
|
248
|
+
@file[:old_name]=get_file_name(m[1])
|
249
|
+
@file[:name]=get_file_name(m[2])
|
250
|
+
elsif
|
251
|
+
m=@line.match(/^diff\s--(?:cc|combined)\s(.*)/)
|
252
|
+
@file[:name]=get_file_name(m[1])
|
253
|
+
end
|
254
|
+
true
|
255
|
+
end
|
256
|
+
end
|
257
|
+
|
258
|
+
def handle_diff_header
|
259
|
+
if detect_diff_header
|
260
|
+
elsif detect_filename
|
261
|
+
elsif detect_perm
|
262
|
+
elsif detect_index
|
263
|
+
elsif detect_delete
|
264
|
+
elsif detect_newfile
|
265
|
+
elsif detect_rename_copy
|
266
|
+
else
|
267
|
+
return reparse(:unknown)
|
268
|
+
end
|
269
|
+
next_mode(:unknown) if detect_end_diff_header
|
270
|
+
handle_line
|
271
|
+
end
|
272
|
+
|
273
|
+
def detect_new_submodule_header
|
274
|
+
if m=@line.chomp.match(/^Submodule\s(.*)\s(.*)/)
|
275
|
+
subname=m[1];
|
276
|
+
return not(@submodule && @submodule[:name]==subname)
|
277
|
+
end
|
278
|
+
false
|
279
|
+
end
|
280
|
+
|
281
|
+
def handle_submodule_header
|
282
|
+
if m=@line.chomp.match(/^Submodule\s(\S*)\s(.*)/)
|
283
|
+
subname=m[1]
|
284
|
+
if @submodule[:name]
|
285
|
+
#we may be dealing with a new submodule
|
286
|
+
#require 'pry'; binding.pry
|
287
|
+
return reparse(:submodule_header) if subname != @submodule[:name]
|
288
|
+
else
|
289
|
+
@submodule[:name]=m[1]
|
290
|
+
end
|
291
|
+
subinfo=m[2]
|
292
|
+
if subinfo == "contains untracked content"
|
293
|
+
@submodule[:untracked]=true
|
294
|
+
elsif subinfo == "contains modified content"
|
295
|
+
@submodule[:modified]=true
|
296
|
+
else
|
297
|
+
(@submodule[:info]||="") << subinfo
|
298
|
+
next_mode(:submodule) if subinfo =~ /^.......\.\.\.?........*:$/
|
299
|
+
end
|
300
|
+
handle_line
|
301
|
+
else
|
302
|
+
return reparse(:unknown)
|
303
|
+
end
|
304
|
+
end
|
305
|
+
|
306
|
+
def submodule_line
|
307
|
+
@line=~/^ [><] /
|
308
|
+
end
|
309
|
+
|
310
|
+
def handle_submodule
|
311
|
+
#we have lines indicating new commits
|
312
|
+
#they always end by a new line except when followed by another submodule
|
313
|
+
return reparse(:unknown) if !submodule_line
|
314
|
+
handle_line
|
315
|
+
end
|
316
|
+
|
317
|
+
def detect_new_commit
|
318
|
+
@line=~/^commit\b/
|
319
|
+
end
|
320
|
+
|
321
|
+
def handle_commit
|
322
|
+
if m=@line.match(/^(\w+):\s(.*)/)
|
323
|
+
@commit[m[1]]=m[2]
|
324
|
+
handle_line
|
325
|
+
else
|
326
|
+
@start_mode ? handle_line : reparse(:unknown)
|
327
|
+
end
|
328
|
+
end
|
329
|
+
|
330
|
+
def reparse(nmode)
|
331
|
+
change_mode(nmode)
|
332
|
+
parse_line
|
333
|
+
end
|
334
|
+
|
335
|
+
def handle_line
|
336
|
+
end
|
337
|
+
|
338
|
+
|
339
|
+
def parse_line
|
340
|
+
case @mode
|
341
|
+
when :unknown, :meta
|
342
|
+
if detect_new_hunk
|
343
|
+
return reparse(:hunk)
|
344
|
+
elsif detect_new_diff_header
|
345
|
+
return reparse(:diff_header)
|
346
|
+
elsif detect_new_submodule_header
|
347
|
+
return reparse(:submodule_header)
|
348
|
+
elsif detect_new_commit
|
349
|
+
return reparse(:commit)
|
350
|
+
else
|
351
|
+
change_mode(:meta) if @mode==:unknown
|
352
|
+
handle_meta
|
353
|
+
end
|
354
|
+
when :commit
|
355
|
+
handle_commit
|
356
|
+
when :submodule_header
|
357
|
+
handle_submodule_header
|
358
|
+
when :submodule
|
359
|
+
handle_submodule
|
360
|
+
when :diff_header
|
361
|
+
handle_diff_header
|
362
|
+
#=> mode=unknown if we detect we are not a diff header anymore
|
363
|
+
when :hunk
|
364
|
+
handle_hunk
|
365
|
+
#=> mode=unknown at end of hunk
|
366
|
+
end
|
367
|
+
end
|
368
|
+
|
369
|
+
def prepare_new_line(line)
|
370
|
+
@orig_line=line
|
371
|
+
@line=@orig_line.uncolor
|
372
|
+
update_mode
|
373
|
+
end
|
374
|
+
|
375
|
+
def parse
|
376
|
+
Enumerator.new do |y|
|
377
|
+
@output=y
|
378
|
+
@diff.each do |line|
|
379
|
+
prepare_new_line(line)
|
380
|
+
parse_line
|
381
|
+
yield if block_given?
|
382
|
+
end
|
383
|
+
change_mode(:unknown) #to trigger the last end_* hook
|
384
|
+
end
|
385
|
+
end
|
386
|
+
|
387
|
+
def each(&b)
|
388
|
+
parse.each(&b)
|
389
|
+
end
|
390
|
+
end
|
391
|
+
|
392
|
+
class GitDiffDebug < GitDiff
|
393
|
+
def initialize(*args,&b)
|
394
|
+
super
|
395
|
+
@cols=`tput cols`.to_i
|
396
|
+
end
|
397
|
+
|
398
|
+
def center(msg)
|
399
|
+
msg.center(@cols,'─')
|
400
|
+
end
|
401
|
+
|
402
|
+
def handle_line
|
403
|
+
super
|
404
|
+
output_line "#{@mode}: #{@orig_line}"
|
405
|
+
#p @hunk if @mode==:hunk
|
406
|
+
end
|
407
|
+
|
408
|
+
%i(commit meta diff_header hunk submodule_header submodule).each do |meth|
|
409
|
+
define_method(:"new_#{meth}") do |*a,&b|
|
410
|
+
super(*a,&b)
|
411
|
+
output_line(center("New #{meth}"))
|
412
|
+
end
|
413
|
+
define_method(:"end_#{meth}") do |*a,&b|
|
414
|
+
super(*a,&b)
|
415
|
+
output_line(center("End #{meth}"))
|
416
|
+
end
|
417
|
+
end
|
418
|
+
end
|
419
|
+
|
420
|
+
#stolen from diff-highlight git contrib script
|
421
|
+
class GitDiffHighlight < GitDiff
|
422
|
+
def new_hunk
|
423
|
+
super
|
424
|
+
@accumulator=[[],[]]
|
425
|
+
end
|
426
|
+
def end_hunk
|
427
|
+
super
|
428
|
+
show_hunk
|
429
|
+
end
|
430
|
+
|
431
|
+
def highlight_pair(old,new)
|
432
|
+
oldc=SimpleColor.color_entities(old).each_with_index
|
433
|
+
newc=SimpleColor.color_entities(new).each_with_index
|
434
|
+
seen_pm=false
|
435
|
+
#find common prefix
|
436
|
+
loop do
|
437
|
+
a=oldc.grep {|c| ! SimpleColor.color?(c)}
|
438
|
+
b=newc.grep {|c| ! SimpleColor.color?(c)}
|
439
|
+
if !seen_pm and a=="-" and b=="+"
|
440
|
+
seen_pm=true
|
441
|
+
elsif a==b
|
442
|
+
else
|
443
|
+
last
|
444
|
+
end
|
445
|
+
#rescue StopIteration
|
446
|
+
end
|
447
|
+
end
|
448
|
+
|
449
|
+
def show_hunk
|
450
|
+
old,new=@accumulator
|
451
|
+
if old.length != new.length
|
452
|
+
output_lines(old+new)
|
453
|
+
else
|
454
|
+
newhunk=[]
|
455
|
+
(0...old.length).each do |i|
|
456
|
+
oldi,newi=highlight_pair(old[i],new[i])
|
457
|
+
output_line oldi
|
458
|
+
newhunk << newi
|
459
|
+
end
|
460
|
+
output_lines(newhunk)
|
461
|
+
end
|
462
|
+
end
|
463
|
+
|
464
|
+
def handle_line
|
465
|
+
if @mode == :hunk && @hunk[:n]==2
|
466
|
+
linemode=@line[0]
|
467
|
+
case linemode
|
468
|
+
when "-"
|
469
|
+
@accumulator[0] << @orig_line
|
470
|
+
when "+"
|
471
|
+
@accumulator[1] << @orig_line
|
472
|
+
else
|
473
|
+
show_hunk
|
474
|
+
@accumulator=[[],[]]
|
475
|
+
output_line @orig_line
|
476
|
+
end
|
477
|
+
else
|
478
|
+
output_line @orig_line
|
479
|
+
end
|
480
|
+
end
|
481
|
+
end
|
482
|
+
|
483
|
+
class GitFancyDiff < GitDiff
|
484
|
+
|
485
|
+
def initialize(*args,**kw,&b)
|
486
|
+
super
|
487
|
+
#when run inside a pager I get one more column so the line overflow
|
488
|
+
#I don't know why
|
489
|
+
cols=`tput cols`.to_i
|
490
|
+
cols==0 && cols=80 #if TERM is not defined `tput cols` returns ''
|
491
|
+
@cols=cols-1
|
492
|
+
end
|
493
|
+
|
494
|
+
def hline
|
495
|
+
'─'*@cols
|
496
|
+
end
|
497
|
+
def hhline
|
498
|
+
#'⬛'*@cols
|
499
|
+
#"━"*@cols
|
500
|
+
"═"*@cols
|
501
|
+
end
|
502
|
+
|
503
|
+
def short_perm_mode(m, prefix: '+')
|
504
|
+
case m
|
505
|
+
when "040000"
|
506
|
+
prefix+"d" #directory
|
507
|
+
when "100644"
|
508
|
+
"" #file
|
509
|
+
when "100755"
|
510
|
+
prefix+"x" #executable
|
511
|
+
when "120000"
|
512
|
+
prefix+"l" #symlink
|
513
|
+
when "160000"
|
514
|
+
prefix+"g" #gitlink
|
515
|
+
end
|
516
|
+
end
|
517
|
+
def perm_mode(m, prefix: ' ')
|
518
|
+
case m
|
519
|
+
when "040000"
|
520
|
+
prefix+"directory"
|
521
|
+
when "100644"
|
522
|
+
"" #file
|
523
|
+
when "100755"
|
524
|
+
prefix+"executable"
|
525
|
+
when "120000"
|
526
|
+
prefix+"symlink"
|
527
|
+
when "160000"
|
528
|
+
prefix+"gitlink"
|
529
|
+
end
|
530
|
+
end
|
531
|
+
|
532
|
+
def diff_header_summary
|
533
|
+
r=case @file[:mode]
|
534
|
+
when :modify
|
535
|
+
"modified: #{@file[:name]}"
|
536
|
+
when :rewrite
|
537
|
+
"rewrote: #{@file[:name]} (dissimilarity: #{@file[:dissimilarity]})"
|
538
|
+
when :new
|
539
|
+
"added#{perm_mode(@file[:new_perm])}: #{@file[:name]}"
|
540
|
+
when :delete
|
541
|
+
"deleted#{perm_mode(@file[:old_perm])}: #{@file[:old_name]}"
|
542
|
+
when :rename
|
543
|
+
"renamed: #{@file[:old_name]} to #{@file[:name]} (similarity: #{@file[:similarity]})"
|
544
|
+
when :copy
|
545
|
+
"copied: #{@file[:old_name]} to #{@file[:name]} (similarity: #{@file[:similarity]})"
|
546
|
+
end
|
547
|
+
r<<" [#{short_perm_mode(@file[:old_perm],prefix:'-')}#{short_perm_mode(@file[:new_perm])}]" if @file[:old_perm] && @file[:new_perm]
|
548
|
+
r
|
549
|
+
end
|
550
|
+
|
551
|
+
def meta_colorize(l)
|
552
|
+
if @opts[:color]
|
553
|
+
l.color(*@colors[:meta])
|
554
|
+
else
|
555
|
+
l
|
556
|
+
end
|
557
|
+
end
|
558
|
+
|
559
|
+
def new_diff_header
|
560
|
+
super
|
561
|
+
output_line meta_colorize(hline)
|
562
|
+
end
|
563
|
+
|
564
|
+
def end_diff_header
|
565
|
+
super
|
566
|
+
output_line meta_colorize(diff_header_summary)
|
567
|
+
output_line meta_colorize(hline)
|
568
|
+
end
|
569
|
+
|
570
|
+
def submodule_header_summary
|
571
|
+
r="Submodule #{@submodule[:name]}"
|
572
|
+
extra=[@submodule[:modified] && "modified", @submodule[:untracked] && "untracked"].compact.join("+")
|
573
|
+
r<<" [#{extra}]" unless extra.empty?
|
574
|
+
r << " #{@submodule[:info]}" if @submodule[:info]
|
575
|
+
r
|
576
|
+
end
|
577
|
+
|
578
|
+
def new_submodule_header
|
579
|
+
super
|
580
|
+
output_line meta_colorize(hline)
|
581
|
+
end
|
582
|
+
|
583
|
+
def end_submodule_header
|
584
|
+
super
|
585
|
+
output_line meta_colorize(submodule_header_summary)
|
586
|
+
output_line meta_colorize(hline)
|
587
|
+
end
|
588
|
+
|
589
|
+
def nonewline_clean
|
590
|
+
@mode==:hunk && @file && (@file[:perm]=="120000" or @file[:old_perm]=="120000" or @file[:new_perm]=="120000") && @line==NoNewLine
|
591
|
+
end
|
592
|
+
|
593
|
+
def new_commit
|
594
|
+
super
|
595
|
+
output_line meta_colorize(hhline)
|
596
|
+
end
|
597
|
+
def end_commit
|
598
|
+
super
|
599
|
+
output_line meta_colorize(hhline)
|
600
|
+
end
|
601
|
+
|
602
|
+
def clean_hunk_col
|
603
|
+
if @opts[:color] && @mode==:hunk && !@start_mode && @hunk[:n]==2
|
604
|
+
bcolor,ecolor,line=SimpleColor.current_colors(@orig_line)
|
605
|
+
m=line.scrub.match(/^([+-])?(.*)/)
|
606
|
+
mode=m[1]
|
607
|
+
cline=m[2]
|
608
|
+
if mode && cline !~ /[^[:space:]]/ #detect blank line
|
609
|
+
output_line SimpleColor.color(bcolor.to_s + (cline.empty? ? " ": cline)+ecolor.to_s,:inverse)
|
610
|
+
else
|
611
|
+
cline.sub!(/^\s/,'') unless mode #strip one blank character
|
612
|
+
output_line bcolor.to_s+cline+ecolor.to_s
|
613
|
+
end
|
614
|
+
true
|
615
|
+
end
|
616
|
+
end
|
617
|
+
|
618
|
+
def hunk_header
|
619
|
+
if @mode==:hunk && @start_mode
|
620
|
+
if @hunk[:lines][0][1] && @hunk[:lines][0][1] != 0
|
621
|
+
header="#{@file[:name]}:#{@hunk[:lines][0][1]}"
|
622
|
+
output_line @orig_line.sub(/(@@+\s)(.*)(\s@@+)/,"\\1#{header}\\3")
|
623
|
+
end
|
624
|
+
true
|
625
|
+
end
|
626
|
+
end
|
627
|
+
|
628
|
+
def binary_file_differ
|
629
|
+
@file and (@file[:mode]==:new && @line =~ %r{^Binary files /dev/null and ./#{@file[:name]} differ$} or
|
630
|
+
@file[:mode]==:delete && @line =~ %r{^Binary files ./#{@file[:old_name]} and /dev/null differ$})
|
631
|
+
end
|
632
|
+
|
633
|
+
def handle_line
|
634
|
+
super
|
635
|
+
#:diff_header and submodule_header are handled at end_*
|
636
|
+
case @mode
|
637
|
+
when :meta
|
638
|
+
if binary_file_differ
|
639
|
+
else output_line @orig_line
|
640
|
+
end
|
641
|
+
when :hunk
|
642
|
+
if hunk_header
|
643
|
+
elsif nonewline_clean
|
644
|
+
elsif clean_hunk_col
|
645
|
+
else output_line @orig_line
|
646
|
+
end
|
647
|
+
when :submodule,:commit
|
648
|
+
output_line @orig_line
|
649
|
+
end
|
650
|
+
end
|
651
|
+
end
|
652
|
+
end
|
653
|
+
|
654
|
+
if __FILE__ == $0
|
655
|
+
require 'optparse'
|
656
|
+
|
657
|
+
@opts={pager: true, diff_highlight: true, color: true, debug: false}
|
658
|
+
optparse = OptionParser.new do |opt|
|
659
|
+
opt.banner = "fancy git diff"
|
660
|
+
opt.on("--[no-]pager", "launch the pager [true]") do |v|
|
661
|
+
@opts[:pager]=v
|
662
|
+
end
|
663
|
+
opt.on("--[no-]highlight", "run the diff through diff-highlight [true]") do |v|
|
664
|
+
@opts[:diff_highlight]=v
|
665
|
+
end
|
666
|
+
opt.on("--[no-]color", "color output [true]") do |v|
|
667
|
+
@opts[:color]=v
|
668
|
+
end
|
669
|
+
opt.on("--raw", "Only parse diff headers") do |v|
|
670
|
+
@opts[:color]=false
|
671
|
+
@opts[:pager]=false
|
672
|
+
@opts[:diff_highlight]=false
|
673
|
+
end
|
674
|
+
opt.on("--[no-]debug", "Debug mode") do |v|
|
675
|
+
@opts[:debug]=v
|
676
|
+
end
|
677
|
+
end
|
678
|
+
optparse.parse!
|
679
|
+
@opts[:pager]=false unless Module.const_defined?('ShellHelpers')
|
680
|
+
@opts[:pager] && ShellHelpers.run_pager #("--pattern '^(Date|added|deleted|modified): '")
|
681
|
+
|
682
|
+
diff_highlight=ENV['DIFF_HIGHLIGHT']||"#{File.dirname(__FILE__)}/contrib/diff-highlight/diff-highlight"
|
683
|
+
|
684
|
+
args=ARGF
|
685
|
+
if @opts[:debug]
|
686
|
+
GitHelpers::GitDiffDebug.new(args,**@opts).output
|
687
|
+
elsif @opts[:diff_highlight]
|
688
|
+
IO.popen(diff_highlight,'r+') do |f|
|
689
|
+
Thread.new do
|
690
|
+
args.each_line do |l|
|
691
|
+
f.write(l)
|
692
|
+
end
|
693
|
+
f.close_write
|
694
|
+
end
|
695
|
+
GitHelpers::GitFancyDiff.new(f,**@opts).output
|
696
|
+
end
|
697
|
+
else
|
698
|
+
#diff=GitDiffHighlight.new(args,**@opts).parse
|
699
|
+
GitHelpers::GitFancyDiff.new(args,**@opts).output
|
700
|
+
end
|
701
|
+
end
|