Files
vs-code-setup/scripts/settings.sh
T
maxandClaude Fable 5.1 c058c09050 Bash install.sh and settings.sh for Linux and macOS
Twins of the PowerShell scripts, needing only bash, curl and one of jq,
node or python3 for JSON. install.sh downloads a release (or installs
from dist/), settings.sh pushes, pulls or diffs User/, both resolve the
code CLI and user folder per platform.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0169iPWwKHZoBTNN9qwXiwqk
2026-09-08 19:50:07 +02:00

154 lines
4.8 KiB
Bash

#!/usr/bin/env bash
# Moves VS Code user settings between this repo and the machine.
#
# The bash twin of settings.ps1:
# --push copies the machine's settings into User/ so you can commit them
# --pull copies User/ onto the machine, backing up whatever was there
# --diff says what differs without touching anything (the default)
#
# It copies rather than symlinks on purpose: an editor that saves atomically replaces a
# symlink with a regular file and the sync silently stops. Copying is explicit.
#
# --push also records marketplace extensions in User/extensions.txt and --pull installs
# the missing ones. Ids starting "local." are skipped: those are the extensions in this
# repo, and install.sh gets them from the Gitea release. --no-extensions skips that part.
#
# Usage:
# scripts/settings.sh [--diff]
# scripts/settings.sh --push
# scripts/settings.sh --pull [--no-extensions]
# shellcheck source=common.sh
. "$(dirname "${BASH_SOURCE[0]}")/common.sh"
mode=diff
extensions=1
while [ $# -gt 0 ]; do
case "$1" in
--push) mode=push; shift ;;
--pull) mode=pull; shift ;;
--diff) mode=diff; shift ;;
--no-extensions) extensions=""; shift ;;
-h|--help) sed -n '2,19p' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;;
*) die "unknown argument: $1 (try --help)" ;;
esac
done
repo_user="$setup_root/User"
code_user="$(code_user_dir)"
[ -d "$code_user" ] || die "VS Code's user folder was not found at $code_user. Set VSCODE_USER_DIR to it or copy by hand."
# Only the things worth version-controlling. Everything else in User/ is state.
items="settings.json keybindings.json tasks.json snippets"
# A hash of a file, or of every file in a directory with its relative path; empty if absent.
fingerprint() {
local path="$1"
if [ -f "$path" ]; then
sha256sum "$path" | cut -d' ' -f1
elif [ -d "$path" ]; then
(cd "$path" && find . -type f | sort | while read -r f; do
printf '%s:%s|' "$f" "$(sha256sum "$f" | cut -d' ' -f1)"
done) | sha256sum | cut -d' ' -f1
fi
}
copy_item() {
local from="$1" to="$2"
mkdir -p "$(dirname "$to")"
if [ -d "$from" ]; then
rm -rf "$to"
cp -R "$from" "$to"
else
cp "$from" "$to"
fi
}
backup_existing() {
local path="$1"
[ -e "$path" ] || return 0
local backup="$path.backup-$(date +%Y%m%d-%H%M%S)"
cp -R "$path" "$backup"
note " backed up to $(basename "$backup")"
}
echo "repo: $repo_user"
echo "code: $code_user"
echo
for item in $items; do
in_repo="$repo_user/$item"
on_disk="$code_user/$item"
repo_print="$(fingerprint "$in_repo")"
disk_print="$(fingerprint "$on_disk")"
if [ -z "$repo_print" ] && [ -z "$disk_print" ]; then
note " $item: absent both sides"; continue
fi
if [ "$repo_print" = "$disk_print" ]; then
note " $item: identical"; continue
fi
case "$mode" in
push)
if [ -z "$disk_print" ]; then warn " $item: not on this machine, leaving the repo copy alone"; continue; fi
copy_item "$on_disk" "$in_repo"
ok " $item: machine -> repo" ;;
pull)
if [ -z "$repo_print" ]; then warn " $item: not in the repo, leaving the machine alone"; continue; fi
backup_existing "$on_disk"
copy_item "$in_repo" "$on_disk"
ok " $item: repo -> machine" ;;
*)
if [ -z "$repo_print" ]; then where="only on this machine"
elif [ -z "$disk_print" ]; then where="only in the repo"
else where="differs"; fi
warn " $item: $where" ;;
esac
done
[ -n "$extensions" ] || exit 0
echo
list="$repo_user/extensions.txt"
code="$(code_command)"
if [ "$mode" = push ]; then
mkdir -p "$repo_user"
"$code" --list-extensions | grep -v '^local\.' | grep -v '^$' | sort > "$list"
ok " extensions.txt: recorded $(wc -l < "$list" | tr -d ' ') marketplace extension(s)"
exit 0
fi
if [ ! -f "$list" ]; then
warn " extensions.txt: not in the repo yet, run --push on your main machine"
exit 0
fi
present="$("$code" --list-extensions)"
missing=()
while read -r wanted; do
[ -n "$wanted" ] || continue
case "$wanted" in \#*) continue ;; esac
grep -qixF "$wanted" <<<"$present" || missing+=("$wanted")
done < "$list"
if [ "$mode" = diff ]; then
if [ ${#missing[@]} -eq 0 ]; then
note " extensions.txt: all present"
else
warn " extensions.txt: ${#missing[@]} missing: ${missing[*]}"
fi
exit 0
fi
if [ ${#missing[@]} -eq 0 ]; then
note " extensions.txt: all present"
exit 0
fi
for id in "${missing[@]}"; do
printf ' installing %s' "$id"
if "$code" --install-extension "$id" >/dev/null 2>&1; then ok ' ok'; else printf '\033[31m FAILED\033[0m\n'; fi
done
echo "Reload the window."