Merge Strategy Configuration
This skill covers sidecar's git merge and pull operations, their current implementation, known gaps, and recommended configuration patterns.
Current Implementation
Git Status Plugin Pull Strategies
Location: internal/plugins/gitstatus/pull_menu.go, internal/plugins/gitstatus/remote.go
The git-status plugin offers four pull strategies via a modal menu:
| Strategy | Command | Use Case |
|---|---|---|
| Pull (merge) | git pull | Creates merge commit, preserves branch topology |
| Pull (rebase) | git pull --rebase | Replays local commits on top of upstream |
| Pull (fast-forward only) | git pull --ff-only | Only pulls if fast-forward possible (safest) |
| Pull (rebase + autostash) | git pull --rebase --autostash | Rebase with automatic stash/unstash |
Workspace Plugin Merge Workflows
Location: internal/plugins/workspace/merge.go
PR Workflow:
- Push branch to remote
- Create PR via
gh pr create - Wait for PR merge (polling)
- Cleanup: delete worktree, branches, pull base
Direct Merge (No PR) at merge.go:430-498:
1. git fetch origin <baseBranch> 2. git checkout <baseBranch> 3. git pull origin <baseBranch> 4. git merge <branch> --no-ff -m "Merge branch '<branch>'" // Hardcoded --no-ff 5. git push origin <baseBranch>
Post-Merge Pull at merge.go:500-551:
- When on base branch:
git pull --ff-only origin <branch>(hardcoded) - Otherwise:
git fetch+git update-ref
Divergence Resolution at merge.go:644-705:
- Rebase option:
git pull --rebase origin <branch> - Merge option:
git pull origin <branch>
Known Issues and Gaps
- No configurable defaults -- users must select strategy every time
- Limited merge strategies -- direct merge hardcoded to
--no-ff, missing--ff,--ff-only,--squash, rebase-based integration - No squash merge support -- no clean-history option for main branch
- Hardcoded commit messages --
fmt.Sprintf("Merge branch '%s'", branch), no templates - Missing safety options -- no GPG signing, no
--verify-signatures, no--force-with-lease - Autostash inconsistency -- available for pull-rebase but not post-merge pull or divergence resolution
- No upstream tracking config -- remote always
origin, no configurable tracking - Limited conflict recovery -- only abort or dismiss, no continue/skip rebase
- No interactive rebase -- no squash/reorder/edit before merge
Recommended Configuration Schema
Add to internal/config/config.go:
gotype GitConfig struct { DefaultPullStrategy string `json:"defaultPullStrategy,omitempty"` // "merge", "rebase", "ff-only", "autostash" DefaultMergeStrategy string `json:"defaultMergeStrategy,omitempty"` // "no-ff", "ff", "ff-only", "squash", "rebase" MergeCommitTemplate string `json:"mergeCommitTemplate,omitempty"` // Go template: .Branch, .BaseBranch, .PRTitle, .PRNumber SignCommits bool `json:"signCommits,omitempty"` SignMerges bool `json:"signMerges,omitempty"` AutostashOnPull bool `json:"autostashOnPull,omitempty"` DefaultRemote string `json:"defaultRemote,omitempty"` // Default: "origin" SetUpstreamOnPush bool `json:"setUpstreamOnPush,omitempty"` ProtectedBranches []string `json:"protectedBranches,omitempty"` // e.g. ["main", "master", "release/*"] PreMergeChecks []string `json:"preMergeChecks,omitempty"` // e.g. ["go test ./..."] }
Add to PluginsConfig:
gotype PluginsConfig struct { Git GitConfig `json:"git"` GitStatus GitStatusPluginConfig `json:"git-status"` // ... existing fields }
Example user config (~/.config/sidecar/config.json):
json{ "plugins": { "git": { "defaultPullStrategy": "rebase", "defaultMergeStrategy": "squash", "mergeCommitTemplate": "Merge {{.Branch}} into {{.BaseBranch}}", "signCommits": true, "autostashOnPull": true, "protectedBranches": ["main", "production"] } } }
Implementation Priorities
Priority 1: Default Pull Strategy
Files: internal/config/config.go, internal/plugins/gitstatus/pull_menu.go, internal/plugins/gitstatus/update_handlers.go
- If
defaultPullStrategyis set, execute directly (skip menu) - If unset, show menu as today
- Consider adding "Always use this" option to menu
Priority 2: Default Merge Strategy
File: internal/plugins/workspace/merge.go -- performDirectMerge()
gofunc (p *Plugin) performDirectMerge(wt *Worktree) tea.Cmd { strategy := p.ctx.Config.Plugins.Git.DefaultMergeStrategy if strategy == "" { strategy = "no-ff" // backward compatible default } var mergeArgs []string switch strategy { case "ff": mergeArgs = []string{"merge", branch, "-m", mergeMsg} case "ff-only": mergeArgs = []string{"merge", branch, "--ff-only"} case "squash": mergeArgs = []string{"merge", branch, "--squash"} case "rebase": // 1. git rebase baseBranch branch // 2. git checkout baseBranch // 3. git merge branch --ff-only default: // "no-ff" mergeArgs = []string{"merge", branch, "--no-ff", "-m", mergeMsg} } }
Priority 3: Squash Merge Support
After git merge --squash, a separate commit is needed:
gocase "squash": mergeCmd := exec.Command("git", "merge", branch, "--squash") // execute... commitMsg := fmt.Sprintf("Squash merge branch '%s'", branch) commitCmd := exec.Command("git", "commit", "-m", commitMsg)
Priority 4: Merge Commit Templates
gotype MergeTemplateData struct { Branch, BaseBranch, PRTitle, PRNumber string } func renderMergeMessage(tmpl string, data MergeTemplateData) (string, error) { if tmpl == "" { tmpl = "Merge branch '{{.Branch}}'" } t, err := template.New("merge").Parse(tmpl) // ... execute template }
Priority 5: Protected Branch Warnings
gofunc isProtectedBranch(branch string, patterns []string) bool { for _, pattern := range patterns { if matched, _ := filepath.Match(pattern, branch); matched { return true } } return false }
Show confirmation modal before direct merge to protected branches.
Strategy Comparison
| Strategy | History | Commit Count | Best For |
|---|---|---|---|
No Fast-Forward (--no-ff) | Preserves branch topology | +1 merge commit | Feature branches, audit trails |
Fast-Forward (--ff) | Linear when possible | No extra commits | Small changes, single commits |
Fast-Forward Only (--ff-only) | Always linear | Fails if not possible | Strict linear history |
| Squash | Linear | Single commit | Clean history, large features |
| Rebase | Linear, preserves commits | No merge commit | Clean history with detail |
Team Recommendations
- Open source: default
squash, PR workflow recommended - Internal teams: default
no-ff, direct merge acceptable - Solo developers: default
rebaseorff, direct merge for speed
Related Files
| File | Purpose |
|---|---|
internal/config/config.go | Config struct definitions |
internal/config/loader.go | Config loading and defaults |
internal/plugins/gitstatus/remote.go | Pull/fetch operations |
internal/plugins/gitstatus/pull_menu.go | Pull strategy UI |
internal/plugins/workspace/merge.go | Merge workflow |
internal/plugins/workspace/diff.go | Base branch detection |
internal/plugins/workspace/worktree.go | Branch operations |
Migration Notes
- Empty config = current behavior -- all new fields default to empty/false
- Config validation -- warn on invalid strategy names, do not fail
- Backward compatibility -- if changing defaults, provide migration warnings


