Skip to content

✨ feat(sync): gwm sync — fetch + rebase/merge onto upstream (#24)#172

Merged
kbrdn1 merged 7 commits into
devfrom
feat/#24-gwm-sync
May 29, 2026
Merged

✨ feat(sync): gwm sync — fetch + rebase/merge onto upstream (#24)#172
kbrdn1 merged 7 commits into
devfrom
feat/#24-gwm-sync

Conversation

@kbrdn1
Copy link
Copy Markdown
Owner

@kbrdn1 kbrdn1 commented May 29, 2026

What

Implements gwm sync [<pattern>] [--merge] (closes #24): fetch a worktree's upstream and rebase its branch onto it (or merge with --merge).

  • Resolves the target worktree by fuzzy pattern; defaults to the worktree containing the CWD (which may legitimately be the main worktree — syncing trunk is valid).
  • git fetch the upstream's remote, recompute ahead/behind, then integrate when behind.
  • Reports the outcome with the 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 to git so the user's configured credentials, SSH agents, and helpers are used — the same rationale as the existing sidebar git log / git status shell-outs. libgit2 fetch would force us to wire credential callbacks for every transport.

Guards (per the issue)

  • Dirty tree → refuse before touching the remote (commit/stash first).
  • No upstream → actionable error naming the git branch --set-upstream-to=… fix.
  • Conflict → abort the rebase/merge so the worktree is left usable, then surface a conflict error telling the user to reconcile by hand.

Deferred

  • The issue also floated a TUI keybinding on 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 TUI Action touches shared src/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 bare origin + tracking local fixture (no network, git only) through: up-to-date, rebase-behind, merge-behind, conflict-abort, dirty-refusal, no-upstream.
  • tests/cli_format_tests.rs — pure format_sync_report contracts (up-to-date / rebased / merged).
  • tests/cli_binary.rs — E2E unknown-pattern + no-upstream, and the help_prints_subcommands canary updated with sync.

Local: cargo test green (full suite), cargo fmt --check, cargo clippy --all-targets -- -D warnings, MSRV lint clean.

kbrdn1 added 3 commits May 29, 2026 11:28
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.
Copilot AI review requested due to automatic review settings May 29, 2026 09:29
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.rs with 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 thread src/cli.rs Outdated
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 thread src/sync.rs Outdated
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
)));
kbrdn1 added 4 commits May 29, 2026 11:37
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.
@kbrdn1 kbrdn1 merged commit 328b225 into dev May 29, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants