-
-
Notifications
You must be signed in to change notification settings - Fork 59
/
git-difft.sh
executable file
·303 lines (261 loc) · 10.4 KB
/
git-difft.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
#!/usr/bin/env bash
# This file is part of eRCaGuy_dotfiles: https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles
# GS
# Nov. 2023
# DESCRIPTION:
#
# Iteratively run `git difftool` on all commits going backwards from HEAD to the first commit, one
# commit at a time, to see the changes in each commit.
# INSTALLATION INSTRUCTIONS:
#
# 1. Create a symlink in ~/bin to this script so you can run it from anywhere.
# 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-difft.sh" ~/bin/git-difft # required
# ln -si "${PWD}/git-difft.sh" ~/bin/git-gs_difft # optional; replace "gs" with your initials
# ln -si "${PWD}/git-difft.sh" ~/bin/gs_git-difft # optional; replace "gs" with your initials
#
# 2. Now you can use this command directly anywhere you like in any of these 5 ways:
#
# git difft <=== my preferred way to use this program, so it feels just like `git diff`!
# git-difft
# git gs_difft
# git-gs_difft
# gs_git-difft
# REFERENCES:
#
# 1. https://github.com/ElectricRCAircraftGuy/eRCaGuy_hello_world/blob/master/bash/argument_parsing__3_advanced__gen_prog_template.sh
# - a very advanced, general Bash program example.
# 1. GitHub Copilot helped me with the algorithm in `main()`.
# 1. "eRCaGuy_hello_world/bash/argument_parsing__3_advanced__gen_prog_template.sh" - for help
# parsing arguments. See:
# https://github.com/ElectricRCAircraftGuy/eRCaGuy_hello_world/blob/master/bash/argument_parsing__3_advanced__gen_prog_template.sh
# 1. ***** I've posted about `git difft` here: https://stackoverflow.com/a/74965393/4561887
#
RETURN_CODE_SUCCESS=0
RETURN_CODE_ERROR=1
VERSION="0.2.0"
AUTHOR="Gabriel Staples"
SCRIPT_NAME="$(basename "$0")"
VERSION_SHORT_STR="'$SCRIPT_NAME' version $VERSION"
VERSION_LONG_STR="\
$VERSION_SHORT_STR
Author = $AUTHOR
See '$SCRIPT_NAME -h' for more info.
"
HELP_STR="\
$VERSION_SHORT_STR
Iterate through all commits going backwards from HEAD to the first commit, one commit at a time,
running 'git difftool' on each one to see the changes it introduced.
- Press Ctrl + C once, then Enter, to break out of the current 'git difftool' command, but continue
on with the previous commit.
- Press Ctrl + C twice to exit out of the whole program.
USAGE:
$SCRIPT_NAME [OPTIONS] [[commit] [commit_start~..commit_end]] -- [file1 file2 file3 ...]
OPTIONS
-h, -?
Print help menu
-v, --version
Print version information.
--
Lists of files or directories go after this point.
EXAMPLE USAGES:
$SCRIPT_NAME -h
Print help menu.
$SCRIPT_NAME
Start running 'git difftool' on the commit starting at HEAD (the currently-checked-out
commit).
$SCRIPT_NAME HEAD
Same as above.
$SCRIPT_NAME HEAD~
Start running 'git difftool' on the commit starting at HEAD~ (one before HEAD).
$SCRIPT_NAME HEAD~2
Start running 'git difftool' on the commit starting at HEAD~2 (two before HEAD).
$SCRIPT_NAME abcdefg
Start running 'git difftool' on commit hash abcdefg.
$SCRIPT_NAME my_branch
Start running 'git difftool' on the commit at the tip of branch 'my_branch'.
$SCRIPT_NAME commit1~..commit2
Run 'git difftool' on all commits between commit1 and commit2, inclusive.
$SCRIPT_NAME commit1..commit2
Run 'git difftool' on all commits between commit1 and commit2, including commit2 but
NOT including commit1.
$SCRIPT_NAME commit1~..commit2 -- file1 file2 file3
Run 'git difftool' on all commits between commit1 and commit2, inclusive, but only
for the files file1, file2, and file3.
$SCRIPT_NAME -- path/to/file1
Start running 'git difftool' on the commit starting at HEAD, but only for the file
\"path/to/file1\".
$SCRIPT_NAME -- path/to/dir1
Start running 'git difftool' on the commit starting at HEAD, but only for the files
in the directory \"path/to/dir1/\".
This program is part of eRCaGuy_dotfiles: https://github.com/ElectricRCAircraftGuy/eRCaGuy_dotfiles
by Gabriel Staples.
"
print_help() {
echo "$HELP_STR" | less -RFX
}
print_version() {
echo "$VERSION_LONG_STR"
}
# A function to do echo-style debug prints only if `DEBUG_PRINTS_ON` is set to "true".
# - From: "eRCaGuy_hello_world/bash/argument_parsing__3_advanced__gen_prog_template.sh"
echo_debug() {
if [ "$DEBUG_PRINTS_ON" = "true" ]; then
printf "%s" "DEBUG: "
echo "$@"
fi
}
# - From: "eRCaGuy_hello_world/bash/argument_parsing__3_advanced__gen_prog_template.sh"
echo_error() {
printf "%s" "ERROR: "
echo "$@"
}
# A function to do printf-style debug prints only if `DEBUG_PRINTS_ON` is set to "true".
# - From: "eRCaGuy_hello_world/bash/argument_parsing__3_advanced__gen_prog_template.sh"
printf_debug() {
if [ "$DEBUG_PRINTS_ON" = "true" ]; then
printf "%s" "DEBUG: "
# See: https://github.com/koalaman/shellcheck/wiki/SC2059
# shellcheck disable=SC2059
printf "$@"
fi
}
# Print all arguments, for debugging.
debugging_print_all_args() {
# echo "$1" # debugging
echo "All args:"
i=0
for arg in "$@"; do
((i++))
echo "arg${i} = $arg"
done
}
parse_args() {
# See my code here for advanced argument parsing:
# https://github.com/ElectricRCAircraftGuy/eRCaGuy_hello_world/blob/master/bash/argument_parsing__3_advanced__gen_prog_template.sh
ALL_ARGS_ARRAY=("$@") # See: https://stackoverflow.com/a/70572787/4561887
# echo "ALL_ARGS_ARRAY = ${ALL_ARGS_ARRAY[@]}" # debugging
POSITIONAL_ARGS_ARRAY=()
filenames_array=()
while [ "$#" -gt 0 ]; do
arg="$1"
# get first letter of `arg`; see: https://stackoverflow.com/a/10218528/4561887
# This is a form of bash "array slicing", treating the string like an array of chars,
# so see also my answer about array slicing here:
# https://unix.stackexchange.com/a/664956/114401
first_letter="${arg:0:1}"
case $arg in
# Help menu
"-h"|"-?"|"--help")
print_help
exit $RETURN_CODE_SUCCESS
;;
# Version
"-v"|"--version")
print_version
exit $RETURN_CODE_SUCCESS
;;
# Filename args
"--")
# echo "Found '--' arg!" # debugging
# Collect *all* remaining args into the `filenames_array`
shift # past argument
all_remaining_args=("$@")
filenames_array=("${all_remaining_args[@]}")
break
;;
# All positional args (ie: unmatched in the switch cases above)
*)
# error out for any unexpected options passed in
if [ "$first_letter" = "-" ]; then
echo_error "Invalid optional argument ('$1'). See help menu for valid options."
exit $RETURN_CODE_ERROR
fi
POSITIONAL_ARGS_ARRAY+=("$1") # save positional arg into array
shift # past argument (`$1`)
;;
esac
done
}
handle_ctrl_c() {
# Unset the Ctrl + C trap
trap - SIGINT
echo -e "\nCtrl + C captured! Exit out of the whole program? (y/N)"
echo "[Press n or Enter to continue on with the previous commit]"
read -r -n 1 answer
echo ""
if [ "$answer" = "y" ] || [ "$answer" = "Y" ]; then
echo "Exiting out of the whole program!"
exit $RETURN_CODE_SUCCESS
else
echo "Continuing on with the next commit..."
fi
}
main() {
# Get a list of all commit hashes
commits=$(git log --pretty=format:"%h" "${ALL_ARGS_ARRAY[@]}")
if [ "$?" -ne "0" ]; then
echo "ERROR: 'git log' command failed!"
debugging_print_all_args "$@"
echo ""
echo "Run '$SCRIPT_NAME -h' for help."
exit $RETURN_CODE_ERROR
fi
# debugging
# echo -e "commits = \n$commits"
echo "Analyzing previous commits in 'git difftool', one commit at a time."
echo "- Press Ctrl + C, then Enter, to skip the current commit and continue on with the previous"\
"commit."
echo "- Press Ctrl + C twice to exit out of the whole program."
echo ""
# Loop through the list of commit hashes
i=0
for commit in $commits; do
echo -e "========================================================"
echo -e "#$i ($i commits prior to HEAD): Analyzing commit: $commit:"
echo -e "========================================================"
# For a 1-line commit message
# git log --oneline -1 $commit
# For the whole commit message
# TODO: make this an option in the future, with the `--oneline` version above being default.
git log -1 $commit
echo -e "\n----------------------------------------"
echo "Running 'git difftool $commit~..$commit -- ${filenames_array[@]}'."
echo "Run this to pick back up here:"
echo " $SCRIPT_NAME $commit -- ${filenames_array[@]}"
echo "----------------------------------------"
# Capture Ctrl + C so we can break out of `git difftool`, but NOT necessarily out of the
# whole program at this time!
trap 'handle_ctrl_c' SIGINT
git difftool $commit~..$commit -- "${filenames_array[@]}"
echo ""
((i++))
done
echo "Done with all commits! Exiting '$SCRIPT_NAME'."
}
# Determine if the script is being sourced or executed (run).
# See:
# 1. "eRCaGuy_hello_world/bash/if__name__==__main___check_if_sourced_or_executed_best.sh"
# 1. My answer: https://stackoverflow.com/a/70662116/4561887
if [ "${BASH_SOURCE[0]}" = "$0" ]; then
# This script is being run.
__name__="__main__"
else
# This script is being sourced.
__name__="__source__"
fi
# ----------------------------------------------------------------------------------------------------------------------
# Main program entry point
# ----------------------------------------------------------------------------------------------------------------------
# Only run `main` if this script is being **run**, NOT sourced (imported).
# - See my answer: https://stackoverflow.com/a/70662116/4561887
if [ "$__name__" = "__main__" ]; then
parse_args "$@"
time main "$@"
exit $RETURN_CODE_SUCCESS
fi