Skip to content

Instantly share code, notes, and snippets.

@ryancdotorg
Created July 29, 2025 10:45
Show Gist options
  • Save ryancdotorg/4ed1f1e2760043cac813dfc190495947 to your computer and use it in GitHub Desktop.
Save ryancdotorg/4ed1f1e2760043cac813dfc190495947 to your computer and use it in GitHub Desktop.
#!/bin/bash
# SPDX-License-Identifier: 0BSD OR MIT-0 OR CC0-1.0+
# Copyright © 2025 Ryan Castellucci <[email protected]>
#
# vim_pack_update.sh
#
# A script to update all vim plugins managed by the native package manager.
# It handles plugins on a branch (fast-forward pull) and plugins on a tag
# (checks out the latest tag).
#
# USAGE:
# Save this script in `~/.vim/pack` and run it from there.
#
# Enable a combination of various unoffical bash 'strict' modes
# http://redsymbol.net/articles/unofficial-bash-strict-mode/
# https://disconnected.systems/blog/another-bash-strict-mode/
set -uo pipefail
trap 's=$?; echo ": Error on line "$LINENO": $BASH_COMMAND"; exit $s' ERR
IFS='\n\t'
GIT_BIN="$(which git)"
# Helper function to run git commands in the correct directory.
# Although git has a -C option to change directory, this is more robust.
function git_in_dir {
# save the target directory
local dir="$1"
# remove the target directory argument
shift
# use the directory stack to enter the target directory, then return
pushd "$dir" > /dev/null 2> /dev/null
"$GIT_BIN" "$@"
popd > /dev/null 2> /dev/null
}
# Find all plugins which are git repos by looking for the `.git` directory.
# NOTE Assumes your current working directory is already e.g. ~/.vim/pack
find . -mindepth 4 -maxdepth 4 -type d -name '.git' | while read -r d
do
# Extract the plugin directory path from the .git path.
DIR="$(dirname "$d")"
# Check if the repository is on a branch or a tag (detached HEAD).
BRANCH="$(git_in_dir "$DIR" branch --show-current)"
if [[ ! -z "$BRANCH" ]]; then
# --- Handle repositories on a branch ---
printf 'Updating %s:%s... ' "$DIR" "$BRANCH"
# Fetch the latest changes and perform a fast-forward-only pull.
# This is a safe way to update without creating merge commits.
# '|| true' prevents the script from exiting if the pull fails
# (e.g., already up to date).
git_in_dir "$DIR" fetch \
&& git_in_dir "$DIR" pull --ff-only \
|| true
else
# --- Handle repositories on a tag (detached HEAD) ---
CURRENT_TAG="$(git_in_dir "$DIR" describe --tags)"
printf 'Updating %s@%s... ' "$DIR" "$CURRENT_TAG"
# Fetch all new tags from the remote.
git_in_dir "$DIR" fetch --tags \
|| true
# Find the commit hash of the most recent tag.
REV="$(git_in_dir "$DIR" rev-list --tags --max-count=1)"
# Get the name of the most recent tag from its commit hash.
LATEST_TAG="$(git_in_dir "$DIR" describe --tags $REV)"
# If the current tag is not the latest tag, check out the latest tag.
if [ "$CURRENT_TAG" != "$LATEST_TAG" ]; then
printf 'checking out %s...\n' "$LATEST_TAG"
git_in_dir "$DIR" checkout "$LATEST_TAG" \
|| true
else
echo Already up to date.
fi
fi
done
printf 'All plugins checked.\n'
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment