├── .gitignore ├── LICENSE.txt ├── README.md ├── git-summary └── screenshot.png /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2018 @ Mirko Ledda 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # git-summary 2 | **If you ever experienced one of the following situations, git-summary is for you.** 3 | 4 | * I don't remember where some of my repositories are... 5 | * Did I forgot to push that commit? 6 | * Do I have a repo in my system that is outdated? 7 | * Did someone pushed new commits to `origin/master` in one of my repos? 8 | * Did I commit that quick change I made before the pizza delivery guy rang my door? 9 | 10 | git-summary is a bash script that will neatly list the current status of any git repos it founds within a directory or your entire system. See the example screenshot below: 11 | 12 | 13 | 14 | ## Requirements 15 | **Currently supported operating systems:** Linux, MacOS and Cygwin 16 | 17 | ### Linux 18 | * `sudo apt-get install gawk` 19 | 20 | ### MacOS 21 | * `brew install coreutils` 22 | 23 | ## Installation 24 | ### Via aliasing 25 | Clone this repo and alias the script. To do so, add the following line to `~\.bashrc`: 26 | 27 | ``` 28 | alias git-summary='/git-summary/git-summary' 29 | ``` 30 | 31 | > `` is the path to the cloned repo. Don't worry, if you ever forget where you cloned this repo, you will be able to easily find it with `git-summary` :wink: 32 | 33 | ### Via executable lookup 34 | Copy `git-summary` in `/usr/local/bin`. 35 | 36 | ## Usage 37 | General usage: 38 | 39 | ``` 40 | git-summary [options] path 41 | ``` 42 | 43 | `path` is optional and the current directory will be used if left blank. 44 | 45 | ### Options 46 | * **-h**: Print help and exit. 47 | * **-l**: Local summary lookup. Checks only local changes which is faster as there is no need to fetch the remote. 48 | * **-d**: Deep lookup. Look for any git repos within the entire current directory tree. Can be slowish for large trees. 49 | * **-q**: Quiet mode. Only print outdated repos. 50 | 51 | ## Branch status 52 | Currently, `git-summary` does not list multiple branches per repo. However, for single repos [`git-branch-status`](https://github.com/bill-auger/git-branch-status) does this beautifully. 53 | 54 | ## Credits 55 | A big thanks :metal: to the amazing people that contributed to the original versions of `git-summary`: 56 | 57 | * **Forked from** [lordadamson](https://github.com/lordadamson/git-summary) 58 | * [mzabriskie](https://github.com/mzabriskie) (Posted the original idea [here](https://gist.github.com/mzabriskie/6631607)) 59 | * [CycleMost](https://github.com/CycleMost) 60 | * [lmj0011](https://github.com/lmj0011) 61 | * [gimbo](https://github.com/gimbo) 62 | * [zartc](https://github.com/zartc) 63 | 64 | Additional thanks go to: 65 | * [ruricolist](https://github.com/ruricolist) - Cygwin support and quiet mode. 66 | -------------------------------------------------------------------------------- /git-summary: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # git-summary - summarize git repos at some path 4 | # 5 | # Forked from https://github.com/lordadamson/git-summary 6 | # 7 | # Freely distributed under the MIT license. 2018@MirkoLedda 8 | 9 | set -eu 10 | 11 | # Colorcode 12 | GREEN='\e[0;32m' 13 | ORANGE='\e[0;33m' 14 | RED='\e[0;31m' 15 | PURPLE='\e[0;35m' 16 | NC='\e[0m' # No Color 17 | 18 | usage() { 19 | sed 's/^ //' <&1 & 108 | (( repo_count+=1 )) 109 | done 110 | wait 111 | 112 | if [ $quiet -eq 1 ]; then 113 | echo "Checked ${repo_count} repositories." 114 | fi 115 | 116 | } 117 | 118 | # Autodetect the OS 119 | detect_OS() { 120 | if [ "$(uname)" == "Darwin" ]; then # macOS 121 | OS=Darwin 122 | readlink_cmd="greadlink" 123 | dirname_cmd="gdirname" 124 | gawk_cmd="awk" 125 | elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then # Linux 126 | OS=Linux 127 | readlink_cmd="readlink" 128 | dirname_cmd="dirname" 129 | gawk_cmd="gawk" 130 | elif [ "$(expr substr $(uname -s) 1 6)" == "CYGWIN" ]; then # Cygwin 131 | OS=CYGWIN 132 | readlink_cmd="readlink" 133 | dirname_cmd="dirname" 134 | gawk_cmd="gawk" 135 | else 136 | echo "Cannot identify OS." 137 | exit 1 138 | fi 139 | } 140 | 141 | GIT4WINDOWS=1 142 | 143 | detect_Git4Windows() { 144 | if [[ "$OS" == "CYGWIN" && "$(git --version)" == *"windows"* ]]; then 145 | GIT4WINDOWS=0 146 | fi 147 | } 148 | 149 | gitC() { 150 | local ldir=$1; shift; 151 | if [ $GIT4WINDOWS -eq 0 ]; then 152 | git -C "$(cygpath -w $ldir)" "$@" 153 | else 154 | git -C "$ldir" "$@" 155 | fi 156 | } 157 | 158 | 159 | print_header () { 160 | local template="$1" 161 | local max_repo_len=$2 162 | local max_branch_len=$3 163 | print_divider () { 164 | printf '=%.0s' $(seq 1 $max_repo_len) 165 | printf ' ' 166 | printf '=%.0s' $(seq 1 $max_branch_len) 167 | printf ' ' 168 | printf '=%.0s' $(seq 1 5) 169 | printf '\n' 170 | }; 171 | 172 | echo 173 | printf "$template\n" $NC Repository Branch State 174 | print_divider 175 | } 176 | 177 | 178 | summarize_one_git_repo () { 179 | 180 | local f=$1 181 | local template=$2 182 | local local_only=$3 183 | local quiet=$4 184 | 185 | local app_name=$f 186 | local branch_name=`gitC $f symbolic-ref HEAD | sed -e "s/^refs\/heads\///"` 187 | local numState=0 188 | 189 | ### Check remote state 190 | local rstate="" 191 | local has_upstream=`gitC $f rev-parse --abbrev-ref @{u} 2> /dev/null | wc -l` 192 | if [ $has_upstream -ne 0 ] ; then 193 | if [ $local_only -eq 0 ] ; then 194 | gitC $f fetch -q &> /dev/null 195 | fi 196 | # Unpulled and unpushed on *current* branch 197 | local unpulled=`gitC $f log --pretty=format:'%h' ..@{u} | wc -c` 198 | local unpushed=`gitC $f log --pretty=format:'%h' @{u}.. | wc -c` 199 | 200 | if [ $unpulled -ne 0 ]; then 201 | rstate="${rstate}v" 202 | numState=1 203 | else 204 | rstate="${rstate} " 205 | fi 206 | 207 | if [ $unpushed -ne 0 ]; then 208 | rstate="${rstate}^" 209 | numState=1 210 | else 211 | rstate="${rstate} " 212 | fi 213 | 214 | else 215 | rstate="--" 216 | fi 217 | 218 | ### Check local state 219 | local state="" 220 | local untracked=`LC_ALL=C gitC $f status | grep Untracked -c` 221 | local new_files=`LC_ALL=C gitC $f status | grep "new file" -c` 222 | local modified=`LC_ALL=C gitC $f status | grep modified -c` 223 | 224 | if [ $untracked -ne 0 ]; then 225 | state="${state}?" 226 | numState=2 227 | else 228 | state="${state} " 229 | fi 230 | 231 | if [ $new_files -ne 0 ]; then 232 | state="${state}+" 233 | numState=2 234 | else 235 | state="${state} " 236 | fi 237 | 238 | if [ $modified -ne 0 ]; then 239 | state="${state}M" 240 | numState=2 241 | else 242 | state="${state} " 243 | fi 244 | 245 | ### Print to stdout 246 | if [ $numState -eq 0 ]; then 247 | if [ $quiet -eq 0 ]; then 248 | printf "$template\n" $GREEN $app_name $branch_name "$state$rstate" >&1 249 | fi 250 | elif [ $numState -eq 1 ]; then 251 | printf "$template\n" $ORANGE $app_name $branch_name "$state$rstate" >&1 252 | elif [ $numState -eq 2 ]; then 253 | printf "$template\n" $RED $app_name $branch_name "$state$rstate" >&1 254 | fi 255 | } 256 | 257 | 258 | # Given the path to a git repo, compute its current branch name. 259 | repo_branch () { 260 | gitC "$1" symbolic-ref HEAD | sed -e "s/^refs\/heads\///" 261 | } 262 | 263 | 264 | # Given a path to a folder containing some git repos, compute the 265 | # names of the folders which actually do contain git repos. 266 | list_repos () { 267 | # https://stackoverflow.com/questions/23356779/how-can-i-store-find-command-result-as-arrays-in-bash 268 | git_directories=() 269 | 270 | local find_cmd 271 | if [ $deeplookup -eq 0 ]; then 272 | find_cmd="find $1 -maxdepth 2 -type d -name .git -print0" 273 | else 274 | find_cmd="find $1 -type d -name .git -print0" 275 | fi 276 | 277 | while IFS= read -r -d $'\0'; do 278 | git_directories+=("$REPLY") 279 | done < <($find_cmd 2>/dev/null) 280 | 281 | for i in ${git_directories[*]}; do 282 | if [[ ! -z $i ]]; then 283 | $dirname_cmd -z $i | xargs -0 -L1 284 | fi 285 | done 286 | } 287 | 288 | 289 | # Given the path to a folder containing git some repos, compute the 290 | # names of the current branches in the repos. 291 | repo_branches () { 292 | local path=$1 293 | local repo 294 | for repo in $(list_repos $path) ; do 295 | echo $(repo_branch $repo) 296 | done 297 | } 298 | 299 | 300 | max_len () { 301 | echo "$1" | $gawk_cmd '{ print length }' | sort -rn | head -1 302 | } 303 | 304 | trap "printf '$NC'" EXIT 305 | 306 | git_summary $@ 307 | 308 | -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/albenik/git-summary/f882885a68e5bac64b9fd126735442f3de50c180/screenshot.png --------------------------------------------------------------------------------