From 320e7212efa5dc957819e97699a8d071fffd25c8 Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Wed, 2 Sep 2026 09:45:57 +0200 Subject: [PATCH] Release Git Tools 0.2.0 --- .gitea/workflows/verify.yml | 39 ++ CHANGELOG.md | 8 + README.md | 9 +- SOURCE.md | 4 +- THIRD_PARTY_NOTICES.md | 8 +- package-lock.json | 52 +- package.json | 11 +- playwright.config.ts | 22 +- public/CHANGELOG.md | 8 + public/LICENSES/npm-runtime-licenses.txt | 33 +- public/README.md | 9 +- public/SOURCE.md | 4 +- public/THIRD_PARTY_NOTICES.md | 8 +- public/sw.js | 2 +- public/toolbox-app.json | 20 +- src/components/HelpDialog.tsx | 6 +- src/components/Workbench.tsx | 486 +++++++++++++++++- src/git/tools.ts | 610 ++++++++++++++++++++++- src/styles.css | 2 +- src/toolbox/manifest.source.json | 38 +- src/version.ts | 2 +- tests/browser/app.spec.ts | 4 +- tests/browser/responsive.spec.ts | 18 + tests/components/app.test.tsx | 2 +- tests/git/tools.test.ts | 124 +++++ 25 files changed, 1453 insertions(+), 76 deletions(-) create mode 100644 .gitea/workflows/verify.yml create mode 100644 tests/browser/responsive.spec.ts diff --git a/.gitea/workflows/verify.yml b/.gitea/workflows/verify.yml new file mode 100644 index 0000000..b84a7cb --- /dev/null +++ b/.gitea/workflows/verify.yml @@ -0,0 +1,39 @@ +name: Verify + +on: + push: + branches: [main] + pull_request: + workflow_dispatch: + +concurrency: + group: verify-${{ gitea.repository }}-${{ gitea.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + verify: + runs-on: ubuntu-latest + timeout-minutes: 45 + env: + CI: "true" + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "22" + cache: npm + - name: Select declared npm version + run: npm install --global npm@11.17.0 + - name: Install dependencies + run: npm ci + - name: Audit runtime dependencies + run: npm audit --omit=dev --audit-level=moderate + - name: Check, test, and build + run: npm run check + - name: Install browser engines + run: npx playwright install --with-deps chromium firefox webkit + - name: Browser tests + run: npm run test:browser diff --git a/CHANGELOG.md b/CHANGELOG.md index 36e1840..98316f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.2.0 - 2026-09-02 + +- Added exact atomic text-patch application/reversal against explicit local + snapshots with deterministic JSON/ZIP export. +- Added layered ignore sources and tracked-state explanations, root + `.gitattributes` evaluation, and advisory release planning from Conventional + Commit headers. + ## 0.1.0 - 2026-09-01 - Added the initial local-first Git Tools workbench. diff --git a/README.md b/README.md index 6336090..83a9074 100644 --- a/README.md +++ b/README.md @@ -8,12 +8,15 @@ Git Tools is a standalone local-first application in the [add·ideas Toolbox](ht - Unified and `git diff` patch parsing with file status, modes, renames, hunk counts, line numbers, statistics, binary markers, and diagnostics - Unified patch authoring from before/after text with bounded LCS, context selection, path safety, and missing-final-newline markers -- Root `.gitignore` matching for comments, escapes, globstars, character classes, directory rules, negation, ordered explanations, and ignored-parent diagnostics +- Exact, atomic text-patch application and reversal against an explicit JSON repository snapshot, with downloadable JSON or deterministic ZIP output and no working-tree access +- Layered `.gitignore` matching for comments, escapes, globstars, character classes, directory rules, negation, global excludes, `.git/info/exclude`, parent-to-child nested files, explicit tracked state, winning-rule explanations, and ignored-parent diagnostics +- Root `.gitattributes` evaluation with set/unset/value/unspecified states, last-match provenance, and directory-pattern diagnostics - Strict SemVer 2.0 precedence plus common exact, comparator, partial, wildcard, hyphen, tilde, caret, AND, and OR ranges - Conventional Commit builder and structural lint with breaking-change/footer diagnostics - Keep-a-Changelog heading/section normalization, stable ordering, and duplicate-entry removal +- Advisory release planning from Conventional Commit headers: SemVer bump, grouped notes, tag name, invalid-input evidence, and review-only command sequence -Patch and before/after inputs are capped at 4 MiB with additional file, line, hunk, and diff-matrix bounds. `.gitignore` evaluates one repository-root file only; it does not read nested ignore files, global excludes, index state, or submodules. No workspace or `.git` directory is accessed. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) and [docs/PRIVACY-SECURITY.md](docs/PRIVACY-SECURITY.md). +Patch and before/after inputs are capped at 4 MiB with additional file, line, hunk, and diff-matrix bounds. Applied snapshots are text-only, exact-match, atomic, limited to 5,000 files/32 MiB, and never touch the working tree. Ignore inputs are an explicit local snapshot; command-line exclude rules and submodule repository boundaries are not modeled. Attribute macros and nested attribute files are not expanded. No workspace or `.git` directory is accessed. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) and [docs/PRIVACY-SECURITY.md](docs/PRIVACY-SECURITY.md). ## Development @@ -27,7 +30,7 @@ npm run test:browser ## Release -`npm run release:artifact` creates a deterministic `release/git-tools-0.1.0.zip` and checksum sidecar. +`npm run release:artifact` creates a deterministic `release/git-tools-0.2.0.zip` and checksum sidecar. ## Licence diff --git a/SOURCE.md b/SOURCE.md index e3abdc4..d726cde 100644 --- a/SOURCE.md +++ b/SOURCE.md @@ -1,7 +1,7 @@ # Corresponding source -The corresponding source for Git Tools 0.1.0 is available at: +The corresponding source for Git Tools 0.2.0 is available at: -https://git.add-ideas.de/lotobo/git-tools/src/tag/v0.1.0 +https://git.add-ideas.de/lotobo/git-tools/src/tag/v0.2.0 Build with Node.js 22, npm 11, `npm ci`, and `npm run release:artifact`. diff --git a/THIRD_PARTY_NOTICES.md b/THIRD_PARTY_NOTICES.md index 34e3918..d74e8c1 100644 --- a/THIRD_PARTY_NOTICES.md +++ b/THIRD_PARTY_NOTICES.md @@ -1,12 +1,12 @@ # Third-party notices -Git Tools 0.1.0 directly depends on these runtime packages: +Git Tools 0.2.0 directly depends on these runtime packages: | Package | Pinned version | Declared licence | | -------------------------------- | -------------: | ---------------- | -| `@add-ideas/toolbox-contract` | 0.2.3 | Apache-2.0 | -| `@add-ideas/toolbox-helpers` | 0.1.0 | GPL-3.0-or-later | -| `@add-ideas/toolbox-shell-react` | 0.2.3 | Apache-2.0 | +| `@add-ideas/toolbox-contract` | 0.3.0 | Apache-2.0 | +| `@add-ideas/toolbox-helpers` | 0.2.0 | GPL-3.0-or-later | +| `@add-ideas/toolbox-shell-react` | 0.3.0 | Apache-2.0 | | `react` | 19.2.8 | MIT | | `react-dom` | 19.2.8 | MIT | diff --git a/package-lock.json b/package-lock.json index 3616057..f6a775b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,22 +1,23 @@ { "name": "git-tools", - "version": "0.1.0", + "version": "0.2.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "git-tools", - "version": "0.1.0", + "version": "0.2.0", "license": "GPL-3.0-or-later", "dependencies": { - "@add-ideas/toolbox-contract": "0.2.3", - "@add-ideas/toolbox-helpers": "0.1.0", - "@add-ideas/toolbox-shell-react": "0.2.3", + "@add-ideas/toolbox-contract": "0.3.0", + "@add-ideas/toolbox-helpers": "0.2.0", + "@add-ideas/toolbox-shell-react": "0.3.0", + "fflate": "0.8.2", "react": "19.2.8", "react-dom": "19.2.8" }, "devDependencies": { - "@add-ideas/toolbox-testkit": "0.2.3", + "@add-ideas/toolbox-testkit": "0.3.0", "@eslint/js": "10.0.1", "@playwright/test": "1.62.1", "@testing-library/jest-dom": "6.9.1", @@ -42,24 +43,24 @@ } }, "node_modules/@add-ideas/toolbox-contract": { - "version": "0.2.3", - "license": "Apache-2.0", - "engines": { - "node": ">=20" - } + "version": "0.3.0", + "resolved": "https://git.add-ideas.de/api/packages/lotobo/npm/%40add-ideas%2Ftoolbox-contract/-/0.3.0/toolbox-contract-0.3.0.tgz", + "integrity": "sha512-dKrK7BjOFwqJaBfJuhKxZKIld4sH0AKjEn6a0yLnbdMUFY+fFv4VSLGV2tNSBD016gumc2iNqOjUj/ld7x4rtA==", + "license": "Apache-2.0" }, "node_modules/@add-ideas/toolbox-helpers": { - "version": "0.1.0", - "license": "GPL-3.0-or-later", - "engines": { - "node": ">=22" - } + "version": "0.2.0", + "resolved": "https://git.add-ideas.de/api/packages/lotobo/npm/%40add-ideas%2Ftoolbox-helpers/-/0.2.0/toolbox-helpers-0.2.0.tgz", + "integrity": "sha512-SdOqkw+P+3J3fa5iVkzb5P15rVepB001GNV21Oh8w0CZcVL+YRltgD/s+MVcTyrNijWQf3E5vtQON/3N2LLyKg==", + "license": "GPL-3.0-or-later" }, "node_modules/@add-ideas/toolbox-shell-react": { - "version": "0.2.3", + "version": "0.3.0", + "resolved": "https://git.add-ideas.de/api/packages/lotobo/npm/%40add-ideas%2Ftoolbox-shell-react/-/0.3.0/toolbox-shell-react-0.3.0.tgz", + "integrity": "sha512-74p6JzAOG0YCAKdlc1hLofV4ZIko7vb448S75cIiM88PKm93EHl5VD7g8YVyfM56Ui97UY9dmy+Whiq4sGzpsg==", "license": "Apache-2.0", "dependencies": { - "@add-ideas/toolbox-contract": "0.2.3" + "@add-ideas/toolbox-contract": "0.3.0" }, "peerDependencies": { "react": ">=18 <20", @@ -67,17 +68,16 @@ } }, "node_modules/@add-ideas/toolbox-testkit": { - "version": "0.2.3", + "version": "0.3.0", + "resolved": "https://git.add-ideas.de/api/packages/lotobo/npm/%40add-ideas%2Ftoolbox-testkit/-/0.3.0/toolbox-testkit-0.3.0.tgz", + "integrity": "sha512-4Fk+oSvZFspOMIXr8Xy040nhAaBsIQAzsGyXWSpjn3+k3yBKq7nB1r5zCHhsXzfdLzvPDAx2KcmSNOhM330D9w==", "dev": true, "license": "Apache-2.0", "dependencies": { - "@add-ideas/toolbox-contract": "0.2.3" + "@add-ideas/toolbox-contract": "0.3.0" }, "bin": { "toolbox-check": "dist/cli.js" - }, - "engines": { - "node": ">=20" } }, "node_modules/@adobe/css-tools": { @@ -2009,6 +2009,12 @@ } } }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "license": "MIT" + }, "node_modules/file-entry-cache": { "version": "8.0.0", "dev": true, diff --git a/package.json b/package.json index ed5005d..f59872c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "git-tools", - "version": "0.1.0", + "version": "0.2.0", "description": "Inspect and author Git interchange formats locally in the browser.", "license": "GPL-3.0-or-later", "author": "Albrecht Degering", @@ -39,14 +39,15 @@ "release:artifact": "npm run check && npm run test:browser && npm run package:release -- --force" }, "dependencies": { - "@add-ideas/toolbox-contract": "0.2.3", - "@add-ideas/toolbox-helpers": "0.1.0", - "@add-ideas/toolbox-shell-react": "0.2.3", + "@add-ideas/toolbox-contract": "0.3.0", + "@add-ideas/toolbox-helpers": "0.2.0", + "@add-ideas/toolbox-shell-react": "0.3.0", + "fflate": "0.8.2", "react": "19.2.8", "react-dom": "19.2.8" }, "devDependencies": { - "@add-ideas/toolbox-testkit": "0.2.3", + "@add-ideas/toolbox-testkit": "0.3.0", "@eslint/js": "10.0.1", "@playwright/test": "1.62.1", "@testing-library/jest-dom": "6.9.1", diff --git a/playwright.config.ts b/playwright.config.ts index 731da62..2ab6a22 100644 --- a/playwright.config.ts +++ b/playwright.config.ts @@ -15,7 +15,25 @@ export default defineConfig({ timeout: 180_000, }, projects: [ - { name: "chromium", use: { ...devices["Desktop Chrome"] } }, - { name: "firefox", use: { ...devices["Desktop Firefox"] } }, + { + name: "chromium", + testIgnore: /responsive\.spec\.ts/, + use: { ...devices["Desktop Chrome"] }, + }, + { + name: "firefox", + testIgnore: /responsive\.spec\.ts/, + use: { ...devices["Desktop Firefox"] }, + }, + { + name: "webkit", + testIgnore: /responsive\.spec\.ts/, + use: { ...devices["Desktop Safari"] }, + }, + { + name: "mobile-chromium", + testMatch: /responsive\.spec\.ts/, + use: { ...devices["Pixel 5"] }, + }, ], }); diff --git a/public/CHANGELOG.md b/public/CHANGELOG.md index 36e1840..98316f6 100644 --- a/public/CHANGELOG.md +++ b/public/CHANGELOG.md @@ -1,5 +1,13 @@ # Changelog +## 0.2.0 - 2026-09-02 + +- Added exact atomic text-patch application/reversal against explicit local + snapshots with deterministic JSON/ZIP export. +- Added layered ignore sources and tracked-state explanations, root + `.gitattributes` evaluation, and advisory release planning from Conventional + Commit headers. + ## 0.1.0 - 2026-09-01 - Added the initial local-first Git Tools workbench. diff --git a/public/LICENSES/npm-runtime-licenses.txt b/public/LICENSES/npm-runtime-licenses.txt index 45a7c95..d1e91be 100644 --- a/public/LICENSES/npm-runtime-licenses.txt +++ b/public/LICENSES/npm-runtime-licenses.txt @@ -1,5 +1,5 @@ ============================================================================== -@add-ideas/toolbox-contract@0.2.3 +@add-ideas/toolbox-contract@0.3.0 Declared licence: Apache-2.0 ============================================================================== --- LICENSE --- @@ -198,7 +198,7 @@ Declared licence: Apache-2.0 ============================================================================== -@add-ideas/toolbox-helpers@0.1.0 +@add-ideas/toolbox-helpers@0.2.0 Declared licence: GPL-3.0-or-later ============================================================================== --- LICENSE --- @@ -879,7 +879,7 @@ Public License instead of this License. But first, please read ============================================================================== -@add-ideas/toolbox-shell-react@0.2.3 +@add-ideas/toolbox-shell-react@0.3.0 Declared licence: Apache-2.0 ============================================================================== --- LICENSE --- @@ -1077,6 +1077,33 @@ Declared licence: Apache-2.0 limitations under the License. +============================================================================== +fflate@0.8.2 +Declared licence: MIT +============================================================================== +--- LICENSE --- +MIT License + +Copyright (c) 2023 Arjun Barrett + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. + ============================================================================== react@19.2.8 Declared licence: MIT diff --git a/public/README.md b/public/README.md index 6336090..83a9074 100644 --- a/public/README.md +++ b/public/README.md @@ -8,12 +8,15 @@ Git Tools is a standalone local-first application in the [add·ideas Toolbox](ht - Unified and `git diff` patch parsing with file status, modes, renames, hunk counts, line numbers, statistics, binary markers, and diagnostics - Unified patch authoring from before/after text with bounded LCS, context selection, path safety, and missing-final-newline markers -- Root `.gitignore` matching for comments, escapes, globstars, character classes, directory rules, negation, ordered explanations, and ignored-parent diagnostics +- Exact, atomic text-patch application and reversal against an explicit JSON repository snapshot, with downloadable JSON or deterministic ZIP output and no working-tree access +- Layered `.gitignore` matching for comments, escapes, globstars, character classes, directory rules, negation, global excludes, `.git/info/exclude`, parent-to-child nested files, explicit tracked state, winning-rule explanations, and ignored-parent diagnostics +- Root `.gitattributes` evaluation with set/unset/value/unspecified states, last-match provenance, and directory-pattern diagnostics - Strict SemVer 2.0 precedence plus common exact, comparator, partial, wildcard, hyphen, tilde, caret, AND, and OR ranges - Conventional Commit builder and structural lint with breaking-change/footer diagnostics - Keep-a-Changelog heading/section normalization, stable ordering, and duplicate-entry removal +- Advisory release planning from Conventional Commit headers: SemVer bump, grouped notes, tag name, invalid-input evidence, and review-only command sequence -Patch and before/after inputs are capped at 4 MiB with additional file, line, hunk, and diff-matrix bounds. `.gitignore` evaluates one repository-root file only; it does not read nested ignore files, global excludes, index state, or submodules. No workspace or `.git` directory is accessed. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) and [docs/PRIVACY-SECURITY.md](docs/PRIVACY-SECURITY.md). +Patch and before/after inputs are capped at 4 MiB with additional file, line, hunk, and diff-matrix bounds. Applied snapshots are text-only, exact-match, atomic, limited to 5,000 files/32 MiB, and never touch the working tree. Ignore inputs are an explicit local snapshot; command-line exclude rules and submodule repository boundaries are not modeled. Attribute macros and nested attribute files are not expanded. No workspace or `.git` directory is accessed. See [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md) and [docs/PRIVACY-SECURITY.md](docs/PRIVACY-SECURITY.md). ## Development @@ -27,7 +30,7 @@ npm run test:browser ## Release -`npm run release:artifact` creates a deterministic `release/git-tools-0.1.0.zip` and checksum sidecar. +`npm run release:artifact` creates a deterministic `release/git-tools-0.2.0.zip` and checksum sidecar. ## Licence diff --git a/public/SOURCE.md b/public/SOURCE.md index e3abdc4..d726cde 100644 --- a/public/SOURCE.md +++ b/public/SOURCE.md @@ -1,7 +1,7 @@ # Corresponding source -The corresponding source for Git Tools 0.1.0 is available at: +The corresponding source for Git Tools 0.2.0 is available at: -https://git.add-ideas.de/lotobo/git-tools/src/tag/v0.1.0 +https://git.add-ideas.de/lotobo/git-tools/src/tag/v0.2.0 Build with Node.js 22, npm 11, `npm ci`, and `npm run release:artifact`. diff --git a/public/THIRD_PARTY_NOTICES.md b/public/THIRD_PARTY_NOTICES.md index 34e3918..d74e8c1 100644 --- a/public/THIRD_PARTY_NOTICES.md +++ b/public/THIRD_PARTY_NOTICES.md @@ -1,12 +1,12 @@ # Third-party notices -Git Tools 0.1.0 directly depends on these runtime packages: +Git Tools 0.2.0 directly depends on these runtime packages: | Package | Pinned version | Declared licence | | -------------------------------- | -------------: | ---------------- | -| `@add-ideas/toolbox-contract` | 0.2.3 | Apache-2.0 | -| `@add-ideas/toolbox-helpers` | 0.1.0 | GPL-3.0-or-later | -| `@add-ideas/toolbox-shell-react` | 0.2.3 | Apache-2.0 | +| `@add-ideas/toolbox-contract` | 0.3.0 | Apache-2.0 | +| `@add-ideas/toolbox-helpers` | 0.2.0 | GPL-3.0-or-later | +| `@add-ideas/toolbox-shell-react` | 0.3.0 | Apache-2.0 | | `react` | 19.2.8 | MIT | | `react-dom` | 19.2.8 | MIT | diff --git a/public/sw.js b/public/sw.js index 160c63b..5bfae78 100644 --- a/public/sw.js +++ b/public/sw.js @@ -1,5 +1,5 @@ const CACHE_PREFIX = "git-tools-shell-"; -const CACHE_NAME = CACHE_PREFIX + "0.1.0"; +const CACHE_NAME = CACHE_PREFIX + "0.2.0"; const CORE = ["./", "./manifest.webmanifest", "./favicon.svg"]; self.addEventListener("install", (event) => { event.waitUntil( diff --git a/public/toolbox-app.json b/public/toolbox-app.json index 258735d..18dd994 100644 --- a/public/toolbox-app.json +++ b/public/toolbox-app.json @@ -3,7 +3,7 @@ "schemaVersion": 1, "id": "de.add-ideas.git-tools", "name": "Git Tools", - "version": "0.1.0", + "version": "0.2.0", "description": "Inspect and author Git interchange formats locally in the browser.", "entry": "./", "icon": "./favicon.svg", @@ -29,9 +29,25 @@ "crossOriginIsolated": false, "topLevelContext": false }, + "io": { + "accepts": [ + { "mediaType": "text/x-diff", "extensions": [".diff", ".patch"] }, + { + "mediaType": "text/plain", + "extensions": [".gitignore", ".gitattributes", ".txt"] + }, + { "mediaType": "application/json", "extensions": [".json"] } + ], + "produces": [ + { "mediaType": "text/x-diff", "extensions": [".patch"] }, + { "mediaType": "application/json", "extensions": [".json"] }, + { "mediaType": "application/zip", "extensions": [".zip"] } + ] + }, + "capabilities": { "required": [], "optional": [] }, "privacy": { "processing": "local", - "fileUploads": true, + "fileUploads": false, "telemetry": false, "label": "Inputs stay in this browser; nothing is uploaded." }, diff --git a/src/components/HelpDialog.tsx b/src/components/HelpDialog.tsx index e4887dc..d440743 100644 --- a/src/components/HelpDialog.tsx +++ b/src/components/HelpDialog.tsx @@ -31,7 +31,11 @@ export function HelpDialog({ × -

Inspect and author Git interchange formats locally in the browser.

+

+ Inspect and author Git interchange formats locally in the browser, apply + or reverse text patches against explicit JSON snapshots, explain ignore + and attribute rules, and draft an advisory release plan. +

All processing is performed in this browser. Imported data is treated as untrusted and bounded before parsing. diff --git a/src/components/Workbench.tsx b/src/components/Workbench.tsx index 9100b96..00b7d6b 100644 --- a/src/components/Workbench.tsx +++ b/src/components/Workbench.tsx @@ -4,15 +4,23 @@ import { CONVENTIONAL_TYPES, buildConventionalCommit, compareSemVer, + applyPatchToSnapshot, + createSnapshotZip, createUnifiedPatch, + evaluateGitAttributes, explainGitignore, + explainGitignoreWorkspace, lintConventionalCommit, normalizeChangelog, parseSemVer, parseUnifiedPatch, + planRelease, satisfiesSemVer, + type GitAttributeResult, type IgnoreResult, + type PatchApplicationResult, type PatchDocument, + type ReleasePlan, } from "../git/tools"; const patchSample = `diff --git a/src/example.ts b/src/example.ts @@ -62,7 +70,25 @@ const changelogSample = `# Changelog - Improve diagnostics `; -type Tab = "patch" | "author" | "ignore" | "versions" | "commit" | "changelog"; +const attributeSample = `* text=auto +*.sh text eol=lf +*.bat text eol=crlf +*.png -text binary +docs/** linguist-documentation +`; +const releaseCommitSample = `feat(git): apply text patches to snapshots +fix(ignore): explain nested precedence +docs: clarify privacy boundary`; +type Tab = + | "patch" + | "apply" + | "author" + | "ignore" + | "attributes" + | "versions" + | "commit" + | "release" + | "changelog"; function download(value: string, filename: string, type = "text/plain") { triggerBlobDownload( @@ -78,6 +104,13 @@ export function Workbench() { const [patch, setPatch] = useState(() => parseUnifiedPatch(patchSample), ); + const [snapshotSource, setSnapshotSource] = useState(() => + JSON.stringify({ "src/example.ts": beforeSample }, null, 2), + ); + const [reverseApply, setReverseApply] = useState(false); + const [applyResult, setApplyResult] = useState( + null, + ); const [before, setBefore] = useState(beforeSample); const [after, setAfter] = useState(afterSample); const [path, setPath] = useState("src/example.ts"); @@ -86,12 +119,25 @@ export function Workbench() { createUnifiedPatch(beforeSample, afterSample, "src/example.ts", 3), ); const [ignoreSource, setIgnoreSource] = useState(ignoreSample); + const [globalExcludes, setGlobalExcludes] = useState(""); + const [infoExcludes, setInfoExcludes] = useState(""); + const [nestedIgnores, setNestedIgnores] = useState( + '{\n "src/generated/.gitignore": "*\\n!README.md\\n"\n}', + ); + const [trackedPaths, setTrackedPaths] = useState(""); const [ignorePaths, setIgnorePaths] = useState( "dist/app.js\nnotes.log\n.keep.log\ncache/README.md\nsrc/main.ts", ); const [ignoreResults, setIgnoreResults] = useState(() => explainGitignore(ignoreSample, ignorePaths.split("\n")), ); + const [attributeSource, setAttributeSource] = useState(attributeSample); + const [attributePaths, setAttributePaths] = useState( + "src/main.ts\nscripts/release.sh\ndocs/guide.md\nassets/logo.png", + ); + const [attributeResults, setAttributeResults] = useState< + GitAttributeResult[] + >(() => evaluateGitAttributes(attributeSample, attributePaths.split("\n"))); const [versions, setVersions] = useState( "1.2.3\n1.3.0-beta.1\n1.3.0\n2.0.0\n0.9.8", ); @@ -110,6 +156,16 @@ export function Workbench() { const [normalized, setNormalized] = useState(() => normalizeChangelog(changelogSample), ); + const [releaseVersion, setReleaseVersion] = useState("0.2.0"); + const [releaseDate, setReleaseDate] = useState("2026-09-01"); + const [releaseCommits, setReleaseCommits] = useState(releaseCommitSample); + const [releasePlan, setReleasePlan] = useState(() => + planRelease({ + currentVersion: "0.2.0", + commits: releaseCommitSample.split("\n"), + date: "2026-09-01", + }), + ); const linted = useMemo(() => lintConventionalCommit(message), [message]); const versionRows = useMemo( () => @@ -166,10 +222,39 @@ export function Workbench() { ); } }; + const applyPatch = () => { + try { + const parsed = JSON.parse(snapshotSource) as unknown; + if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) + throw new Error( + "The repository snapshot must be a JSON path-to-text object.", + ); + const result = applyPatchToSnapshot( + patchSource, + parsed as Record, + reverseApply, + ); + setApplyResult(result); + setError(""); + } catch (reason) { + setError( + reason instanceof Error ? reason.message : "Patch application failed.", + ); + } + }; const testIgnore = () => { try { setIgnoreResults( - explainGitignore(ignoreSource, ignorePaths.split(/\r?\n/u)), + explainGitignoreWorkspace( + { + root: ignoreSource, + globalExclude: globalExcludes, + infoExclude: infoExcludes, + nested: JSON.parse(nestedIgnores || "{}") as Record, + tracked: trackedPaths.split(/\r?\n/u).filter(Boolean), + }, + ignorePaths.split(/\r?\n/u), + ), ); setError(""); } catch (reason) { @@ -178,6 +263,20 @@ export function Workbench() { ); } }; + const testAttributes = () => { + try { + setAttributeResults( + evaluateGitAttributes(attributeSource, attributePaths.split(/\r?\n/u)), + ); + setError(""); + } catch (reason) { + setError( + reason instanceof Error + ? reason.message + : ".gitattributes test failed.", + ); + } + }; const buildCommit = () => { try { setMessage( @@ -210,6 +309,22 @@ export function Workbench() { ); } }; + const buildReleasePlan = () => { + try { + setReleasePlan( + planRelease({ + currentVersion: releaseVersion, + commits: releaseCommits.split(/\r?\n/u), + date: releaseDate, + }), + ); + setError(""); + } catch (reason) { + setError( + reason instanceof Error ? reason.message : "Release planning failed.", + ); + } + }; return (

@@ -233,18 +348,20 @@ export function Workbench() { {( [ "patch", + "apply", "author", "ignore", + "attributes", "versions", "commit", + "release", "changelog", ] as const ).map((value) => ( + +

+ Supply a JSON object whose keys are repository-relative paths and + values are complete text files. Every hunk must match exactly; + binary patches, unsafe paths, partial and fuzzy application are + rejected, and failure leaves the last valid result unchanged. +

+