Skip to content

Commit 9edbba7

Browse files
committed
Add active worktree sync actions, publish modal, and source ref tracking
- SPROUTGIT_SOURCE_REF env var passed to hooks (persisted via worktree_provenance table) - Remote refs included in list_refs (upstream/* → origin/* → other remotes order) - Source ref picker defaults to upstream-first ref when creating worktrees - Fetch/Pull toolbar buttons now functional for the active worktree - Push detects missing upstream and opens a Publish modal with remote selection - Branch is deleted when its managed worktree is removed - E2E daily-workflow test updated to reflect branch-deletion behaviour
1 parent 9ee80b2 commit 9edbba7

14 files changed

Lines changed: 1015 additions & 26 deletions

File tree

.github/copilot-instructions.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,11 @@ SproutGit manages user repos in a prescribed directory layout:
156156

157157
Representative command groups currently include:
158158

159-
- Git operations and worktree lifecycle (`git_info`, `list_worktrees`, `create_managed_worktree`, `delete_managed_worktree`, `checkout_worktree`, `reset_worktree_branch`)
159+
- Git operations and worktree lifecycle (`git_info`, `list_worktrees`, `create_managed_worktree`, `delete_managed_worktree`, `checkout_worktree`, `reset_worktree_branch`, `get_worktree_push_status`, `fetch_worktree`, `pull_worktree`, `push_worktree_branch`)
160160
- Diff and staging (`get_diff_files`, `get_diff_content`, `get_worktree_status`, `stage_files`, `unstage_files`, `create_commit`, `get_working_diff`)
161161
- Workspace and config (`create_sproutgit_workspace`, `import_git_repo_workspace`, `inspect_sproutgit_workspace`, recent workspaces, app settings)
162162
- Hooks (`list_workspace_hooks`, create/update/delete/toggle, `run_workspace_hook`)
163+
- Worktree metadata (`list_worktree_provenance`, `get_worktree_provenance`, nested repo sync rule CRUD)
163164
- Editor/Git tool integration (`open_in_editor`, editor detection, git config read/write)
164165
- Terminal and watcher (`spawn_terminal`, `terminal_input`, `start_watching_worktrees`)
165166
- Optional E2E-only helpers (`set_window_size` when `e2e-testing` feature is enabled)
@@ -192,9 +193,10 @@ Representative export groups include:
192193

193194
- `getGitInfo()`, `createWorkspace()`, `inspectWorkspace()`
194195
- Workspace import and recents/settings
195-
- Worktree lifecycle (`createManagedWorktree`, delete, checkout, reset)
196+
- Worktree lifecycle (`createManagedWorktree`, delete, checkout, reset, push status, fetch, pull, push/publish)
196197
- Diff and staging helpers
197198
- Hook CRUD + progress listeners (`onHookProgress`)
199+
- Worktree provenance + nested repo sync rule helpers
198200
- File watcher helpers
199201
- Terminal lifecycle helpers
200202
- GitHub auth/repo helpers

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,9 @@ No conflicts, no stash juggling, no waiting. Each agent works independently on i
7272
- **Interactive commit graph** — Lane-based SVG graph with search, selection, and context menus
7373
- **Diff viewer** — Single-commit and multi-commit range diffs with file list and unified diff display
7474
- **Branch management** — Checkout, reset (soft/mixed/hard), and create branches from any ref
75+
- **Publish-aware push** — First push publishes with upstream (`-u`) when none is configured; later pushes use normal tracking
76+
- **Active worktree sync actions** — Fetch, pull (`--ff-only`), and push/publish from the toolbar for the selected worktree
77+
- **Remote-first source refs** — Worktree creation prioritizes remote refs (prefers `upstream/*`) to reduce stale local-base mistakes
7578
- **Workspace hooks** — Run before/after create, remove, and switch operations with dependency ordering and per-hook output
7679
- **Editor integration** — Open worktrees in your configured editor (respects `GIT_EDITOR`, `core.editor`, `VISUAL`, `EDITOR`)
7780
- **Dark mode** — Automatic light/dark theme via system preferences

docs/branch-worktree-policy.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,16 @@ Remote refs are source of truth for synchronization status, but local lifecycle
132132
- Remote state does not unilaterally force local deletion.
133133
- Local branch deletion always requires explicit user confirmation (except future policy opt-ins).
134134

135+
## Upstream And Publish Policy
136+
137+
To avoid accidental pushes to the wrong remote branch, SproutGit uses an explicit publish model for new worktree branches.
138+
139+
1. On managed worktree create, upstream tracking is cleared intentionally.
140+
2. The first push from a branch without upstream requires explicit publish setup: user selects the remote, then SproutGit runs publish semantics (`git push -u <remote> <branch>`).
141+
3. Publish picks remote in this order: `branch.<name>.pushRemote`, `remote.pushDefault`, `branch.<name>.remote`, then `origin`, then `upstream`, then first configured remote.
142+
4. Once upstream exists, subsequent pushes use normal `git push` behavior.
143+
5. Source ref selection for new worktrees should prefer remote refs, with `upstream/*` ranked ahead of local branches.
144+
135145
## Default Product Policy (Recommended)
136146

137147
1. Enforce 1:1 binding for managed branches.

docs/worktree-hooks.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,7 @@ Provide minimal, explicit environment variables:
212212
- `SPROUTGIT_TRIGGER`
213213
- `SPROUTGIT_TRIGGER_PHASE`
214214
- `SPROUTGIT_TRIGGER_ACTION`
215+
- `SPROUTGIT_SOURCE_REF` (set for worktree create triggers when available)
215216
- `SPROUTGIT_HOOK_ID`
216217
- `SPROUTGIT_HOOK_NAME`
217218
- `SPROUTGIT_HOOK_SCOPE`

e2e/specs/daily-workflow.spec.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -437,10 +437,9 @@ test.describe('Daily developer workflow', () => {
437437
const worktreeListAfter = runGit(gitRoot, ['worktree', 'list', '--porcelain']);
438438
expect(worktreeListAfter).not.toContain('null-pointer');
439439

440-
// Git: bugfix branch still exists (SproutGit removes the worktree dir and prunes the
441-
// worktree ref, but does NOT delete the branch — the commit remains reachable).
440+
// Git: bugfix branch is deleted with managed worktree cleanup.
442441
const branchesAfter = runGit(gitRoot, ['branch']);
443-
expect(branchesAfter).toContain('bugfix/null-pointer');
442+
expect(branchesAfter).not.toContain('bugfix/null-pointer');
444443

445444
// Git: feature/new-dashboard worktree and branch still exist
446445
expect(worktreeListAfter).toContain('feature/new-dashboard');
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
CREATE TABLE IF NOT EXISTS worktree_provenance (
2+
worktree_path TEXT PRIMARY KEY,
3+
branch TEXT NOT NULL,
4+
source_ref TEXT NOT NULL,
5+
initiating_worktree_path TEXT,
6+
root_repo_path TEXT NOT NULL,
7+
created_at INTEGER NOT NULL,
8+
updated_at INTEGER NOT NULL
9+
);
10+
11+
CREATE INDEX IF NOT EXISTS idx_worktree_provenance_source_ref
12+
ON worktree_provenance (source_ref);
13+
14+
CREATE INDEX IF NOT EXISTS idx_worktree_provenance_created_at
15+
ON worktree_provenance (created_at);
16+
17+
CREATE TABLE IF NOT EXISTS nested_repo_sync_rules (
18+
repo_relative_path TEXT PRIMARY KEY,
19+
enabled INTEGER NOT NULL DEFAULT 1,
20+
created_at INTEGER NOT NULL,
21+
updated_at INTEGER NOT NULL
22+
);
23+
24+
CREATE INDEX IF NOT EXISTS idx_nested_repo_sync_rules_enabled
25+
ON nested_repo_sync_rules (enabled, repo_relative_path);

src-tauri/src/db.rs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,9 @@ static WORKSPACE_MIGRATIONS: LazyLock<Migrations<'static>> = LazyLock::new(|| {
4141
M::up(include_str!(
4242
"../migrations/workspace/003_hook_execution_preferences.sql"
4343
)),
44+
M::up(include_str!(
45+
"../migrations/workspace/004_worktree_provenance_and_nested_repo_rules.sql"
46+
)),
4447
])
4548
});
4649

src-tauri/src/git/helpers.rs

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,11 +59,16 @@ pub enum GitAction {
5959
StageFiles,
6060
UnstageFiles,
6161
CreateCommit,
62+
DeleteBranch,
63+
Push,
64+
Fetch,
65+
Pull,
66+
ListRemotes,
6267
}
6368

6469
impl GitAction {
6570
#[cfg(test)]
66-
pub const ALL: [GitAction; 26] = [
71+
pub const ALL: [GitAction; 31] = [
6772
GitAction::GitInfo,
6873
GitAction::WorktreeList,
6974
GitAction::ListRefs,
@@ -90,6 +95,11 @@ impl GitAction {
9095
GitAction::StageFiles,
9196
GitAction::UnstageFiles,
9297
GitAction::CreateCommit,
98+
GitAction::DeleteBranch,
99+
GitAction::Push,
100+
GitAction::Fetch,
101+
GitAction::Pull,
102+
GitAction::ListRemotes,
93103
];
94104

95105
pub fn label(self) -> &'static str {
@@ -120,6 +130,11 @@ impl GitAction {
120130
GitAction::StageFiles => "stage_files",
121131
GitAction::UnstageFiles => "unstage_files",
122132
GitAction::CreateCommit => "create_commit",
133+
GitAction::DeleteBranch => "delete_branch",
134+
GitAction::Push => "push",
135+
GitAction::Fetch => "fetch",
136+
GitAction::Pull => "pull",
137+
GitAction::ListRemotes => "list_remotes",
123138
}
124139
}
125140
}

0 commit comments

Comments
 (0)