Show sibling projects' versions, and let version update them
shellcheck / shellcheck (push) Failing after 19s
shellcheck / shellcheck (push) Failing after 19s
A sibling's version is its latest git tag (v1.4.0, or v1.4.0+3 for commits after it), or, without tags, its commit's date and short hash, like CoreDesktop's own. The version shown as available is that of the newest commit on the default branch, which is what update installs. sibling list shows each sibling's version. sibling check fetches every sibling at once and shows which have a newer version, and sibling update [name...] updates the named siblings or all of them. 71-siblings.sh now runs sibling update, so update and version share one way of updating. version lists the installed siblings with any newer version and, when run in a terminal, asks for each one whether to update it now. version --check and the update notification still only look at CoreDesktop. The sibling skill docs describe how to release a version by tagging it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,10 @@
|
||||
# If it has an install.sh, that runs once when the sibling is added.
|
||||
# Every `update` pulls each sibling and runs its update.sh, if it has one
|
||||
# (see updates.d/71-siblings.sh).
|
||||
#
|
||||
# A sibling's version is its latest git tag, e.g. v1.4.0, or v1.4.0+3 for three
|
||||
# commits after it. A sibling without tags gets CoreDesktop's kind of version:
|
||||
# the commit's date and short hash, e.g. 2026.11.02 (a1b2c3d).
|
||||
|
||||
SIBLINGS="$HOME/.local/share/siblings"
|
||||
|
||||
@@ -13,12 +17,139 @@ usage() {
|
||||
cat << EOF
|
||||
Usage: sibling [command]
|
||||
|
||||
add [url] Install a sibling project from a git repository (default)
|
||||
list Show installed sibling projects
|
||||
remove <name> Uninstall a sibling project
|
||||
add [url] Install a sibling project from a git repository (default)
|
||||
list Show installed sibling projects and their versions
|
||||
check Show which sibling projects have a newer version
|
||||
update [name...] Update the named sibling projects, or all of them
|
||||
remove <name> Uninstall a sibling project
|
||||
EOF
|
||||
}
|
||||
|
||||
# Version of the sibling in $1 at commit $2 (HEAD by default)
|
||||
sibling_version() {
|
||||
local dir="$1" rev="${2:-HEAD}" tag ahead
|
||||
if tag="$(git -C "$dir" describe --tags --abbrev=0 "$rev" 2> /dev/null)"; then
|
||||
ahead="$(git -C "$dir" rev-list --count "$tag..$rev" 2> /dev/null)"
|
||||
if [[ "$ahead" =~ ^[0-9]+$ ]] && (( ahead > 0 )); then
|
||||
echo "$tag+$ahead"
|
||||
else
|
||||
echo "$tag"
|
||||
fi
|
||||
else
|
||||
git -C "$dir" log -1 --format='%cd (%h)' --date=format:%Y.%m.%d "$rev"
|
||||
fi
|
||||
}
|
||||
|
||||
# Installed siblings' directories, one per line, in alphabetical order
|
||||
sibling_dirs() {
|
||||
local dir
|
||||
for dir in "$SIBLINGS"/*/; do
|
||||
[ -d "$dir/.git" ] && echo "${dir%/}"
|
||||
done
|
||||
}
|
||||
|
||||
# Fetch the sibling in $1 and print "name<TAB>installed<TAB>available", where
|
||||
# available is the version update would install, empty if it's up to date, or
|
||||
# ? if the sibling's remote couldn't be checked.
|
||||
check_one() {
|
||||
local dir="$1" installed available="" behind
|
||||
installed="$(sibling_version "$dir")"
|
||||
if [ -n "$(git -C "$dir" status --porcelain --untracked-files=no 2> /dev/null)" ]; then
|
||||
installed="$installed, modified"
|
||||
fi
|
||||
if ! GIT_TERMINAL_PROMPT=0 timeout 30 git -C "$dir" fetch --quiet &> /dev/null \
|
||||
|| ! behind="$(git -C "$dir" rev-list --count 'HEAD..@{upstream}' 2> /dev/null)"; then
|
||||
available="?"
|
||||
elif (( behind > 0 )); then
|
||||
available="$(sibling_version "$dir" '@{upstream}')"
|
||||
fi
|
||||
printf '%s\t%s\t%s\n' "$(basename "$dir")" "$installed" "$available"
|
||||
}
|
||||
|
||||
# check_one for every sibling, all at once so a slow remote doesn't hold up the
|
||||
# rest, printed in alphabetical order. Prints nothing if none are installed.
|
||||
check_all() {
|
||||
local dirs dir i=0 tmp
|
||||
mapfile -t dirs < <(sibling_dirs)
|
||||
(( ${#dirs[@]} > 0 )) || return 0
|
||||
tmp="$(mktemp -d)"
|
||||
for dir in "${dirs[@]}"; do
|
||||
check_one "$dir" > "$tmp/$i" &
|
||||
i=$(( i + 1 ))
|
||||
done
|
||||
wait
|
||||
for (( i = 0; i < ${#dirs[@]}; i++ )); do
|
||||
cat "$tmp/$i"
|
||||
done
|
||||
rm -rf "$tmp"
|
||||
}
|
||||
|
||||
# Print check_all's lines as a table. Returns 0 if any sibling has a newer
|
||||
# version, 1 if none does.
|
||||
show_checked() {
|
||||
local name installed available newer=1 width=0 vwidth=0 lines=()
|
||||
mapfile -t lines
|
||||
for line in "${lines[@]}"; do
|
||||
IFS=$'\t' read -r name installed _ <<< "$line"
|
||||
(( ${#name} > width )) && width=${#name}
|
||||
(( ${#installed} > vwidth )) && vwidth=${#installed}
|
||||
done
|
||||
for line in "${lines[@]}"; do
|
||||
IFS=$'\t' read -r name installed available <<< "$line"
|
||||
case "$available" in
|
||||
"") available="up to date" ;;
|
||||
"?") available="couldn't check" ;;
|
||||
*) available="$available is available"; newer=0 ;;
|
||||
esac
|
||||
printf ' %-*s %-*s %s\n' "$width" "$name" "$vwidth" "$installed" "$available"
|
||||
done
|
||||
return $newer
|
||||
}
|
||||
|
||||
check_siblings() {
|
||||
if [ "$1" = "--porcelain" ]; then
|
||||
check_all
|
||||
return
|
||||
fi
|
||||
local checked
|
||||
checked="$(check_all)"
|
||||
if [ -z "$checked" ]; then
|
||||
echo "No sibling projects installed."
|
||||
return 1
|
||||
fi
|
||||
show_checked <<< "$checked"
|
||||
}
|
||||
|
||||
# Pull each named sibling (all of them if none are named) and run its update.sh
|
||||
update_siblings() {
|
||||
local dirs=() dir name failed=0
|
||||
if (( $# == 0 )); then
|
||||
mapfile -t dirs < <(sibling_dirs)
|
||||
fi
|
||||
for name in "$@"; do
|
||||
if [[ -z "$name" || "$name" == */* || "$name" == .* || ! -d "$SIBLINGS/$name/.git" ]]; then
|
||||
echo "No sibling named '$name' is installed."
|
||||
failed=1
|
||||
continue
|
||||
fi
|
||||
dirs+=("$SIBLINGS/$name")
|
||||
done
|
||||
for dir in "${dirs[@]}"; do
|
||||
name="$(basename "$dir")"
|
||||
echo "Updating $name..."
|
||||
if ! git -C "$dir" pull --ff-only --quiet; then
|
||||
echo " Could not pull $name."
|
||||
failed=1
|
||||
continue
|
||||
fi
|
||||
if [ -f "$dir/update.sh" ]; then
|
||||
(cd "$dir" && bash update.sh) || { echo " $name's update.sh failed."; failed=1; continue; }
|
||||
fi
|
||||
echo " $name is at $(sibling_version "$dir")."
|
||||
done
|
||||
return $failed
|
||||
}
|
||||
|
||||
# Derive a directory name from a git URL, e.g.
|
||||
# https://host/user/my-project.git -> my-project
|
||||
sibling_name() {
|
||||
@@ -71,7 +202,7 @@ add_sibling() {
|
||||
|
||||
# Confirm the install
|
||||
if [ -d "$dir/.git" ] && git -C "$dir" rev-parse --verify --quiet HEAD > /dev/null; then
|
||||
echo "Installed $name at $dir ($(git -C "$dir" log -1 --format='%h %s'))."
|
||||
echo "Installed $name $(sibling_version "$dir") at $dir."
|
||||
echo "'update' will keep it up to date."
|
||||
else
|
||||
echo "$name does not look installed correctly at $dir."
|
||||
@@ -80,15 +211,25 @@ add_sibling() {
|
||||
}
|
||||
|
||||
list_siblings() {
|
||||
local dir found=0
|
||||
for dir in "$SIBLINGS"/*/; do
|
||||
[ -d "$dir/.git" ] || continue
|
||||
found=1
|
||||
printf '%-24s %s (%s)\n' "$(basename "$dir")" \
|
||||
"$(git -C "$dir" remote get-url origin 2>/dev/null)" \
|
||||
"$(git -C "$dir" log -1 --format='%h, %cr' 2>/dev/null)"
|
||||
local dirs dir width=0 vwidth=0 versions=()
|
||||
mapfile -t dirs < <(sibling_dirs)
|
||||
if (( ${#dirs[@]} == 0 )); then
|
||||
echo "No sibling projects installed."
|
||||
return
|
||||
fi
|
||||
for dir in "${dirs[@]}"; do
|
||||
versions+=("$(sibling_version "$dir")")
|
||||
dir="$(basename "$dir")"
|
||||
(( ${#dir} > width )) && width=${#dir}
|
||||
done
|
||||
for version in "${versions[@]}"; do
|
||||
(( ${#version} > vwidth )) && vwidth=${#version}
|
||||
done
|
||||
for i in "${!dirs[@]}"; do
|
||||
printf '%-*s %-*s %s
|
||||
' "$width" "$(basename "${dirs[$i]}")" "$vwidth" "${versions[$i]}" \
|
||||
"$(git -C "${dirs[$i]}" remote get-url origin 2> /dev/null)"
|
||||
done
|
||||
[ $found -eq 1 ] || echo "No sibling projects installed."
|
||||
}
|
||||
|
||||
remove_sibling() {
|
||||
@@ -116,6 +257,8 @@ remove_sibling() {
|
||||
case "$1" in
|
||||
""|add) add_sibling "$2" ;;
|
||||
list|ls) list_siblings ;;
|
||||
check) check_siblings "$2" ;;
|
||||
update) shift; update_siblings "$@" ;;
|
||||
remove|rm) remove_sibling "$2" ;;
|
||||
-h|--help|help) usage ;;
|
||||
*) usage; exit 1 ;;
|
||||
|
||||
Reference in New Issue
Block a user