Last active
August 29, 2015 14:06
-
-
Save elpete/84070bda23adb51056e5 to your computer and use it in GitHub Desktop.
git-jump from Ben Alman
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment