Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
17 commits
Select commit Hold shift + click to select a range
e494a93
fix: audit wave 1 — security hardening, release pipeline, UX and serv…
erwin-wee Oct 1, 2026
dd3dab9
feat: audit wave 2 — AI infrastructure, AI settings, UX/perf and smal…
erwin-wee Oct 1, 2026
65b6274
feat: audit wave 3 — graph, accounts, windows, undo/bisect, stacks, s…
erwin-wee Oct 1, 2026
2f26535
fix: start the launch-time watched-folder scan once git is located
erwin-wee Oct 1, 2026
9717d72
docs: regroup README, add CHANGELOG and SECURITY, archive completed o…
erwin-wee Oct 1, 2026
c3ac88d
fix: gate sparse checkout and expose AI commit-message cancellation
erwin-wee Oct 1, 2026
d2894c4
refactor: split renderer actions and core handlers by domain
erwin-wee Oct 1, 2026
696245b
fix(ci): enable cross-platform suites and normalize Windows fixture p…
erwin-wee Oct 1, 2026
e47eb3e
fix: normalize MCP repository paths and settle diff smoke scrolling
erwin-wee Oct 1, 2026
52f894d
docs: record cross-platform CI and archive main-process test change
erwin-wee Oct 1, 2026
be0bf3d
fix(git): preserve undo boundaries, stack tracking and rename diffs
erwin-wee Oct 1, 2026
d285c76
fix(ai): isolate concurrent jobs and cancellation by client
erwin-wee Oct 1, 2026
9355853
fix(repo): inherit worktree policies and gate updates across clients
erwin-wee Oct 1, 2026
c47cf6b
fix(security): decode secret paths and validate shell placeholder scope
erwin-wee Oct 1, 2026
a44c2da
fix(release): upload nested artifact files instead of directories
erwin-wee Oct 1, 2026
e26ab59
fix(shortcuts): confirm reset conflicts before unbinding another action
erwin-wee Oct 1, 2026
4951ee4
docs: record fixes from local branch review
erwin-wee Oct 1, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
10 changes: 10 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: npm
directory: /
schedule:
interval: weekly
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly
76 changes: 69 additions & 7 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,13 @@ on:
tags:
- "v*"

# Nothing below can write by default. Only the publish job gets contents: write,
# and it never runs project code (no checkout, no npm install).
permissions:
contents: write
contents: read

jobs:
release:
build:
strategy:
fail-fast: false
matrix:
Expand All @@ -21,12 +23,16 @@ jobs:
- os: ubuntu-latest
dist: dist:linux
runs-on: ${{ matrix.os }}
permissions:
contents: read
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- uses: actions/setup-node@v4
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
node-version: 24
cache: npm

- name: Check the tag matches package.json
Expand All @@ -44,7 +50,63 @@ jobs:
- name: Typecheck
run: npm run typecheck

