f4286edff2
hub sync silently dropped any local branch whose upstream it could not resolve: it cleared its internal remote branch while leaving the "gone" flag false, so neither the update nor the delete path ran. A run could then print "Already up to date" having done nothing, which is why a workspace-wide sync looked like it worked when it did not. - report unmatched branches as UNTRACKED with the reason, so "Already up to date" is only said when it is true - resolve the remote default branch via `git ls-remote --symref` when refs/remotes/<remote>/HEAD is missing, so merged branches whose upstream was deleted are still cleaned up - make --create-missing opt-in, keeping hub's default of never creating a local branch that does not already exist - summarise runs as changed/unchanged with per-action counts, and list deletions and failures in full instead of leaving them in scrollback - restore per-command flag help, which the custom Usage had dropped - add an in-place CLI progress line, drawn only to a terminal so piped and --json output stay free of cursor control - group the TUI selection screen by outcome so blocked and dirty repositories are visible before a run starts 🤖 Generated with Codebuff Co-Authored-By: Codebuff <noreply@codebuff.com>
152 lines
8.5 KiB
Markdown
152 lines
8.5 KiB
Markdown
# Stack Sync
|
|
|
|
`stack-sync` safely fetches and synchronizes local branches across every Git repository in a workspace. It is designed for directory trees such as JezzWTF where the root may not be a repository and a parent repository may contain deliberately untracked, nested repositories.
|
|
|
|
It provides both a full-screen [Bubble Tea](https://github.com/charmbracelet/bubbletea) control deck for interactive work and a conventional CLI/JSON interface for SSH sessions and automation. Both interfaces use the same discovery and safety engine.
|
|
|
|
## Safety model
|
|
|
|
Before a repository is eligible, Stack Sync verifies that it:
|
|
|
|
- is on a branch (not a detached `HEAD`);
|
|
- has at least one remote;
|
|
- has no merge, rebase, cherry-pick, revert, or bisect in progress.
|
|
|
|
Modified, staged, deleted, conflicted, and untracked files are reported but do not block the repository by default. If the checked-out branch would otherwise be fast-forwarded or deleted, Stack Sync protects that branch and continues synchronizing safe inactive branches. Branches checked out in another linked worktree are protected too. Use `--strict` when every repository must be completely clean before anything is synchronized.
|
|
|
|
Untracked paths that are themselves discovered nested Git repositories are excluded from the parent repository's dirty report. All other worktree changes remain visible in the CLI, JSON, and TUI.
|
|
|
|
Stack Sync never runs `git stash`, `git reset`, `git clean`, `git commit`, or any other command that saves or discards work. A repository is checked again immediately before synchronization, and the worktree is checked again before changing its checked-out branch.
|
|
|
|
The branch behavior is derived from the MIT-licensed `hub sync` implementation and is now built directly into Stack Sync. It fetches and prunes the main remote, fast-forwards outdated branches, warns about divergent/unpushed branches, and removes a branch only when its configured upstream was deleted and the branch is already merged into the remote default branch. It prefers a remote named `upstream`, `github`, or `origin`; a sole differently named remote is also accepted. See [third-party notices](THIRD_PARTY_NOTICES.md) for attribution.
|
|
|
|
## Branch handling
|
|
|
|
`hub sync` only ever moved branches that already existed locally, and a branch whose upstream it could not resolve was dropped from the report entirely. Stack Sync keeps hub's safety model but stops the silent behaviour:
|
|
|
|
- **Unmatched branches are reported, not hidden.** A local branch with no configured upstream and no same-named branch on the remote cannot be fast-forwarded. `hub` set its internal `remoteBranch` to an empty string and left `gone` false, so neither of its branches ran and the branch vanished without a word; the run then reported "Already up to date" while having done nothing. Such a branch is now reported as `UNTRACKED` with the reason. This makes the outcome visible, but it does not fast-forward the branch — there is no upstream to fast-forward from.
|
|
- **The remote default branch is resolved properly.** Which branch a deleted-and-merged branch had been merged into was inferred from `refs/remotes/<remote>/HEAD`, a local convenience symref that is missing from plenty of real clones. When it is absent Stack Sync now asks the remote directly with `git ls-remote --symref`, so merged branches are still cleaned up instead of being kept with a vague warning.
|
|
|
|
By default, like `hub`, Stack Sync never creates a local branch that does not already exist. Pass `--create-missing` to additionally create local branches for branches that exist only on the remote; they are created with tracking configured and reported as `CREATED`, and this never touches the worktree, moves an existing branch, or checks anything out.
|
|
|
|
Branch actions reported per repository are `UPDATED`, `DELETED`, `CREATED`, `PROTECTED`, `WARNING`, and `UNTRACKED`.
|
|
|
|
## Requirements
|
|
|
|
Stack Sync supports Linux and Windows. It requires:
|
|
|
|
- Go 1.24 or newer to build;
|
|
- Git on `PATH`.
|
|
|
|
There is no separate `hub` dependency.
|
|
|
|
## Build and install
|
|
|
|
`go install` is the simplest cross-platform installation method. From the repository root, run:
|
|
|
|
```text
|
|
go install .
|
|
```
|
|
|
|
Go automatically installs `stack-sync` on Linux and `stack-sync.exe` on Windows into your Go binary directory. Make sure that directory is on `PATH` (normally `$HOME/go/bin` on Linux and `%USERPROFILE%\go\bin` on Windows).
|
|
|
|
To build a binary in the repository instead, use the command for your platform.
|
|
|
|
### Linux
|
|
|
|
```bash
|
|
cd stack-sync
|
|
go test ./...
|
|
go build -o stack-sync .
|
|
install -Dm755 stack-sync ~/.local/bin/stack-sync
|
|
```
|
|
|
|
### Windows (PowerShell)
|
|
|
|
```powershell
|
|
Set-Location stack-sync
|
|
go test ./...
|
|
go build -o stack-sync.exe .
|
|
New-Item -ItemType Directory -Force "$env:LOCALAPPDATA\Programs\stack-sync" | Out-Null
|
|
Copy-Item .\stack-sync.exe "$env:LOCALAPPDATA\Programs\stack-sync\stack-sync.exe"
|
|
```
|
|
|
|
Add `%LOCALAPPDATA%\Programs\stack-sync` to your user `PATH` if it is not already present. The `.exe` suffix is important when choosing an explicit output name on Windows; `go build -o stack-sync .` creates an extensionless binary there.
|
|
|
|
## Usage
|
|
|
|
### Interactive TUI
|
|
|
|
Launch the control deck for the workspace:
|
|
|
|
```bash
|
|
stack-sync tui --root ~/Coding/jwtf
|
|
```
|
|
|
|
PowerShell accepts the same options with a Windows path:
|
|
|
|
```powershell
|
|
stack-sync tui --root C:\Users\you\Coding\jwtf
|
|
```
|
|
|
|
Every eligible repository starts selected. The list is grouped by what will happen if you start a run now, so problems are visible before you commit to anything:
|
|
|
|
- `BLOCKED` — not eligible, and cannot be selected. The reason (detached `HEAD`, merge in progress, no remotes) is shown for the focused row.
|
|
- `DIRTY` — eligible, but the checked-out branch will be protected rather than fast-forwarded.
|
|
- `READY` — fully eligible.
|
|
|
|
Move with the arrow keys or `j`/`k`, toggle the focused repository with Space, select all with `a`, clear the selection with `n`, and refresh with `r`. Press `s` or Enter to review the branch-deletion warning, then `y` to begin. Blocked repositories cannot be selected.
|
|
|
|
During synchronization, the TUI shows overall progress, elapsed time, active repositories, live success/failure/skip totals, and the latest completed result. The final view lists failures and protected or divergent branches, prioritizes and focuses the first failure automatically, and retains per-repository branch details. Press `f` to cycle through every repository that needs attention.
|
|
|
|
### CLI and automation
|
|
|
|
Scan the current workspace. This is read-only and is the default command:
|
|
|
|
```bash
|
|
stack-sync scan --root ~/Coding/jwtf
|
|
# equivalent:
|
|
stack-sync --root ~/Coding/jwtf
|
|
```
|
|
|
|
```powershell
|
|
stack-sync scan --root C:\Users\you\Coding\jwtf
|
|
```
|
|
|
|
Review the same plan, confirm it, and sync every eligible repository:
|
|
|
|
```bash
|
|
stack-sync sync --root ~/Coding/jwtf
|
|
```
|
|
|
|
While a run is in progress, an updating line keeps the workspace totals on screen so you can see what is happening without waiting for the final summary:
|
|
|
|
```text
|
|
17/40 · 2 active · 9 changed · 8 updated · 1 deleted · 2 protected · 1 warning · 1 untracked
|
|
```
|
|
|
|
That line is drawn only when stdout is a terminal. Redirected output and `--json` never contain cursor control characters, and `--json` is the right interface for scripts.
|
|
|
|
For automation, suppress the prompt and optionally require the entire workspace to be clean. In strict mode, either a blocked repository or any worktree change aborts the whole run:
|
|
|
|
```bash
|
|
stack-sync sync --root ~/Coding/jwtf --yes --strict
|
|
```
|
|
|
|
Useful options:
|
|
|
|
```text
|
|
--jobs 4 maximum concurrent inspections or syncs
|
|
--timeout 5m per-repository fetch and sync timeout
|
|
--create-missing create local branches that exist only on the remote
|
|
--exclude temp skip a directory name anywhere in the tree
|
|
--exclude Org/old skip a root-relative path
|
|
--json emit structured output
|
|
```
|
|
|
|
Both `/` and `\` are accepted in Windows paths. Quote a root or exclusion containing spaces, for example `--root "C:\Users\you\Source Repositories"`.
|
|
|
|
Dependency caches, build outputs, and tool-managed directories such as `node_modules`, `target`, `.claude`, and `.codex` are skipped during discovery by default. These exclusions only affect repository discovery; they never make real changes inside a discovered repository disappear from its dirty-worktree check.
|
|
|
|
Exit codes are `0` for a successful scan or sync (including protected branches and divergence warnings), `1` for an operational/sync failure, `2` for invalid or unconfirmed non-interactive use, and `3` when strict mode refuses the plan or a previously eligible repository fails its immediate pre-sync safety check.
|