#!/bin/sh # Update dotfiles and some other common repositories. # This script accepts no arguments, but accepts additional repositories through the REPOS_TO_UPDATE environment variable set -eu # # Setup functions and variables # # global name of script script="${0##*/}" cleanup() { exit } # update_list_append from https://superuser.com/questions/39751/add-directory-to-path-if-its-not-already-there # checks if a path is already in the PATH and if it exists update_list_append() { if [ -d "$1" ]; then # shellcheck disable=SC3010 if command '[[' > /dev/null 2>&1 && [[ ":$REPOS_TO_UPDATE:" != *":$1:"* ]]; then REPOS_TO_UPDATE="${REPOS_TO_UPDATE:+"$REPOS_TO_UPDATE:"}$1" else REPOS_TO_UPDATE="$REPOS_TO_UPDATE:$1" fi fi } # Do the arguments contain a search? # Usage: contains search "${array[@]}" contains() { search="$1"; shift split="$1"; shift array="$1"; shift ifs="$IFS" IFS="$split" for str in $array; do if [ "$str" = "$search" ]; then IFS="$ifs" return 0 fi done IFS="$ifs" return 1 } # Shorten the repo name to use a ~ for the home dir instead of the absolute path pretty_path() { arg="$1" case "$arg" in # If the arg starts with our home dir, "$HOME"*) # replace the home dir part with a ~ printf '~%s' "$(printf '%s' "$arg" | sed -E 's/^.{'"${#HOME}"'}//')" ;; # Otherwise, *) # Just print the arg printf '%s' "${arg}" ;; esac | # Pass through some other filters sed -E 's_/+$__' | # Remove trailing slash tr -s '/' # Remove duplicate slashes } # Warn about incomplete changes to a repository do_checks() { repo_pretty="$1" shift # Uncommitted changes if ! "$@" diff --quiet; then echo "${yellow}Warning: Working tree for $repo_pretty is dirty${nc}" fi # Unpushed changes unpushed="$("$@" for-each-ref --format=" %(refname:short) %(push:track)" refs/heads)" if echo "$unpushed" | grep -Fq ahead; then printf '%s' "${yellow}Warning: Unpushed commits in $repo_pretty on branches:" printf '%s' "$unpushed" | tr '\n' ',' echo "${nc}" fi } do_pull() { update_dotfiles="${update_dotfiles:-false}" # Which repos have we updated so far updated='' # # Main loop: `git pull` everything mentioned in the REPOS_TO_UPDATE variable # repo='' for repo in "$@"; do if [ -n "$repo" ]; then repo_pretty="$(pretty_path "$repo")" repo="$(realpath "$repo" 2>/dev/null)" repo="$(git -C "$repo" rev-parse --show-toplevel 2> /dev/null || true)" if [ -n "$repo" ]; then if ! contains "$repo" ':' "$updated"; then # Pull the repository echo " Pulling ${green}$repo_pretty${nc}‥" if ! timeout 10 git -C "$repo" pull; then exit=$# if [ "$ignore_git_errors" != true ]; then return "$exit" fi fi updated="$repo:$updated" # Warn about incomplete changes to a repository do_checks "$repo_pretty" git -C "$repo" did_updates=true if [ "$update_dotfiles" = 'false' ]; then echo fi fi fi fi done # update dotfiles repo separately bc it's a bare repo if [ "$update_dotfiles" = true ]; then # check for dotfiles updates ${did_updates:-false} && echo echo " Updating ${green}dotfiles${nc}…" dotfiles pull do_checks "$(dotfiles rev-parse --git-dir)" dotfiles fi } main() { trap cleanup INT # Check for git if ! command -v git > /dev/null; then echo "$script: git is not installed, please install git and try again" >&2 exit 1 fi # Set colors if command -v tput > /dev/null; then yellow="$(tput setaf 3)" green="$(tput setaf 2)" nc="$(tput sgr0)" elif [ -n "$TERMUX_VERSION" ]; then yellow="" green="" nc="(B" fi # Default repos to update ignore_git_errors=false if [ $# = 0 ]; then # Default paths to check REPOS_TO_UPDATE="${REPOS_TO_UPDATE:-}" set -- "$@" \ "${DOCS_DIR:-}" \ "$HOME/bin" \ "${NIXOS_DIR:-}" \ "$HOME/code/faust-ideas" \ "$HOME/code/nixvim-config" \ "$HOME/.config/nvim" split="$IFS" IFS=':' for dir in $REPOS_TO_UPDATE; do set -- "$dir" "$@" done IFS="$split" update_dotfiles=true else ignore_git_errors="${IGNORE_GIT_ERRORS:-true}" fi do_pull "$@" } main "$@"