Add --fetch flag to delete command#103
Conversation
When branches are merged remotely (e.g., via GitHub PR), the local repo doesn't know about it. Without --fetch, deleting such a branch fails because Git thinks it has unmerged changes. The --fetch flag updates local refs before the merge check, solving this without needing --force. Supports CLI flags (--fetch/--no-fetch), config key (gitflow.<type>.delete.fetch), and silently skips fetch when no remote is configured. Resolves #88
Add --fetch/--no-fetch options to the delete manpage with examples and configuration reference. Add gitflow.<type>.delete.fetch config key to the configuration manpage.
There was a problem hiding this comment.
Pull request overview
Adds optional remote fetching to git flow <type> delete so users can update refs before deletion checks, aiming to improve deletion of branches merged via remote PR workflows.
Changes:
- Adds
--fetch/--no-fetchflags (tri-state via*bool) to topic-branch delete and shorthand delete commands. - Implements
gitflow.<type>.delete.fetchconfiguration support and performs a guarded, non-fatal fetch before delete. - Updates delete/config manpages and adds 4 new delete tests for flag/config/override/no-remote cases.
Reviewed changes
Copilot reviewed 6 out of 6 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
cmd/delete.go |
Adds fetch resolution + guarded fetch execution before performing deletion. |
cmd/topicbranch.go |
Wires new delete flags into the per-branch-type delete subcommand. |
cmd/shorthand.go |
Wires new delete flags into git flow delete shorthand command. |
docs/gitflow-config.5.md |
Documents the new gitflow.<type>.delete.fetch config key. |
docs/git-flow-delete.1.md |
Documents new CLI flags and provides examples/config snippet. |
test/cmd/delete_test.go |
Adds tests covering flag/config/override/no-remote behavior for fetch. |
| if shouldFetch && git.RemoteExists(cfg.Remote) { | ||
| fmt.Printf("Fetching from remote '%s'...\n", cfg.Remote) | ||
| if err := git.Fetch(cfg.Remote); err != nil { | ||
| fmt.Fprintf(os.Stderr, "Warning: %v\n", err) | ||
| } | ||
| } |
There was a problem hiding this comment.
The current --fetch implementation only runs git fetch <remote>, which updates remote-tracking refs but does not update the local base branch (e.g., develop). Since deletion still relies on git branch -d/-D (merge check against local refs), fetching alone will not change whether Git considers the branch “fully merged”, so this likely won’t resolve the remotely-merged-PR scenario described in the PR/Issue #88.
To make --fetch achieve the intended behavior, consider checking merge status against the remote-tracking base (e.g., refs/remotes/<remote>/<parent>) after fetching and, when it’s merged there, proceed with a safe force-delete (or an explicit, well-documented alternative).
| // TestDeleteFeatureWithFetchFlag tests that the --fetch flag fetches before deleting. | ||
| // Steps: | ||
| // 1. Sets up a test repository with remote (includes git-flow init) | ||
| // 2. Starts a feature branch | ||
| // 3. Runs 'git flow feature delete my-feature --fetch' | ||
| // 4. Verifies output contains "Fetching from remote" | ||
| // 5. Verifies branch is deleted | ||
| func TestDeleteFeatureWithFetchFlag(t *testing.T) { |
There was a problem hiding this comment.
These new tests assert that the output contains a fetch message, but they don’t set up the failure mode from Issue #88 (branch merged into the remote base branch while the local base is behind). As written, they won’t catch regressions where fetch runs but deletion still fails without --force in the real-world remotely-merged-PR scenario.
Consider adding a test that: (1) pushes the base branch to a remote, (2) merges the topic branch into the remote base via a second clone, (3) leaves the original repo’s local base behind, and (4) verifies delete --fetch succeeds without --force where delete (without fetch) fails.
| **gitflow.*type*.delete.fetch** | ||
| : Fetch from remote before deleting a topic branch. When enabled, updates local refs so that Git can correctly detect whether the branch has been merged remotely (e.g., via a GitHub PR merge), avoiding the need for `--force`. | ||
| : *Type*: boolean | ||
| : *Default*: false | ||
|
|
There was a problem hiding this comment.
A new configuration key gitflow.<type>.delete.fetch is introduced here, but it’s not documented in CONFIGURATION.md (which appears to be the central config reference). Please add the same key there to keep the two configuration docs in sync.
| ### Fetch Before Delete | ||
|
|
||
| Delete a remotely-merged branch without needing `--force`: | ||
| ```bash | ||
| git flow feature delete my-feature --fetch | ||
| ``` |
There was a problem hiding this comment.
The docs claim --fetch lets users “Delete a remotely-merged branch without needing --force”. With the current implementation only doing git fetch before git branch -d, this may not be true because fetching doesn’t update the local base branch used by Git’s “fully merged” check. Either adjust the implementation to match this behavior, or reword the documentation to set correct expectations (e.g., that users may still need to update/fast-forward their base branch).
The --fetch flag only ran git fetch, which updates remote-tracking refs but not the local parent branch. This meant git branch -d still could not detect branches merged remotely (e.g., via GitHub PR). Now --fetch also fetches the topic branch, fast-forwards the parent branch from its remote-tracking counterpart, and checks if the local topic branch is in sync with remote (aborting if behind, unless --force is given). - Fetch both parent and topic branch individually - Fast-forward parent after checkout so git branch -d works - Add CompareBranchWithRemote sync check (matching finish behavior) - Add MergeFFOnly helper to internal/git/repo.go - Document delete.fetch in CONFIGURATION.md - Clarify manpage description for --fetch behavior
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 8 out of 8 changed files in this pull request and generated 2 comments.
Comments suppressed due to low confidence (1)
cmd/delete.go:38
executeDeleteloads config but doesn’t enforce that git-flow is initialized. Most other commands explicitly callconfig.IsInitialized()and returnerrors.NotInitializedError{}when false (e.g.,cmd/start.go:31-38,cmd/list.go:29-36). Adding the same guard here would preventdeletefrom silently running withDefaultConfig()in uninitialized repos.
func executeDelete(branchType string, name string, force *bool, remote *bool, fetch *bool) error {
// Load configuration
cfg, err := config.LoadConfig()
if err != nil {
return &errors.GitError{Operation: "load configuration", Err: err}
}
// Get branch configuration
branchConfig, ok := cfg.Branches[branchType]
if !ok {
return &errors.InvalidBranchTypeError{BranchType: branchType}
| var fetch *bool | ||
| if cmd.Flags().Changed("fetch") { | ||
| f, _ := cmd.Flags().GetBool("fetch") | ||
| fetch = &f | ||
| } else if cmd.Flags().Changed("no-fetch") { | ||
| f := false | ||
| fetch = &f | ||
| } |
There was a problem hiding this comment.
The fetch/no-fetch parsing duplicates the existing getBoolFlag(positive, negative) helper used in cmd/topicbranch.go. Consider reusing that helper here as well so flag-to-*bool conversion stays consistent across commands and is easier to maintain.
| var fetch *bool | |
| if cmd.Flags().Changed("fetch") { | |
| f, _ := cmd.Flags().GetBool("fetch") | |
| fetch = &f | |
| } else if cmd.Flags().Changed("no-fetch") { | |
| f := false | |
| fetch = &f | |
| } | |
| fetch := getBoolFlag(cmd, "fetch", "no-fetch") |
| cmd := exec.Command("git", "merge", "--ff-only", branch) | ||
| output, err := cmd.CombinedOutput() | ||
| if err != nil { | ||
| return fmt.Errorf("fast-forward merge failed: %s", strings.TrimSpace(string(output))) |
There was a problem hiding this comment.
MergeFFOnly drops useful context by not including the branch name and not wrapping the underlying err. Consider returning an error that includes the branch argument and wraps err (while still attaching trimmed command output) to improve diagnostics and allow callers to inspect the cause.
| return fmt.Errorf("fast-forward merge failed: %s", strings.TrimSpace(string(output))) | |
| outputStr := strings.TrimSpace(string(output)) | |
| return fmt.Errorf("fast-forward merge failed for branch %q: %w: %s", branch, err, outputStr) |
Use the existing getBoolFlag helper in shorthand delete command instead of duplicating flag-to-*bool conversion. Also include branch name and wrap error in MergeFFOnly for better diagnostics. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds
--fetch/--no-fetchflags to the delete command so it can fetch from remote before checking merge status, solving the common case where branches are merged remotely (e.g., via GitHub PR) but Git thinks they have unmerged changes.The implementation fetches both the parent and topic branch, fast-forwards the local parent from its remote-tracking counterpart so
git branch -dcorrectly detects remote merges, and checks whether the local topic branch is in sync with remote (aborting if behind, unless--force). This mirrors the finish command's fetch behavior. Config keygitflow.<type>.delete.fetch(default false),RemoteExistsguard, and non-fatal fetch errors. Changes touchcmd/delete.go,cmd/topicbranch.go,cmd/shorthand.go,internal/git/repo.go, manpages, and add 4 new tests.Resolves #88
Remarks
false(no fetch), consistent with start; users opt in via flag or configgit branch -dcan detect remote merges--force)git flow deletealso supports the new flagsReview focus:
cmd/delete.go:94-144- fetch, fast-forward, and sync check logicinternal/git/repo.go- newMergeFFOnlyhelper