- name: Build and publish
run: npm run ${{ matrix.dist }} -- --publish always
# Signing and notarization are driven by these secrets and only happen when
# they are set (see docs/RELEASING.md); unset secrets expand to empty
# strings, which electron-builder treats as "not provided".
- name: Build
run: npm run ${{ matrix.dist }} -- --publish never
env:
CSC_LINK: ${{ secrets.CSC_LINK }}
CSC_KEY_PASSWORD: ${{ secrets.CSC_KEY_PASSWORD }}
APPLE_ID: ${{ secrets.APPLE_ID }}
APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_APP_SPECIFIC_PASSWORD }}
APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }}

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-${{ matrix.os }}
path: |
release/*/*.exe
release/*/*.dmg
release/*/*.zip
release/*/*.AppImage
release/*/*.deb
release/*/*.blockmap
release/*/latest*.yml
if-no-files-found: error
retention-days: 7

publish:
needs: build
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: dist
merge-multiple: true

# The release must stay a DRAFT (review, add notes, publish by hand), and
# electron-updater's latest*.yml files are uploaded with the installers.
- name: Create or update the draft release
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
GH_REPO: ${{ github.repository }}
run: |
if gh release view "$GITHUB_REF_NAME" >/dev/null 2>&1; then
is_draft=$(gh release view "$GITHUB_REF_NAME" --json isDraft -q .isDraft)
if [ "$is_draft" != "true" ]; then
echo "::error::Release $GITHUB_REF_NAME is already published; refusing to overwrite its assets."
exit 1
fi
else
gh release create "$GITHUB_REF_NAME" --draft --verify-tag --title "$GITHUB_REF_NAME" --notes ""
fi
# download-artifact preserves the version directory beneath dist/.
mapfile -d '' -t assets < <(find dist -type f -print0)
if [ "${#assets[@]}" -eq 0 ]; then
echo "::error::No release assets were downloaded."
exit 1
fi
gh release upload "$GITHUB_REF_NAME" "${assets[@]}" --clobber
33 changes: 16 additions & 17 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,38 +2,45 @@ name: Test

on:
push:
branches: [main]
branches: [main, "ci/**"]
pull_request:
workflow_dispatch:

permissions:
contents: read

jobs:
test:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, windows-latest]
os: [ubuntu-latest, windows-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false

- uses: actions/setup-node@v4
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: 22
node-version: 24
cache: npm

- name: Install dependencies
run: npm ci

# Debian/Ubuntu's xvfb package ships xvfb-run; scripts/smoke.mjs picks it
# up automatically (and only uses it on Linux). Windows runners already
# have a desktop session, so nothing extra is needed there.
# have a desktop session and macOS renders offscreen, so nothing extra is
# needed there.
- name: Install xvfb
if: runner.os == 'Linux'
run: |
sudo apt-get update
sudo apt-get install -y xvfb

- name: Typecheck
run: npm run typecheck
- name: Lint and typecheck
run: npm run lint

# Headless server bundle: fails if server-reachable code imports electron.
- name: Build server
Expand All @@ -42,23 +49,15 @@ jobs:
- name: Unit tests
run: npm run test:unit

# Linux only for now. The fixture and smoke suites had never run on Windows
# (the unit step failed first and masked them); when they finally did, 16
# fixture tests failed for unrelated reasons -- cherry-pick conflicts,
# worktrees, signing and the gh-stub. Typecheck and the unit suite still
# run on Windows, which is what covers the Windows-specific code paths.
# Tracked in #3.
- name: Fixture tests
if: runner.os == 'Linux'
run: npm run test:fixture

- name: Smoke tests
if: runner.os == 'Linux'
run: npm run test:smoke

- name: Upload smoke artifacts
if: always()
uses: actions/upload-artifact@v4
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: smoke-${{ matrix.os }}
path: test/smoke/out/
Expand Down
127 changes: 127 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
# Changelog

All notable changes to GitGood. Versions follow [semver](https://semver.org); release notes for each version are also on the [Releases page](https://github.com/erwin-wee/gitgood/releases).

## Unreleased

A security, privacy and robustness pass over the whole app, plus the features that came out of it.

### Security

- **Repository files can no longer leak through symlinks.** Every repo-controlled file that feeds the AI, templates or configuration (review guidelines, PR and issue templates, `.gitgood/config.json`, untracked files in diffs, conflict reads) is read through one helper that refuses symlinks and checks the real path stays inside the repository. A symlinked file diffs as its link text, like git. AI-written files (conflict resolutions, applied suggestions) go through a matching safe write.
- **Secrets are protected at rest and in logs.** `secrets.json` and every other store file are written `0600` in a `0700` directory (POSIX); the log file is `0600` and scrubs tokens and credentials from every line, including git argv; the server token is never logged (printed once on a TTY at first start, otherwise only its file path). A corrupt store file is renamed to `<file>.corrupt-<timestamp>` instead of being silently reset.
- **Server mode.** `repo.readFile`/`repo.writeFile` and every path argument reject absolute and `../` paths (including through symlinked parents); `/gitgood-bridge.js` is refused to cross-origin requests and sent with `Cross-Origin-Resource-Policy: same-origin`.
- **AI privacy.** Secret-shaped files (`.env*`, `*.pem`, `*.key`, `id_rsa*`, `.netrc`, credential-bearing `.npmrc`, …) are never uploaded, known token shapes are masked in the text of every read-only feature, and error explanation scrubs the failing command. AI can be switched off per repository, on your machine or for everyone via `.gitgood/config.json`.
- **Settings sync** uses secret gists only: it refuses to upload to (or adopt) a public gist, re-checks visibility before every upload, and keeps `agentCustomCommand` machine-local. A settings file that changes the OpenAI-compatible base URL deletes the stored API key so it can't be redirected.
- **Prompt-injection and confused-deputy hardening.** The repository check-command trust prompt binds to the exact command shown; the `gitgood://review/rerun` link needs a per-run one-time token, otherwise it asks for confirmation before anything is sent to an AI provider; custom agent commands must quote every `{file}`; the natural-language palette allows only an explicit option list per inspect command (and disables external diff and textconv drivers); branch, tag and remote names starting with `-` are rejected.
- **Electron.** IPC is accepted only from the app's own main frame, and navigation is limited to the exact entry document.
- **Release pipeline.** GitHub Actions are pinned by commit SHA, workflows run read-only with credentials not persisted, builds use `--publish never` and a separate publish job holds `contents: write`; the Linux installer fails closed when it cannot verify the download's SHA-512 (`--insecure-skip-verify` overrides); `release:prepare` refuses to continue when the CI check can't be confirmed (`--skip-ci-check` overrides). Code signing and notarization are wired to repository secrets and stay inactive until configured. Dependabot watches npm and GitHub Actions. See [SECURITY.md](SECURITY.md).

### Added

- **Commit graph** in History with coloured branch lanes, merges and octopus merges, computed incrementally.
- **Multiple GitHub accounts**, with a per-repository account whose token is used for that repository's `gh` calls and network git commands.
- **Multiple windows** (File → New Window, Open in New Window), each with its own repository and live updates.
- **Undo history** (reflog browser with restore, create-branch and checkout) and **Undo Last Git Operation**.
- **Bisect** from the History context menu, with a progress banner.
- **Stacked branches**: stack markers, `--update-refs` on rebase and **Push stack**.
- **Rebindable keyboard shortcuts**, included in exported and synced settings.
- **MCP server** for coding agents (`gitgood_latest_review`, `gitgood_request_rereview`, `gitgood_open_repository`), registered by the `gitgood-review` plugin.
- **Clone options**: depth, single branch, blobless, sparse directories and a submodules toggle.
- **Pinned repositories and custom groups** in the repository list.
- **PR auto-merge** (`gh pr merge --auto`) and a **Files changed** view in Compare.
- **Commit options**: sign-off per repository, skip hooks for one commit, and `commit.template` prefill.
- **External diff and merge tools** (`git difftool`/`git mergetool`).
- **AI**: an OpenAI-compatible provider (OpenAI, OpenRouter, Ollama, LM Studio, …), per-feature model overrides, a monthly token-usage view, and prompt caching for Anthropic requests.
- **`gitgood <path>`** opens a folder as a repository, and dropping a folder on the window adds it.
- **Managed background server on macOS and Windows** (LaunchAgent and a per-user login item; not yet verified on real machines), a "Reload to update" banner when the server was upgraded, and a "Reconnecting…" banner with exponential backoff for browser tabs.
- **Accessibility**: an error boundary with Reload and Copy details, forced-colors / high-contrast styles, and a resizable History file pane.
- Minimum tool versions (Git 2.30, gh 2.40) are checked, with a dismissible banner when one is older.
- Installer: `install-linux.sh --uninstall`. Packaging: macOS universal, Windows x64 + ARM64, Linux AppImage and `.deb`.
- Lint: ESLint with the React hooks rules, run in CI.

### Changed

- `ai.cancel` now cancels one feature, and only the calling window or web client's job, instead of every AI service.
- The renderer is code-split (heavy dialogs and syntax-highlighting grammars load on demand); the main chunk is about 30% smaller.
- Startup no longer makes git wait for the `gh`, `claude` and `gpg` probes; each tool appears as soon as its own probe finishes.
- Repository indicators use a light `git status --porcelain=v2 --branch` parse with bounded concurrency.
- The web folder picker is a regular dialog (keyboard, ARIA and theme aware).
- `.deb` installs are treated as unable to self-update, matching how `electron-updater` behaves on Debian.
- CI runs on Node 24; `engines.node` is `^20.19.0 || >=22.12.0`. Electron 44.5.1 and `@anthropic-ai/sdk` 0.131.
- Unit, fixture and Electron smoke suites are enabled on Linux, Windows and macOS; the Test workflow also supports manual dispatch and `ci/**` validation branches.
- Neutral wording in shared AI backend messages (they used to read like conflict-resolution copy for every feature).

### Fixed

- Undo groups exactly one rebase, including GitGood's pull-with-rebase command, and restores the pre-operation commit.
- Release publishing uploads files inside downloaded version directories, including filenames containing spaces, and refuses an empty asset set.
- AI secret-file exclusion decodes Git-quoted paths and checks both sides of a rename.
- Concurrent AI requests retain per-client cancellation and remain visible to the update gate until every job settles.
- Linked worktrees inherit their main repository's AI opt-out and selected GitHub account.
- Agent templates are validated against the selected shell; escaped quotes, unbalanced quotes and command-substitution placeholders are rejected before execution.
- Push Stack resolves a publishing remote rather than pushing to `.`, and preserves existing local parent-tracking relationships.
- Update installation checks every open repository, not only the last-opened window.
- Resetting a keyboard shortcut asks before unbinding a conflicting action.
- Compare preserves rename metadata and displays only the actual changed lines.

- The diff line-number gutter now meets contrast guidelines.
- Several AI services shared one cancel controller, so cancelling one (or, in server mode, another client's job) could abort an unrelated request; each now cancels only its own.
- Git commands stayed blocked for several seconds after launch on machines where `claude --version` or `gh --version` is slow.
- Windows batch-file arguments survive both `cmd.exe` parsing passes; Git worktree and MCP repository paths use native separators.
- Smoke fixtures use canonical paths (including Windows 8.3 aliases) and wait for asynchronous dialog content rather than fixed loading delays.
- Settings export no longer carries machine-local custom agent commands; repository pins, groups and AI opt-outs also stay out of it.

## 0.3.2 — 2026-09-25

- Opening an AI review comment, following a link in an AI explanation, expanding hunk context, blame cards and History search matches no longer make the diff flash or jump, including across files and after a save reloads it.
- The History list on phones no longer jumps when more commits load.

## 0.3.1 — 2026-09-24

- The web app loads far faster from the server (about 2.8 s instead of 12.7 s on a slow mobile connection) via compression, caching and a service worker; the installed phone app shows a "Can't reach your GitGood server" page with Retry.
- Large diffs and merge conflicts are cheaper to deliver and render; the app appears before git and gh are located; saves made elsewhere show up almost instantly; no spinner flash for quick diffs.

## 0.3.0 — 2026-09-24

- **File → Run GitGood server in the background…** (Linux) installs a service running the server bundled with the app, copies settings and repositories into it the first time, and keeps it on the desktop's version.
- A desktop connected to a server checks for and installs its own updates.
- The version-mismatch warning offers Restart server, Copy command, Reload or Check for updates.

## 0.2.0 — 2026-09-24

- **Server mode:** run GitGood headless and use it from a browser on your Tailscale tailnet, locked to your Tailscale login.
- **Desktop as a client** of a server; **phone and tablet layout** for the web UI (installable to the home screen).
- In-app help, a condensed toolbar, two-line commit rows, searchable shortcuts and clearer discard wording.
- Push progress shows pre-push hook output; watched folders are scanned correctly at launch; the pull request list stays within GitHub's API limits.

## 0.1.5 — 2026-09-22

- Fixed History and pull request state not refreshing after ref changes while another tab was showing.

## 0.1.4 — 2026-09-21

- Performance: repository watching and word-level diffs kept off hot paths.

## 0.1.3 — 2026-09-19

- Fixes for `release:tag` opening an unreachable editor, the Linux installer's `--version` handling, `cmd.exe` quoting and the gh stub on Windows.
- UI/UX audit fixes: keyboard access, dialog focus, labels, contrast and safety guards.
- Snappier lists, diffs and repository opens.

## 0.1.2 — 2026-09-18

- The setup screen no longer flashes on every launch.
- Added the release script (`npm run release:prepare` / `release:tag`); real-git test suites get enough time on Windows.

## 0.1.1 — 2026-09-18

- **Watched folders** that auto-discover repositories.
- **Fix with agent**: hand AI review findings to a terminal coding agent.
- Linux install script that detects the distro; documentation split into `docs/`.
- Fixed first-sync notification flood, the commit button label and blame gutter contrast; release assets are no longer silently skipped.

## 0.1.0 — 2026-09-18

- First release: a GitHub Desktop–style client driving `git` and `gh`, with AI conflict resolution, pull request review and the other AI features, release CI and automatic updates through `electron-updater`.
- Fixed process cancellation and Windows CLI spawning, and an unconfirmed-git-command path in the AI palette.
Loading
Loading