-
Notifications
You must be signed in to change notification settings - Fork 20
Fix merge state not cleared when branch deletion fails #105
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -419,3 +419,84 @@ func TestFinishDeleteBranchUsesConfiguredRemote(t *testing.T) { | |||||||||||||||||||||
| t.Error("Expected remote branch to be deleted from upstream") | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // TestFinishClearsMergeStateWhenBranchDeletionFails tests that merge state is cleared even when | ||||||||||||||||||||||
| // remote branch deletion fails during finish. | ||||||||||||||||||||||
| // Steps: | ||||||||||||||||||||||
| // 1. Sets up a test repository with a remote and initializes git-flow | ||||||||||||||||||||||
| // 2. Creates a feature branch with changes and pushes to remote | ||||||||||||||||||||||
| // 3. Configures the remote repository to reject branch deletions so deletion will fail | ||||||||||||||||||||||
| // 4. Finishes the feature branch (merge succeeds, remote deletion fails) | ||||||||||||||||||||||
| // 5. Verifies the merge state file does not exist (cleared despite deletion error) | ||||||||||||||||||||||
| // 6. Verifies the merge completed successfully (changes are on develop) | ||||||||||||||||||||||
| func TestFinishClearsMergeStateWhenBranchDeletionFails(t *testing.T) { | ||||||||||||||||||||||
| dir := testutil.SetupTestRepo(t) | ||||||||||||||||||||||
| defer testutil.CleanupTestRepo(t, dir) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| output, err := testutil.RunGitFlow(t, dir, "init", "--defaults") | ||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||
| t.Fatalf("Failed to initialize git-flow: %v\nOutput: %s", err, output) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Add a remote repository | ||||||||||||||||||||||
| remoteDir, err := testutil.AddRemote(t, dir, "origin", true) | ||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||
| t.Fatalf("Failed to add remote: %v", err) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| defer testutil.CleanupTestRepo(t, remoteDir) | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Create a feature branch with changes | ||||||||||||||||||||||
| output, err = testutil.RunGitFlow(t, dir, "feature", "start", "delete-fail-test") | ||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||
| t.Fatalf("Failed to start feature branch: %v\nOutput: %s", err, output) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| testutil.WriteFile(t, dir, "feature-file.txt", "feature content") | ||||||||||||||||||||||
| _, err = testutil.RunGit(t, dir, "add", "feature-file.txt") | ||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||
| t.Fatalf("Failed to add file: %v", err) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
| _, err = testutil.RunGit(t, dir, "commit", "-m", "Add feature file") | ||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||
| t.Fatalf("Failed to commit: %v", err) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Push feature branch to remote | ||||||||||||||||||||||
| _, err = testutil.RunGit(t, dir, "push", "origin", "feature/delete-fail-test") | ||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||
| t.Fatalf("Failed to push feature branch: %v", err) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Configure the remote bare repo to reject branch deletions | ||||||||||||||||||||||
| _, err = testutil.RunGit(t, remoteDir, "config", "receive.denyDeletes", "true") | ||||||||||||||||||||||
| if err != nil { | ||||||||||||||||||||||
| t.Fatalf("Failed to configure receive.denyDeletes: %v", err) | ||||||||||||||||||||||
| } | ||||||||||||||||||||||
|
|
||||||||||||||||||||||
| // Finish the feature branch — merge should succeed but remote deletion should fail | ||||||||||||||||||||||
| output, err = testutil.RunGitFlow(t, dir, "feature", "finish", "delete-fail-test") | ||||||||||||||||||||||
| _ = output // finish may or may not return an error depending on how deletion failure is handled | ||||||||||||||||||||||
|
||||||||||||||||||||||
| _ = output // finish may or may not return an error depending on how deletion failure is handled | |
| if err != nil { | |
| deletionRejected := strings.Contains(output, "denyDeletes") || | |
| strings.Contains(output, "deletion") || | |
| strings.Contains(output, "delete") || | |
| strings.Contains(output, "remote rejected") | |
| if !deletionRejected { | |
| t.Fatalf("Expected feature finish to either succeed or fail due to remote deletion rejection, got err: %v\nOutput: %s", err, output) | |
| } | |
| } |
Copilot
AI
Apr 8, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test doesn’t currently guarantee that remote branch deletion actually fails: it logs when err == nil, which means the key scenario may not be exercised and the assertion could pass even if deletion succeeded. Tighten the test by requiring a non-nil error (or checking for a specific deletion-failure message / that the remote branch still exists) so it truly validates the regression fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Clearing merge state before attempting branch deletion means a deletion error (e.g., transient remote failure) will now return an error without leaving a merge state to resume via
finish --continue. SincedeleteBranchesIfNeededcurrently returns a fatal error on remote deletion failure, consider either (a) making branch deletion failures non-fatal warnings at this final step, or (b) keeping enough state to allow a retry while still guaranteeing state cleanup (e.g., clear state via a defer that always runs on exit).