0034: Site changes from the installed Git
On this page
Status: accepted. Its read-only rule is superseded by 0081; its rules for reading stay.
Context
Each site gets a Changes page: the files changed since the last commit and their diffs, often to review what a coding agent did. Reading Git means picking a library or the git program. Rust's Git libraries either lag on submodules, worktrees and a stable API (gitoxide) or are GPL with a linking exception and need a C toolchain on Windows (libgit2). A repository is also untrusted input: its config can name programs that Git runs while it compares files, such as an fsmonitor hook, clean and process filters, textconv and an external diff. On macOS, /usr/bin/git is a stand-in that opens Apple's installer when the Command Line Tools are missing.
Decision
- Short Leash runs the installed
git(leash_core::changes), so renames, conflicts, line endings and worktrees mean what they mean to the person's own Git. On macOS it uses/usr/bin/gitonly whenxcode-select -pnames a developer folder with Git in it, and runs that Git directly. Otherwise the page says Git isn't installed. - Every command runs with:
--no-optional-locksandGIT_OPTIONAL_LOCKS=0, so Short Leash never takes Git's index lock.--no-pager.-c core.fsmonitor=falseand-c diff.external=, with--no-ext-diff --no-textconvon diffs.- Fixed output settings: prefixes, relative paths, quoting and color.
GIT_TERMINAL_PROMPT=0,GIT_CEILING_DIRECTORIESset to the home folder (so a home folder kept in Git doesn't claim every site), and theGIT_*variables that point Git elsewhere removed.--ignore-submodules=alland-c submodule.recurse=false, so no command goes into a submodule.- Only the absolute folders of the
PATH, so a program Git starts is never found inside the repository. GIT_NO_LAZY_FETCH=1,--no-lazy-fetchon Git 2.44 and later, andGIT_ALLOW_PROTOCOLset and empty, so a read never reaches a remote. A partial clone would otherwise download the objects it left on its remote, running the upload-pack, SSH command or credential helper the repository's config names. A diff that needs such an object says the repository is a partial clone and names the command to run once in a terminal to download what's missing:git difffor uncommitted changes,git diff <base>...HEADfor This branch. The base is named only when it holds nothing but ASCII letters, digits,.,-and/(and doesn't start with-); otherwise the command isgit diff <merge base id> HEAD, which downloads the same. A branch name comes from the repository, so it's never quoted for one shell: what's shown means the same in sh, PowerShell and cmd.exe.
- Filter drivers defined in the repository's own config (
git config --local) are turned off. Filters in the person's own config, such as Git LFS, stay on; turning them off would show every file they filter as changed. - Git's own safety check (
safe.directory) is respected: a refusal is a state on the page with the command to run, never an override. - The page is read-only: no stage, commit or discard. It compares the working tree and index with
HEADonly. - A diff is asked for per file as it scrolls near and must be one of the files Git lists for the site. Untracked files are read by Short Leash, regular files only and at most 1 MB, never pipes or devices. At most three of these Git commands run at once.
- Diffs are drawn by Short Leash's own React code, with words marked from a longest common sequence of tokens and syntax colors from the Lezer parsers the scratchpad editor already ships. No diff library is added.
- The page asks Git again every 10 seconds while it's in view and when the window comes back; there's no file watcher.
Consequences
- People need Git installed; the page says so and how to get it.
- A Git command per refresh costs a process, more on Windows. Large repositories with ignored folders are fast; one with an unignored
node_modulesstops at 10,000 files and names the folders. - Whatever a filter in the person's own config does still runs, as it would in their own terminal.
- Changes can be up to 10 seconds old while the page is open; a watcher can come later without changing the API.
- Discarding, comparing against another branch and checkpoints for agents are left for later.