#!/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."