Worktrees are created with `git worktree add --no-checkout` and populated
with `git reset --hard`, neither of which runs git's post-checkout hook.
Invoke the hook explicitly after population with git's standard arguments
(null ref previous HEAD, checked-out HEAD, flag 1) and the worktree as cwd,
mirroring `git worktree add` without --no-checkout.
A missing or non-executable hook is skipped (matching git) and a failing
hook is logged as a warning, never failing worktree creation or session
bootstrap. Applied to both the web server git service and the VS Code
runtime's git service.
Follow-up to #2629, which stopped the walkthrough from comparing against a
branch that does not exist. The same guessing, and the same near-misses in how
the answer was applied, were left elsewhere:
- The default branch travelled as `rootBranchHint`, whose documented meaning is
"the branch the project root worktree is on". It gets its own option, because
a parameter that means two things is one the next caller gets wrong.
- A candidate equal to the branch being compared is skipped. In a plain checkout
the root hint *is* the current branch, so it won every time and produced a
comparison with itself; the repository default now wins there.
- The Changes and pull-request surfaces read the default branch too. A pull
request opened against a branch that does not exist is a worse failure than a
walkthrough that will not generate.
- `hasResolvableBaseBranch` matched `origin/feature/main` for a base of `main`,
passing the check and then failing the comparison it exists to prevent.
- `getRangeDiff` promoted only `origin/<base>`. A base carried by any other
remote stayed a bare name, which git resolves against refs/heads and nowhere
else, so it failed exactly as before.
- `getBranches` dropped every branch of a remote that did not answer, turning
"we could not ask" into "these branches are gone" — offline, that silently
removed comparisons that work fine against local remote-tracking refs.
- A remote with no `remote/HEAD` is asked once with `ls-remote --symref` rather
than falling back to the guess this data exists to replace.
The `defaultBranches` contract was documented under the status response; it
belongs to the branches response, which now has a section of its own.
Untracked symlinks now show as link entries in diff output.
File diffs display symlink targets instead of following them.
Added tests for patch and split diff behavior.
simple-git without baseDir inherits process.cwd(), so launching
OpenChamber from a neutral directory (e.g. $HOME) and opening a git
project elsewhere produced repeated "not a git repository" status
errors and could abort project/session enumeration. Always require an
explicit baseDir, soft-handle non-repo GitErrors on status/check
routes, and cover non-git, foreign-cwd, and nested-repo cases.
Co-authored-by: Serhii Dziupin <makeittech@users.noreply.github.com>
Add a root-session action that creates a generated worktree from the session directory's current branch, transfers uncommitted changes, and moves the parent session plus its descendants through OpenCode's control-plane API.
Reuse existing project/worktree topology and quick-create behavior, keep the UI non-blocking, reconcile live and global session state across directories, and roll back partial moves and failed worktree creation safely.
Split worktree bootstrap readiness into directory-created, git-ready, and setup-ready phases across web and VS Code. Session moves wait for Git readiness while existing setup-aware flows continue waiting for full setup completion, and worktree removal is serialized with active bootstrap tasks.
Expose the move only for idle root sessions, show localized progress and explanatory tooltips in the sidebar, and keep pending/ready worktree metadata synchronized with authoritative session attachments to avoid stale setup indicators.
Add coverage for control-plane payloads, session-state migration, bootstrap phase ordering and compatibility, removal races, progress metadata, and fast-ready attachment races.
Retries transient index.lock conflicts during worktree population
Removes unchanged stale locks automatically and continues bootstrap
Adds coverage for stale lock recovery
Add per-hunk staging, unstaging, and discarding to the Changes diff
view, so a single change region inside a file can be acted on in
isolation instead of forcing whole-file stage/revert. The change is
wired end-to-end across the web server, the shared UI runtime API
contract, and the VS Code extension, with Electron inheriting the web
path unchanged (it boots the server in-process).
Server
------
- New `applyHunk(directory, filePath, { patch, action })` in
packages/web/server/lib/git/service.js. It resolves the repository
context and validates the file path with the same helpers used by
stageFiles/unstageFiles (resolveGitFileContext +
validateRepositoryFilePaths), then writes the single-hunk patch to a
temporary file in the OS temp dir (never inside the repo, so it
cannot show up as an untracked file) and runs `git apply` with flags
chosen per action:
stage -> git apply --cached (working tree -> index)
unstage -> git apply --cached --reverse (index -> working tree)
discard -> git apply --reverse (revert in working tree)
A `git apply --check` runs first with the same flags, so a stale
hunk that no longer applies fails with a clear "Hunk no longer
applies - refresh and try again" message instead of leaving a
partial mutation. The patch's target path is parsed and must match
the requested file (with /dev/null tolerated for new/deleted files),
preventing a patch from silently targeting a different path. The
whole operation runs inside withGitIndexMutationQueue to avoid
racing with concurrent stage/unstage. The temp file is removed in a
finally block.
- New `POST /api/git/apply-hunk` route in routes.js, registered
alongside stage/unstage. Validates directory, path, non-empty patch,
and action before delegating.
- DOCUMENTATION.md updated with the new service entry.
Patch extraction
----------------
- packages/ui/src/lib/diff/patchFileDiff.ts gains
splitPatchIntoHunks(patch) and extractHunkPatch(patch, hunkIndex).
They keep the original file header (diff --git / index / --- / +++)
and emit exactly one @@ hunk per standalone patch, which is what
`git apply` expects. Each emitted patch is guaranteed to end with a
trailing newline (without it git apply reports "corrupt patch").
Runtime API contract
--------------------
- GitAPI (packages/ui/src/lib/api/types.ts) gains optional
stageGitHunk / unstageGitHunk / revertGitHunk, matching the
stageGitFiles? / unstageGitFiles? precedent so runtimes that do not
support it degrade gracefully.
- gitApi.ts delegates to the registered runtime git API, falling back
to gitApiHttp, exactly like the existing whole-file helpers.
- gitApiHttp.ts posts to /api/git/apply-hunk.
- Web runtime composes the three methods in packages/web/src/api/git.ts.
VS Code parity
--------------
- packages/vscode/src/gitService.ts adds applyGitHunk(), implemented
natively with the existing execGit helper + a temp patch file +
`git apply` (--cached / --cached --reverse / --reverse), mirroring
the server's --check-first safety and temp-file cleanup.
- bridge-git-runtime.ts handles the new api:git/apply-hunk bridge
message; webview/api/git.ts sends it. VS Code users get identical
stage/unstage/discard-hunk behavior.
UI
--
- New DiffHunkActions component renders a compact per-hunk strip
above each expanded file diff in the Changes view. Each hunk chip
shows its +additions / -deletions counts and offers:
working scope -> Stage + Discard
staged scope -> Unstage
Clicking extracts that hunk's standalone patch via
extractHunkPatch(patch, hunkIndex) and calls the runtime git API.
Because the chip index comes directly from fileDiff.hunks[] and the
patch is sliced in the same order, the hunk the user sees is always
the hunk that gets applied. While any action is in flight all buttons
disable to prevent conflicting concurrent mutations; the per-hunk
spinner reflects in-flight state.
- DiffView wires DiffHunkActions into InlineDiffViewer (text diffs
only; binary/image and full-file-content modes are excluded since
they have no patch). MultiFileDiffEntry passes directory/staged
through and handles onHunkApplied by bumping the diff reload nonce
(so the file's diff re-fetches and the affected hunk disappears)
and refreshing git status (so file counts and the staged/changed
scope update). Hunk actions are therefore available wherever the
default patch-context diff is shown.
i18n
----
- 10 new keys (diffView.hunk.*) added to all 9 locales (en, es, fr,
ko, pl, pt-BR, uk, zh-CN, zh-TW), including stage/unstage/discard
labels, tooltips with the hunk index, a stale-hunk error message,
and an unsupported-runtime fallback.
Tests
-----
- packages/ui/src/lib/diff/patchFileDiff.test.ts covers
splitHunks/extractHunkPatch: multi-hunk split, header preservation,
single-hunk and empty patches, out-of-range indices.
- service.test.js adds an applyHunk suite that builds real temp repos
with two separate hunks and verifies: staging one hunk leaves the
other unstaged, discarding reverts only the targeted hunk in the
working tree, unstaging removes only one hunk from the index, and a
retargeted patch (different file path) is rejected. Also covers
invalid-action / missing-hunk-header validation.
- packages/web/src/api/git.test.ts mock completed with the new methods
(and previously-missing exports that prevented the test from
loading) and asserts the three hunk methods are exposed.
- routes.test.js continues to pass under bun.
CHANGELOG updated under [Unreleased].
* feat(types): add parents to GitLogEntry and new commit action types
* feat(git): add parent hashes and --all flag to getLog
* fix(git): move record separator to start of log format string
* feat(git): add checkoutCommit server function and route
* feat(git): add cherryPick server function and route
* feat(git): add revertCommit server function and route
* feat(git): add resetToCommit server function and route
* fix(tests): make git service tests branch-name portable, add error path tests
* feat(client): add checkoutCommit, cherryPick, revertCommit, resetToCommit API wrappers
* feat(git-graph): add lane assignment algorithm with tests
* feat(git-graph): add GitGraphSegment per-row SVG renderer
* feat(i18n): add locale strings for git graph action buttons
* fix(git-graph): handle lane convergence, fix SVG path coords, add connector tests
* feat(git-graph): add ref badges and action buttons to HistoryCommitRow
* fix(git-graph): add loading guards to reset actions, use theme tokens for ref badges
* fix(git-graph): conditional hooks, stale graph log, conflict handling, i18n
* fix(types): replace toBeDefined with toBeTruthy, fix toast API usage
* fix(lint): remove unused variables
* fix(git-graph): fix SVG height causing 150px row spacing
* fix(git-graph): smooth bezier curves, fill row height, round line caps
* fix(git-graph): non-scaling-stroke fixes bezier white spaces, sort curves on top
* fix(git-graph): remove viewBox scaling, match SVG height to actual row height
* fix(git-graph): ResizeObserver tracks actual row height, eliminates SVG height mismatch
* feat(git-graph): replace SVG with Canvas for graph rendering
* fix(git-graph): isolate canvas from flex layout to prevent replaced-element height leak
* feat(git-graph): align action buttons, add confirmation popups for all actions
* fix(git-graph): address code review findings CR-001 through CR-005
- CR-001: VS Code getGitLog now forwards 'all' option and parses %P parents
- CR-002: VS Code bridge/gitService implement checkoutCommit, cherryPick,
revertCommit, resetToCommit with conflict detection and hard-reset guard
- CR-003: server-side commit hash validated with /^[0-9a-fA-F]{7,40}$/
in both routes.js and service.js; 12 new rejection tests added
- CR-004: cherry-pick/revert conflict path now refreshes fetchStatus/
fetchBranches/fetchLog; conflict toast uses i18n keys in all 7 locales
- CR-005: corrected O(n) comment to O(n x lanes)
* fix(i18n): add zh-TW locale and common.language.traditionalChinese key to all locales
upstream/main added zh-TW.ts after branch diverged; CI type-check fails
when PR is merged because zh-TW.ts was missing all gitView.history.actions.*
keys and loadMore/loadingMore. Also adds common.language.traditionalChinese
to en.ts and all 6 non-English files to match upstream en.ts.
* fix: harden git history actions
* feat: split git history graph view
* chore: remove git graph planning docs
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
* fix: make upstream sync actions target the selected remote
Ensure fetch and pull actually honor upstream selection so fork maintenance works from the Git sidebar, and surface upstream branch status alongside the primary origin-tracking indicators.
* feat: add Windows Electron desktop foundation
* fix(electron): stabilize Windows desktop packaging
* fix(electron): stabilize Windows desktop chrome
Use native Windows titlebar behavior with an Alt-accessible hidden menu, and harden Windows dev command launching so the desktop app follows platform conventions.
* fix(electron): stabilize Windows dev startup
* fix(electron): clarify desktop artifact names
* fix(electron): harden Windows desktop release and launch
* fix(electron): address Windows release review
* fix(electron): point updater and release links to org repo
* Fix Windows settings persistence fallback
* Fix Windows Electron dev startup
* Add Windows Electron window controls
* Fix Windows Electron install and opencode launch
* fix: resolve git status for repositories without upstream
Fixes repository detection stuck on Checking repository
Handles git status when no upstream is configured
Adds regression coverage for git status loading
* Add Windows app menu button
* fix: preserve file editor line endings
* ci: add desktop release smoke workflow
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>
* chore: add .worktrees/ to gitignore for worktree workflow
* fix: resolve remote-tracking base ref in getLog for PR description generation
getLog was calling git log <base>..<head> with a bare branch name that
often doesn't exist locally (e.g. main when only origin/main is present),
causing a fatal 'unknown revision' error and HTTP 500.
Apply the same origin/<base> resolution already used in getRangeDiff and
getRangeFiles: check refs/remotes/origin/<base> first and prefer that ref
if it exists.
Also fix getGitLog in gitApiHttp.ts to read the JSON error body on
failure instead of falling back to response.statusText, so the actual
git error message surfaces in the toast instead of 'Internal Server Error'.
* fix(git): use local-first ref resolution in getLog and port to VS Code
- Replace unconditional origin/<from> preference in getLog() with a
local-first fallback: prefer the local ref, only use origin/<from>
when the local ref cannot be resolved, and pass through unchanged
when neither resolves so git surfaces a meaningful error.
- Extract the logic into an exported resolveBaseRefForLog(from, checkRef)
helper so it is unit-testable without a real git repo.
- Add service.test.js with 6 cases covering local-wins, origin-fallback,
neither-exists passthrough, and falsy/empty inputs.
- Port the same local-first resolution to packages/vscode/src/gitService.ts
getGitLog() to close the cross-runtime parity gap; also handles
from-only ranges as from..HEAD, matching the web service contract.
* fix(vscode): add missing to-only range branch in getGitLog
When only 'to' is supplied (no 'from'), the web service appends it as a
positional git-log argument. The VS Code port was missing this branch and
silently returned unbounded history instead. Adds the else-if to restore
full cross-runtime parity.
* fix(vscode): surface git log errors
---------
Co-authored-by: Bohdan Triapitsyn <artmore@protonmail.com>