Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,8 @@ jobs:
libayatana-appindicator3-dev \
librsvg2-dev \
libssl-dev \
build-essential
build-essential \
git-lfs

- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion PRD.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ It will be built as a **Tauri 2** application with a **Rust** git backend and a
| App shell | **Tauri 2** | True cross-platform, small bundles (~10MB vs Electron's ~100MB+), native webview per OS, signed installers built-in. |
| Backend language | **Rust** | Speed, safety, and a great Git ecosystem (`gix`, `git2`). Pairs natively with Tauri. |
| Git engine (read) | **`gix` (gitoxide)** | Pure-Rust, modern, dramatically faster than libgit2 for log/diff/status on large repos. |
| Git engine (write) | **`git2` (libgit2)** + shell-out to `git` | Use `git2` for commit/branch/merge/rebase where stable. Shell out to the user's `git` binary for ops that need it: interactive rebase, GPG signing, Git LFS, Git-flow, hooks. This is what Sublime Merge and Tower do — it's the right call. |
| Git engine (write) | **`git2` (libgit2)** + shell-out to `git` | Use `git2` for index/branch writes where stable. Commit/amend always use system Git for hook parity. Shell out to the user's `git` binary for ops that need it: interactive rebase, GPG signing, Git LFS, Git-flow, hooks. This is what Sublime Merge and Tower do — it's the right call. |
| Frontend | **React + TypeScript** | Required by `@pierre/diffs` and `@pierre/trees`. |
| Diff & code rendering | **`@pierre/diffs`** | Split/stacked diffs, merge conflict UI, line selection, annotations, Shiki themes. Covers most of §6.3. |
| File tree | **`@pierre/trees`** | Virtualized (handles 100k+ files), Git status badges built in, drag-and-drop, search, keyboard nav, accessible. Covers §6.5. |
Expand Down
20 changes: 19 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,20 @@ the resolved app appearance automatically.

## Features

- **Repository size controls** — clone a chosen branch with optional depth,
single-branch fetching, on-demand file contents (`blob:none`), and recursive
submodules. Inspect clone scope and download more or full history from the
network menu or palette. Sparse checkout selects cone directories, distinguishes
excluded files from deletions, and preserves external sparse indexes on reads.
Selection changes refuse dirty trees and ignored-file removal.

- **Git LFS** — repository setup, tracking patterns, object/transfer status,
downloads/uploads and server locks from the sidebar and command palette.
Whole-file staging, checkout, discard and hard reset honor LFS filters;
history is never migrated.
- **Submodule lifecycle** — add, remove, deinitialize, synchronize URLs, and
inspect nested modules in pages. Network work is cancellable; dirty module
contents, ignored files and unrecorded commits block removal/deinitialization.
- **Responsive refreshes** — repository updates coalesce during bursts of
agent edits, hidden diff panes load patches when opened, and Files reuses
its inventory until paths or ignore rules change. Workspace scans run with
Expand Down Expand Up @@ -210,7 +224,11 @@ the resolved app appearance automatically.
(fetch-first for remote bases) and copies gitignored setup files listed in
`.worktreeinclude` (`.env`, local settings) so agents can run out of the
box. Stale entries whose directories are already gone prune immediately.
- **Everyday Git** — stage, unstage, or recoverably discard whole change
- **Everyday Git** — hook-aware signed/unsigned commit and amend with checkout
session drafts and bounded output; effective repository author/committer identity
with local overrides in Settings → Git; repository/worktree signing settings,
per-operation signing choices, and signed-tag creation/verification; stage,
unstage, or recoverably discard whole change
blocks or individually selected lines inline in the diff; bulk tree actions
include every selected file and every changed file beneath selected folders;
initialize a repository with an initial branch, optional
Expand Down
64 changes: 61 additions & 3 deletions ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -2155,16 +2155,21 @@ and Store certification remain external gates.
+ task breakdown in TASKS.md. Pre-1.0 guardrails (opaque repo paths,
everything through the `commands.rs` seam) are active now.
- Git-flow (start/finish feature/release/hotfix; shells out to `git-flow`)
- Git LFS (status badges + progress)
- GPG / SSH commit signing UI
- ☑ Git LFS — filter-correct staging/checkout/discard/hard reset and explicit
setup, patterns, object/transfer status and server locks (`LfsDialog`,
`Repo::lfs_action`); real Git fixtures and native dialog verification pass.
- ☑ GPG / SSH commit signing UI (repository/worktree settings and per-operation
choices; signed-tag creation and verification, 2026-09-06).
- Selectable beta updater channel (1.0 remains pinned to the signed stable
GitHub Releases channel)
- Opt-in product telemetry only if a concrete post-1.0 decision, disclosure,
retention policy, and backend justify adding it
- Guided Git bisect
- Sparse checkout (cone mode first)
- Patch import/mailbox and Git bundle workflows
- Expanded submodule lifecycle (add/remove/deinit/sync/URL/nested status)
- ☑ Expanded submodule lifecycle — guarded add/remove/deinit/sync/URL, paged
nested inspection and cancellable updates (`SubmoduleDialog`); real Git
preservation fixtures and native lifecycle/keyboard checks pass.
- Repository/ref/file custom actions with safe argv templates
- **CLI companion binary (`strand`)** — `strand <path>` opens the repo
in the app; `strand diff/log/status/review --json` gives AI agents
Expand Down Expand Up @@ -2808,8 +2813,61 @@ implementation rows while the July audit is labeled historical. This is a
planning update, not a claim that these features shipped; existing local Git,
GitHub/Azure review, Workbench and performance work retain their own status.

**Sparse checkout and clone controls shipped (2026-09-06, F08/F09):** Clone now
offers branch, independent depth/single-branch choices, blob filtering and
recursive submodules. Repository history controls inspect external clones and
deepen/unshallow with progress and cancellation. Cone sparse selections can be
inspected, changed and disabled, with dirty/ignored-file guards and compatible
reads and mutations for external sparse indexes. Excluded files no longer look
deleted. Windows native UI verification and 13 integration fixtures cover
these workflows; normal repositories retain their existing in-process paths.
See `docs/sparse-clone-verification.md` for validation and supported boundaries.

**LFS implementation shipped (2026-09-06):** Real fixtures exposed raw asset
blobs from git2 staging. LFS paths now stage in one literal NUL-delimited Git
batch; checkout, discard and hard reset honor required filters, and partial
pointer patches are rejected. The lazy sidebar/palette dialog exposes setup,
patterns, objects, transfers and server locks with bounded output and process
tree cancellation. Exact pointer/commit/network/lock fixtures and native
setup, staging, keyboard, cancellation and recovery checks pass.

**Submodule lifecycle shipped (2026-09-06):** Added explicit add/remove/deinit,
URL changes/sync, lazy paged nested inspection, repository opening and
cancellable updates through the sidebar and palette. Removal and deinit retain
Git history and refuse dirty, unrecorded or ignored local data, including nested
modules. Real Git and native UI checks cover registration/index preservation,
network cancellation, URL edits, nesting, confirmations and reinitialization.

---

**Commit hook parity shipped (2026-09-06, F01):** Signed and unsigned commit/amend
now run system Git’s applicable hooks and honor custom hooksPath, rejection
and rewritten messages. Checkout session drafts survive rejection/navigation;
expandable output retains bounded diagnostics. Native Ctrl+Enter flows and
core/store regressions passed; the loaded-host no-hook cost is recorded in
`docs/hooks-identity-signing-validation-2026-09-06.md`. Index/status paths are
unchanged; the commit-policy exception is explicit in PRD and learnings.


**Repository identity shipped (2026-09-06, F02):** Settings → Git and its palette
entry show effective author/committer identity with per-field source/scope.
Individual local name/email overrides can be saved or removed without editing
global or included files. Linked worktrees share local values; existing
worktree overrides remain effective. Conditional, two-repository and linked
worktree fixtures passed; native settings save/remove and repository switching
were exercised with isolated fixtures.


**Signing controls and signed tags shipped (2026-09-06, F03):** Repository and
enabled worktree settings show effective signing defaults, format, key and SSH
allowed-signers sources. Commit/amend and tag forms offer inherited, signed or
unsigned operations without changing defaults. Signed tags require an
annotation; lazy verification displays the immutable object and Git trust
diagnostics. Real GPG/SSH fixtures cover hooks, amend, linked worktrees,
unsigned overrides, tampering and failed signers. Native Windows settings,
commit/amend and palette tag flows passed; validation and platform limits are
recorded in `docs/hooks-identity-signing-validation-2026-09-06.md`.

## Cross-cutting tracks (run in parallel with all milestones)

**Performance audit kick (2026-09-06):** Rechecked `main` at `8e83c8c` on
Expand Down
52 changes: 35 additions & 17 deletions TASKS.md
Original file line number Diff line number Diff line change
Expand Up @@ -89,31 +89,44 @@ Detailed comparison and sequencing: [`docs/git-client-1.0-audit.md`](./docs/git-
(`docs/git-client-feature-audit-2026-09-06.md`: 19 missing/partial feature
families, code evidence, priorities, fallbacks, and acceptance criteria).
Priorities below are current recommendations, not historical PRD release gates.
- **F01 / P1 — Hook parity for unsigned commit/amend.** Resolve the recorded
- **F01 / P1 — Hook parity for unsigned commit/amend.** Resolve the recorded
git2 commit-policy versus Git-hook contract tension; honor `core.hooksPath`,
rejection and message rewriting, preserve drafts and bounded diagnostics,
and measure the no-hook path (`commit.rs`; signed commits already use Git).
- ☐ **F02 / P1 — Effective repository identity and scoped overrides.** Show
and measure the no-hook path (`Repo::commit`, bounded `git_output`, checkout
`commitDrafts`; evidence in `docs/hooks-identity-signing-validation-2026-09-06.md`).
- ☑ **F02 / P1 — Effective repository identity and scoped overrides.** Show
the current author/committer identity, set/remove repo-local name/email
without changing global/conditional config, and verify linked worktrees.
- ☐ **F03 / P1 — Signing controls and signed tags.** Keep configured commit
without changing global/conditional config, and verify linked worktrees
(`repository_identity` / `repo_set_identity`, Settings → Git source display).
- ☑ **F03 / P1 — Signing controls and signed tags.** Keep configured commit
signing/verification; add scoped format/key controls and signed-tag creation
with agent delegation and visible signing failures.
- ☐ **F04 / P1 — LFS compatibility and management.** First prove pointer/filter
correctness across single/bulk staging, checkout, commit and network flows;
then add setup/tracking/status/locks/progress. System-Git networking alone
does not establish end-to-end LFS support.
- ☐ **F05 / P1 — Submodule lifecycle.** Extend existing open/status/init/update
with add/remove/deinit/sync/URL/nested inspection; verify dirty-state handling,
`.gitmodules` and index changes, plus cancellable network operations.
with agent delegation and visible signing failures (`signing_settings` /
`set_signing_config`, commit/tag `SigningChoice`, `TagVerificationDialog`;
real GPG/SSH and native Windows evidence in the F01–F03 validation note).
- ☑ **F04 / P1 — LFS compatibility and management.** Filter-aware single/bulk
staging, discard, checkout and hard reset; exact pointer/commit/push/pull and
missing-filter fixtures pass (`lfs.rs`). Local setup, patterns, object/transfer
status, bounded locks and cancellable transfers are exposed in `LfsDialog`.
Real lock-API fixtures and native setup/staging/palette/cancellation/recovery
checks pass; no eager LFS network or status subprocesses.
- ☑ **F05 / P1 — Submodule lifecycle.** Add/remove/deinit/sync/URL changes,
paged nested inspection and cancellable updates (`SubmoduleDialog`,
`Repo::{submodule_action,submodule_children}`). Real Git transport, dirty,
ignored and nested files, unrecorded commits and `.gitmodules`/index
preservation fixtures pass. Native lifecycle, keyboard/palette, module
opening, destructive guards and cancellation checks pass.
- ☐ **F07 / P2 — Patch/mailbox/bundle import and interchange.** Build on exact
patch export and hunk apply with preview/validation, explicit targets,
mailbox continue/skip/abort and bundle prerequisites/ref summaries.
- ☐ **F08 / P2 — Sparse checkout.** Cone-directory inspect/change/disable and
compatibility fixtures for excluded paths, dirty trees and sparse indexes.
- ☐ **F09 / P2 — Advanced clone options.** Branch, depth/single-branch,
- ☑ **F08 / P2 — Sparse checkout.** Cone-directory inspect/change/disable and
compatibility fixtures for excluded paths, dirty trees and sparse indexes
(`Repo::set_sparse_checkout`, `SparseCheckoutDialog`, `sparse_checkout.rs` fixtures).
- ☑ **F09 / P2 — Advanced clone options.** Branch, depth/single-branch,
partial-clone filter and recursive-submodule options; deepen/unshallow,
progress/cancellation, and safe argument construction.
progress/cancellation, and safe argument construction (`clone_with_options`,
`repo_expand_history`, `CloneScopeDialog`; `docs/sparse-clone-verification.md`).
Follow-up: verify real LFS clone checkout on Git 2.45.1 / LFS 3.5.1;
see `docs/git-assets-validation-2026-09-06.md` for the hook rejection.
- ☐ **F10 / P2 — Guided bisect.** Good/bad/skip, operation progress, external
session resume and safe reset to the original checkout; defer test-command
execution until the manual workflow is complete.
Expand Down Expand Up @@ -1473,6 +1486,11 @@ community plugins, performance and platform certification from Git feature gaps.
and focus-restore to the opener on close (captured pre-`autoFocus`).

### Cross-cutting
- ☐ Investigate Windows watcher burst timing: unchanged
`watch::tests::debounce_collapses_a_burst_into_one_callback` observed two
callbacks instead of one in the 2026-09-06 F01–F03 final full run and isolated
retry, after earlier full-suite passes. Reproduce and distinguish OS event
delivery from debounce/test timing before changing production behavior.
- ☑ Resizable panes everywhere (`react-resizable-panels`); sizes
persisted per-region via `autoSaveId` (`strand:body`, `strand:lc-main`,
`strand:lc-files`)
Expand Down
3 changes: 3 additions & 0 deletions crates/strand-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,6 @@ serde.workspace = true
serde_json.workspace = true
thiserror.workspace = true
tracing.workspace = true

[target.'cfg(unix)'.dependencies]
libc = "0.2"
17 changes: 17 additions & 0 deletions crates/strand-core/src/apply.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,23 @@ impl Repo {
ApplyTarget::Workdir => (patch.to_owned(), git2::ApplyLocation::WorkDir),
};
let diff = git2::Diff::from_buffer(buf.as_bytes())?;
for delta in diff.deltas() {
for file in [delta.old_file(), delta.new_file()] {
if let Some(path) = file.path() {
if self.is_lfs_path(path)? {
return Err(crate::Error::Other("LFS files must be staged, unstaged or discarded as a whole file; partial patches would corrupt the pointer.".into()));
}
}
}
}
if self.sparse_enabled() {
let mut args = vec!["apply", "--whitespace=nowarn"];
if matches!(target, ApplyTarget::Index | ApplyTarget::IndexReverse) { args.push("--cached"); }
if matches!(target, ApplyTarget::IndexReverse | ApplyTarget::WorkdirReverse) { args.push("--reverse"); }
args.push("-");
self.sparse_git(&args, Some(patch.as_bytes()))?;
return Ok(());
}
repo.apply(&diff, location, None)?;
Ok(())
}
Expand Down
Loading
Loading