You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
# git_manager version
VERSION = :0.8.2
# Customize below to fit your system
# paths
PREFIX = /usr/local
MANPREFIX = ${PREFIX}/share/man
Makefile
# gitmanager Manage git repos# Copyright (C) 2019-2021 Nan0Scho1ar (Christopher Mackinga)# See LICENSE file for copyright and license details.include config.mk
SRC = gitmanager
install: all
mkdir -p ${DESTDIR}${PREFIX}/bin
cp -f ${SRC}${DESTDIR}${PREFIX}/bin/gitmanager
chmod 755 ${DESTDIR}${PREFIX}/bin/gitmanager
mkdir -p ${DESTDIR}${MANPREFIX}/man1
sed "s/VERSION/${VERSION}/g"< gitmanager.1 >${DESTDIR}${MANPREFIX}/man1/gitmanager.1
chmod 644 ${DESTDIR}${MANPREFIX}/man1/gitmanager.1
uninstall:
rm -f ${DESTDIR}${PREFIX}/bin/gitmanager\${DESTDIR}${MANPREFIX}/man1/gitmanager.1
.PHONY: all install uninstall
File Header
#!/bin/bash# gitmanager: Manage multiple git repositories and branches# Copyright (C) 2019-2021 Nan0Scho1ar (Christopher Mackinga)# This program is free software: you can redistribute it and/or modify# it under the terms of the GNU General Public License as published by# the Free Software Foundation, either version 3 of the License, or# (at your option) any later version.# This program is distributed in the hope that it will be useful,# but WITHOUT ANY WARRANTY; without even the implied warranty of# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the# GNU General Public License for more details.# You should have received a copy of the GNU General Public License# along with this program. If not, see <https://www.gnu.org/licenses/>.
Setup
options=("Search for git repos""Check for changes""List changes""Pull and push""Pull and push auto""Add all changes + commit + push""Clean old branches""Merge origin/master into branches""List branches""Compare master""Compare remote""Quit")
if [ -t 1 ] &&command -v tput > /dev/null;then# see if it supports colors
ncolors=$(tput colors)if [ -n"$ncolors" ] && [ $ncolors-ge 8 ];then
bold="$(tput bold || echo)"
blink="$(tput blink || echo)"
reset="$(tput sgr0 || echo)"
black="$(tput setaf 0 || echo)"
red="$(tput setaf 1 || echo)"
green="$(tput setaf 2 || echo)"
yellow="$(tput setaf 3 || echo)"
blue="$(tput setaf 4 || echo)"
magenta="$(tput setaf 5 || echo)"
cyan="$(tput setaf 6 || echo)"
white="$(tput setaf 7 || echo)"fifi
repos=""
repo_auto=false
branch_auto=false
if [ -z$XDG_CONFIG_HOME ];then
cfg_path="$HOME/.config/gitmanager"else
cfg_path="$XDG_CONFIG_HOME/gitmanager"fi
mkdir -p "$cfg_path"
Helpers
show_menu()
# Displays the option menu (if no args provided to program)show_menu() {
selectoptionin"${options[@]}";doecho$optionbreakdone
}
# Sets repos and prints them to the userusing_repos() { set_repos &&echo"Using repos:${cyan}$(cat ${cfg_path}/repos.cache | grep -vf ${cfg_path}/repos.exclude)${reset}"; }
Colour
colour_pwd()
# pwd but colouredcolour_pwd() { echo${cyan}$(pwd)${reset}; }
# Used to make all calls to branch use the same formatting.git_branch() { git branch; }
git_commit()
# Used to make all calls to commit use the same formatting.git_commit() { read -p "Please enter commit message: "&& git commit -m "$REPLY"; }
git_push()
# Used to make all calls to push use the same formatting.git_push() { echo"${magenta}Pushing...${reset}"&& git push &&echo; }
git_pull()
# Used to make all calls to pull use the same formatting.git_pull() { echo"${magenta}Pulling...${reset}"&& git pull; }
git_merge()
# Used to make all calls to merge use the same formatting.git_merge() { echo"${magenta}Merging...${reset}"&& git merge $1; }
git_merge_abort()
# Used to make all calls to merge use the same formatting.git_merge_abort() { echo"${magenta}Aborting Merge...${reset}"&& git merge --abort; }
git_rebase()
# Used to make all calls to rebase use the same formatting.git_rebase() { echo"${magenta}Rebasing...${reset}"&& git rebase $1; }
git_checkout()
# Used to make all calls to checkout use the same formatting.git_checkout() { git checkout $12>&11>/dev/null | sed -E "s/(.+')(.+)('.*)/\1${blue}\2${reset}\3/"; }
# Calls git status with ability to use cached valuegit_status() {
git status
}
git_get_branches()
# Checks out master and updates list of branchesgit_get_branches() {
git_checkout master
branches=$(git for-each-ref --format='%(refname)' refs/heads/ | sed "s|refs/heads/||")echo -e "\nFound branches:\n$(echo ${blue}${branches}${reset}| tr """\n")\n"
}
git_summary()
# Provides a one line summary on the current repo/branchgit_summary() {
tree_is_clean && branch_up_to_date "CACHED"&&echo"$(colour_pwd): ${green}No changes found${reset}"&&return 0
tree_is_clean "CACHED"&&echo"$(colour_pwd): ${yellow}Out of sync${reset}"&&return 1
has_conflicts "CACHED"&&echo"$(colour_pwd): ${red}Merge conflict detected${reset}"&&return 2
echo"$(colour_pwd): ${red}Changes detected${reset}"&&return 3
}
git_state()
# Provides a one line summary on the current repo/branchgit_state() {
tree_is_clean && branch_up_to_date "CACHED"&&return 0
tree_is_clean "CACHED"&&return 1
has_conflicts "CACHED"&&return 2
return 3
}
Checks
tree_is_clean()
# Checks if the current branch working tree is clean. arg1 can be used to toggle CACHEDtree_is_clean() { git_status $1| grep -q "nothing to commit, working tree clean"&&return 0 ||return 1; }
check_should_skip_repo()
# Checks if a repo should be skippedcheck_should_skip_repo() {
[[ "$repo_auto"==true ]] || ask "Update project $(colour_path)"||return 1
git_fetch "$path/.."
ask_if_skip_dirty &&return 0 ||echo;return 1
}
branch_up_to_date()
# Checks if the current branch is up to date. arg1 can be used to toggle CACHEDbranch_up_to_date() { git_status $1| grep -q "Your branch is up to date with "&&return 0 ||return 1; }
has_conflicts()
# Checks if the current branch has conflicts. arg1 can be used to toggle CACHEDhas_conflicts() { git_status $1| grep -Eq "both added|both modified"&&return 0 ||return 1; }
# Promts the user to answer a yes/no question.# Returns after a single char is entered without hitting return.ask() {
read -p "${1}${yellow}y/n${reset}" -n 1 -r
echo
[[ $REPLY=~ ^[Yy]$ ]] &&return 0 ||return 1
}
ask_if_push()
# Asks the user if they want to push then pushes and shows status.ask_if_push() {
git_checkout $branch| grep -q "but the upstream is gone."&&echo -e "Remote has been deleted. Pushing will recreate it.\n"
ask "Push"&& git_push && git_status &&echo
}
ask_if_skip_repo()
# Prompts the user to skip if the current branch is dirtyask_if_skip_dirty() {
tree_is_clean &&return 0
git_status
ask "Working tree is not clean, would you like to skip this project (y to skip, n to recheck)"&&return 1
ask_if_skip_dirty
}
ask_if_skip_dirty_merge()
# Prompts the user to skip if the current branch is dirty or revert the merge and continueask_if_skip_dirty_merge() {
tree_is_clean &&return 0
git_status
echo"${red}Auto merge failed, conflicts found${reset}."echo"Would you like to skip remaining branches in this project or revert the merge?"read -p "(y to skip, n to recheck, r to revert merge and continue) ${yellow}y/n/r${reset}" -n 1 -r
echo -e "\n\n"
[[ $REPLY=~ ^[Yy]$ ]] &&return 1
[[ $REPLY=~ ^[Rr]$ ]] && git_merge_abort
ask_if_skip_dirty_merge
}
Cmds
find()_repos
# Finds repos on the systemfind_repos() {
cd$1
find ~+ -name .git -type d -prune 2> /dev/null | grep -v -f ${cfg_path}/repos.cache >>${cfg_path}/repos.cache
echo -e "Updated repos.cache\n"if [[ !-e${cfg_path}/repos.cache ]];thenecho"Cannot find repos.cache. Exiting..."read -p "Press enter to continue"exit 1
fiecho"Found the following repos (in repos.cache)"
cat ${cfg_path}/repos.cache
echoif [[ !-e${cfg_path}/repos.exclude ]];thenecho"Cannot find repos.exclude"
touch ${cfg_path}/repos.exclude
echo -e "Created repos.exclude\nCopy the path of any unwanted repos in the above output to a new line of this file.\n"read -p "Press enter to continue once you have completed this step"fiecho"Excluding the following repos (in repos.exclude)"
cat ${cfg_path}/repos.exclude
echo -e "\nFinal list"
cat ${cfg_path}/repos.cache | grep -vf ${cfg_path}/repos.exclude
repos=$(cat ${cfg_path}/repos.cache | grep -v -f ${cfg_path}/repos.exclude)
}
repos_summary()
# Displays a brief summary of all reposrepos_summary() {
git_fetch_all_repos
forpathin$repos;docd"$path/..";
git_summary;done| column -t -c 1 -s ":";
}
repos_summary_compact()
# Displays a brief summary of all reposrepos_summary_compact() {
git_fetch_all_repos
total_repos=0
clean=0
out_of_sync=0
merge_conflicts=0
changes_detected=0
forpathin$repos;docd"$path/..";
git_state
exit_code=$?
total_repos=$((total_repos+1))case$exit_codein
0) clean=$((clean+1)) ;;
1) out_of_sync=$((out_of_sync+1)) ;;
2) merge_conflicts=$((merge_conflicts+1)) ;;
3) changes_detected=$((changes_detected+1)) ;;
esacdoneecho"Total Repos: $total_repos"echo"Clean: $green$clean$reset | Out Of Sync: $yellow$out_of_sync$reset | Merge Conflicts: $red$merge_conflicts$reset | Changes Detected: $red$changes_detected$reset"
}
repos_status()
# Displays the status of all reposrepos_status() {
forpathin$repos;docd"$path/.."
git_summary
git_status
echo -e '\n'done
}
compare_branches()
# Compares all the branches of all the repos to a specific branchcompare_branches() {
[ -z"$1" ] &&continue;forpathin$repos;docd"$path/.."
header=$(echo -e "----------------------------------------~-----< $(pwd | xargs basename) >-----~----------------------------------------")
footer=$(echo -e "________________________________________~______________________~________________________________________")
git for-each-ref --format="%(refname:short) %(upstream:short)" refs/heads | \
whilereadlocal remote;do
[[ $1=="origin/master" ]] && remote="origin/master"
get_ahead_behind $local$remoteecho -e "${local}~(ahead ${ahead}) | (behind ${behind})~$remote\n"done|echo -e "${header}\n$(cat)\n${footer}"done| cat | column -t -c 1 -s "~"echo
}
commit_and_push()
# Commit changes and push in all branchescommit_and_push() {
forpathin$repos;docd"$path/.."
git_summary &&continue
[[ $?== 1 ]] &&continue
git_status
# prompt for diff unless NODIFF specified
[[ $1!="NODIFF" ]] && ask "Show diff"&&echo -e "$(git diff --color=always)\n"if ask "Stage all changes + commit";then
git add -A
git_status
git_commit
git_status
ask_if_push
fiecho -e "$(git_status)\n\n"done
}
rebase_branches()
# Rebase all branchesrebase_branches() {
forpathin$repos;do
check_should_skip_repo && git_get_branches ||continueforbranchin$branches;do
git_checkout
if [[ $2!="AUTOREBASE" ]];then
ask "Rebase this branch on $1"||echo -e "Skipping...\n";continuefi
git_rebase
ask_if_skip_dirty_merge ||continue
ask_if_push
donedone
}
merge_into_branches()
# Merge a branch into all branchesmerge_into_branches() {
forpathin$repos;do
check_should_skip_repo && git_get_branches ||continueforbranchin$branches;do
git_checkout
if [[ $1!="AUTOMERGE" ]];then
ask "Merge origin/master into this branch"||echo"Skipping...";continuefi
git_merge
ask_if_skip_dirty_merge ||continue
ask_if_push
donedone
}
clean_branches()
# Clean old branches already merged into masterclean_branches() {
forpathin$repos;do
check_should_skip_repo && git_get_branches ||continueforbranchin$branches;do
get_ahead_behind "$branch""origin/master"printf"$branch (ahead $ahead) | (behind $behind) origin/master\n"if [[ "$AHEAD"== 0 ]] && ask "This branch is ${green}0${reset} commits ahead of origin/master. Would you like to delete it";thenecho -e "\n$(git branch -D $branch)\n"elseecho -e "\n\n"fidonedone
}
# List all branches in all reposlist_branches() {
forpathin$repos;doecho"Branches in $(colour_path)"cd${path}/..
git_branch
done
}
everything()
# Bring everything up to dateeverything() {
forpathin$repos;do
[[ "$repo_auto"==true ]] || ask "Update project $(colour_path)"||continue
git_fetch "$path/.."
git_get_branches
forbranchin$branches;do
git_summary &&continue
ret="$?"# If out of sync then pull pushif [[ "$ret"== 1 ]];then
[[ "$branch_auto"==true ]] || ask "pull+push"||continue
git_pull
ask_if_skip_dirty_merge ||continue
git_push
# If conflicts detected ask to skip (auto skip if auto enabled)elif [[ "$ret"== 2 ]];then
[[ "$branch_auto"==true ]] &&continue
ask_if_skip_dirty_merge ||continue
git_push
# If changes detected, commit themelif [[ "$ret"== 3 ]];then
git_status
# prompt for diff unless NODIFF specified
[[ $1!="NODIFF" ]] && ask "Show diff"&&echo -e "$(git diff --color=always)\n"if ask "Stage all changes + commit";then
git add -A
git_status
git_commit
git_status
ask_if_push
fiecho -e "$(git_status)\n\n"fidonedone
}
die()
die() { echo"$*">&2;exit 2; } # complain to STDERR and exit with error
needs_arg()
needs_arg() { if [ -z"$OPTARG" ];then die "No arg for --$OPT option";fi; }
tui()
# Show menutui() {
show_help
whiletrue;do
opt=$(show_menu)if [[ $opt=="Search for git repos" ]];thenecho -e "\nFinding git repos in \$HOME"
find_repos "$HOME"elif [[ $opt=="Quit" ]];thenbreakelseecho
using_repos
fiif [[ $opt=="Pull and push" ]];thenecho -e "\n${yellow}Prompts you to pull+push each branch in your git repositories\n${reset}"
push_pull
elif [[ $opt=="Pull and push auto" ]];thenecho -e "\n${yellow}Prompts you to pull+push each branch in your git repositories\n${reset}"
branch_auto=true
push_pull
elif [[ $opt=="Add all changes + commit + push" ]];thenecho -e "\n${yellow}Prompts you to Add all changes + commit + push each of your git repositories\n${reset}"
commit_and_push
elif [[ $opt=="Clean old branches" ]];thenecho -e "\n${yellow}Prompts you to delete any branches in your git repositories which are 0 commits ahead of master\n${reset}"
clean_branches
elif [[ $opt=="List branches" ]];thenecho -e "\n${yellow}Lists all the local branches in your git repositories\n${reset}"
list_branches
elif [[ $opt=="Compare master" ]];thenecho -e "\n${yellow}Compares each branch in your git repositories against origin/master\n${reset}"
git_fetch_all_repos
compare_branches "origin/master"elif [[ $opt=="Compare remote" ]];thenecho -e "\n${yellow}Compares each branch in your git repositories against it's remote branch\n${reset}"
git_fetch_all_repos
compare_branches "remote"elif [[ $opt=="Check for changes" ]];thenecho -e "\n${yellow}Checks each repository for changes which have not been comitted\n${reset}"
repos_summary
elif [[ $opt=="List changes" ]];thenecho -e "\n${yellow}Checks each repository for changes which have not been comitted\n${reset}"
repos_status
elif [[ $opt=="Merge origin/master into branches" ]];thenecho -e "\n${yellow}Prompts you to merge origin/master into each branch in your git repositories\n${reset}"
merge_into_branches
fiechodone
}
Help
show_help()
# Prints the help functionshow_help() {
echo"Usage: gitmanage [OPTION]..."echo -e "Used to manage multiple branches across multiple git repositories\n"echo" -F Search for git repos"echo -e " Searches home directory for git repos"echo -e " (This must completed at least once to update the cache used by other functions)\n"echo" -a Branch auto"echo -e " Automatically approves per branch confirmation prompts (To avoid pressing 'y' a bunch of times). Can be combined with -A\n"echo" -A Repo auto"echo -e " Automatically approves per repo confirmation prompts (To avoid pressing 'y' a bunch of times). Can be combined with -a\n"echo" -f Fetch repos"echo -e " Fetch and prune all repos\n"echo" -s Check for changes"echo -e " Checks each repository for changes which have not been comitted and provides a simple summary\n"echo" -S List changes"echo -e " List all changes in each repository which have not been comitted\n"echo" -p Pull"echo -e " Prompts you to pull each branch in your git repositories\n"echo" -P Pull and push"echo -e " Prompts you to pull+push each branch in your git repositories\n"echo" -c Add all changes + commit + push"echo -e " Prompts you to add all changes + commit + push each git repository\n"echo" -C Add all changes + commit + push NO DIFF"echo -e " Prompts you to add all changes + commit + push each git repository but will not prompt to show diff\n"echo" -m Merge origin/master into branches"echo -e " Prompts you to merge origin/master into each branch in your git repositories\n"echo" -M Automerge origin/master into branches"echo -e " Attempts to automatically merge origin/master into each branch in your git repositories\n"echo" -r Rebase branches"echo -e " Prompts you to rebase into each branch onto origin/master for every repo in your git repositories\n"echo" -R Autorebase branches"echo -e " Attempts to automatically rebase each branch onto origin/master for every repo in your git repositories\n"echo" -e Everything"echo -e " Prompts you to add, commit, pull, push all branches of all repos\n"echo" -E Everything Auto"echo -e " Attempts to automatically add, commit, pull, push all branches of all repos\n"echo" -b master Compare master"echo -e " Compares each branch in your git repositories against origin/master\n"echo" -b clean Clean old branches"echo -e " Prompts you to delete any branches in your git repositories which are 0 commits ahead of origin/master\n"echo" -B master Compare master no fetch"echo -e " Compares each branch in your git repositories against origin/master\n"echo" -b remote Compare remote"echo -e " Compares each branch in your git repositories against it's remote branch\n"echo" -B remote Compare remote no fetch"echo -e " Compares each branch in your git repositories against it's remote branch\n"echo" --sync Sync local and remote"echo -e " Push and pull all branches of all repositories.\n"echo" -h --help Help"echo -e " Displays this message"
}
Process args
# Process argswhilegetopts"aAeEfsSpPcCmMhiF:r:b:B:c-:" OPT;doif [ "$OPT"="-" ];then# long option: reformulate OPT and OPTARG
OPT="${OPTARG%%=*}"# extract long option name
OPTARG="${OPTARG#$OPT}"# extract long option argument (may be empty)
OPTARG="${OPTARG#=}"# if long option argument, remove assigning `=`fi
set_repos
case"$OPT"in
f) git_fetch_all_repos &&exit;;
s) repos_summary &&exit;;
S) repos_status &&exit;;
p) pull &&exit;;
P) push_pull &&exit;;
c) commit_and_push &&exit;;
C) commit_and_push "NODIFF"&&exit;;
m) merge_into_branches &&exit;;
M) merge_into_branches "AUTOMERGE"&&exit;;
r) rebase_branches "origin/master"&&exit;;
R) rebase_branches "origin/master""AUTOREBASE"&&exit;;
e) everything &&exit;;
E) repo_auto=true; branch_auto=true; everything &&exit;;
h|help) show_help &&exit;;
a) branch_auto=true;;
A) repo_auto=true;;
sync) repo_auto=true; branch_auto=true; push_pull &&exit;;
i|interactive) tui &&exit;;
b)
set_repos
if [[ $OPTARG=="clean" ]];then clean_branches;fi
git_fetch_all_repos
if [[ $OPTARG=="master" ]];then compare_branches "origin/master"elif [[ $OPTARG=="remote" ]];then compare_branches "remote"else
compare_branches "$OPTARG"fiexit
;;
B)
set_repos
if [[ $OPTARG=="master" ]];then compare_branches "origin/master"elif [[ $OPTARG=="remote" ]];then compare_branches "remote"else
compare_branches "$OPTARG"fiexit
;;
F)
if [[ $OPTARG=="clean" ]];thenecho"TODO Clean repo.cache files"else
find_repos $OPTARGfiexit
;;
r)
set_repos
git_fetch_all_repos
if [[ $OPTARG=="master" ]];then rebase_branches "origin/master"else
rebase_branches "$OPTARG"fiexit
;;
??*) die "Illegal option --$OPT" ;; # bad long option?) exit 2 ;; # bad short option (error reported via getopts)esacdoneshift$((OPTIND-1))# remove parsed options and args from $@ list
set_repos
repos_summary_compact
Manpage
.TH GIT_MANAGER 1 git_manager\-VERSION.SH NAME
git_manager \- git repository manager
.SH SYNOPSIS
.Bgit_manager
.RB [ \-v ]
.SH DESCRIPTION
git_manager is a script for managing multiple git repositories and branches
.P
git_manager has options for findiding and updating multiple repos
.SH OPTIONS
.TP.B\-F
SEARCH FOR GIT REPOS.
Searches home directory for git repos
(This must completed at least once to update the cache used by other functions)
.TP.B\-a
BRANCH AUTO.
Automatically approves per branch confirmation prompts (To avoid pressing 'y' a bunch of times). Can be combined with -A
.TP.B\-A
REPO AUTO.
Automatically approves per repo confirmation prompts (To avoid pressing 'y' a bunch of times). Can be combined with -a
.TP.B\-f
FETCH REPOS.
Fetch and prune all repos
.TP.B\-s
CHECK FOR CHANGES.
Checks each repository for changes which have not been comitted and provides a simple summary
.TP.B\-S
LIST CHANGES.
List all changes in each repository which have not been comitted
.TP.B\-p
PULL.
Prompts you to pull each branch in your git repositories
.TP.B\-P
PULL AND PUSH.
Prompts you to pull+push each branch in your git repositories
.TP.B\-c
ADD ALL CHANGES + COMMIT + PUSH.
Prompts you to add all changes + commit + push each git repository
.TP.B\-C
ADD ALL CHANGES + COMMIT + PUSH NO DIFF.
Prompts you to add all changes + commit + push each git repository but will not prompt to show diff
.TP.B\-m
MERGE ORIGIN/MASTER INTO BRANCHES.
Prompts you to merge origin/master into each branch in your git repositories
.TP.B\-M
AUTOMERGE ORIGIN/MASTER INTO BRANCHES.
Attempts to automatically merge origin/master into each branch in your git repositories
.TP.B\-r
REBASE BRANCHES.
Prompts you to rebase each branch on origin/master for each repo in your git repositories
.TP.B\-R
AUTOREBASE BRANCHES.
Attempts to rebase each branch on origin/master for each repo in your git repositories
.TP.B\-e
EVERYTHING.
Prompts you to add, commit, pull, push all branches of all repos
.TP.B\-E
EVERYTHING AUTO.
Attempts to automatically add, commit, pull, push all branches of all repos
.TP.B\-bclean
CLEAN OLD BRANCHES.
Prompts you to delete any branches in your git repositories which are 0 commits ahead of origin/master
.TP.B\-bmaster
COMPARE MASTER.
Compares each branch in your git repositories against origin/master
.TP.B\-Bmaster
COMPARE MASTER NO FETCH.
Compares each branch in your git repositories against origin/master
.TP.B\-bremote
COMPARE REMOTE.
Compares each branch in your git repositories against it's remote branch
.TP.B\-Bremote
COMPARE REMOTE NO FETCH.
Compares each branch in your git repositories against it's remote branch
.TP.B\-\-sync
PUSH AND PULL ALL BRANCHES OF ALL REPOSITORIES.
.TP.B\-h--help
HELP.
Displays this message
.SH USAGE
provide no args for interactive mode.
.SH SEE ALSO
.BR git (1)