diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d0e5877..f1d5722 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,8 +13,13 @@ # one repository); without it the job leaves a notice. # summary one table of results; fails the run when anything failed. # -# workflow_dispatch builds and signs the desktop apps (version 0.0.0) as -# workflow artifacts without releasing anything: a way to check signing. +# workflow_dispatch, run by hand: +# tag empty builds and signs the desktop apps (version 0.0.0) as +# workflow artifacts and releases nothing: a signing check. +# tag v2.4.0 finishes that existing DRAFT release with this workflow: +# builds at the tag, uploads, publishes, updates the tap. +# For a release whose run failed after the draft was made +# (a re-run would replay the old workflow file). # # The web app deploys separately (pages.yml), so a failed release never # takes the site down. @@ -29,6 +34,11 @@ on: push: branches: [main] workflow_dispatch: + inputs: + tag: + description: "Existing draft release to finish (e.g. v2.4.0). Empty: build and sign only." + required: false + default: "" permissions: contents: read @@ -40,7 +50,6 @@ concurrency: jobs: release: name: Version and draft release - if: github.event_name == 'push' runs-on: ubuntu-latest permissions: contents: write @@ -52,17 +61,21 @@ jobs: tag: ${{ steps.tag.outputs.tag }} steps: - uses: actions/checkout@v4 + if: github.event_name == 'push' with: fetch-depth: 0 - uses: actions/setup-node@v4 + if: github.event_name == 'push' with: node-version: 20 cache: npm - - run: npm ci + - if: github.event_name == 'push' + run: npm ci - name: semantic-release + if: github.event_name == 'push' run: npx semantic-release env: GITHUB_TOKEN: ${{ github.token }} @@ -72,7 +85,28 @@ jobs: # tag there means this push did not warrant a release. - name: The new tag id: tag + env: + GH_TOKEN: ${{ github.token }} + INPUT_TAG: ${{ inputs.tag }} run: | + if [ "$GITHUB_EVENT_NAME" = "workflow_dispatch" ]; then + if [ -z "$INPUT_TAG" ]; then + echo "Build and sign only; nothing is released." + echo "published=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + [[ "$INPUT_TAG" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo "::error::tag must look like v1.2.3"; exit 1; } + draft="$(gh release view "$INPUT_TAG" --repo "$GITHUB_REPOSITORY" --json isDraft --jq .isDraft)" \ + || { echo "::error::No release $INPUT_TAG"; exit 1; } + [ "$draft" = "true" ] || { echo "::error::$INPUT_TAG is already published; only a draft can be finished"; exit 1; } + { + echo "published=true" + echo "tag=$INPUT_TAG" + echo "version=${INPUT_TAG#v}" + } >> "$GITHUB_OUTPUT" + echo "Finishing the draft release $INPUT_TAG" + exit 0 + fi tag="$(git tag --points-at HEAD --list 'v*' | head -n1)" if [ -z "$tag" ]; then echo "No release for this push." @@ -89,7 +123,7 @@ jobs: desktop: name: Desktop (${{ matrix.name }}) needs: release - if: ${{ !cancelled() && (github.event_name == 'workflow_dispatch' || needs.release.outputs.published == 'true') }} + if: github.event_name == 'workflow_dispatch' || needs.release.outputs.published == 'true' runs-on: ${{ matrix.os }} environment: release permissions: @@ -232,7 +266,8 @@ jobs: archs="$(lipo -archs "$app/Contents/MacOS/gitcontext-desktop")" case "$archs" in *x86_64*arm64*|*arm64*x86_64*) echo "Architectures: $archs" ;; *) echo "::error::Not universal: $archs"; exit 1 ;; esac # Only system libraries: a Homebrew dylib would not exist on users' Macs. - if otool -L "$app/Contents/MacOS/gitcontext-desktop" | tail -n +2 | grep -v -E '^\s*(/System/|/usr/lib/)'; then + # Library lines are indented; unindented lines are file/architecture headers. + if otool -L "$app/Contents/MacOS/gitcontext-desktop" | grep -E '^[[:space:]]' | grep -v -E '^[[:space:]]*(/System/|/usr/lib/)'; then echo "::error::The binary links a library outside the OS"; exit 1 fi diff --git a/README.md b/README.md index 55cdaa5..aaa1674 100644 --- a/README.md +++ b/README.md @@ -16,20 +16,20 @@

- Try it in your browser · - Install · + Install the desktop app · + Try it in your browser · How it works

- GitContext comparing two branches: file tree with added, modified and removed files, prompt template, model picker and token usage + GitContext desktop app comparing two branches: file tree with added, modified and removed files, selected files with per-file token counts, and the total against the model's context window

