Skip to content

Instantly share code, notes, and snippets.

@HaleTom
Created August 30, 2016 09:33
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save HaleTom/631efef6fb6ae86618128647dc887aee to your computer and use it in GitHub Desktop.
Save HaleTom/631efef6fb6ae86618128647dc887aee to your computer and use it in GitHub Desktop.
My bash shell functions
# Functions to be sourced by .bashrc
#
# Ensure that ones to be accessed outside of .bashrc are added
# to the 'export' line at the very end.
# The absolute directory name of a file(s) or directory(s)
function abs_dirname {
for _ in $(eval echo "{1..$#}"); do
(cd "${dir:="$(dirname "$1")"}" && pwd || exit 1 )
[[ $? -ne 0 ]] && return 1
shift
done
}
# Wrapper for systems that don't support `readlink -f`
function abs_path {
for _ in $(eval echo "{1..$#}"); do
# Warning: Doesn't work when called with '..'
# echo $(cd $(dirname "$1") && pwd -P)/$(basename "$1")
# path=$(cd $(dirname "$1" || return 1) && pwd -P) &&
# path="$path"/$(basename "$1")
# [[ $? -ne 0 ]] && return 1
# echo "$path"
readlink -f "$1" || return 1
shift
done
}
# The name of the git directory (usually '.git') of the current repository
function git_dir {
# $_ is currently overwitten by chruby_auto
local dir
dir=$(git rev-parse --git-dir) || return 1
abs_path "$dir"
}
# Print the name of the git repository's working tree's root directory
# Search for 'Tom Hale' in http://stackoverflow.com/questions/957928/is-there-a-way-to-get-the-git-root-directory-in-one-command
# Or, shorter:
# (root=$(git rev-parse --git-dir)/ && cd ${root%%/.git/*} && git rev-parse && pwd)
# but this doesn't cover external $GIT_DIRs which are named other than .git
function git_root {
local root first_commit
# git displays its own error if not in a repository
root=$(git rev-parse --show-toplevel) || return
if [[ -n $root ]]; then
echo "$root"
return
elif [[ $(git rev-parse --is-inside-git-dir) = true ]]; then
# We're inside the .git directory
# Store the commit id of the first commit to compare later
# It's possible that $GIT_DIR points somewhere not inside the repo
first_commit=$(git rev-list --parents HEAD | tail -1) ||
echo "$0: Can't get initial commit" 2>&1 && false && return
root=$(git rev-parse --git-dir)/.. &&
# subshell so we don't change the user's working directory
( cd "$root" &&
if [[ $(git rev-list --parents HEAD | tail -1) = "$first_commit" ]]; then
pwd
else
echo "${FUNCNAME[0]}: git directory is not inside its repository" 2>&1
false
fi
)
else
echo "${FUNCNAME[0]}: Can't determine repository root" 2>&1
false
fi
}
# Change working directory to git repository root
function cd_git_root {
local root
root=$(git_root) || return # git_root will print any errors
cd "$root" || return
}
# Generate *+%$ decorations very cleanly:
# https://github.com/mathiasbynens/dotfiles
# Or see https://github.com/magicmonty/bash-git-prompt (not used here)
# Set the prompt #
##################
# set variable identifying the chroot you work in (used in the prompt below)
if [ -z "${debian_chroot:-}" ] && [ -r /etc/debian_chroot ]; then
debian_chroot=$(cat /etc/debian_chroot)
fi
# Select git info displayed, see /usr/lib/git-core/git-sh-prompt for more
export GIT_PS1_SHOWDIRTYSTATE=1 # '*'=unstaged, '+'=staged
export GIT_PS1_SHOWSTASHSTATE=1 # '$'=stashed
export GIT_PS1_SHOWUNTRACKEDFILES=1 # '%'=untracked
export GIT_PS1_SHOWUPSTREAM="verbose" # 'u='=no difference, 'u+1'=ahead by 1 commit
export GIT_PS1_STATESEPARATOR='' # No space between branch and index status
export GIT_PS1_DESCRIBE_STYLE="describe" # detached HEAD style:
# contains relative to newer annotated tag (v1.6.3.2~35)
# branch relative to newer tag or branch (master~4)
# describe relative to older annotated tag (v1.6.3.1-13-gdd42c2f)
# default exactly eatching tag
# Check if we support colours
__colour_enabled() {
local -i colors
colors=$(tput colors 2>/dev/null)
[[ $? -eq 0 ]] && [[ $colors -gt 2 ]]
}
# Sets prompt like: ravi@boxy:~/prj/sample_app
__set_bash_prompt() {
local exit="$?" # Save the exit status of the last command
# PS1 is made from $PreGitPS1 + <git-status> + $PostGitPS1
local PreGitPS1="${debian_chroot:+($debian_chroot)}"
local PostGitPS1=""
# Disable unused variables check for unused colours
# shellcheck disable=SC2034
# https://github.com/koalaman/shellcheck/issues/145
if __colour_enabled; then
export GIT_PS1_SHOWCOLORHINTS=1;
# Wrap the colour codes between \[ and \], so that
# bash counts the correct number of characters for line wrapping:
local Red='\[\e[0;31m\]'; local BRed='\[\e[1;31m\]'
local Gre='\[\e[0;32m\]'; local BGre='\[\e[1;32m\]'
local Yel='\[\e[0;33m\]'; local BYel='\[\e[1;33m\]'
local Blu='\[\e[0;34m\]'; local BBlu='\[\e[1;34m\]'
local Mag='\[\e[0;35m\]'; local BMag='\[\e[1;35m\]'
local Cya='\[\e[0;36m\]'; local BCya='\[\e[1;36m\]'
local Whi='\[\e[0;37m\]'; local BWhi='\[\e[1;37m\]'
local None='\[\e[0m\]' # Return to default colour
else # No colour
unset GIT_PS1_SHOWCOLORHINTS
local Red BRed Gre BGre Yel BYel Blu BBlu Mag BMag Cya BCya Whi BWhi None
fi
# No username and bright colour if root
if [[ ${EUID} = 0 ]]; then
PreGitPS1+="$BRed\h "
else
PreGitPS1+="$Red\u@\h$None:"
fi
PreGitPS1+="$Blu\w$None"
# Now build the part after git's status
# Highlight non-standard exit codes
if [[ $exit != 0 ]]; then
PostGitPS1="$Red[$exit]"
fi
# Change colour of prompt if root
if [[ ${EUID} == 0 ]]; then
PostGitPS1+="$BRed"'\$ '"$None"
else
PostGitPS1+="$Mag"'\$ '"$None"
fi
# Set PS1 from $PreGitPS1 + <git-status> + $PostGitPS1
__git_ps1 "$PreGitPS1" "$PostGitPS1" '(%s)'
# echo '$PS1='"$PS1"
# defaut user prompt:
# PS1='${debian_chroot:+($debian_chroot)}\[\033[01;32m\]\u@\h\[\033[01;34m\] \w\[\033[00m\] $(__git_ps1 "(%s)") \$ '
}
# apt: Update only given sources
apt-update-repo() {
for source in "$@"; do
sudo apt-get update -o Dir::Etc::sourcelist="sources.list.d/${source}" \
-o Dir::Etc::sourceparts="-" -o APT::Get::List-Cleanup="0"
done
}
@CrispyDrone
Copy link

