diff --git a/.config/shell/bin/upd b/.config/shell/bin/upd index 7b0bf4c..4a0ff38 100755 --- a/.config/shell/bin/upd +++ b/.config/shell/bin/upd @@ -1,33 +1,23 @@ -#!/bin/bash -# updates some important repositories in my home folder (and elsewhere) that exist on multiple devices +#!/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 -# Check for git -if ! command -v git > /dev/null; then - echo "git is not installed, please install git and try again" - exit 1 -fi +# +# Setup functions and variables +# -# 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 +# global name of script +script="${0##*/}" -# Avoiding copy pasting this over and over -g() { git -C "$REPO" "$@"; } +cleanup() { + exit +} -REPOS_TO_UPDATE="${REPOS_TO_UPDATE:-}" - -# path_append from https://superuser.com/questions/39751/add-directory-to-path-if-its-not-already-there +# 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 -path_append() { +update_list_append() { if [ -d "$1" ]; then # shellcheck disable=SC3010 if command '[[' > /dev/null 2>&1 && [[ ":$REPOS_TO_UPDATE:" != *":$1:"* ]]; then @@ -38,31 +28,162 @@ path_append() { fi } -# shellcheck disable=SC2016 -REPO_ABBR='echo ${REPO/"$HOME"/"~"}' +# Do the arguments contain a search? +# Usage: contains search "${array[@]}" +contains() { + search="$1"; shift + split="$1"; shift + array="$1"; shift -path_append "${DOCS_DIR:-}" -path_append "$HOME/bin" -path_append "${NIXOS_DIR:-}" -path_append "$HOME/code/faust-ideas" -path_append "$HOME/code/nixvim-config" -path_append "$HOME/.config/nvim" - -# `git pull` everything mentioned in the REPOS_TO_UPDATE variable -IFS=: read -ra REPOS_TO_UPDATE_ARR <<< "${REPOS_TO_UPDATE:-}" -for REPO in "${REPOS_TO_UPDATE_ARR[@]}"; do - if [ -n "$REPO" ]; then - if g rev-parse > /dev/null 2>&1; then - echo " ${GREEN}Pulling $(eval "$REPO_ABBR")‥${NC}" - g pull - g diff --quiet || echo "${YELLOW}Warning: Working tree for $(eval "$REPO_ABBR") is dirty${NC}" + ifs="$IFS" + IFS="$split" + for str in $array; do + if [ "$str" = "$search" ]; then + IFS="$ifs" + return 0 fi - fi - UPDATED_STUFF=true -done + done + IFS="$ifs" + return 1 +} -# check for dotfiles updates -${UPDATED_STUFF:-false} && echo -echo " ${GREEN}Updating dotfiles…${NC}" -dotfiles pull -dotfiles diff --quiet || echo "${YELLOW}Warning: Working tree for dotfiles is dirty${NC}" +# 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 "$@"