Last active
December 16, 2022 19:41
-
-
Save Jeff-Russ/5d1f08c10c235557dc502a3e78a823e0 to your computer and use it in GitHub Desktop.
gitReview allows navigation through commit histories and release tags, avoiding detached HEAD annoyances.
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
# Copyright (c) 2022, Jeff Russ | |
# All rights reserved. | |
# | |
# This source code is licensed under the BSD-style license found in the | |
# LICENSE file in the root directory of this source tree. | |
# NOTE: This script requires zsh and peco (see https://github.com/peco/peco) | |
# It be sourced from ~/.zshrc and the gitReviewSetup function should be called | |
# once per terminal session where the $PWD is under git revision control. | |
# One option is to place something line this in ~/.zshrc: | |
# | |
# source path/to/gitReview | |
# | |
# function useGitReview() { | |
# git rev-parse --is-inside-work-tree &> /dev/null && gitReviewSetup | |
# } | |
# | |
function git-head-detached() { # used by gitReview | |
# If you use in an if statement, do not use [ ] Rather... | |
# call this in an if like this: `if git-head-detached ; then` ... | |
# or in a shell-style "ternary" like: `git-head-detached && callable || other` | |
local result=$(git rev-parse --abbrev-ref --symbolic-full-name HEAD) | |
if [ "$result" = "HEAD" ]; then | |
if [ "$1" != "--quiet" ] ; then printf "You are in 'detached HEAD' state.\n"; fi | |
return 0 # 0 means no failure, which is kind of like true | |
else | |
if [ "$1" != "--quiet" ] ; then printf "HEAD is not detached.\n* $result\n"; fi | |
return 1 # means some failure, which is kind of like false | |
fi | |
} | |
function git-undetach-head() { # used by gitReview | |
if ! git-head-detached --quiet ; then | |
[ "$1" != "--quiet" ] && printf "HEAD was not detached\n" | |
return 0 | |
fi | |
if git switch - >/dev/null 2>&1 ; then | |
if ! git-head-detached --quiet ; then # try checking out current branch | |
[ "$1" != "--quiet" ] && printf "Running \`git switch -\`...HEAD is no longer detached.\n" | |
return 0 | |
# else we'll try re-checking out the current branch | |
fi | |
# else we'll try re-checking out the current branch | |
fi | |
local curr_branch="$GR_PREV_BRANCH" | |
if [ -z $curr_branch ] ; then | |
if git branch | grep main ; then | |
curr_branch=main | |
elif git branch | grep master ; then | |
curr_branch=master | |
else | |
[ "$1" != "--quiet" ] && printf "Unable to recover detached HEAD state! Try git checkout <branchname>\n" | |
return 1 | |
fi | |
fi | |
if git checkout "$curr_branch" >/dev/null 2>&1 ; then | |
if ! git-head-detached --quiet ; then | |
[ "$1" != "--quiet" ] && printf "Running \`git checkout "$curr_branch"\`...HEAD is no longer detached.\n" | |
return 0 | |
# else we are hopeless | |
fi | |
# else we are hopeless | |
fi | |
[ "$1" != "--quiet" ] && printf "Unable to recover detached HEAD state!\n" | |
return 1 | |
} | |
function gitReviewSetup() { | |
# gitReviewSetup is a constructor-like function for the user function gitReview | |
# (and non-user functions/vars it needs), which are only available to the user this contructor | |
# finds it needs to be defined and can safely be used. | |
function _GR_CLEAR_ALL() { | |
GR_PREV_BRANCH="" | |
GR_PREV_BRANCH_COMMIT_CNT="" | |
GR_PREV_SHORT_HASH="" | |
GLOG_ITEMS=() | |
GLOG_MENU=() | |
GLOG_MENU_IDX=-1 | |
} | |
_GR_CLEAR_ALL | |
function _GR_MENU_REFRESH() { | |
local repopulated=false | |
if ! git-head-detached --quiet ; then | |
# Normally we would repopulate GLOG_ITEMS (and, by extension, GLOG_MENU which is a | |
# decorated copy of it) the GLOG_MENU (via ) whenever the current branch is not | |
# the same as the last branch. But we cannot determine the branch when in detached | |
# HEAD state, yet we still want this function to be runnable in this case so we | |
# can move the '>' log pointer if need be. | |
local curr_branch="$(git symbolic-ref --short HEAD)" | |
local curr_branch_commmit_cnt=$(git rev-list --count "$curr_branch") | |
if [[ "$curr_branch" != "$GR_PREV_BRANCH" ]] || [[ "$curr_branch_commmit_cnt" != "$GR_PREV_BRANCH_COMMIT_CNT" ]] ; then | |
repopulated=true | |
if [ "$1" != "--quiet" ] && printf "repopulating gitReview log..." | |
GR_PREV_BRANCH="$curr_branch" | |
GR_PREV_BRANCH_COMMIT_CNT="$curr_branch_commmit_cnt" | |
GLOG_ITEMS=("${(@f)$(git --no-pager log --tags --pretty='%h%d %as (%an) %s')}") | |
fi | |
fi | |
local curr_short_hash=$(git rev-parse --short HEAD) | |
local repoint=false | |
if [[ "$curr_short_hash" != "$GR_PREV_SHORT_HASH" ]] ; then | |
if [ "$1" != "--quiet" ] && printf "moving log pointer..." | |
repoint=true | |
GR_PREV_SHORT_HASH="$curr_short_hash" | |
fi | |
if $repopulated || $repoint; then | |
GLOG_MENU=() | |
for i in {1..$#GLOG_ITEMS}; do | |
local short_hash="${GLOG_ITEMS[${i}]%% *}" | |
local prepend=" [$i]" | |
if [[ $short_hash == $curr_short_hash ]] ; then | |
GLOG_MENU_IDX=$i | |
prepend=">[$i]" | |
fi | |
GLOG_MENU[$i]="${prepend} ${GLOG_ITEMS[$i]}" | |
done | |
if [ "$1" != "--quiet" ] && printf "done.\n" | |
fi | |
} | |
if git-head-detached --quiet; then | |
_GR_CLEAR_ALL | |
printf "NOTE: cannot generate complete log menu for gitReview because of detached HEAD state.\n" | |
printf "If you need to run gitReview and you don't have any unsaved changes, first try running:\n git-undetach-head && gitReviewSetup\n" | |
else | |
printf "setting up gitReview..." | |
GR_CURR_BRANCH=$(git symbolic-ref --short HEAD) | |
_GR_MENU_REFRESH --quiet | |
function gitReview() | |
{ | |
if [ "$1" = "--help" ];then | |
printf "gitReview allows navigation through commit histories with easy \n" | |
printf "(often automatically performed) recovery from detached HEAD state.\n\n" | |
printf "gitReview (no args) You'll be prompted to select a commit from a list of all\n" | |
printf " commits on the current branch. Selection is made via peco's search.\n\n" | |
printf "gitReview --tag You'll be prompted to select a commit from a list of only commits\n" | |
printf "gitReview -t with tags on the current branch. Selection is made via peco's search.\n\n" | |
printf "gitReview --tag=<n> where n is any string without spaces (no quotes allowed) allows you\n" | |
printf "gitReview -t=<n> to directly select a commit by it's tag value (such as a version number)\n\n" | |
printf "gitReview -i=<i> Directly select commit by index in output (line number, where first is 1).\n\n" | |
printf "gitReview --remote=remotename/branchname Display the remote log in the same formatting as local\n" | |
printf "gitReview -r=remotename/branchname (these are view only - no checking out hashes)\n" | |
printf " If you add ^branchname as second arg, you'll only see\n" | |
printf " newly fetched (but not pulled/rebased parts of the log),\n" | |
printf " but this is equivalent to -r=branchname..origin/branchname\.\nn" | |
printf "gitReview --remote Without a specified value, these default to\n" | |
printf "gitReview -r to the first line of output from \`git remote\`n\n" | |
return 0 | |
fi | |
_GR_MENU_REFRESH | |
if [ $# -eq 0 ] || [[ "$1" == --tag* ]] || [[ "$1" == "-t"* ]] || [[ "$1" == -i=* ]] ; then | |
local selected | |
if [ $# -eq 0 ] ; then | |
if which peco >/dev/null 2>&1 ; then | |
current_i="$GLOG_I" | |
selected=$(printf '%s\n' "${GLOG_MENU[@]}" | peco --initial-index=$((GLOG_MENU_IDX - 1)) --prompt="select a commit") # see: peco --help | |
else | |
printf "peco not installed! see https://github.com/peco/peco\n" | |
return 1 | |
fi | |
elif [[ "$1" == -i=* ]] ; then | |
local i=${1#*=} | |
selected="${GLOG_MENU[$i]}" | |
elif [[ "$1" == "--tag="* ]] || [[ "$1" == "-t="* ]] ; then | |
local tag=${1#*=} | |
selected=$(printf '%s\n' "${GLOG_MENU[@]}" | grep "tag: $tag" | head -1) | |
else # [[ "$1" == --tag* ]] ; then | |
if which peco >/dev/null 2>&1 ; then | |
selected=$(printf '%s\n' "${GLOG_MENU[@]}" | grep "tag: " | peco --initial-index=$((GLOG_MENU_IDX - 1)) --prompt="select a commit") # see: peco --help | |
else | |
printf "peco not installed! See https://github.com/peco/peco\n" | |
return 1 | |
fi | |
fi | |
[ -z "$selected" ] && printf "No selection\n" && return 0; | |
local new_i=$(echo "$selected" | cut -d "[" -f2 | cut -d "]" -f1) | |
if [ $new_i -eq 1 ]; then | |
printf "running \`git-undetach-head\`\n" | |
if git-undetach-head --quiet; then | |
_GR_MENU_REFRESH --quiet | |
return 0 | |
else | |
printf "Unable to recover detached HEAD state!\n" | |
printf "If you need to run gitReview and you don't have any unsaved changes, first try running:\n git-undetach-head && gitReviewSetup\n" | |
return 1 | |
fi | |
fi | |
[[ ${selected:0:1} == ">" ]] && printf "That is not a different commmit\n" && return 0; | |
local shorthash=$(echo ${selected:1} | cut -d " " -f 2) | |
git switch "$shorthash" --detach | |
_GR_MENU_REFRESH --quiet | |
elif [[ "$1" == --remote* ]] || [[ "$1" == "-r"* ]] ; then # view remote log (view only) | |
local remotename_branchname | |
if [[ "$1" == --remote=* ]] || [[ "$1" == "-r="* ]] ; then | |
remotename_branchname=${1#*=} | |
else | |
if ! git-head-detached --quiet ; then | |
local curr_branch="$(git symbolic-ref --short HEAD)" | |
local remotename="$(git remote | head -n 1)" # gets first line of output | |
[ -z "$remotename" ] && printf "No git remote found!\n" && return 1; | |
remotename_branchname="${remotename}/${curr_branch}" | |
fi | |
fi | |
shift | |
echo "git log $remotename_branchname $@ --tags --pretty='%C(auto)%h%d %as (%an) %s'" | |
git log $remotename_branchname $@ --tags --pretty='%C(auto)%h%d %as (%an) %s' | |
return 0 | |
else | |
printf "Option not recognized.\n\n" | |
gitReview --help | |
return 1 | |
fi | |
} | |
printf "done.\n" | |
fi | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment