Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 2 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,18 @@ git-summary summarizes the status of all cloned git repositories founds within a

* Linux
* MacOS
* Cygwin
* Cygwin/Windows Busybox/MinGW
* SunOS
* Alpine based containers on Google Cloud Platform - Container Optimised OS (Chromium OS)

## Requirements

### Linux
* `sudo apt-get install gawk`

### MacOS
* `brew install coreutils`

### Alpine based containers on Google Cloud Platform (Chromium OS)
* `apk add gawk findutils`

> xargs in Chromium OS does not support -L option, findutils puts an xargs with support for -L

## Installation (on Linux-based machines)

Clone this repo somewhere and alias the script by adding this line to `~\.bashrc` (modify `$PATH` to point to the location of the cloned repo on your machine):
Clone this repo somewhere and alias the script by adding this line to your shell's rc file such as `~/.profile`, `~/.bashrc` or `~/.zshrc` (modify `$PATH` to point to the location of the cloned repo on your machine):

```
alias git-summary='<PATH>/git-summary/git-summary'
Expand Down
189 changes: 70 additions & 119 deletions git-summary
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
#!/bin/bash
#!/bin/sh

# git-summary - summarize git repos at some path
#
Expand Down Expand Up @@ -93,15 +93,15 @@ git_summary() {

# Use provided path, or default to pwd.
# Here we also decide if we want relative or full path names printouts.
local target=$(${readlink_cmd} -f ${1:-`pwd`})
if [ $full_path -eq 0 ] && command -v realpath >/dev/null && command -v python >/dev/null ; then
local rtarget=$(get_relative_path $target)
local target="$(${readlink_cmd} -f ${1:-$PWD})"
if [ $full_path -eq 0 ] && command -v realpath >/dev/null; then
local rtarget="$(get_relative_path $target)"
else
local rtarget=$target
local rtarget="$target"
fi
local repos=$(list_repos $rtarget $deeplookup)
local repos="$(list_repos $rtarget $deeplookup)"

if [[ -z $repos ]]; then
if [ -z "$repos" ]; then
exit
fi

Expand All @@ -117,37 +117,40 @@ git_summary() {
local header_space_branch=${#HEADER_BRANCH}
local header_space_state=${#HEADER_STATE}

local longest_repo_name=$(max_len "$repos")
local longest_repo_name=$(( $(max_len "$repos") - 11 )) # echo '/.git/HEAD' | wc -c = 11
local space_repo=$(( $longest_repo_name < $header_space_repo ? $header_space_repo : $longest_repo_name ))

local branches=$(repo_branches $rtarget)
local branches="$(printf '%s' "${repos}" | xargs -I {} cut -d/ -f3- '{}')"
local longest_branch_name=$(max_len "$branches")
local longest_branch_name=$(( $longest_branch_name < $header_space_branch ? $header_space_branch : $longest_branch_name )) # Factor in the header
longest_branch_name=$(( $longest_branch_name < $header_space_branch ? $header_space_branch : $longest_branch_name )) # Factor in the header
local space_branch=$longest_branch_name # If we don't have tput, allow full width

if command -v tput >/dev/null && eval 'tput cols' >/dev/null 2>&1;
then
# If we have tput, adapt to terminal
local TERM_COLUMNS=$(tput cols)
local space_branch=$(( $TERM_COLUMNS - $space_repo - $header_space_state - 4)) # The 4 is the spaces between columns
local space_branch=$(( $space_branch < $header_space_branch ? $header_space_branch : $space_branch ))
local space_branch=$(( $space_branch > $longest_branch_name ? $longest_branch_name : $space_branch )) # If this is commented then prompt will be full terminal width
else
local space_branch=$longest_branch_name # If we don't have tput, allow full width
space_branch=$(( $TERM_COLUMNS - $space_repo - $header_space_state - 4)) # The 4 is the spaces between columns
space_branch=$(( $space_branch < $header_space_branch ? $header_space_branch : $space_branch ))
space_branch=$(( $space_branch > $longest_branch_name ? $longest_branch_name : $space_branch )) # If this is commented then prompt will be full terminal width
fi

local template=$(printf "%%b%%-%ds %%-%d.%ds %%-%ds" $space_repo $space_branch $space_branch $header_space_state) # template that truncates branch names to fit terminal width
local template="$(printf "%%b%%-%ds %%-%d.%ds %%-%ds" $space_repo $space_branch $space_branch $header_space_state)" # template that truncates branch names to fit terminal width
print_header "$template" $space_repo $space_branch $header_space_state

local repo_count=0

IFS='
'
local repo
for repo in $repos ; do
for repo in $repos; do
repo_count=$((repo_count + 1))
repo="${repo%/.git/HEAD}"
branch=$(printf "%s" "$branches" | sed -n "${repo_count}p")
if [ $sort -eq 0 ] ; then
summarize_one_git_repo $repo "$template" "$local_only" "$quiet" >&1 & # parallelized - FIFO stdout
summarize_one_git_repo "$repo" "$branch" "$template" "$local_only" "$quiet" >&1 & # parallelized - FIFO stdout
else
summarize_one_git_repo $repo "$template" "$local_only" "$quiet" >&1 # sequential - sorted stdout
summarize_one_git_repo "$repo" "$branch" "$template" "$local_only" "$quiet" >&1 # sequential - sorted stdout
fi
(( repo_count+=1 ))
done
wait

Expand All @@ -159,39 +162,23 @@ git_summary() {

# Autodetect the OS
detect_OS() {

if [ "$(uname)" == "Darwin" ]; then # macOS
OS=Darwin
readlink_cmd="greadlink"
dirname_cmd="gdirname"
gawk_cmd="awk"
elif [ "$(uname)" == "SunOS" ]; then # SunOS (solaris/smartos/illumos)
OS=SunOS
readlink_cmd="greadlink"
dirname_cmd="gdirname"
gawk_cmd="awk"
elif [ "$(expr substr $(uname -s) 1 5)" == "Linux" ]; then # Linux-based system
if [[ $(cat /proc/version) == *"Chromium OS"* ]]; then # Chromium OS
OS=Linux
readlink_cmd="readlink"
dirname_cmd="dirname_zero"
gawk_cmd="gawk"
else # Vanilla Linux
OS=Linux
readlink_cmd="readlink"
dirname_cmd="dirname"
gawk_cmd="gawk"
fi
elif [ "$(expr substr $(uname -s) 1 6)" == "CYGWIN" ]; then # Cygwin
OS=CYGWIN
OS="$(uname -s)"
readlink_cmd="readlink"
dirname_cmd="dirname"
gawk_cmd="gawk"
else
echo "Oups, I cannot identify your current OS."
echo "Please open a ticket or a pull request so we can add your OS to the list of supported systems."
exit 1
fi
case "$OS" in
Darwin) # macOS
readlink_cmd="greadlink"
dirname_cmd="gdirname" ;;
SunOS) # SunOS (solaris/smartos/illumos)
readlink_cmd="greadlink"
dirname_cmd="gdirname" ;;
Linux)
case "$(grep ^NAME /etc/os-release | cut -d'=' -f2 | tr -d \")" in
Chromium\ OS)
dirname_cmd="dirname_zero" ;;
esac ;;
esac

}

GIT4WINDOWS=1
Expand All @@ -202,9 +189,10 @@ dirname_zero(){
}

detect_Git4Windows() {
if [[ "$OS" == "CYGWIN" && "$(git --version)" == *"windows"* ]]; then
GIT4WINDOWS=0
fi
if [ "${OS#CYGWIN}" != "$OS" ]; then case "$(git --version)" in
*windows*) GIT4WINDOWS=0 ;;
esac
fi
}

gitC() {
Expand Down Expand Up @@ -236,30 +224,29 @@ print_header () {
print_divider
}


summarize_one_git_repo () {

local f=$1
local template=$2
local local_only=$3
local quiet=$4

local app_name=$f
local branch_name=`gitC $f symbolic-ref HEAD | sed -e "s/^refs\/heads\///"`
local f="$1"
local branch_name=$2
local template=$3
local local_only=$4
local quiet=$5

local app_name="$f"
local git_status="$(LC_ALL=C gitC "$f" status --porcelain=v1 2>/dev/null)"
local numState=0

### Check remote state
local rstate=""
local has_upstream=`gitC $f rev-parse --abbrev-ref @{u} 2> /dev/null | wc -l`
if [ $has_upstream -ne 0 ] ; then
local has_upstream=$(gitC "$f" rev-list --count '@{u}..' 2>/dev/null)
if [ ${has_upstream:=0} -ne 0 ] ; then
if [ $local_only -eq 0 ] ; then
gitC $f fetch -q &> /dev/null
gitC "$f" fetch -q & >/dev/null
fi
# Unpulled and unpushed on *current* branch
local unpulled=`gitC $f log --pretty=format:'%h' ..@{u} | wc -c`
local unpushed=`gitC $f log --pretty=format:'%h' @{u}.. | wc -c`
local unpulled="$(gitC "$f" rev-list --count '..@{u}' 2>/dev/null)"
local unpushed="$has_upstream"

if [ $unpulled -ne 0 ]; then
if [ ${unpulled:=0} -ne 0 ]; then
rstate="${rstate}v"
numState=1
else
Expand All @@ -279,9 +266,10 @@ summarize_one_git_repo () {

### Check local state
local state=""
local untracked=`LC_ALL=C gitC $f status | grep Untracked -c`
local new_files=`LC_ALL=C gitC $f status | grep "new file" -c`
local modified=`LC_ALL=C gitC $f status | grep modified -c`

local untracked="$(echo "${git_status}" | grep -c '^??')"
local new_files="$(echo "${git_status}" | grep -c '^A')"
local modified="$(echo "${git_status}" | grep -c '^ [MRD]')"

if [ $untracked -ne 0 ]; then
state="${state}?"
Expand All @@ -307,74 +295,37 @@ summarize_one_git_repo () {
### Print to stdout
if [ $numState -eq 0 ]; then
if [ $quiet -eq 0 ]; then
printf "$template\n" $GREEN $app_name $branch_name "$state$rstate" >&1
printf "$template\n" $GREEN "$app_name" $branch_name "$state$rstate" >&1
fi
elif [ $numState -eq 1 ]; then
printf "$template\n" $ORANGE $app_name $branch_name "$state$rstate" >&1
printf "$template\n" $ORANGE "$app_name" $branch_name "$state$rstate" >&1
elif [ $numState -eq 2 ]; then
printf "$template\n" $RED $app_name $branch_name "$state$rstate" >&1
printf "$template\n" $RED "$app_name" $branch_name "$state$rstate" >&1
fi
}


# Given the path to a git repo, compute its current branch name.
repo_branch () {
gitC "$1" symbolic-ref HEAD | sed -e "s/^refs\/heads\///"
}


# Given a path to a folder containing some git repos, compute the
# names of the folders which actually do contain git repos.
list_repos () {
# https://stackoverflow.com/questions/23356779/how-can-i-store-find-command-result-as-arrays-in-bash
git_directories=()

local find_cmd
if [ $deeplookup -eq 0 ]; then
find_cmd="find -L $1 -maxdepth 2 -type d -name .git -print0"
else
find_cmd="find -L $1 -type d -name .git -print0"
find_args="-maxdepth 3"
fi

while IFS= read -r -d $'\0'; do
git_directories+=("$REPLY")
done < <($find_cmd | sort -z 2>/dev/null)

for i in ${git_directories[*]}; do
if [[ ! -z $i ]]; then
$dirname_cmd -z $i | xargs -0 -L1
fi
done
}


# Given the path to a folder containing git some repos, compute the
# names of the current branches in the repos.
repo_branches () {
local path=$1
local repo
for repo in $(list_repos $path) ; do
echo $(repo_branch $repo)
done
find -L $1 $find_args -wholename '*/.git/HEAD' -type f 2>/dev/null | sort
}


max_len () {
echo "$1" | $gawk_cmd '{ print length }' | sort -rn | head -1
# TODO Replace with wc -L after checking portability
echo "$1" | tr -c '\n' 1 | sort -rn | head -n1 | wc -c
}


# Function to get the relative path of a file or directory
get_relative_path() {
local target="$1"
local base=$(pwd)

# Convert paths to absolute paths
local target_absolute=$(realpath "$target")
local base_absolute=$(realpath "$base")
local target_absolute="$(realpath "$1")/"

# Use Python to calculate the relative path
python -c "import os.path; print(os.path.relpath('$target_absolute', '$base_absolute'))"
local out="${target_absolute#"$PWD/"}"
echo "${out:-./}"
}

trap "printf '$NC'" EXIT
Expand Down