Hi,

I came across your script on stackoverflow for finding the root of a repository. Since git doesn't store a reference from a GIT_DIR to any worktree, it seems it's not possible to find the repository root in that case. Since in your post on stackoverflow you seem to mention "Robust solution that supports differently named or external .git or $GIT_DIR directories.", I was curious and gave it a try and indeed I get the following error message when trying it inside an external .git directory:
"fatal: not a git repository (or any of the parent directories): .git
git_root: git directory is not inside its repository"

However, unrelated to that. Could you explain the following lines in your script? I don't really understand what's going on here:

first_commit=$(git rev-list --parents HEAD | tail -1) ||
      echo "$0: Can't get initial commit" 2>&1 && false && return

What does the && false && return mean? As far as I can tell, the $(git rev-list --parents HEAD | tail -1) || echo "$0: Can't get initial commit" 2>&1 part will always return true, and then the && false will always result in false, which means that the return is never executed.

Furthermore, in actuality, if the git rev-list --parents HEAD command fails, the pipe to tail -1 will ensure a success status code which means the echo statement is never executed. I replaced this by git rev-list --max-parents=0 HEAD.

Finally, what is the reason behind redirecting the error output to the standard output i.e. 2>&1?

If I understand the desired control flow correctly, I think this could be a solution:

first_commit=$(git rev-list --max-parents=0 HEAD) || { echo "$0: Can't get initial commit"; return; }

Now if the $(git rev-list ...) command fails, it will echo the error message and exit the function.

Please let me know your thoughts. Thanks!

@HaleTom
Copy link
Author

HaleTom commented Sep 11, 2020

Thanks for your interest! Here is the updated version.

It's been a while since I wrote this code.

&& false && return was a brain-dead way of writing && return 1.

The man page says: --max-parents=0 gives all root commits, so you run into a case where there is more than one (which can happen if they are merged later in time). Not a problem if comparing apples with apples though.

shopt pipefail can ensure a failure is caught anywhere in the pipeline.

Thanks very much for helping me improve my code!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment