git_helpers 0.1.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.
- checksums.yaml +7 -0
- data/.gitignore +6 -0
- data/.travis.yml +10 -0
- data/.yardopts +6 -0
- data/ChangeLog.md +4 -0
- data/Gemfile +9 -0
- data/LICENSE.txt +20 -0
- data/README.md +55 -0
- data/Rakefile +29 -0
- data/bin/diff-fancy.rb +699 -0
- data/bin/gitstatus.rb +349 -0
- data/fixtures/git-diff.diff +254 -0
- data/gemspec.yml +18 -0
- data/git_helpers.gemspec +71 -0
- data/lib/git_helpers.rb +344 -0
- data/lib/git_helpers/extra_helpers.rb +359 -0
- data/lib/git_helpers/version.rb +4 -0
- data/test/helper.rb +13 -0
- data/test/test_git_helpers.rb +12 -0
- metadata +178 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 89d50a9fc84a8451a75811aed19d8d5763114dbddda62abb44a2f32ee9784b60
|
4
|
+
data.tar.gz: 2cec1efd8ac009542d1cc883dc94c8f56b2f0326434cbbea8e59a916b03eb6a0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: f4a638373b1051726dab1a69543347cea204aa63b3241499d911485f3448b65da34c7f6a42b5934b7e63bdd80dfa06e1f949de007cefca932db57d679dfb05b9
|
7
|
+
data.tar.gz: 24577fa3a75b6a422bbb584a95519d9c269261161919e36c7e98f8d6b390541922d2775e00b796a3b0d7d99532052c598064fd7113c64c4730e4168cf9811f0b
|
data/.gitignore
ADDED
data/.travis.yml
ADDED
data/.yardopts
ADDED
data/ChangeLog.md
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright © 2016–2017 Damien Robert
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,55 @@
|
|
1
|
+
# git_helpers
|
2
|
+
|
3
|
+
* [Homepage](https://github.com/DamienRobert/git_helpers#readme)
|
4
|
+
* [Issues](https://github.com/DamienRobert/git_helpers/issues)
|
5
|
+
* [Documentation](http://rubydoc.info/gems/git_helpers)
|
6
|
+
* [Email](mailto:Damien.Olivier.Robert+gems at gmail.com)
|
7
|
+
|
8
|
+
[](https://rubygems.org/gems/git_helpers)
|
9
|
+
[](https://travis-ci.org/DamienRobert/git_helpers)
|
10
|
+
|
11
|
+
## Description
|
12
|
+
|
13
|
+
- diff-fancy.rb: like [diff so fancy](https://github.com/so-fancy/diff-so-fancy) but in ruby and with more features
|
14
|
+
- gitsatus.rb: lie [zsh git prompt](https://github.com/olivierverdier/zsh-git-prompt) but in ruby and with more features too!
|
15
|
+
|
16
|
+
## Diff Fancy
|
17
|
+
|
18
|
+
The output is very similar to diff-fancy.rb. With the following
|
19
|
+
differences:
|
20
|
+
- diff-fancy.rb implement a parser of git diff. It is then very easy to
|
21
|
+
tweak the output afterwards. The original diff-fancy relies on regexp,
|
22
|
+
which makes it harder to customize.
|
23
|
+
- support for submodules change in the diff
|
24
|
+
- support for octopus merge
|
25
|
+
- clean up 'No new line at end of file' for symlinks (which never have a new line)
|
26
|
+
|
27
|
+
TODO:
|
28
|
+
- support 'git log -p --graph'
|
29
|
+
- support git config to activate features on a repo basis
|
30
|
+
|
31
|
+
## Examples
|
32
|
+
|
33
|
+
- `gitstatus.rb folders`
|
34
|
+
- `git diff | diff-fancy.rb`
|
35
|
+
|
36
|
+
Here is my .gitconfig using diff-fancy.rb:
|
37
|
+
|
38
|
+
~~~
|
39
|
+
highlight = "!f() { [ \"$GIT_PREFIX\" != \"\" ] && cd \"$GIT_PREFIX\"; GIT_PAGER=\"diff-fancy.rb\" git $@; }; f"
|
40
|
+
di = "!f() { [ \"$GIT_PREFIX\" != \"\" ] && cd \"$GIT_PREFIX\"; GIT_PAGER=\"diff-fancy.rb\" git diff -B $@; }; f"
|
41
|
+
dc = "!f() { [ \"$GIT_PREFIX\" != \"\" ] && cd \"$GIT_PREFIX\"; GIT_PAGER=\"diff-fancy.rb\" git diff -B --staged $@; }; f"
|
42
|
+
dw = "!f() { [ \"$GIT_PREFIX\" != \"\" ] && cd \"$GIT_PREFIX\"; GIT_PAGER=\"diff-fancy.rb --no-highlight\" git diff -B --color-words $@; }; f"
|
43
|
+
dcw = "!f() { [ \"$GIT_PREFIX\" != \"\" ] && cd \"$GIT_PREFIX\"; GIT_PAGER=\"diff-fancy.rb --no-highlight\" git diff -B --staged --color-words $@; }; f"
|
44
|
+
~~~
|
45
|
+
|
46
|
+
## Install
|
47
|
+
|
48
|
+
#TODO: release the gem
|
49
|
+
$ gem install git_helpers
|
50
|
+
|
51
|
+
## Copyright
|
52
|
+
|
53
|
+
Copyright © 2016–2017 Damien Robert
|
54
|
+
|
55
|
+
MIT License. See [LICENSE.txt](./LICENSE.txt) for details.
|
data/Rakefile
ADDED
@@ -0,0 +1,29 @@
|
|
1
|
+
require 'rake'
|
2
|
+
|
3
|
+
begin
|
4
|
+
require 'rubygems/tasks'
|
5
|
+
Gem::Tasks.new(sign: {checksum: true, pgp: true},
|
6
|
+
scm: {status: true}) do |tasks|
|
7
|
+
tasks.console.command = 'pry'
|
8
|
+
end
|
9
|
+
rescue LoadError => e
|
10
|
+
warn e.message
|
11
|
+
end
|
12
|
+
|
13
|
+
require 'rake/testtask'
|
14
|
+
Rake::TestTask.new do |test|
|
15
|
+
test.libs << 'test'
|
16
|
+
test.pattern = 'test/**/test_*.rb'
|
17
|
+
test.verbose = true
|
18
|
+
end
|
19
|
+
|
20
|
+
begin
|
21
|
+
require 'yard'
|
22
|
+
YARD::Rake::YardocTask.new
|
23
|
+
rescue LoadError => e
|
24
|
+
task :yard do
|
25
|
+
warn e.message
|
26
|
+
end
|
27
|
+
end
|
28
|
+
task :doc => :yard
|
29
|
+
|
data/bin/diff-fancy.rb
ADDED
@@ -0,0 +1,699 @@
|
|
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
|
+
class GitDiff
|
17
|
+
def self.output(gdiff, **opts)
|
18
|
+
if gdiff.respond_to?(:each_line)
|
19
|
+
enum=gdiff.each_line
|
20
|
+
else
|
21
|
+
enum=gdiff.each
|
22
|
+
end
|
23
|
+
self.new(enum, **opts).output
|
24
|
+
end
|
25
|
+
|
26
|
+
attr_reader :output
|
27
|
+
include Enumerable
|
28
|
+
NoNewLine="\\n"
|
29
|
+
|
30
|
+
def initialize(diff,**opts)
|
31
|
+
@diff=diff #Assume diff is a line iterator ['gitdiff'.each_line]
|
32
|
+
@current=0
|
33
|
+
@mode=:unknown
|
34
|
+
@opts=opts
|
35
|
+
@opts[:color]=@opts.fetch(:color,true)
|
36
|
+
#modes:
|
37
|
+
#- unknown (temp mode)
|
38
|
+
#- commit
|
39
|
+
#- meta
|
40
|
+
#- submodule_header
|
41
|
+
#- submodule
|
42
|
+
#- diff_header
|
43
|
+
#- hunk
|
44
|
+
@colors={meta: [:bold]}
|
45
|
+
end
|
46
|
+
|
47
|
+
def output_line(l)
|
48
|
+
@output << l.chomp+"\n"
|
49
|
+
end
|
50
|
+
def output_lines(lines)
|
51
|
+
lines.each {|l| output_line l}
|
52
|
+
end
|
53
|
+
def output
|
54
|
+
each {|l| puts l}
|
55
|
+
end
|
56
|
+
|
57
|
+
def next_mode(nmode)
|
58
|
+
@next_mode=nmode
|
59
|
+
end
|
60
|
+
def update_mode
|
61
|
+
@start_mode=false
|
62
|
+
@next_mode && change_mode(@next_mode)
|
63
|
+
@next_mode=nil
|
64
|
+
end
|
65
|
+
def change_mode(nmode)
|
66
|
+
@start_mode=true
|
67
|
+
send :"end_#{@mode}" unless @mode==:unknown
|
68
|
+
@mode=nmode
|
69
|
+
send :"new_#{@mode}" unless @mode==:unknown
|
70
|
+
end
|
71
|
+
|
72
|
+
def new_commit; @commit={}; end
|
73
|
+
def end_commit; end
|
74
|
+
def new_meta; end
|
75
|
+
def end_meta; end
|
76
|
+
def new_hunk; end
|
77
|
+
def end_hunk; end
|
78
|
+
def new_submodule_header; @submodule={}; end
|
79
|
+
def end_submodule_header; end
|
80
|
+
def new_submodule; end
|
81
|
+
def end_submodule; end
|
82
|
+
def new_diff_header; @file={mode: :modify} end
|
83
|
+
def end_diff_header; end
|
84
|
+
|
85
|
+
def detect_new_diff_header
|
86
|
+
@line =~ /^diff\s/
|
87
|
+
end
|
88
|
+
def detect_end_diff_header
|
89
|
+
@line =~ /^\+\+\+\s/
|
90
|
+
end
|
91
|
+
|
92
|
+
def detect_new_hunk
|
93
|
+
@line.match(/^@@+\s.*\s@@/)
|
94
|
+
end
|
95
|
+
def detect_end_hunk
|
96
|
+
@hunk[:lines_seen].each_with_index.all? { |v,i| v==@hunk[:lines][i].first }
|
97
|
+
end
|
98
|
+
|
99
|
+
def handle_meta
|
100
|
+
handle_line
|
101
|
+
end
|
102
|
+
|
103
|
+
def parse_hunk_header
|
104
|
+
m=@line.match(/^@@+\s(.*)\s@@\s*(.*)/)
|
105
|
+
hunks=m[1]
|
106
|
+
@hunk={lines: []}
|
107
|
+
@hunk[:header]=m[2]
|
108
|
+
filenumber=0
|
109
|
+
hunks.split.each do |hunk|
|
110
|
+
hunkmode=hunk[0]
|
111
|
+
hunk=hunk[1..-1]
|
112
|
+
line,length=hunk.split(',').map(&:to_i)
|
113
|
+
#handle hunks of the form @@ -1 +0,0 @@
|
114
|
+
length,line=line,length unless length
|
115
|
+
case hunkmode
|
116
|
+
when '-'
|
117
|
+
filenumber+=1
|
118
|
+
@hunk[:lines][filenumber]=[length,line]
|
119
|
+
when '+'
|
120
|
+
@hunk[:lines][0]=[length,line]
|
121
|
+
end
|
122
|
+
end
|
123
|
+
@hunk[:n]=@hunk[:lines].length
|
124
|
+
@hunk[:lines_seen]=Array.new(@hunk[:n],0)
|
125
|
+
end
|
126
|
+
|
127
|
+
def handle_hunk
|
128
|
+
if @start_mode
|
129
|
+
parse_hunk_header
|
130
|
+
else
|
131
|
+
#'The 'No new line at end of file' is sort of part of the hunk, but
|
132
|
+
#is not considerer in the hunkheader
|
133
|
+
unless @line == NoNewLine
|
134
|
+
#we need to wait for a NoNewLine to be sure we are at the end of the hunk
|
135
|
+
return reparse(:unknown) if detect_end_hunk
|
136
|
+
linemodes=@line[0...@hunk[:n]-1]
|
137
|
+
newline=true
|
138
|
+
#the line is on the new file unless there is a '-' somewhere
|
139
|
+
if linemodes=~/-/
|
140
|
+
newline=false
|
141
|
+
else
|
142
|
+
@hunk[:lines_seen][0]+=1
|
143
|
+
end
|
144
|
+
(1...@hunk[:n]).each do |i|
|
145
|
+
linemode=linemodes[i-1]
|
146
|
+
case linemode
|
147
|
+
when '-'
|
148
|
+
@hunk[:lines_seen][i]+=1
|
149
|
+
when ' '
|
150
|
+
@hunk[:lines_seen][i]+=1 if newline
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
154
|
+
end
|
155
|
+
handle_line
|
156
|
+
end
|
157
|
+
|
158
|
+
def get_file_name(file)
|
159
|
+
#remove prefix (todo handle the no-prefix option)
|
160
|
+
file.gsub(/^[abciow12]\//,'')
|
161
|
+
end
|
162
|
+
|
163
|
+
def detect_filename
|
164
|
+
if m=@line.match(/^---\s(.*)/)
|
165
|
+
@file[:old_name]=get_file_name(m[1])
|
166
|
+
return true
|
167
|
+
end
|
168
|
+
if m=@line.match(/^\+\+\+\s(.*)/)
|
169
|
+
@file[:name]=get_file_name(m[1])
|
170
|
+
return true
|
171
|
+
end
|
172
|
+
false
|
173
|
+
end
|
174
|
+
|
175
|
+
def detect_perm
|
176
|
+
if m=@line.match(/^old mode\s+(.*)/)
|
177
|
+
@file[:old_perm]=m[1]
|
178
|
+
return true
|
179
|
+
end
|
180
|
+
if m=@line.match(/^new mode\s+(.*)/)
|
181
|
+
@file[:new_perm]=m[1]
|
182
|
+
return true
|
183
|
+
end
|
184
|
+
false
|
185
|
+
end
|
186
|
+
|
187
|
+
def detect_index
|
188
|
+
if m=@line.match(/^index\s+(.*)\.\.(.*)/)
|
189
|
+
@file[:oldhash]=m[1].split(',')
|
190
|
+
@file[:hash],perm=m[2].split
|
191
|
+
@file[:perm]||=perm
|
192
|
+
return true
|
193
|
+
end
|
194
|
+
false
|
195
|
+
end
|
196
|
+
|
197
|
+
def detect_delete
|
198
|
+
if m=@line.match(/^deleted file mode\s+(.*)/)
|
199
|
+
@file[:old_perm]=m[1]
|
200
|
+
@file[:mode]=:delete
|
201
|
+
return true
|
202
|
+
end
|
203
|
+
false
|
204
|
+
end
|
205
|
+
|
206
|
+
def detect_newfile
|
207
|
+
if m=@line.match(/^new file mode\s+(.*)/)
|
208
|
+
@file[:new_perm]=m[1]
|
209
|
+
@file[:mode]=:new
|
210
|
+
return true
|
211
|
+
end
|
212
|
+
false
|
213
|
+
end
|
214
|
+
|
215
|
+
def detect_rename_copy
|
216
|
+
if m=@line.match(/^similarity index\s+(.*)/)
|
217
|
+
@file[:similarity]=m[1]
|
218
|
+
return true
|
219
|
+
end
|
220
|
+
if m=@line.match(/^dissimilarity index\s+(.*)/)
|
221
|
+
@file[:mode]=:rewrite
|
222
|
+
@file[:dissimilarity]=m[1]
|
223
|
+
return true
|
224
|
+
end
|
225
|
+
#if we have a rename with 100% similarity, there won't be any hunks so
|
226
|
+
#we need to detect the filenames there
|
227
|
+
if m=@line.match(/^(?:rename|copy) from\s+(.*)/)
|
228
|
+
@file[:old_name]=m[1]
|
229
|
+
end
|
230
|
+
if m=@line.match(/^(?:rename|copy) to\s+(.*)/)
|
231
|
+
@file[:name]=m[1]
|
232
|
+
end
|
233
|
+
if m=@line.match(/^rename\s+(.*)/)
|
234
|
+
@file[:mode]=:rename
|
235
|
+
return true
|
236
|
+
end
|
237
|
+
if m=@line.match(/^copy\s+(.*)/)
|
238
|
+
@file[:mode]=:copy
|
239
|
+
return true
|
240
|
+
end
|
241
|
+
false
|
242
|
+
end
|
243
|
+
|
244
|
+
def detect_diff_header
|
245
|
+
if @start_mode
|
246
|
+
if m=@line.chomp.match(/^diff\s--git\s(.*)\s(.*)/)
|
247
|
+
@file[:old_name]=get_file_name(m[1])
|
248
|
+
@file[:name]=get_file_name(m[2])
|
249
|
+
elsif
|
250
|
+
m=@line.match(/^diff\s--(?:cc|combined)\s(.*)/)
|
251
|
+
@file[:name]=get_file_name(m[1])
|
252
|
+
end
|
253
|
+
true
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def handle_diff_header
|
258
|
+
if detect_diff_header
|
259
|
+
elsif detect_filename
|
260
|
+
elsif detect_perm
|
261
|
+
elsif detect_index
|
262
|
+
elsif detect_delete
|
263
|
+
elsif detect_newfile
|
264
|
+
elsif detect_rename_copy
|
265
|
+
else
|
266
|
+
return reparse(:unknown)
|
267
|
+
end
|
268
|
+
next_mode(:unknown) if detect_end_diff_header
|
269
|
+
handle_line
|
270
|
+
end
|
271
|
+
|
272
|
+
def detect_new_submodule_header
|
273
|
+
if m=@line.chomp.match(/^Submodule\s(.*)\s(.*)/)
|
274
|
+
subname=m[1];
|
275
|
+
return not(@submodule && @submodule[:name]==subname)
|
276
|
+
end
|
277
|
+
false
|
278
|
+
end
|
279
|
+
|
280
|
+
def handle_submodule_header
|
281
|
+
if m=@line.chomp.match(/^Submodule\s(\S*)\s(.*)/)
|
282
|
+
subname=m[1]
|
283
|
+
if @submodule[:name]
|
284
|
+
#we may be dealing with a new submodule
|
285
|
+
#require 'pry'; binding.pry
|
286
|
+
return reparse(:submodule_header) if subname != @submodule[:name]
|
287
|
+
else
|
288
|
+
@submodule[:name]=m[1]
|
289
|
+
end
|
290
|
+
subinfo=m[2]
|
291
|
+
if subinfo == "contains untracked content"
|
292
|
+
@submodule[:untracked]=true
|
293
|
+
elsif subinfo == "contains modified content"
|
294
|
+
@submodule[:modified]=true
|
295
|
+
else
|
296
|
+
(@submodule[:info]||="") << subinfo
|
297
|
+
next_mode(:submodule) if subinfo =~ /^.......\.\.\.?........*:$/
|
298
|
+
end
|
299
|
+
handle_line
|
300
|
+
else
|
301
|
+
return reparse(:unknown)
|
302
|
+
end
|
303
|
+
end
|
304
|
+
|
305
|
+
def submodule_line
|
306
|
+
@line=~/^ [><] /
|
307
|
+
end
|
308
|
+
|
309
|
+
def handle_submodule
|
310
|
+
#we have lines indicating new commits
|
311
|
+
#they always end by a new line except when followed by another submodule
|
312
|
+
return reparse(:unknown) if !submodule_line
|
313
|
+
handle_line
|
314
|
+
end
|
315
|
+
|
316
|
+
def detect_new_commit
|
317
|
+
@line=~/^commit\b/
|
318
|
+
end
|
319
|
+
|
320
|
+
def handle_commit
|
321
|
+
if m=@line.match(/^(\w+):\s(.*)/)
|
322
|
+
@commit[m[1]]=m[2]
|
323
|
+
handle_line
|
324
|
+
else
|
325
|
+
@start_mode ? handle_line : reparse(:unknown)
|
326
|
+
end
|
327
|
+
end
|
328
|
+
|
329
|
+
def reparse(nmode)
|
330
|
+
change_mode(nmode)
|
331
|
+
parse_line
|
332
|
+
end
|
333
|
+
|
334
|
+
def handle_line
|
335
|
+
end
|
336
|
+
|
337
|
+
|
338
|
+
def parse_line
|
339
|
+
case @mode
|
340
|
+
when :unknown, :meta
|
341
|
+
if detect_new_hunk
|
342
|
+
return reparse(:hunk)
|
343
|
+
elsif detect_new_diff_header
|
344
|
+
return reparse(:diff_header)
|
345
|
+
elsif detect_new_submodule_header
|
346
|
+
return reparse(:submodule_header)
|
347
|
+
elsif detect_new_commit
|
348
|
+
return reparse(:commit)
|
349
|
+
else
|
350
|
+
change_mode(:meta) if @mode==:unknown
|
351
|
+
handle_meta
|
352
|
+
end
|
353
|
+
when :commit
|
354
|
+
handle_commit
|
355
|
+
when :submodule_header
|
356
|
+
handle_submodule_header
|
357
|
+
when :submodule
|
358
|
+
handle_submodule
|
359
|
+
when :diff_header
|
360
|
+
handle_diff_header
|
361
|
+
#=> mode=unknown if we detect we are not a diff header anymore
|
362
|
+
when :hunk
|
363
|
+
handle_hunk
|
364
|
+
#=> mode=unknown at end of hunk
|
365
|
+
end
|
366
|
+
end
|
367
|
+
|
368
|
+
def prepare_new_line(line)
|
369
|
+
@orig_line=line
|
370
|
+
@line=@orig_line.uncolor
|
371
|
+
update_mode
|
372
|
+
end
|
373
|
+
|
374
|
+
def parse
|
375
|
+
Enumerator.new do |y|
|
376
|
+
@output=y
|
377
|
+
@diff.each do |line|
|
378
|
+
prepare_new_line(line)
|
379
|
+
parse_line
|
380
|
+
yield if block_given?
|
381
|
+
end
|
382
|
+
change_mode(:unknown) #to trigger the last end_* hook
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
def each(&b)
|
387
|
+
parse.each(&b)
|
388
|
+
end
|
389
|
+
end
|
390
|
+
|
391
|
+
class GitDiffDebug < GitDiff
|
392
|
+
def initialize(*args,&b)
|
393
|
+
super
|
394
|
+
@cols=`tput cols`.to_i
|
395
|
+
end
|
396
|
+
|
397
|
+
def center(msg)
|
398
|
+
msg.center(@cols,'─')
|
399
|
+
end
|
400
|
+
|
401
|
+
def handle_line
|
402
|
+
super
|
403
|
+
output_line "#{@mode}: #{@orig_line}"
|
404
|
+
#p @hunk if @mode==:hunk
|
405
|
+
end
|
406
|
+
|
407
|
+
%i(commit meta diff_header hunk submodule_header submodule).each do |meth|
|
408
|
+
define_method(:"new_#{meth}") do |*a,&b|
|
409
|
+
super(*a,&b)
|
410
|
+
output_line(center("New #{meth}"))
|
411
|
+
end
|
412
|
+
define_method(:"end_#{meth}") do |*a,&b|
|
413
|
+
super(*a,&b)
|
414
|
+
output_line(center("End #{meth}"))
|
415
|
+
end
|
416
|
+
end
|
417
|
+
end
|
418
|
+
|
419
|
+
#stolen from diff-highlight git contrib script
|
420
|
+
class GitDiffHighlight < GitDiff
|
421
|
+
def new_hunk
|
422
|
+
super
|
423
|
+
@accumulator=[[],[]]
|
424
|
+
end
|
425
|
+
def end_hunk
|
426
|
+
super
|
427
|
+
show_hunk
|
428
|
+
end
|
429
|
+
|
430
|
+
def highlight_pair(old,new)
|
431
|
+
oldc=SimpleColor.color_entities(old).each_with_index
|
432
|
+
newc=SimpleColor.color_entities(new).each_with_index
|
433
|
+
seen_pm=false
|
434
|
+
#find common prefix
|
435
|
+
loop do
|
436
|
+
a=oldc.grep {|c| ! SimpleColor.color?(c)}
|
437
|
+
b=newc.grep {|c| ! SimpleColor.color?(c)}
|
438
|
+
if !seen_pm and a=="-" and b=="+"
|
439
|
+
seen_pm=true
|
440
|
+
elsif a==b
|
441
|
+
else
|
442
|
+
last
|
443
|
+
end
|
444
|
+
#rescue StopIteration
|
445
|
+
end
|
446
|
+
end
|
447
|
+
|
448
|
+
def show_hunk
|
449
|
+
old,new=@accumulator
|
450
|
+
if old.length != new.length
|
451
|
+
output_lines(old+new)
|
452
|
+
else
|
453
|
+
newhunk=[]
|
454
|
+
(0...old.length).each do |i|
|
455
|
+
oldi,newi=highlight_pair(old[i],new[i])
|
456
|
+
output_line oldi
|
457
|
+
newhunk << newi
|
458
|
+
end
|
459
|
+
output_lines(newhunk)
|
460
|
+
end
|
461
|
+
end
|
462
|
+
|
463
|
+
def handle_line
|
464
|
+
if @mode == :hunk && @hunk[:n]==2
|
465
|
+
linemode=@line[0]
|
466
|
+
case linemode
|
467
|
+
when "-"
|
468
|
+
@accumulator[0] << @orig_line
|
469
|
+
when "+"
|
470
|
+
@accumulator[1] << @orig_line
|
471
|
+
else
|
472
|
+
show_hunk
|
473
|
+
@accumulator=[[],[]]
|
474
|
+
output_line @orig_line
|
475
|
+
end
|
476
|
+
else
|
477
|
+
output_line @orig_line
|
478
|
+
end
|
479
|
+
end
|
480
|
+
end
|
481
|
+
|
482
|
+
class GitFancyDiff < GitDiff
|
483
|
+
|
484
|
+
def initialize(*args,&b)
|
485
|
+
super
|
486
|
+
#when run inside a pager I get one more column so the line overflow
|
487
|
+
#I don't know why
|
488
|
+
cols=`tput cols`.to_i
|
489
|
+
cols==0 && cols=80 #if TERM is not defined `tput cols` returns ''
|
490
|
+
@cols=cols-1
|
491
|
+
end
|
492
|
+
|
493
|
+
def hline
|
494
|
+
'─'*@cols
|
495
|
+
end
|
496
|
+
def hhline
|
497
|
+
#'⬛'*@cols
|
498
|
+
#"━"*@cols
|
499
|
+
"═"*@cols
|
500
|
+
end
|
501
|
+
|
502
|
+
def short_perm_mode(m, prefix: '+')
|
503
|
+
case m
|
504
|
+
when "040000"
|
505
|
+
prefix+"d" #directory
|
506
|
+
when "100644"
|
507
|
+
"" #file
|
508
|
+
when "100755"
|
509
|
+
prefix+"x" #executable
|
510
|
+
when "120000"
|
511
|
+
prefix+"l" #symlink
|
512
|
+
when "160000"
|
513
|
+
prefix+"g" #gitlink
|
514
|
+
end
|
515
|
+
end
|
516
|
+
def perm_mode(m, prefix: ' ')
|
517
|
+
case m
|
518
|
+
when "040000"
|
519
|
+
prefix+"directory"
|
520
|
+
when "100644"
|
521
|
+
"" #file
|
522
|
+
when "100755"
|
523
|
+
prefix+"executable"
|
524
|
+
when "120000"
|
525
|
+
prefix+"symlink"
|
526
|
+
when "160000"
|
527
|
+
prefix+"gitlink"
|
528
|
+
end
|
529
|
+
end
|
530
|
+
|
531
|
+
def diff_header_summary
|
532
|
+
r=case @file[:mode]
|
533
|
+
when :modify
|
534
|
+
"modified: #{@file[:name]}"
|
535
|
+
when :rewrite
|
536
|
+
"rewrote: #{@file[:name]} (dissimilarity: #{@file[:dissimilarity]})"
|
537
|
+
when :new
|
538
|
+
"added#{perm_mode(@file[:new_perm])}: #{@file[:name]}"
|
539
|
+
when :delete
|
540
|
+
"deleted#{perm_mode(@file[:old_perm])}: #{@file[:old_name]}"
|
541
|
+
when :rename
|
542
|
+
"renamed: #{@file[:old_name]} to #{@file[:name]} (similarity: #{@file[:similarity]})"
|
543
|
+
when :copy
|
544
|
+
"copied: #{@file[:old_name]} to #{@file[:name]} (similarity: #{@file[:similarity]})"
|
545
|
+
end
|
546
|
+
r<<" [#{short_perm_mode(@file[:old_perm],prefix:'-')}#{short_perm_mode(@file[:new_perm])}]" if @file[:old_perm] && @file[:new_perm]
|
547
|
+
r
|
548
|
+
end
|
549
|
+
|
550
|
+
def meta_colorize(l)
|
551
|
+
if @opts[:color]
|
552
|
+
l.color(*@colors[:meta])
|
553
|
+
else
|
554
|
+
l
|
555
|
+
end
|
556
|
+
end
|
557
|
+
|
558
|
+
def new_diff_header
|
559
|
+
super
|
560
|
+
output_line meta_colorize(hline)
|
561
|
+
end
|
562
|
+
|
563
|
+
def end_diff_header
|
564
|
+
super
|
565
|
+
output_line meta_colorize(diff_header_summary)
|
566
|
+
output_line meta_colorize(hline)
|
567
|
+
end
|
568
|
+
|
569
|
+
def submodule_header_summary
|
570
|
+
r="Submodule #{@submodule[:name]}"
|
571
|
+
extra=[@submodule[:modified] && "modified", @submodule[:untracked] && "untracked"].compact.join("+")
|
572
|
+
r<<" [#{extra}]" unless extra.empty?
|
573
|
+
r << " #{@submodule[:info]}" if @submodule[:info]
|
574
|
+
r
|
575
|
+
end
|
576
|
+
|
577
|
+
def new_submodule_header
|
578
|
+
super
|
579
|
+
output_line meta_colorize(hline)
|
580
|
+
end
|
581
|
+
|
582
|
+
def end_submodule_header
|
583
|
+
super
|
584
|
+
output_line meta_colorize(submodule_header_summary)
|
585
|
+
output_line meta_colorize(hline)
|
586
|
+
end
|
587
|
+
|
588
|
+
def nonewline_clean
|
589
|
+
@mode==:hunk && @file && (@file[:perm]=="120000" or @file[:old_perm]=="120000" or @file[:new_perm]=="120000") && @line==NoNewLine
|
590
|
+
end
|
591
|
+
|
592
|
+
def new_commit
|
593
|
+
super
|
594
|
+
output_line meta_colorize(hhline)
|
595
|
+
end
|
596
|
+
def end_commit
|
597
|
+
super
|
598
|
+
output_line meta_colorize(hhline)
|
599
|
+
end
|
600
|
+
|
601
|
+
def clean_hunk_col
|
602
|
+
if @opts[:color] && @mode==:hunk && !@start_mode && @hunk[:n]==2
|
603
|
+
bcolor,ecolor,line=SimpleColor.current_colors(@orig_line)
|
604
|
+
m=line.scrub.match(/^([+-])?(.*)/)
|
605
|
+
mode=m[1]
|
606
|
+
cline=m[2]
|
607
|
+
if mode && cline !~ /[^[:space:]]/ #detect blank line
|
608
|
+
output_line SimpleColor.color(bcolor.to_s + (cline.empty? ? " ": cline)+ecolor.to_s,:inverse)
|
609
|
+
else
|
610
|
+
cline.sub!(/^\s/,'') unless mode #strip one blank character
|
611
|
+
output_line bcolor.to_s+cline+ecolor.to_s
|
612
|
+
end
|
613
|
+
true
|
614
|
+
end
|
615
|
+
end
|
616
|
+
|
617
|
+
def hunk_header
|
618
|
+
if @mode==:hunk && @start_mode
|
619
|
+
if @hunk[:lines][0][1] && @hunk[:lines][0][1] != 0
|
620
|
+
header="#{@file[:name]}:#{@hunk[:lines][0][1]}"
|
621
|
+
output_line @orig_line.sub(/(@@+\s)(.*)(\s@@+)/,"\\1#{header}\\3")
|
622
|
+
end
|
623
|
+
true
|
624
|
+
end
|
625
|
+
end
|
626
|
+
|
627
|
+
def binary_file_differ
|
628
|
+
@file and (@file[:mode]==:new && @line =~ %r{^Binary files /dev/null and ./#{@file[:name]} differ$} or
|
629
|
+
@file[:mode]==:delete && @line =~ %r{^Binary files ./#{@file[:old_name]} and /dev/null differ$})
|
630
|
+
end
|
631
|
+
|
632
|
+
def handle_line
|
633
|
+
super
|
634
|
+
#:diff_header and submodule_header are handled at end_*
|
635
|
+
case @mode
|
636
|
+
when :meta
|
637
|
+
if binary_file_differ
|
638
|
+
else output_line @orig_line
|
639
|
+
end
|
640
|
+
when :hunk
|
641
|
+
if hunk_header
|
642
|
+
elsif nonewline_clean
|
643
|
+
elsif clean_hunk_col
|
644
|
+
else output_line @orig_line
|
645
|
+
end
|
646
|
+
when :submodule,:commit
|
647
|
+
output_line @orig_line
|
648
|
+
end
|
649
|
+
end
|
650
|
+
end
|
651
|
+
|
652
|
+
if __FILE__ == $0
|
653
|
+
require 'optparse'
|
654
|
+
|
655
|
+
@opts={pager: true, diff_highlight: true, color: true, debug: false}
|
656
|
+
optparse = OptionParser.new do |opt|
|
657
|
+
opt.banner = "fancy git diff"
|
658
|
+
opt.on("--[no-]pager", "launch the pager [true]") do |v|
|
659
|
+
@opts[:pager]=v
|
660
|
+
end
|
661
|
+
opt.on("--[no-]highlight", "run the diff through diff-highlight [true]") do |v|
|
662
|
+
@opts[:diff_highlight]=v
|
663
|
+
end
|
664
|
+
opt.on("--[no-]color", "color output [true]") do |v|
|
665
|
+
@opts[:color]=v
|
666
|
+
end
|
667
|
+
opt.on("--raw", "Only parse diff headers") do |v|
|
668
|
+
@opts[:color]=false
|
669
|
+
@opts[:pager]=false
|
670
|
+
@opts[:diff_highlight]=false
|
671
|
+
end
|
672
|
+
opt.on("--[no-]debug", "Debug mode") do |v|
|
673
|
+
@opts[:debug]=v
|
674
|
+
end
|
675
|
+
end
|
676
|
+
optparse.parse!
|
677
|
+
@opts[:pager]=false unless Module.const_defined?('ShellHelpers')
|
678
|
+
@opts[:pager] && ShellHelpers.run_pager #("--pattern '^(Date|added|deleted|modified): '")
|
679
|
+
|
680
|
+
diff_highlight=ENV['DIFF_HIGHLIGHT']||"#{File.dirname(__FILE__)}/contrib/diff-highlight"
|
681
|
+
|
682
|
+
args=ARGF
|
683
|
+
if @opts[:debug]
|
684
|
+
GitDiffDebug.new(args,**@opts).output
|
685
|
+
elsif @opts[:diff_highlight]
|
686
|
+
IO.popen(diff_highlight,'r+') do |f|
|
687
|
+
Thread.new do
|
688
|
+
args.each_line do |l|
|
689
|
+
f.write(l)
|
690
|
+
end
|
691
|
+
f.close_write
|
692
|
+
end
|
693
|
+
GitFancyDiff.new(f,**@opts).output
|
694
|
+
end
|
695
|
+
else
|
696
|
+
#diff=GitDiffHighlight.new(args,**@opts).parse
|
697
|
+
GitFancyDiff.new(args,**@opts).output
|
698
|
+
end
|
699
|
+
end
|