Permalink
Switch branches/tags
Nothing to show
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
executable file 200 lines (165 sloc) 7.2 KB
ABOUT_THIS_SCRIPT="
Q: Would you lose any work if your local clone of a git repository were lost?
A: Run this script to find out.
Checks for untracked files, detached heads, diffs (staged or unstaged), stashes, unpushed tags, and branches not yet pushed to some remote.
Returns 0 if the repository passes all checks (no untracked files, no diffs, ...), and 1 otherwise.
With the -a flag, take the first relevant automatic action. In every case, ask permission
before doing anything. If an action feels unsafe or inappropriate, just say no or hit <ctrl>-C to leave.
∙ If this is not even a git repository, run git init.
∙ If there are untracked files, run git istatus.
∙ If there are diffs, run 'git diff'.
∙ If there is one stash, run 'git stash show -p' [i.e., print the stash diff].
∙ If there are multiple stashes, run 'git stash list'.
∙ If there are tags not pushed to origin, push them.
∙ If in a detached HEAD state, merge to master.
∙ If a branch (incl. master) needs to be 'git push origin'ed, do so.
∙ If there are submodules, recurse into them and run all of the above; else print nothing.
∙ If everything is clean, remove the directory!
The last step is intended for people who have the work habit of not leaving copies of
repositories in their home directory if they aren't needed—effectively putting them
back on the shelf to take down later. If that isn't your habit, it is safe to remove the
last block of this script that takes that action. If you do delete the directory entirely,
you will still need to `cd` to another directory yourself (a subshell can't change
its parent shell's working directory.)
Setup:
Add this to your ~/.gitconfig:
[alias]
isclean = !/path/to/git-isclean
Now you can run
git isclean
or
git isclean -a
from any repository.
Limitations:
∙ Misses empty subdirectories---including those with only dot files!
∙ Assumes your only remote is origin.
License: CC BY SA [creative commons, attribution, share-alike]
By Ben Klemens. See http://modelingwithdata.org/arch/00000194.htm for discussion.
Some portions based on:
http://0xfe.blogspot.com/2010/04/adding-git-status-information-to-your.html
http://0xfe.blogspot.com/2010/04/improved-git-enabled-shell-prompt.html
"
green="\033[0;32m"
bold_green="\033[1;32m"
cyan="\033[0;36m"
bold_cyan="\033[1;36m"
red="\033[0;31m"
bold_red="\033[1;31m"
no_color="\033[0m"
Red_X="${bold_red}✘${no_color}"
Check="${bold_cyan}✓${no_color}"
Divider="${bold_cyan}―――${no_color}"
Dirty=0
test "$1" = "-a"
Action=$?
onetest () { if eval $3 ; then echo -e $Red_X $2; Dirty=1; else echo -e $Check $1; fi }
# unpushed branches, which is the list of remote branches that are not synced to the local HEAD.
# Original author had:
## Returns "unpushed:N" where N is the number of unpushed local and remote branches (if any).
## local unpushed=`expr $( (git branch --no-color -r --contains $1; git branch --no-color -r) | sort | uniq -u | wc -l )`
# I just need to know that a given branch is contained by at least one remote.
# Assume the network of repos is not super-complicated so there's just the origin
allbranches(){
for i in `git branch --no-color | grep -v "(HEAD.*)" | sed 's/\* //'`; do
unpushed=`expr $( (git branch --no-color -r --contains $i) | wc -l )`
onetest "$i pushed to origin" "$i not pushed to origin" '[ "$unpushed" = "0" -o "`git branch -r`" = "" ]'
if [ "$unpushed" = "0" ] || [ "`git branch -r`" = "" ] ; then
if [ $Action = 0 ] ; then
echo -n "Run git push origin $i? [y/n] "
read yesno
if [ "$yesno" != "n" -a "$yesno" != 'N' ]; then
git push origin $i
fi
fi
fi
done
}
# Optional test to add for unmerged branches, if that's your workflow.
# local unmerged=`expr $(git branch --no-color -a --no-merged | wc -l)`
# onetest "no unmerged files" "unmerged files" '[ "$unmerged" != "0" ]'
# main sequence: run each test
if [ "$1" = "-h" ]; then echo "Check whether a git repository can be deleted. Use -a to automatically run some next steps."; fi
is_repository=$(git status > /dev/null 2> /dev/null; echo $?)
if [ $is_repository -gt 0 ]; then
echo -e $Red_X "Not a git repository"; Dirty=1;
if [ $Action -eq 0 ] ; then
echo -n "Initialize repository? [y/n] "
read yesno
if [ "$yesno" != "n" -a "$yesno" != 'N' ]; then
git init
fi
fi
fi
if [ $is_repository -eq 0 ]; then #skip to the end if not a repository
Untracked=$Dirty
if [ $Action -eq 0 ] && [ "$Untracked" != 0 ]; then git istatus; Action=1; fi;
onetest "no untracked files" "untracked files" '[ `git ls-files --others --exclude-standard | wc -l` -ne 0 ]'
Untracked=$Dirty
if [ $Action = 0 ] && [ "$Untracked" != 0 ]; then git istatus; Action=1; fi;
onetest "no unstaged diffs" "diffs to commit" '[ "$(git diff --shortstat 2> /dev/null)" != "" -o "$(git diff --staged --shortstat 2> /dev/null)" != "" ]'
Diffs=$((Dirty - Untracked))
if [ $Action = 0 ] && [ "$Diffs" != 0 ]; then git --no-pager diff; Action=1; fi;
stash=`expr $(git stash list 2>/dev/null| wc -l)` # Return the number of stashed states, not just 0|1.
onetest "no pending stashes" "stashes pending" '[ "$stash" != "0" ]'
if [ $Action = 0 ] && [ "$stash" != "0" ] ; then
if [ $stash -eq 1 ]; then
git --no-pager stash show -p
elif [ $stash != 0 ]; then
git stash list
fi
Action=1
fi
unpushed_tags=`git show-ref --tags | grep -v -F "$(git ls-remote --tags origin | grep -v '\^{}' | cut -f 2)" | sed 's#.*tags/##'`
onetest "no unpushed tags" "unpushed tags: $unpushed_tags" '[ ! -z "$unpushed_tags" ]'
if [ $Action = 0 ] && [ ! -z "$unpushed_tags" ] ; then
echo -n "Push all tags? [y/n] "
read yesno
if [ "$yesno" != "n" -a "$yesno" != 'N' ]; then
git push --tags
fi
Action=1
fi
detached=`grep -v "^ref" $(git rev-parse --show-toplevel)/.git/HEAD |wc -l`
onetest "head attached" "detached head" '[ $detached -gt 0 ]'
if [ $Action = 0 ] && [ $detached -gt 0 ] ; then
if [ $detached -eq 1 ]; then
echo -n "Merge detached head into master? [y/n] "
read yesno
if [ "$yesno" != "n" -a "$yesno" != 'N' ]; then
git checkout -b HEADregraft
git commit -m "Check in HEAD to merge"
git checkout master
git merge HEADregraft
git branch -d HEADregraft
fi
Action=1
else
echo "Multiple detached HEADs. This is biologically impossible."
fi
fi
allbranches
# run this very program foreach submodule
if [ -e `git rev-parse --show-toplevel`/.gitmodules ] ; then
echo -e "${Divider}Checking submodules"
if [ $Action = 0 ] ; then
git submodule foreach "$0 -a"
else
git submodule foreach "$0"
fi
Subdirty=$?
Dirty=$((Dirty + $Subdirty))
#else: submodules are so infrequently used, don't even mention this test if no .gitmodules.
#$onetest "no submodules to check" "--" false
fi
#OK, checks done. Check to remove working directory
if [ "$1" = "-a" ] && [ "$Dirty" = 0 ]; then
WD=`git rev-parse --show-toplevel`
echo -n "Removing $WD. OK? [y/n] "
read yesno
if [ "$yesno" != "n" -a "$yesno" != 'N' ]; then
cd $WD/..
rm -rf $WD
fi
fi
fi #if isnt_repository we skipped almost all of the above
[ $Dirty -eq 0 ] #return value