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
154 lines
4.8 KiB
Bash
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."
|