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 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
@@ -0,0 +1,6 @@
1
+ /.bundle
2
+ /.yardoc/
3
+ /Gemfile.lock
4
+ /doc/
5
+ /pkg/
6
+ /vendor/cache/*.gem
data/.travis.yml ADDED
@@ -0,0 +1,10 @@
1
+ ---
2
+ language: ruby
3
+ rvm:
4
+ - 2.4.0
5
+ - 2.3.3
6
+ - 2.2.6
7
+ - 2.1.10
8
+ #- ruby-head
9
+ #- ruby-head-clang
10
+ script: bundle exec rake test
data/.yardopts ADDED
@@ -0,0 +1,6 @@
1
+ --markup markdown
2
+ --title "git_helpers Documentation"
3
+ --protected
4
+ -
5
+ ChangeLog.md
6
+ LICENSE.txt
data/ChangeLog.md ADDED
@@ -0,0 +1,4 @@
1
+ ### 0.1.0 / 2016-06-03
2
+
3
+ * Initial release:
4
+
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+ gem 'drain', :github => 'DamienRobert/drain'
5
+ gem 'shell_helpers', :github => 'DamienRobert/shell_helpers'
6
+
7
+ group :development do
8
+ gem 'kramdown'
9
+ end
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
+ [![Gem Version](https://img.shields.io/gem/v/git_helpers.svg)](https://rubygems.org/gems/git_helpers)
9
+ [![Build Status](https://travis-ci.org/DamienRobert/git_helpers.svg?branch=master)](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