--- **GitContext** turns the part of a repository you care about into one clean prompt for ChatGPT, Claude or any other LLM. Pick two branches, tick the files, and copy a single block with the file tree, the diffs and your instructions, with a live token count against the model's context window. -Everything runs on your machine. There is no server and no upload: the desktop app reads Git through native Rust (`libgit2`), and the web app runs Git in your browser with `isomorphic-git`. +Everything runs on your machine. There is no server and no upload: the desktop app reads Git through native Rust (`libgit2`). A web version runs Git in your browser with `isomorphic-git` if you want to try it before installing. It is similar to Repomix or GitIngest, with a few differences: @@ -41,6 +41,8 @@ It is similar to Repomix or GitIngest, with a few differences: ## Install +The **desktop app is the recommended way** to use GitContext: native Git through `libgit2`, direct access to your folders, saved workspaces, and no browser limits on large repositories. + ### macOS (Homebrew) ```sh @@ -59,23 +61,21 @@ The cask installs the same signed and notarized universal DMG (Apple silicon and All from the [latest release](https://github.com/kccarlos/gitcontext/releases/latest). -### Web app +### Try it in the browser -Open **[kccarlos.github.io/gitcontext](https://kccarlos.github.io/gitcontext/)** in Chrome or Edge (it needs the File System Access API). Nothing to install, and your code still never leaves the browser. +The web version at **[kccarlos.github.io/gitcontext](https://kccarlos.github.io/gitcontext/)** is a quick way to try GitContext without installing anything. Your code still never leaves the browser. It needs Chrome or Edge (File System Access API), and it is slower and less robust on large repositories than the desktop app, which is the one to use day to day. --- ## Screenshots -These show the web app. The desktop app has the same core workflow with a native folder picker, saved workspaces and a tabbed side panel. - - - + + - +
Side-by-side diff preview of a modified file
Diff preview: unified or side-by-side, before you include a file.
Main view in dark mode
Dark mode, following the system or set by hand.
Side-by-side diff preview of a modified file in the desktop app
Diff preview: unified or side-by-side, with word-level highlights, before you include a file.
Desktop app in dark mode
Dark mode, following the system or set by hand.
Selected files with per-file token counts and the Copy All Selected button
Selected files with per-file token counts, sorted by size, then one click to copy the whole prompt.
Settings tab with target model, prompt template, output options and token totals
Settings: target model and its context window, prompt templates, file tree and binary options, then one click to copy the whole prompt.
diff --git a/docs/screenshots/01-desktop-overview.png b/docs/screenshots/01-desktop-overview.png new file mode 100644 index 0000000..cdaa48d Binary files /dev/null and b/docs/screenshots/01-desktop-overview.png differ diff --git a/docs/screenshots/01-diff-overview.png b/docs/screenshots/01-diff-overview.png deleted file mode 100644 index b96b974..0000000 Binary files a/docs/screenshots/01-diff-overview.png and /dev/null differ diff --git a/docs/screenshots/02-desktop-diff-preview.png b/docs/screenshots/02-desktop-diff-preview.png new file mode 100644 index 0000000..2636135 Binary files /dev/null and b/docs/screenshots/02-desktop-diff-preview.png differ diff --git a/docs/screenshots/02-file-preview.png b/docs/screenshots/02-file-preview.png deleted file mode 100644 index 4c0ee29..0000000 Binary files a/docs/screenshots/02-file-preview.png and /dev/null differ diff --git a/docs/screenshots/03-desktop-dark.png b/docs/screenshots/03-desktop-dark.png new file mode 100644 index 0000000..92bcc22 Binary files /dev/null and b/docs/screenshots/03-desktop-dark.png differ diff --git a/docs/screenshots/03-output.png b/docs/screenshots/03-output.png deleted file mode 100644 index f0db7ea..0000000 Binary files a/docs/screenshots/03-output.png and /dev/null differ diff --git a/docs/screenshots/04-dark-mode.png b/docs/screenshots/04-dark-mode.png deleted file mode 100644 index 325e26d..0000000 Binary files a/docs/screenshots/04-dark-mode.png and /dev/null differ diff --git a/docs/screenshots/04-desktop-output.png b/docs/screenshots/04-desktop-output.png new file mode 100644 index 0000000..f928bba Binary files /dev/null and b/docs/screenshots/04-desktop-output.png differ diff --git a/packages/ui/src/components/PreviewModal.tsx b/packages/ui/src/components/PreviewModal.tsx index 40fa916..ce2a340 100644 --- a/packages/ui/src/components/PreviewModal.tsx +++ b/packages/ui/src/components/PreviewModal.tsx @@ -152,6 +152,9 @@ export default function PreviewModal({ open, onClose, path, status, baseLabel, c light: { codeFoldGutterBackground: 'transparent' }, dark: { codeFoldGutterBackground: 'transparent' }, } as any, + // The library's inline-flex + padding turns each Prism token inside a + // changed word into a flex item, splitting words ("c on st"). + wordDiff: { display: 'inline', padding: 0 }, }} /> )}