-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy pathgit.sh
452 lines (393 loc) · 11.6 KB
/
git.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
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
alias gl='glg $(git show-ref | cut -d " " -f 2 | grep -v stash$) HEAD'
alias glw='glp --word-diff'
alias gsw='git show --format=fuller --stat --patch'
alias gco='git checkout'
alias gcp='git checkout -p'
alias gs='git status --untracked-files=all'
alias gst='git stash --include-untracked --keep-index'
alias gstp='git stash pop'
alias gd='git diff -M40'
alias gdw='gd --word-diff=color --word-diff-regex="[A-z0-9_-]+"'
alias gbdw='gbd --word-diff=color --word-diff-regex="[A-z0-9_-]+"'
alias gds='gd --cached'
alias gdsw='gdw --cached'
alias gbd='_git_branch_base > /dev/null && gd $(git merge-base $(_git_branch_base) HEAD)..'
alias gbdd='_git_branch_base && git diffall $(git merge-base $(_git_branch_base) HEAD) HEAD'
alias gbdt='_git_branch_base && git difftool $(git merge-base $(_git_branch_base) HEAD)..'
alias gbl='_git_branch_base && git-log --reverse $(git merge-base $(_git_branch_base) HEAD)..'
alias gblp='_git_branch_base && glp $(git merge-base $(_git_branch_base) HEAD)..'
alias gblg='gblp --no-patch'
alias gar='git reset HEAD'
alias garp='git reset -p HEAD'
alias ga='git add'
alias gld="git fsck --lost-found | grep '^dangling commit' | cut -d ' ' -f 3- | xargs git show -s --format='%ct %H' | sort -nr | cut -d ' ' -f 2 | xargs git show --stat"
alias gc='git commit -v'
alias gca='gc --amend'
alias gru='git rebase @{u}'
alias grc='git rebase --continue'
alias gp='git push'
alias gpt='git push -u origin $(git_current_branch)'
alias gws='git wip save WIP --untracked'
alias gwd='git update-ref -d refs/wip/$(git_current_branch)'
alias gdt='git difftool'
alias grl="git reflog --format='%C(auto)%h %<|(17)%gd %C(blue)%ci%C(reset) %gs'"
alias gtl='grep -v -F -f <(git ls-remote --tags origin | cut -d / -f 3) <(git tag)'
alias gcs='git commit --allow-empty --squash'
# helper for git aliases
function git_current_branch()
{
git symbolic-ref --short -q HEAD
}
function git_current_tracking()
{
local BRANCH="$(git_current_branch)"
local REMOTE="$(git config branch.$BRANCH.remote)"
local MERGE="$(git config branch.$BRANCH.merge)"
if [ -n "$REMOTE" -a -n "$MERGE" ]
then
echo "$REMOTE/$(echo "$MERGE" | sed 's#^refs/heads/##')"
else
echo "\"$BRANCH\" is not a tracking branch." >&2
return 1
fi
}
function _git_assert_origin_head() {
if ! git rev-parse origin/HEAD &> /dev/null; then
if git rev-parse origin/develop &> /dev/null; then
local TARGET=develop
elif git rev-parse origin/main &> /dev/null; then
local TARGET=main
else
local TARGET=master
fi
echo fatal: origin/HEAD is not set. >&2
echo >&2
echo "Maybe run \`git remote set-head origin $TARGET\`?" >&2
return 1
fi
}
_git_branch_target() {
if git rev-parse @{u} &> /dev/null; then
echo "@{u}"
else
_git_assert_origin_head || return 1
echo "origin/HEAD"
fi
}
_git_branch_base() {
if git_current_branch | grep -q ^hotfix/; then
echo origin/master
else
_git_assert_origin_head || return 1
echo origin/HEAD
fi
}
# git add --patch
function gap() {
(
local args=("$@")
local c i arg
if test "$ZSH_VERSION"; then c=1; else c=0; fi
for ((i = 0; i < "${#args}"; i++)); do
arg="${args[$i+$c]}"
if [[ "$arg" =~ ^-U[0-9]+$ ]]; then
export GIT_DIFF_OPTS="${arg/-U/-u}"
if test "$ZSH_VERSION"; then args[$i+$c]=(); else unset args[$i]; fi
fi
done
set -- "${args[@]}"
git add --patch "$@"
)
}
function git-log() {
git log -M40 --pretty=format:'%Cred%h%Creset%C(yellow)%d%Creset %s %C(green bold)- %an %C(black bold)%cd (%cr)%Creset' --abbrev-commit --date=short "$@"
}
# git log
function glg() {
if [[ $# == 0 ]] && git rev-parse @{u} &> /dev/null; then
git-log --graph @{u} HEAD
else
git-log --graph "$@"
fi
}
# git log patch
function glp()
{
# don't use the pager if in word-diff mode
local pager="$(echo "$*" | grep -q -- '--word-diff' && echo --no-pager)"
# use reverse mode if we have a range
local reverse="$(echo "$*" | grep -q '\.\.' && echo --reverse)"
# if we have no non-option args then default to listing unpushed commits in reverse moode
if ! (for ARG in "$@"; do echo "$ARG" | grep -v '^-'; done) | grep -q . && git_current_tracking > /dev/null 2>&1
then
local default_range="@{upstream}..HEAD"
local reverse='--reverse'
else
local default_range=''
fi
git $pager log --patch-with-stat -M40 $reverse "$@" $default_range
}
# git log file
function glf()
{
git log -M40 --format=%H --follow -- "$@" | xargs --no-run-if-empty git show --stat
}
# git log search
function gls()
{
local phrase="$1"
shift
if [[ $# == 0 ]]
then
local default_range=HEAD
fi
glp --pickaxe-all -S"$phrase" "$@" $default_range
}
# git pull request
# checkout a GitHub pull request as a local branch
function gpr()
{
local TEMP_FILE="$(mktemp "${TMPDIR:-/tmp}/gpr.XXXXXX")"
echo '+refs/pull/*/head:refs/remotes/origin/pr/*' > "$TEMP_FILE"
git config --get-all remote.origin.fetch | grep -v 'refs/remotes/origin/pr/\*$' >> "$TEMP_FILE"
git config --unset-all remote.origin.fetch
cat "$TEMP_FILE" | while read LINE
do
git config --add remote.origin.fetch "$LINE"
done
rm "$TEMP_FILE"
git fetch
if [[ -n "$1" ]]; then
git checkout "pr/$1"
fi
}
# git update
function gup
{
if type git-up > /dev/null 2>&1
then
git-up "$@"
else
echo 'git-up not installed. Run `gem install git-up`.' 2> /dev/null
return 1
fi
}
# git add untracked
gau() {
git ls-files --other --exclude-standard -z "$@" | xargs -0 git add -Nv
}
# git add untracked reset
gaur() {
git ls-files --exclude-standard --modified -z "$@" | xargs -0 git ls-files --stage | while read MODE OBJECT STAGE NAME; do
if [[ "$OBJECT" == e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 ]]; then
echo "reset '$NAME'"
if git rev-parse --quiet --verify HEAD > /dev/null; then
git reset -q -- "$NAME" 2>&1
else
git rm --cached --quiet -- "$NAME"
fi
fi
done
}
# git commit fixup
gcf() {
if [[ $(git diff --staged --name-only | wc -l) -lt 1 ]]; then
echo Nothing staged to commit. >&2
return 1
fi
if [[ $# -gt 0 ]]; then
if git rev-parse --quiet --verify "$1" &> /dev/null; then
git commit --fixup "$@"
return
else
COMMITS="$(
set -e
TARGET="$(_git_branch_base)"
git log --reverse --pretty=format:'%h %s' "$(git merge-base HEAD "$TARGET").." |
egrep -i -- "$@" |
awk '{ if ($2 != "fixup!" && $2 != "squash!") { print $1} }'
)"
fi
else
COMMITS="$(
set -e
TARGET="$(_git_branch_base)"
git diff --staged --name-only -z |
xargs -0 git log --pretty=format:'%H %s' "$(git merge-base HEAD "$TARGET").." -- |
awk '{ if ($2 != "fixup!" && $2 != "squash!") { print $1} }'
)"
fi
case $(echo "$COMMITS" | grep . | wc -l | tr -d -c 0-9) in
0)
echo No fixup candidates found. >&2
return 1
;;
1)
git commit --fixup "$COMMITS"
;;
*)
echo Staged files:
git diff --staged --name-only | sed 's/^/ /'
echo
echo Multiple fixup candidates:
echo "$COMMITS" | xargs git show -s --oneline | sed 's/^/ /'
echo
echo Old hunks:
git diff --staged --no-prefix | grep -e ^--- -e ^@@ | sed -e 's/ / /' -e 's/^\(@@[^ ]*\) /\1 /' | while IFS=' ' read MARKER VALUE _; do
case "$MARKER" in
---)
FILENAME="$VALUE"
;;
@@)
RANGE="$(echo "$VALUE" | sed s/-//)"
if ! echo "$RANGE" | grep -q ,; then
RANGE="$RANGE,1"
fi
RANGE="$(echo "$RANGE" | sed s/,/,+/)"
if [ "$FILENAME" != /dev/null ]; then
((COUNT+=1))
if [ $COUNT -gt 1 ]; then
echo
fi
echo " $FILENAME":"$RANGE"
git --no-pager blame -s -L "$RANGE" HEAD "$FILENAME" 2>&1 | sed 's/^/ /'
fi
;;
esac
done
return 1
;;
esac
}
# git commit reword
gcr() {
local commit="${1?commit required}"
shift
git commit --fixup=reword:"${commit?}" "$@"
}
# git rebase tracking
grt() {
(
set -e
TARGET="$(_git_branch_target)"
git rebase --interactive --keep-empty $(git merge-base HEAD "$TARGET") "$@"
)
}
# git rebase branch
grb() {
(
set -e
TARGET="$(_git_branch_base)"
git rebase --interactive --keep-empty $(git merge-base HEAD "$TARGET") "$@"
)
}
# git cleanup
gcu() {
if _git_assert_origin_head; then
git remote prune origin
HEAD_NAME="$(git rev-parse --abbrev-ref origin/HEAD | sed 's/^origin\///')"
git branch --merged origin/HEAD | grep -v '^[*+]' | awk '{print $1}' | grep -Fxv -e "$HEAD_NAME" -e develop -e main -e master | xargs git branch -d
git branch --remotes --merged origin/HEAD | grep -v origin/HEAD | grep '^ *origin/' | sed 's#^ *origin/##' | grep -v ^pr/ | grep -Fxv "$HEAD_NAME"
fi
}
# git difftool show <ref> [path...]
gdts() {
REF="${1:-HEAD}"
shift
git difftool "${REF}^..${REF}" "$@"
}
_gbr() {
local CMD="$1"
shift
local LOCAL_HEAD="${1:-HEAD}"
shift
local REMOTE_HEAD="${1:-"@{u}"}"
shift
(
set -e
SCRATCH_DIR="$(mktemp -d -t gbr.XXXXXX)"
trap '{ rm -rf "$SCRATCH_DIR"; }' EXIT
BASE="$(_git_branch_base)"
LOCAL_RANGE="$(git merge-base $BASE $LOCAL_HEAD)..$LOCAL_HEAD"
UPSTREAM_RANGE="$(git merge-base $BASE $REMOTE_HEAD)..$REMOTE_HEAD"
$CMD "$LOCAL_RANGE" "$@" > "$SCRATCH_DIR/local"
$CMD "$UPSTREAM_RANGE" "$@" > "$SCRATCH_DIR/upstream"
chmod -w "$SCRATCH_DIR/local" "$SCRATCH_DIR/upstream"
vimdiff "$SCRATCH_DIR/local" "$SCRATCH_DIR/upstream"
)
}
_gbr_diff() {
(
set -e
set -o pipefail
git diff "$@" | grep -v '^index'
)
}
# git branch rebased log
alias gbrl='_git_branch_base > /dev/null && git range-diff "$(git merge-base $(_git_branch_base) HEAD)..HEAD" "$(git merge-base $(_git_branch_base) @{u})..@{u}"'
# git branch rebased diff
alias gbrd='_gbr _gbr_diff'
alias grs='git rebase --show-current-patch'
grds() {
(
set -e
(cd .git && mkdir -p tmp)
git rebase --show-current-patch > .git/tmp/rebase-expected.patch
git diff --staged > .git/tmp/rebase-actual.patch
vimdiff .git/tmp/rebase-{expected,actual}.patch
)
}
grepdiff-hunk() {
(
set -euo pipefail
if [[ $# -lt 1 ]] || [[ "$1" == -* ]]; then
echo 'usage: grepdiff-hunk <pattern> [options]' >&2
exit 1
fi
if ! command -v grepdiff > /dev/null; then
echo grepdiff-hunk: patchutils not installed. >&2
exit 1
fi
local pattern="$1"; shift
grepdiff --extended-regexp "$pattern" --only-match=modifications --output-matching=hunk "$@" |
(if [ -t 1 ] && command -v diff-highlight > /dev/null; then diff-highlight | ${PAGER:-less}; else exec cat; fi)
)
}
# git diff with grep
# greps diff for hunks matching pattern
gdg() {
(
set -euo pipefail
if [[ $# -lt 1 ]] || [[ "$1" == -* ]]; then
echo 'usage: gdg <pattern> [git-diff-options]' >&2
exit 1
fi
if ! command -v grepdiff > /dev/null; then
echo gdg: patchutils not installed. >&2
exit 1
fi
local pattern="$1"; shift
git diff -G "$pattern" "$@" |
grepdiff-hunk "$pattern"
)
}
# git add patch with grep
# only asks about files where the diff matches a pattern
gapg() {
(
set -euo pipefail
if [[ $# -lt 1 ]] || [[ "$1" == -* ]]; then
echo 'usage: gapg <pattern> [git-diff-options]' >&2
exit 1
fi
if ! command -v grepdiff > /dev/null; then
echo gapg: patchutils not installed. >&2
exit 1
fi
local pattern="$1"; shift
git diff -G "$pattern" -U0 "$@" |
grepdiff --extended-regexp "$pattern" --strip=1 |
tr '\n' '\0' |
xargs -0 -o -- git add --patch --
)
}