Skip to content

Instantly share code, notes, and snippets.

@blech75
Last active August 29, 2015 14:25
Show Gist options
  • Select an option

  • Save blech75/5f9c6e56a670f7e453d3 to your computer and use it in GitHub Desktop.

Select an option

Save blech75/5f9c6e56a670f7e453d3 to your computer and use it in GitHub Desktop.
findgitrepos: For a given path, find all child dirs that appear to be local git repos and print their user config and a list of remotes
#!/usr/bin/env bash
# findgitrepos
#
# For a given path, find all child dirs that appear to be local Git repos and
# print some useful info (user config, list of remotes, hooks). Allows you to
# review your Git config across many projects. Called without arguments, it
# looks in $HOME.
#
# Author: justin@worksperfectly.net
#
# TODO
# ====
#
# * colorize output to make it easier to read
# * add ability to pass in more $IGNORE_PATHS
# * add help/usage on command line
# * better error handling
# * add support for bare repos
# * address NOTEs and FIXMEs below
# * add option to just print dirs found (for passing to other tools)
# * possibly add other useful Git info/config
# * add option to output only specified info (ex: name/email only)
# * add option to filter list based on matching certain values for each kind of
# info
# default to looking in your home dir
# FIXME: does this make sense? should it just be the current dir? real question
# is: is it a 'global' tool? or a 'local' tool?
if [[ $# -eq 0 ]]; then
TARGET_PATH=$HOME
elif [[ $# -eq 1 ]]; then
if [[ ! -d $1 ]]; then
echo "Error: '$1' is not a directory." >&2
exit 1
fi
# resolve symlinks of passed dir to be root-relative.
# NOTE: may want to make this optional, especially w/r/t/ it being
# root-relative since it would affect $IGNORE_PATHS. (see below)
TARGET_PATH="$( cd $1 ; pwd -P )"
else
echo "Usage: "`basename "$0"` "TARGET_PATH" >&2
exit 1
fi
function indent_values {
sed -e 's/^/ /'
}
# http://stackoverflow.com/a/17841619/2284440
function join { local IFS="$1"; shift; echo "$*"; }
# space-seprarted paths to ignore
IGNORE_PATHS=""
# if we're looking in $HOME, we don't care about things in ~/Library
if [ $TARGET_PATH = $HOME ]; then
# FIXME: this is rather platform-specific
IGNORE_PATHS=$HOME/Library/
fi
# array of args to pass to `find`
# see http://wiki.bash-hackers.org/syntax/quoting#comment_53ff2ad966e2d5e9ace47c4c025c34fb
FIND_IGNORE_ARGS=()
# compose list of arguments based on $IGNORE_PATHS
# NOTE: does not account for spaces in paths
for p in $IGNORE_PATHS; do
# IGNORE_PATHS are currently root-relative because we resolve symlinks when
# setting TARGET_PATH. this allows us to anchor the -path at the beginning.
FIND_IGNORE_ARGS=(${FIND_IGNORE_ARGS[@]} -not -path '${p}.*')
done
# pull name/email from global git config for late comparison to each git repo's
# local config
global_name="`git config --global user.name`"
global_email="`git config --global user.email`"
# from `man githook` (as of 2.4.5)
ALL_GIT_HOOKS=(applypatch-msg
pre-applypatch
post-applypatch
pre-commit
prepare-commit-msg
commit-msg
post-commit
pre-rebase
post-checkout
post-merge
pre-push
pre-receive
update
post-receive
post-update
push-to-checkout
pre-auto-gc
post-rewrite)
FIND_GITHOOK_REGEXP="(`join '|', "${ALL_GIT_HOOKS[@]}"`)"
# FIXME: hardcode the regexp. bash string quoting and interpolation is killing
# me. i give up.
# FIND_GITHOOK_ARGS=(-regex '.*/${FIND_GITHOOK_REGEXP}\$')
FIND_GITHOOK_ARGS=(-regex '.*/(applypatch-msg|pre-applypatch|post-applypatch|pre-commit|prepare-commit-msg|commit-msg|post-commit|pre-rebase|post-checkout|post-merge|pre-push|pre-receive|update|post-receive|post-update|push-to-checkout|pre-auto-gc|post-rewrite)$')
# perform the find, locating all GIT_DIRs in target dir. report the parent dir
# of the GIT_DIR. pipe find's output so we display results immediately and
# incrementally.
echo "Finding directories that appear to be Git repos in $TARGET_PATH..."
echo
find $TARGET_PATH -type d -path '*/.git' ${FIND_IGNORE_ARGS[@]} -exec dirname '{}' \; | \
while read g; do
# TODO: option to display full path or relative path
echo $g
# descend into the git project via pushd to extract data.
# TODO: maybe use GIT_DIR to avoid pushd/popd
#
# quote the dir to deal with spaces
pushd "$g" > /dev/null
# get our local name/email from the repo's git config
local_name="`git config --local user.name`"
local_email="`git config --local user.email`"
echo -n " user: "
# FIXME: treat name and email independently w/r/t global vs. local
if [[ ("$local_name" = "") && ("$local_email" = "") ]]; then
echo "$global_name <$global_email> (using global config)"
elif [[ ("$global_name" = "$local_name") && ("$global_email" = "$local_email") ]]; then
echo "$local_name <$local_email> (same as global config)"
else
echo "$local_name <$local_email>"
fi
# get all the unique remotes (collapsing get/push) and format them in
# columns with an indent
remotes="`git remote -v | awk '{ print $1" "$2 }' | sort | uniq | column -t`"
echo -n " remotes: "
if [[ "$remotes" =~ ^\s*$ ]]; then
echo "(none)"
else
echo "$remotes" | wc -l | (read n; echo "($n)")
echo "$remotes" | indent_values
fi
# get all the active hooks (files or symlinks) in the current repo.
hooks="`find -E .git/hooks \( -type f -or -type l \) -perm -u+x -not -name '*.sample' ${FIND_GITHOOK_ARGS[@]} | sed -e 's/.git\/hooks\///g'`"
echo -n " hooks: "
if [[ "$hooks" =~ ^\s*$ ]]; then
echo "(none)"
else
echo "$hooks" | wc -l | (read n; echo "($n)")
{
for h in $hooks; do
link="`readlink .git/hooks/$h`"
if [ "$link" != "" ]; then
echo "$h (-> $link)"
else
echo $h
fi
done
} | indent_values
fi
echo
popd > /dev/null
done
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment