Three-pane git TUI on the SugarCraft stack — port of jesseduffield/lazygit. Status / branches / log laid out side-by-side, single-key stage / unstage, refresh, and ahead/behind branch summary.
composer require sugarcraft/sugar-stash
sugar-stash # run inside any git working tree| Key | Action |
|---|---|
Tab |
Cycle pane focus |
↑/↓ or k/j |
Move cursor in active pane |
s (status pane) |
Stage / unstage the highlighted entry |
a (status pane) |
Stage all files |
d (status pane) |
Discard changes to the highlighted file |
P (status pane) |
Open diff viewer for the highlighted file |
Space (branches pane) |
Checkout branch |
c |
Commit (type message, Enter to confirm, Esc to cancel) |
A |
Amend last commit |
n |
Create and switch to a new branch |
u |
Undo last operation |
Ctrl+r |
Redo last undone operation |
D (branches pane) |
Delete the selected branch |
M |
Merge selected branch into current |
r |
Open rebase menu (continue / abort / skip / cancel) |
S |
Open stash manager (list / apply / drop) |
V |
Cherry-pick — type a commit ref and press Enter |
w (branches pane) |
Open worktrees manager (list / add / remove) |
i |
Interactive rebase — select N commits, set actions |
R |
Refresh from disk |
? |
Context-sensitive help |
q / Esc |
Quit |
| Key | Action |
|---|---|
Space |
Stage current hunk |
↑/↓ or k/j |
Navigate between hunks |
Esc |
Close diff viewer |
| File | Role |
|---|---|
Git |
Concrete git shell-out — status --porcelain=v1 -b, for-each-ref, log --pretty=format:…, add, restore --staged. Throws RuntimeException on non-zero exit. |
GitDriver |
Interface for all git operations. Tests inject a fixture-backed driver so transition correctness is asserted without staging a real repo. |
Pane |
Enum — Status, Branches, Log — with next() for Tab cycling. |
App (Model) |
Owns the three lists + cursors + focus + overlays (stash/cherry-pick/worktrees/interactive-rebase). Pure-state — every key returns a fresh App. |
Renderer |
Pure view function — three Style::border(rounded) panes joined horizontally / vertically; the focused pane gets a brighter accent. |
StashEntry |
Immutable stash entry — index, sha, branch, message + stashRef() / displayLine(). |
StashManager |
Immutable stash list state — stashes, cursor, withCursor(), withStashes(), current(), count(). |
CherryPick |
Immutable cherry-pick state — collecting flag + commitRef accumulator + withChar()/cancel(). |
WorktreeEntry |
Immutable worktree entry — path, branch, isBare, HEAD. |
Worktrees |
Immutable worktree manager state — worktrees, cursor, adding/removing modes + path/branch collection. |
RebaseAction |
Enum — Pick, Reword, Edit, Squash, Drop. |
RebaseCommit |
Immutable rebase todo entry — sha, subject, action + withAction() / displayLine(). |
InteractiveRebase |
Immutable interactive-rebase state — commits todo list, cursor, selectingN/countInput, cycleAction(), dropCurrent(). |
SugarStash shells out to git directly via the system, so users keep their existing aliases, hooks, and signing config.
Note: All git operations are synchronous and block the event loop. This is by design for v1 — a TTY application naturally runs synchronously at the terminal. Async git operations are planned for v2 (see GitDriver interface for deprecation notes).
User-facing strings are internationalized via SugarCraft\Stash\Lang::t().
All translatable strings live in lang/en.php under the 'stash' namespace.
Available keys (lang/en.php):
| Key | Default string | Parameters |
|---|---|---|
git.spawn_failed |
git: failed to spawn |
— |
git.error |
git: {stderr} |
{stderr} |
cli.not_a_repo |
sugar-stash: not a git repository (no .git in {cwd}) |
{cwd} |
ui.error_prefix |
error: |
— |
status.clean |
clean working tree |
— |
branches.empty |
(no branches) |
— |
log.empty |
(empty log) |
— |
help.keyhints |
tab switch pane · j/k move · s stage/unstage · R refresh · q quit |
— |
To add a locale, copy lang/en.php to lang/<code>.php and translate the
values. The lookup chain follows SugarCraft\Core\I18n\T:
exact locale → base language → en → raw key.
Using the facade:
use SugarCraft\Stash\Lang;
$msg = Lang::t('status.clean'); // 'clean working tree'
$hint = Lang::t('help.keyhints'); // 'tab switch pane · ...'
$err = Lang::t('ui.error_prefix') . $error; // 'error: <msg>'Adding new translatable strings:
// In any source file:
use SugarCraft\Stash\Lang;
// Simple key:
$msg = Lang::t('status.clean');
// With placeholder:
$msg = Lang::t('git.error', ['stderr' => $stderr]);sugar-stash uses candy-fuzzy for Smith-Waterman scored matching infrastructure, laying the groundwork for future fuzzy stash search.
composer install
vendor/bin/phpunit
