✨ feat(sync): gwm sync — fetch + rebase/merge onto upstream (#24)#172
Merged
Conversation
Hoist the dirty-tree check out of `compute_status` into a public `worktree::is_dirty` so `gwm sync` (#24) and the status column share one definition of "dirty" — staged, unstaged, or untracked, ignored files excluded. `compute_status` now delegates to it, preserving the `unknown` fallback on a libgit2 status error.
New `gwm sync [<pattern>] [--merge]` subcommand. Resolves the target worktree (defaults to the CWD worktree), runs `git fetch` for its upstream's remote, then rebases the branch onto the upstream — or merges with `--merge`. Reports the outcome with the ✓ sigil. Read-side inspection (dirty check, upstream resolution, ahead/behind) goes through libgit2; the mutating steps (fetch/rebase/merge) shell out to `git` so the user's configured credentials and helpers are used — same rationale as the existing sidebar `git log`/`git status` shell-outs. Guards, per the issue: - dirty working tree → refuse before touching the remote; - no upstream configured → actionable error naming the fix; - conflicting rebase/merge → abort so the worktree stays usable, then surface a conflict error telling the user to reconcile by hand. Tests (TDD): offline integration suite in tests/sync_tests.rs drives a bare `origin` + tracking `local` fixture through up-to-date / rebase / merge / conflict / dirty / no-upstream paths; pure formatter contracts in tests/cli_format_tests.rs; E2E + help canary in tests/cli_binary.rs.
There was a problem hiding this comment.
Pull request overview
Adds a new CLI-scoped gwm sync command for fetching a target worktree’s upstream and integrating upstream commits via rebase by default or merge with --merge.
Changes:
- Introduces
src/sync.rswith sync inspection, fetch, rebase/merge, conflict abort, and report types. - Adds CLI dispatch and success formatting for
gwm sync. - Adds integration, binary, and formatter tests plus changelog coverage.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
src/sync.rs |
Implements sync workflow and git shell-out helper. |
src/cli.rs |
Adds sync subcommand, dispatch, and report formatting. |
src/worktree.rs |
Extracts reusable dirty-worktree detection. |
src/lib.rs |
Exposes the new sync module. |
tests/sync_tests.rs |
Adds offline integration coverage for sync behavior. |
tests/cli_format_tests.rs |
Adds formatter tests for sync reports. |
tests/cli_binary.rs |
Adds CLI help and sync error-path tests. |
CHANGELOG.md |
Documents the new command under Unreleased. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+1540
to
+1545
| let cwd = std::env::current_dir()?; | ||
| let name = cwd | ||
| .file_name() | ||
| .map(|n| n.to_string_lossy().into_owned()) | ||
| .unwrap_or_else(|| "worktree".into()); | ||
| (cwd, name) |
Comment on lines
+162
to
+170
| if integrate.is_err() { | ||
| let _ = run_git(&workdir, &[strategy.verb(), "--abort"]); | ||
| return Err(GwmError::Other(format!( | ||
| "{} onto {} hit conflicts and was aborted; reconcile manually with `git {} {}`", | ||
| strategy.verb(), | ||
| upstream_short, | ||
| strategy.verb(), | ||
| upstream_short | ||
| ))); |
Add a `gwm sync` section to the CLI reference (usage, target resolution, the three guard rails, and the libgit2-vs-git-shell-out split), list it in the CLI index, and surface it in the bundled SKILL.md cheat-sheets (full command list, when-to-use line, quick reference).
…onfig The rebase/merge that `sync()` runs shells out to `git` with no identity env, so on a GitHub runner (no global user.name/user.email) those commits would fail with "committer identity unknown". Pin identity + commit.gpgsign=false as LOCAL config on the fixture repo so sync's git invocations resolve them. Also drop the `/dev/null` GIT_CONFIG_GLOBAL/SYSTEM envs — not portable on Windows. Validated with HOME + GIT_CONFIG_GLOBAL/SYSTEM pointed at an empty dir.
1. cmd_sync: in the no-pattern case, discover the worktree *containing* the CWD and report its workdir basename, not the CWD basename — so `gwm sync` from a subdirectory names/targets the worktree root instead of e.g. `src`. 2. sync: don't treat every non-zero rebase/merge as a conflict. Inspect the index for conflict stages (libgit2, language-independent) before aborting; only the genuine-conflict path emits the conflict message, other failures surface the underlying git error verbatim so the user isn't sent down the wrong recovery path. Tests: new E2E pins the subdir naming (bare origin + tracking clone, run from src/deep, asserts the ✓ line names the worktree root). The existing conflict test still exercises the index-based conflict path.
# Conflicts: # CHANGELOG.md
This was referenced May 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Implements
gwm sync [<pattern>] [--merge](closes #24): fetch a worktree's upstream and rebase its branch onto it (or merge with--merge).git fetchthe upstream's remote, recompute ahead/behind, then integrate when behind.✓sigil (format_sync_report, a pure fn).Design
Read-side inspection (dirty check, upstream resolution, ahead/behind) goes through libgit2; the mutating steps (
fetch/rebase/merge) shell out togitso the user's configured credentials, SSH agents, and helpers are used — the same rationale as the existing sidebargit log/git statusshell-outs. libgit2 fetch would force us to wire credential callbacks for every transport.Guards (per the issue)
git branch --set-upstream-to=…fix.Deferred
s. That key is now taken by sidebar stashes mode ([Feature]: Sidebar stashes view (toggle with 's') #34, shipped in v0.8.0-rc.3), and wiring a new TUIActiontouches sharedsrc/tui/*files. Deferring the TUI surface to a focused follow-up keeps this PR atomic and CLI-scoped.Tests (TDD)
tests/sync_tests.rs— offline integration suite driving a bareorigin+ trackinglocalfixture (no network,gitonly) through: up-to-date, rebase-behind, merge-behind, conflict-abort, dirty-refusal, no-upstream.tests/cli_format_tests.rs— pureformat_sync_reportcontracts (up-to-date / rebased / merged).tests/cli_binary.rs— E2E unknown-pattern + no-upstream, and thehelp_prints_subcommandscanary updated withsync.Local:
cargo testgreen (full suite),cargo fmt --check,cargo clippy --all-targets -- -D warnings, MSRV lint clean.