-
-
Notifications
You must be signed in to change notification settings - Fork 59
/
Copy pathgit-diffn.sh
executable file
·321 lines (287 loc) · 15.8 KB
/
git-diffn.sh
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
#!/bin/bash
# This file is part of eRCaGuy_dotfiles: https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles
# Status: IT WORKS! USE AS A COMPLETE, 100% SYNTAX-COMPATIBLE, DROP-IN REPLACEMENT FOR `git diff`!
# See details just below.
# Author: Gabriel Staples
# www.ElectricRCAircraftGuy.com
# DESCRIPTION:
# git-diffn.sh
# - a drop-in replacement for `git diff` which also shows line 'n'umbers! Use it *exactly* like
# `git diff`, except you'll see these beautiful line numbers as well to help you make sense of
# your changes.
# - since it's just a light-weight awk-language-based wrapper around `git diff`, it accepts ALL
# options and parameters that `git diff` accepts. Examples:
# - `git diffn HEAD~`
# - `git diffn HEAD~3..HEAD~2`
# - works with any of your `git diff` color settings, even if you are using custom colors
# - See my answer here for how to set custom diff colors, as well as to see a screenshot of
# custom-color output from `git diffn`:
# https://stackoverflow.com/questions/26941144/how-do-you-customize-the-color-of-the-diff-header-in-git-diff/61993060#61993060
# - Here are some sample `git config` commands from my answer above to set custom `git diff`
# colors and attributes (text formatting):
# git config --global color.diff.meta "blue"
# git config --global color.diff.old "black red strike"
# git config --global color.diff.new "black green italic"
# git config --global color.diff.context "yellow bold"
# - in `git diffn`, color output is ON by default; if you want to disable the output color, you
# must use `--no-color` or `--color=never`. See `man git diff` for details. Examples:
# - `git diffn --color=never HEAD~`
# - `git diffn --no-color HEAD~3..HEAD~2`
# INSTALLATION INSTRUCTIONS:
# 1. Create a symlink in ~/bin to this script so you can run it from anywhere as `git diffn` OR
# as `git-diffn` OR as `gs_git-diffn` OR as `git gs_diffn`. Note that "gs" is my initials.
# I do these versions with "gs_" in them so I can find all scripts I've written really easily
# by simply typing "gs_" + Tab + Tab, or "git gs_" + Tab + Tab.
# cd /path/to/here
# mkdir -p ~/bin
# ln -si "${PWD}/git-diffn.sh" ~/bin/git-diffn # required
# ln -si "${PWD}/git-diffn.sh" ~/bin/git-gs_diffn # optional; replace "gs" with your initials
# ln -si "${PWD}/git-diffn.sh" ~/bin/gs_git-diffn # optional; replace "gs" with your initials
# 2. Now you can use this command directly anywhere you like in any of these 5 ways:
# 1. `git diffn` <=== my preferred way to use this program, so it feels just like `git diff`!
# 2. `git-diffn`
# 3. `git gs_diffn`
# 4. `git-gs_diffn`
# 3. `gs_git-diffn`
# FUTURE WORK:
# 1. Make work with standard awk?
# This has been tested on Linux Ubuntu 18.04. If anyone can't get this working on their system,
# such as in the git bash terminal that comes with Git for Windows, or on MacOS, due to
# compatibility probems with `gawk`, I can rewrite the few places relying on `gawk` extensions
# to just use basic awk instead. That should solve any compatibility problems, but there's no
# sense in doing it if there's no need. If I ever need to do this in the future though, I'm
# going to need this trick to obtain a substring using standard awk:
# https://stackoverflow.com/questions/5536018/how-to-print-matched-regex-pattern-using-awk/5536342#5536342
# 1. Also, look into this option in gawk for testing said compatibility:
# 1. `--lint` - https://www.gnu.org/software/gawk/manual/html_node/Options.html
# 1. `--traditional` and `--posix` - https://www.gnu.org/software/gawk/manual/html_node/Compatibility-Mode.html
# 1. Currently, `--lint` is telling me that the 3rd argument to `match()` (ie: the array
# parameter) is a gawk extension.
# References:
# 1. This script borrows from @PFudd's script here:
# https://stackoverflow.com/questions/24455377/git-diff-with-line-numbers-git-log-with-line-numbers/33249416#33249416
# 2. @PFudd expands on @Andy Talkowski's code from here:
# https://stackoverflow.com/questions/24455377/git-diff-with-line-numbers-git-log-with-line-numbers/32616440#32616440
# 3. I also received help from @Ed Morton and @Inian here:
# https://stackoverflow.com/questions/61932427/git-diff-with-line-numbers-and-proper-code-alignment-indentation
# 4. And then I did a crap-ton of research all over the place, with most of it being in the official GNU Awk (gawk)
# manual here: https://www.gnu.org/software/gawk/manual/html_node/index.html#SEC_Contents
# Awk-language-specific References:
# 1. Great awk intro, description, & examples to get started!: https://en.wikipedia.org/wiki/AWK
# 1. awk cheatsheet: https://www.shortcutfoo.com/app/dojos/awk/cheatsheet
# 1. How to obtain a matched substring in awk; see here:
# 1. https://stackoverflow.com/questions/5536018/how-to-print-matched-regex-pattern-using-awk/5536342#5536342
# 1. Also be sure to read the references he posts to the gawk manual!
# 2. and here: https://stackoverflow.com/questions/5536018/how-to-print-matched-regex-pattern-using-awk/30641727#30641727
# 1. *****"The Essential Syntax of AWK": https://www.grymoire.com/Unix/Awk.html#uh-5
# 1. https://www.gnu.org/software/gawk/manual/html_node/Using-Shell-Variables.html
# 1. Dynamic Regexps: https://www.gnu.org/software/gawk/manual/html_node/Computed-Regexps.html
# 1. https://www.gnu.org/software/gawk/manual/html_node/Quoting.html
# 1. awk print: https://www.gnu.org/software/gawk/manual/html_node/Print.html
# 1. awk printf: https://www.gnu.org/software/gawk/manual/html_node/Basic-Printf.html
# 1. awk printf examples: https://www.gnu.org/software/gawk/manual/html_node/Printf-Examples.html
# 1. Sample data files for all awk examples: https://www.gnu.org/software/gawk/manual/html_node/Sample-Data-Files.html#Sample-Data-Files
# 1. awk `next` statement: https://www.gnu.org/software/gawk/manual/html_node/Next-Statement.html
# 1. awk String Functions: https://www.gnu.org/software/gawk/manual/html_node/String-Functions.html; Including:
# 1. gsub()
# 1. match()
# 1. gensub()
# 1. etc.
# 1. awk `next` statement: https://www.gnu.org/software/gawk/manual/html_node/Next-Statement.html
# 1. awk variable and shell variable usage
# 1. see the last example here:
# https://www.gnu.org/software/gawk/manual/html_node/Printf-Examples.html
# 1. and also this info here:
# https://www.gnu.org/software/gawk/manual/html_node/Using-Shell-Variables.html
# 1. See also my Q & the answers & comments here:
# https://stackoverflow.com/questions/61932427/git-diff-with-line-numbers-and-proper-code-alignment-indentation
# Awk-language Notes:
# The gist of awk: pattern {action}
# - See here: https://en.wikipedia.org/wiki/AWK
# Meaning of tidle (~): "matches a regular expression against a string" (https://en.wikipedia.org/wiki/AWK)
# - Can be read as: "check the operands on either side to see if they match" (http://billconner.com/techie/awk.html)
# - See also here: https://www.grymoire.com/Unix/Awk.html#uh-11
# - Ex: `my_var ~ /regex/ { action }` means: "if the contents of my_var has a match against
# the regular expression "regex", then do `action`".
# IMPORTANT: SINCE I'M USING SINGLE QUOTES (' ') AROUND THE WHOLE AWK PROGRAM BELOW, ***NO***
# UNESCAPED SINGLE QUOTES ARE ALLOWED IN THE CODE BELOW, ***INCLUDING IN AWK COMMENTS!*** Since
# this comment says "I'm" in it, if you move it down into the awk code below, it will make the awk
# code fail to run too!
# ANSI Color Code Examples to help make sense of the regex expressions below
# Git config color code descriptions; see here:
# https://stackoverflow.com/questions/26941144/how-do-you-customize-the-color-of-the-diff-header-in-git-diff/61993060#61993060
# --------------- ----------------------------------------------------------------
# Git config color code desription
# ANSI Color Code Order: text_color(x1) background_color(x1) attributes(0 or more)
# ---------------- ----------------------------------------------------------------
# \033[m # code to turn off or "end" the previous color code
# \033[1m # "white"
# \033[31m # "red"
# \033[32m # "green"
# \033[33m # "yellow"
# \033[34m # "blue"
# \033[36m # "cyan"
# \033[1;33m # "yellow bold"
# \033[1;36m # "cyan bold"
# \033[3;30;42m # "black green italic" = black text with green background, italic text
# \033[9;30;41m # "black red strike" = black text with red background, strikethrough line through the text
# Use this website to help you decipher and build regular expressions: https://regex101.com/
# A regex expression to match any of the "single code" (text color only) color codes above,
# including `1m` through `99m`, is as follows:
# ^(\033\[[0-9]{1,2}m)?
# Now, expanding upon that, here's a regexp which can handle "multiple code" (text color (x1) +
# background color (x1) + attributes (0 or more)) color codes, with 1 to 10 groups of "1;", "30;",
# "41", "42", etc. codes is this:
# ^(\033\[(([0-9]{1,2};?){1,10})m)?
# Be sure to place all args (`"$@"`) AFTER `--color=always` so that if the user passes in
# `--color=never` or `--no-color` they will override my `--color=always` here, since later
# options override earlier ones.
git diff --color=always "$@" | \
gawk \
'
# -------------------------------
# Awk Program Start
# -------------------------------
BEGIN {
# color code to turn color and text formatting OFF at this location in a string
COLOR_OFF = "\033[m"
# color code for the left side, or lines deleted (-); this will be auto-detected later
color_L = ""
# color code for the right side, or lines added (+); this will be auto-detected later
color_R = ""
# color code for **Context lines**, which are unchanged lines around added or deleted lines
# which `git diff` shows for context; this will be auto-detected later
color_C = ""
# Note that "knowing" any of the below color codes could also mean knowing that there are
# no color codes for these lines, because they are uncolored and unformatted. If we
# are able to detect that, we know that the code is empty/nonexistant, so we will set these
# values to "true" in that case as well since we "know" the color code.
# true if the -/left (deletion) color code is known; false otherwise
color_L_known = "false"
# true if the +/right (addition) color code is known; false otherwise
color_R_known = "false"
# true if the Context line color code is known; false otherwise
color_C_known = "false"
}
{
raw_line = $0
}
# 1. First, find an uncolored or (usually cyan) colored line like this which indicates the
# line numbers: `@@ -159,6 +159,13 @@`
match(raw_line, /^(\033\[(([0-9]{1,2};?){1,10})m)?@@ -([0-9]+),[0-9]+ \+([0-9]+),[0-9]+ @@/, array) {
# The array indices below are according to the parenthetical group number in the regex
# above; see:
# https://www.gnu.org/software/gawk/manual/html_node/String-Functions.html#index-match_0028_0029-function
left_num = array[4] # left (deletion) starting line number
right_num = array[5] # right (addition) starting line number
print raw_line
# for debugging the array capture group indices above
# printf "===left_num = %i, right_num = %i===\n", left_num, right_num
next
}
# 2. Match uncolored or colored (usually white) lines like this:
# `--- a/my/file` and `+++ b/my/file`, as well as ANY OTHER LINE WHICH DOES
# *NOT* BEGIN WITH A -, +, or space (optional color code at the start accounted for).
/^(\033\[(([0-9]{1,2};?){1,10})m)?(--- a\/|\+\+\+ b\/|[^-+ \033])/ {
print raw_line
next
}
# 3. Match lines beginning with a minus (`-`), plus (`+`), or space (` `), optionally with
# ANY color code in front of them too
# match lines deleted (-)
# Check to see if raw_line matches this regexp
/^(\033\[(([0-9]{1,2};?){1,10})m)?-/ {
# Detect the color code if we dont yet know it
if (color_L_known == "false") {
match_index = match(raw_line, /^(\033\[(([0-9]{1,2};?){1,10})m)?/, array)
if (match_index > 0) {
# `git diff` color is ON, so lets save the color being used!
# Index zero stores the string matched by regexp: "...the zeroth element of array
# is set to the entire portion of string matched by regexp." See:
# https://www.gnu.org/software/gawk/manual/html_node/String-Functions.html#index-match_0028_0029-function
color_L = array[0]
}
# Set this to true in BOTH CASES because if a match was found above, we now know the color
# code, and if a match was NOT found, we now know these lines are NOT color-coded!
color_L_known = "true"
}
if (color_L == "") {
color_off = ""
}
else {
color_off = COLOR_OFF
}
# Print a **deleted line** with the appropriate colors based on whatever `git diff` is using
printf color_L"-%+4s "color_off":%s\n", left_num, raw_line
left_num++
next
}
# match lines added (+)
# Check to see if raw_line matches this regexp
/^(\033\[(([0-9]{1,2};?){1,10})m)?\+/ {
# Detect the color code if we dont yet know it
if (color_R_known == "false") {
match_index = match(raw_line, /^(\033\[(([0-9]{1,2};?){1,10})m)?/, array)
if (match_index > 0) {
color_R = array[0]
}
color_R_known = "true"
}
if (color_R == "") {
color_off = ""
}
else {
color_off = COLOR_OFF
}
# Print an **added line** with the appropriate colors based on whatever `git diff` is using
printf color_R"+ %+4s"color_off":%s\n", right_num, raw_line
right_num++
next
}
# match lines not changed (these begin with an empty space ` `)
# These lines have no color or other attribute formatting by default (such as bold, italics, etc),
# but the user can add this in their git config settings if desired, so we must be able to handle
# color and attribute formatting on this text too.
# Check to see if raw_line matches this regexp
/^(\033\[(([0-9]{1,2};?){1,10})m)? / {
# Detect the color code if we dont yet know it
if (color_C_known == "false") {
match_index = match(raw_line, /^(\033\[(([0-9]{1,2};?){1,10})m)?/, array)
if (match_index > 0) {
color_C = array[0]
}
color_C_known = "true"
}
if (color_C == "") {
color_off = ""
}
else {
color_off = COLOR_OFF
}
# Print a **context line** with the appropriate colors based on whatever `git diff` is using
printf color_C" %+4s,%+4s"color_off":%s\n", left_num, right_num, raw_line
left_num++
right_num++
next
}
# 4. Error-checking for sanity: this code should never be reached
{
print "============== GIT DIFFN ERROR =============="
print "THIS CODE SHOULD NEVER BE REACHED! If you see this, open up an issue for `git diffn`"
print " here: https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles/issues"
print " It may be because you have some custom `git config` color or text formatting settings"
print " or something, which perhaps I am failing to handle correctly."
print "Raw line: "raw_line
print "============================================="
}
# -------------------------------
# Awk Program End
# -------------------------------
# Note that we are piping the output to `less` with -R to interpret ANSI color codes, -F to
# quit immediately if the output takes up less than one-screen, and -X to not clear
# the screen when less exits! This way `git diffn` will provide exactly identical behavior
# to what `git diff` does! See:
# 1. https://stackoverflow.com/questions/2183900/how-do-i-prevent-git-diff-from-using-a-pager/14118014#14118014
# 2. https://unix.stackexchange.com/questions/38634/is-there-any-way-to-exit-less-without-clearing-the-screen/38638#38638
' \
| less -R -F -X