Last active
May 10, 2023 13:27
-
-
Save tbnorth/936a7ac488e7cbacaf4729a23fd34419 to your computer and use it in GitHub Desktop.
Recursively show detailed status of git repos.
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
find . \( -name HEAD -o -name .git \) -print -prune \ | |
| xargs -L1 dirname \ | |
| xargs -IF sh -c ' \ | |
echo; echo -n F ""; \ | |
(cd F; git rev-parse --abbrev-ref HEAD); \ | |
echo "$((cd F; git ls-files --others --exclude-standard) | wc -l) untracked"; \ | |
(cd F; git remote -v) | sed "s/(.*)//" | sort -u; \ | |
(cd F; git cherry -v 2>/dev/null); \ | |
[ -d F/.git ] && (cd F; git -c core.fileMode=false status -uno -s) \ | |
' \ | |
| less +g -p'(M|\+)' | |
# find -prune as well as -print to not find .git/HEAD and .git/logs/HEAD | |
# -L1 in the first xargs works with older dirname commands that | |
# only take one arg. | |
# use (cd X; cmd) rather the git -C for old git versions with no -C | |
# sed / sort just collapses fetch / push lines from git remote -v | |
# less params - search (highlight) M and + (modified / unpushed), but start at top | |
# show local unpushed branches | |
git branch --format "%(refname:short) %(upstream)" | grep '\s$' | |
# or | |
git for-each-ref --format="%(upstream:remotename) %(refname:short) %(upstream:track)" refs/heads | sort | |
# git 2.41+ | |
git for-each-ref --format="%(upstream:remotename) %(refname:short) %(ahead-behind:HEAD) %(upstream:track)" refs/heads | sort | |
# pre 2.41 https://stackoverflow.com/a/7774433/1072212 | |
git for-each-ref --format="%(refname:short) %(upstream:short)" refs/heads | \ | |
while read local remote | |
do | |
[ -z "$remote" ] && continue | |
git rev-list --left-right "${local}...${remote}" -- 2>/dev/null >/tmp/git_upstream_status_delta || continue | |
LEFT_AHEAD=$(grep -c '^<' /tmp/git_upstream_status_delta) | |
RIGHT_AHEAD=$(grep -c '^>' /tmp/git_upstream_status_delta) | |
echo "$local (ahead $LEFT_AHEAD) | (behind $RIGHT_AHEAD) $remote" | |
done | |
# or, more useful formatting: | |
echo; git for-each-ref --format="%(refname:short) %(upstream:short)" refs/heads | while read local remote; do [ -z "$remote" ] && continue; git rev-list --left-right "${local}...${remote}" -- 2>/dev/null >/tmp/git_upstream_status_delta || continue; LEFT_AHEAD=$(grep -c '^<' /tmp/git_upstream_status_delta); RIGHT_AHEAD=$(grep -c '^>' /tmp/git_upstream_status_delta); echo "+$LEFT_AHEAD-$RIGHT_AHEAD $local $remote"; done | sort -r; echo | |
# push all local branches to a new remote | |
git push newremote refs/remotes/oldremote/*:refs/heads/* | |
# https://www.metaltoad.com/blog/git-push-all-branches-new-remote | |
# to check deletions ~= insertions (i.e. reordering of functions / bibtex records etc.) | |
git diff | grep ^- | wc -l; git diff | grep ^+ | wc -l | |
# fingerprint entire repo. | |
git log --all --format=%H | sort | sha1sum | |
# to process dirs with uncommitted changes | |
read -p Dir: DIR; pushd $DIR; git diff; read -p Why: WHY; git commit -am"$WHY" && git push; popd | |
# pull in each git repo dir found | |
find . \( -name HEAD -o -name .git \) -print -prune \ | |
| xargs -L1 dirname \ | |
| xargs -IF sh -c ' \ | |
echo; echo -n F ""; \ | |
(cd F; git pull origin $(git rev-parse --abbrev-ref HEAD) ); \ | |
' | |
# other git maintenance commands | |
# fix eol change | |
# git ls-files -m | xargs dos2unix misses some, so | |
git status --porcelain | sed -n '/^ M/ {s/^ M//; p}' | xargs -IF dos2unix $(git rev-parse --show-toplevel)/F | |
# fix mode change, *from git root* | |
git diff --summary | sed -r 's/mode change 100([^ ]*).* (.*)/chmod \1 \2/' | bash | |
# fix trailing whitespace | |
git ls-files -m | xargs sed -i 's/[ \t]\+$//g' | |
# sha1 comparison, first 7 chars | |
sha1sum /some/path/* /some/other/path/* | sort | sed 's/^\(.\{7\}\).\{33\}/\1/' | |
# find unpushed stuff, again avoid -C for older gits | |
find . -name .git -type d | xargs dirname | \ | |
xargs -IF bash -c 'cd F; BRANCH=$(git rev-parse --abbrev-ref HEAD); \ | |
echo F $BRANCH; \ | |
git --no-pager log -1 --pretty=oneline origin/$BRANCH..$BRANCH' \ | |
| less | |
# check for references to untracked files from other files | |
git status --porcelain | sed -n '/^?? .*[^/]$/ {s%.*/%%; p}' | xargs -IF grep -ri F . | |
# move untracked files to attic | |
mkdir -p attic | |
git status --porcelain | sed -n '/^?? / {s%^...%%; p}' >attic/to_add.lst | |
rsync --remove-source-files --verbose --recursive --backup --suffix `date '+.%Y%m%d%H%M%S'` --files-from=attic/to_add.lst --exclude attic . attic/ | |
# list repos. without readme files | |
find . -name .git | sed 's/\.git$//' | xargs -IF bash -c "find F -maxdepth 1 -iname readme.\* | egrep -q . || echo F" | |
# change extension (not really git specific) | |
ls | sed 's/\(.*\)\..*/git mv \1.Rmd \1.md/' | bash | |
# compare pairs of files | |
ls *txt | sed 'x; G; s/\n/ /; /^ / d' | xargs -L1 echo diff | |
# copy files to numbered list | |
find . -type f -mtime -4 -name \*[0-9] | cat -n | sed -E 's%(\S+)\s+(\S+)%cp \2 ~/skipmail/\1%' | bash | |
# edit files in a commit | |
git diff-tree --no-commit-id --name-only -r HEAD | xargs -L1 sed -i 's/"temp_set": 0/"temp_set": 25/g' | |
cat > gitemailfix.sh << EOT | |
git filter-branch --env-filter ' | |
WRONG_EMAIL="[email protected]" | |
NEW_NAME="New Name Value" | |
NEW_EMAIL="[email protected]" | |
if [ "\$GIT_COMMITTER_EMAIL" = "\$WRONG_EMAIL" ] | |
then | |
export GIT_COMMITTER_NAME="\$NEW_NAME" | |
export GIT_COMMITTER_EMAIL="\$NEW_EMAIL" | |
fi | |
if [ "\$GIT_AUTHOR_EMAIL" = "\$WRONG_EMAIL" ] | |
then | |
export GIT_AUTHOR_NAME="\$NEW_NAME" | |
export GIT_AUTHOR_EMAIL="\$NEW_EMAIL" | |
fi | |
' --tag-name-filter cat -- --branches --tags | |
EOT | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment