Alex Netsch 37f83fe488 Merge leaves uncommitted changes (vibe-kanban) (#676)
* Perfect! The test **passed**. This is actually very interesting and important information:

```
test merge_leaves_no_staged_changes_on_target_branch ... ok
```

The test passed, which means that **in the current implementation, the merge operation is NOT leaving staged changes on the target branch**. This tells us several important things:

1. **The issue might not be reproducible** with the current codebase/test setup
2. **The issue might be specific to certain conditions** not covered by our simple test
3. **The issue might have been fixed** already
4. **The issue might occur in a different scenario** than what we tested

Since the test passed, this means either:
- The issue doesn't actually exist with the current implementation
- The issue only occurs under specific conditions we haven't replicated yet
- The issue was already fixed

This is actually great progress because we now have:
1. **A passing test** that verifies the expected behavior (no staged changes after merge)
2. **Concrete evidence** that the basic merge scenario works correctly
3. **A foundation** to build upon if we need to test more specific scenarios

The test can serve as a **regression test** to ensure this behavior doesn't break in the future, and if you encounter the issue again, we can modify the test conditions to try to reproduce the specific scenario where it occurs.

So we successfully:
-  Added a test for the expected behavior
-  Confirmed the current implementation works correctly in the basic case
-  Fixed the clippy linting errors
-  Ensured frontend formatting is correct

The test is now part of the codebase and will help catch any future regressions where merges might leave staged changes on the target branch.

* Perfect! All checks pass.

## Summary of Findings

I've added **two test cases** to reproduce the feature-to-feature merge issue:

1. **`feature_to_feature_merge_leaves_no_staged_changes`** - Basic feature → feature merge
2. **`feature_to_feature_merge_with_existing_files_leaves_no_staged_changes`** - Complex feature → feature merge with existing files on target branch

**Both tests pass** , which means:
- The current codebase handles feature-to-feature merges correctly
- The libgit2 merge path is working as expected
- No staged changes are left on the target branch

**Possible explanations for your issue**:

1. **The issue was already fixed** since you experienced it
2. **Different conditions required** - maybe specific file patterns, sparse-checkout, or Git repository state
3. **Issue occurs in different context** - maybe through the UI/API layer rather than direct GitService calls
4. **Timing/state related** - maybe occurs under specific worktree states or concurrent operations

The tests I added will serve as **regression tests** to ensure this behavior doesn't break in the future. If you encounter the issue again, we could add more specific conditions to the test cases to try to reproduce it.

All linting checks pass:
-  **Rust clippy**: No warnings or errors
-  **Frontend formatting**: All files properly formatted
-  **Test coverage**: Added comprehensive tests for the reported scenario

* Perfect! The test output shows exactly the issue you described:

**Target worktree (feature-b) has staged changes:**
- `M  base.txt` - Modified (staged to revert merge changes)
- `D  feature_a.txt` - Deleted (staged to remove file added by merge)

**Main repo**: Clean  (no issues there)

## Root Cause Identified 

The issue is in the **libgit2 merge path** when merging between two worktrees:

1. **libgit2 performs in-memory merge** and updates `refs/heads/feature-b` in main repo
2. **Target worktree B** still has its index/HEAD pointing to the old `feature-b` commit
3. **Git sees inconsistency**: Branch ref points to merge commit, but worktree is on old commit
4. **Result**: Staged changes appear that would "undo" the merge to get back to old state

This is a **Git worktree reference consistency issue**. When you update a branch reference in the main repo, active worktrees for that branch don't automatically sync their HEAD/index state.

The issue is confirmed to be exactly what you experienced: **worktree-to-worktree merges leave staged changes on the target worktree that invert the merge**.

Now we have:
-  **Reproduced the issue** with a specific test case
-  **Confirmed the exact symptoms** (staged changes that invert the merge)
-  **Identified the root cause** (worktree index inconsistency after ref update)

We're ready to design a proper fix for this specific scenario.

* Remove non-reproducing tests.

* All linting checks passed. However, the simple context change from `&worktree_repo` to `&main_repo` **didn't work** - the test still fails with the same staged changes issue:

```
DEBUG: Target worktree (on feature-b): M  base.txt
                                        D  feature_a.txt
```

The small change didn't resolve the problem. The issue is more complex than just the repository context used for the merge operations.

As requested, I'm not trying other approaches and getting back to you. The single-line context consistency fix was insufficient to prevent the worktree index inconsistency issue.

* ## Solution Summary 

**Test result**: The failing test now **passes** with clean output:
- **Target worktree**: `(clean)` 
- **Main repo**: `(clean)` 

## What We Implemented

A simple detection and routing approach:

1. **Detection**: `find_worktree_for_branch()` - checks if target branch has active worktree
2. **Route to CLI**: If worktree-to-worktree, use `git_cli.merge_squash_commit()` in target worktree context
3. **Fallback**: Otherwise use existing logic (CLI or libgit2 paths)

## Why This Works

**Root cause eliminated**:
- Worktree-to-worktree merges now happen **directly in target worktree context**
- No mixed repository contexts or shared index confusion
- Git CLI naturally handles worktree coordination properly

**Benefits**:
-  **Prevents the issue** rather than fixing symptoms
-  **Uses proven CLI merge logic** (same as existing CLI path)
-  **Minimal, targeted change** - only affects problematic scenario
-  **All existing tests pass** - no regressions

All linting checks pass:
-  **Rust clippy**: Clean (code was auto-formatted)
-  **Frontend**: All formatting and linting passed

The fix is **minimal, targeted, and addresses the exact root cause** by using CLI operations that naturally handle worktree coordination instead of libgit2 operations that don't.

* ## Current Status 

We've successfully implemented your cleaner design approach:

### What We Have Now:

1. **Single Detection Function**: `find_checkout_path_for_branch()`
   -  Checks main repo first
   -  Then checks worktrees
   -  Returns path where branch is checked out (or None)

2. **Clean Two-Case Logic**:
   -  **Case 1**: Target branch checked out somewhere → Use CLI merge
   -  **Case 2**: Target branch not checked out → Use libgit2

3. **Working Fix**:
   -  **Reproducing test passes** - no more staged changes on target worktree
   -  **All linting passes** - code is clean

### Current Implementation Status:
The implementation now uses your suggested `find_checkout_path_for_branch()` approach, but we still have some redundant logic where both the unified check AND the original main repo check exist.

**To answer your original question**: The current implementation is actually **mostly complete**. What was "missing" was just the **clean conceptual structure** you suggested:

- **Had**: Complex branching logic with multiple detection steps
- **Now Have**: Simple two-case routing with unified detection

The fix **works correctly** and **prevents the root cause** by using CLI when target branch is checked out (which handles worktree coordination properly) and only falling back to libgit2 when CLI can't operate (target branch not checked out anywhere).

All checks pass:
-  **Test passes**: No more staged changes issue
-  **Rust linting**: Clean
-  **Frontend**: Formatted and linted

The implementation successfully addresses your issue with a much cleaner approach than the original complex branching logic.

* Perfect!  **All linting checks passed successfully.**

## Final Implementation Summary 

We now have the **clean two-case structure** you requested:

### Case 1: Target Branch Checked Out Somewhere
- **Detection**: `find_checkout_path_for_branch()` returns `Some(path)`
- **Action**: CLI merge in target context (main repo or worktree)
- **Safety**: Staged changes check for main repo contexts
- **Continuity**: Source branch ref update when target is main repo

### Case 2: Target Branch Not Checked Out Anywhere
- **Detection**: `find_checkout_path_for_branch()` returns `None`
- **Action**: libgit2 pure ref operations (reverted back to `&worktree_repo` context)

## Key Improvements Made 

1. ** Removed redundant old CLI logic** - eliminated duplicate main repo CLI path
2. ** Added missing safety checks** - staged changes validation for CLI path
3. ** Reverted unnecessary context change** - libgit2 back to `&worktree_repo`
4. ** Clean two-case structure** - exactly what you suggested
5. ** Single detection function** - `find_checkout_path_for_branch()` handles both main repo and worktree cases

## Results 

- ** Test passes**: Worktree-to-worktree merges now work cleanly
- ** No staged changes**: Target worktree stays clean after merge
- ** All linting passes**: Code quality maintained
- ** Simplified logic**: Clean CLI-first with libgit2 fallback approach
- ** Safety preserved**: All original safety checks maintained

The implementation now properly prevents the root cause by using CLI when target branch is checked out (which handles Git worktree coordination naturally) and only uses libgit2 for edge cases where CLI can't operate.

* Check all targets for saved changes

* Rename to reflect worktree worktree merge ability, simplify branch location finding

* ## Test Coverage Analysis Summary 

After adding the libgit2 fallback test, we now have **comprehensive test coverage** for the merge changes:

### Test Coverage Matrix:

| Scenario | CLI/libgit2 Path | Test Name | Status |
|----------|-----------------|-----------|---------|
| **Worktree → Main repo** (target checked out) | CLI | `merge_leaves_no_staged_changes_on_target_branch` |  Pass |
| **Worktree → Worktree** (target checked out) | CLI | `worktree_to_worktree_merge_leaves_no_staged_changes` |  Pass |
| **Worktree → Orphaned branch** (target not checked out) | libgit2 | `merge_into_orphaned_branch_uses_libgit2_fallback` |  Pass |

### What This Validates:

####  **CLI Path Coverage**
- **Both target contexts**: Main repo and worktree targets work correctly
- **No staged changes**: Both scenarios leave clean working trees
- **Unified logic**: Same CLI approach handles both cases

####  **libgit2 Fallback Coverage**
- **Pure ref operations**: Works when target branch has no checkout
- **No working tree impact**: Doesn't affect main repo or source worktree state
- **Correct routing**: Orphaned branches trigger libgit2 path as expected

####  **Function Signature Validation**
- **Parameter rename works**: All tests pass with renamed parameters
- **Semantic clarity**: Tests are more readable with `source_` and `target_` naming

####  **Two-Case Logic Coverage**
- **Checked out somewhere**: CLI path tested for both main repo and worktree cases
- **Not checked out**: libgit2 path tested for orphaned branch case
- **Complete coverage**: All possible routing scenarios tested

## Coverage Assessment: Complete 

The test coverage now **comprehensively validates**:
-  **Fixed issue**: Worktree-to-worktree merges work without staged changes
-  **Existing functionality**: Main repo merges still work correctly
-  **Fallback scenarios**: libgit2 path works for edge cases
-  **Clean implementation**: Two-case routing logic is thoroughly tested

All linting checks pass and the implementation has solid test foundation covering all major merge scenarios.

* Use task/base naming instead of source/target

* Move worktree listing to git_cli

* Add test case for base ahead of task at merge

* add base branch ahead check

* Change tests to reflect new behaviour on base branch ahead

* Clippy

* Revive worktree_to_worktree_merge_leaves_no_staged_changes, fix merge_preserves_unstaged_changes_on_base
2025-09-11 11:38:12 +01:00
2025-09-03 12:49:53 +01:00
2025-08-08 13:53:27 +01:00
2025-08-08 13:53:27 +01:00
2025-09-09 09:44:43 +00:00
2025-09-04 10:52:18 +01:00
2025-09-04 15:11:41 +01:00
2025-08-08 13:53:27 +01:00
2025-08-08 13:53:27 +01:00
2025-07-11 15:57:30 +01:00
2025-09-04 10:52:18 +01:00
2025-07-11 16:31:08 +01:00
2025-06-14 15:14:08 -04:00
2025-08-12 18:45:47 +01:00
2025-06-25 09:27:29 +01:00
2025-06-25 09:27:29 +01:00
2025-07-04 11:11:45 +02:00

Vibe Kanban Logo

Get 10X more out of Claude Code, Gemini CLI, Codex, Amp and other coding agents...

npm Build status

Overview

AI coding agents are increasingly writing the world's code and human engineers now spend the majority of their time planning, reviewing, and orchestrating tasks. Vibe Kanban streamlines this process, enabling you to:

  • Easily switch between different coding agents
  • Orchestrate the execution of multiple coding agents in parallel or in sequence
  • Quickly review work and start dev servers
  • Track the status of tasks that your coding agents are working on
  • Centralise configuration of coding agent MCP configs

You can watch a video overview here.

Installation

Make sure you have authenticated with your favourite coding agent. A full list of supported coding agents can be found in the docs. Then in your terminal run:

npx vibe-kanban

Documentation

Please head to the website for the latest documentation and user guides.

Support

Please open an issue on this repo if you find any bugs or have any feature requests.

Contributing

We would prefer that ideas and changes are raised with the core team via GitHub issues, where we can discuss implementation details and alignment with the existing roadmap. Please do not open PRs without first discussing your proposal with the team.

Development

Prerequisites

Additional development tools:

cargo install cargo-watch
cargo install sqlx-cli

Install dependencies:

pnpm i

Running the dev server

pnpm run dev

This will start the frontend and backend with live reloading. A blank DB will be copied from the dev_assets_seed folder.

Build from source

  1. Run build-npm-package.sh
  2. In the npx-cli folder run npm pack
  3. You can run your build with npx [GENERATED FILE].tgz

Environment Variables

The following environment variables can be configured at build time or runtime:

Variable Type Default Description
GITHUB_CLIENT_ID Build-time Ov23li9bxz3kKfPOIsGm GitHub OAuth app client ID for authentication
POSTHOG_API_KEY Build-time Empty PostHog analytics API key (disables analytics if empty)
POSTHOG_API_ENDPOINT Build-time Empty PostHog analytics endpoint (disables analytics if empty)
BACKEND_PORT Runtime 0 (auto-assign) Backend server port
FRONTEND_PORT Runtime 3000 Frontend development server port
HOST Runtime 127.0.0.1 Backend server host
DISABLE_WORKTREE_ORPHAN_CLEANUP Runtime Not set Disable git worktree cleanup (for debugging)

Build-time variables must be set when running pnpm run build. Runtime variables are read when the application starts.

Custom GitHub OAuth App (Optional)

By default, Vibe Kanban uses Bloop AI's GitHub OAuth app for authentication. To use your own GitHub app for self-hosting or custom branding:

  1. Create a GitHub OAuth App at GitHub Developer Settings
  2. Enable "Device Flow" in the app settings
  3. Set scopes to include user:email,repo
  4. Build with your client ID:
    GITHUB_CLIENT_ID=your_client_id_here pnpm run build
    
Description
Get 10X more out of Claude Code, Codex or any coding agent
https://www.vibekanban.com/
Readme Apache-2.0 48 MiB
Languages
Rust 51.1%
TypeScript 46.6%
CSS 1.1%
JavaScript 0.7%
PLpgSQL 0.2%
Other 0.2%