-
Notifications
You must be signed in to change notification settings - Fork 494
/
git-jump
executable file
·182 lines (161 loc) · 4.8 KB
/
git-jump
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
#!/usr/bin/env bash
function help() {
cat <<HELP
Git Jump (Forward & Back)
http://benalman.com/
Usage: $(basename "$0") [command]
Commands:
next Jump forward to the next commit in this branch
prev Jump backward to the next commit in this branch
clean Remove current unstaged changes/untracked files**
cleanall Remove all saved tags, unstaged changes and untracked files**
** This action is destructive and cannot be undone!
Git config:
git-jump.branch Branch to jump through. If not set, defaults to master
Description:
"Replay" Git commits by moving forward / backward through a branch's
history. Before jumping, any current unstaged changes and untracked
files are saved in a tag for later retrieval, which is restored when
jumped back to.
Copyright (c) 2014 "Cowboy" Ben Alman
Licensed under the MIT license.
http://benalman.com/about/license/
HELP
}
function usage() {
echo "Usage: $(basename "$0") [next | prev | clean | cleanall]"
}
# Get branch stored in Git config or default to master
git_branch="$(git config git-jump.branch || echo "master")"
# Get some (short) SHAs
function git_branch_sha() {
git rev-parse --short "$git_branch"
}
function git_head_sha() {
git rev-parse --short HEAD
}
function git_prev_sha() {
git log --format='%h' "$git_branch" "$@" | awk "/^$(git_head_sha)/{getline; print}"
}
function git_next_sha() {
git_prev_sha --reverse
}
# Get absolute path to root of Git repo
function git_repo_toplevel() {
git rev-parse --show-toplevel
}
# Get subject of specified commit
function git_commit_subject() {
git log --format='%s' -n 1 $1
}
# Save changes for later retrieval
function save() {
local status=""
local head_sha=$(git_head_sha)
# Checkout current HEAD by SHA to force detached state
git checkout -q $head_sha
# Add all files in repo
git add "$(git_repo_toplevel)"
# Commit changes (if there were any)
git commit --no-verify -m "Git Jump: saved changes for $head_sha" >/dev/null
# If the commit was successful, tag it (overwriting any previous tag)
if [[ $? == 0 ]]; then
status="*"
git tag -f "git-jump-$head_sha" >/dev/null
fi
echo "Previous HEAD was $head_sha$status, $(git_commit_subject $head_sha)"
}
# Restore previously-saved changes
function restore() {
local status=""
# Save current changes before restoring
save
# Attempt to restore saved changes for specified commit
git checkout "git-jump-$1" 2>/dev/null
if [[ $? == 0 ]]; then
# If the restore was successful, figure out exactly what was saved, check
# out the original commit, then restore the saved changes on top of it
status="*"
local patch="$(git format-patch HEAD^ --stdout)"
git checkout HEAD^ 2>/dev/null
echo "$patch" | git apply -
else
# Otherwise, just restore the original commit
git checkout "$1" 2>/dev/null
fi
echo "HEAD is now $1$status, $(git_commit_subject $1)"
}
# Clean (permanently) current changes and remove the current saved tag
function clean() {
local head_sha=$(git_head_sha)
git tag -d "git-jump-$head_sha" &>/dev/null
if [[ $? == 0 ]]; then
echo "Removed stored data for commit $head_sha."
fi
local repo_root="$(git_repo_toplevel)"
git reset HEAD "$repo_root" >/dev/null
git clean -f -d -q -- "$repo_root" >/dev/null
git checkout -- "$repo_root" >/dev/null
echo "Unstaged changes and untracked files removed."
}
# Remove (permanently) all saved tags
function clean_all_tags() {
git for-each-ref refs/tags --format='%(refname:short)' | \
while read tag; do
if [[ "$tag" =~ ^git-jump- ]]; then
git tag -d "$tag"
fi
done
}
# Jump to next commit
function next() {
local next_sha=$(git_next_sha)
if [[ "$next_sha" == "$(git_head_sha)" ]]; then
# Abort if no more commits
echo "Already at last commit in $git_branch. Congratulations!"
else
# Checkout branch by name if at its HEAD
if [[ "$next_sha" == "$(git_branch_sha)" ]]; then
next_sha="$git_branch"
fi
echo "Jumping ahead to next commit."
restore $next_sha
fi
}
# Jump to previous commit
function prev() {
local prev_sha=$(git_prev_sha)
if [[ "$prev_sha" == "$(git_head_sha)" ]]; then
# Abort if no more commits
echo "Already at first commit in $git_branch."
else
echo "Jumping back to previous commit."
restore $prev_sha
fi
}
# Show help if requested
if [[ "$1" == "--help" || "$1" == "-h" ]]; then
help
exit
fi
# Check if branch is valid
git rev-parse "$git_branch" &>/dev/null
if [[ $? != 0 ]]; then
echo "Error: Branch \"$git_branch\" does not appear to be valid."
echo "Try $(basename "$0") --help for more information."
exit 1
fi
# Handle CLI arguments
if [[ "$1" == "next" ]]; then
next
elif [[ "$1" == "prev" ]]; then
prev
elif [[ "$1" == "clean" ]]; then
clean
elif [[ "$1" == "cleanall" ]]; then
clean_all_tags
clean
else
usage
exit 1
fi