mirror of
https://github.com/jesseduffield/lazygit.git
synced 2025-05-11 04:15:48 +02:00
Auto-forward main branches after fetching (#4493)
- **PR Description** Add a new user config for auto-forwarding branches after fetching; by default this is set to "onlyMainBranches", but it can be set to "allBranches" to extend it to feature branches too, or to "none" to disable it. This is used both when fetching manually by pressing `f` in the files panel, and for automatic background fetching.
This commit is contained in:
commit
bd1e34b39d
14 changed files with 262 additions and 17 deletions
|
@ -337,6 +337,10 @@ git:
|
|||
# If true, periodically refresh files and submodules
|
||||
autoRefresh: true
|
||||
|
||||
# If not "none", lazygit will automatically forward branches to their upstream after fetching. Applies to branches that are not the currently checked out branch, and only to those that are strictly behind their upstream (as opposed to diverged).
|
||||
# Possible values: 'none' | 'onlyMainBranches' | 'allBranches'
|
||||
autoForwardBranches: onlyMainBranches
|
||||
|
||||
# If true, pass the --all arg to git fetch
|
||||
fetchAll: true
|
||||
|
||||
|
|
|
@ -285,3 +285,11 @@ func (self *BranchCommands) IsBranchMerged(branch *models.Branch, mainBranches *
|
|||
|
||||
return stdout == "", nil
|
||||
}
|
||||
|
||||
func (self *BranchCommands) UpdateBranchRefs(updateCommands string) error {
|
||||
cmdArgs := NewGitCmd("update-ref").
|
||||
Arg("--stdin").
|
||||
ToArgv()
|
||||
|
||||
return self.cmd.New(cmdArgs).SetStdin(updateCommands).Run()
|
||||
}
|
||||
|
|
|
@ -21,6 +21,9 @@ type ICmdObj interface {
|
|||
// outputs args vector e.g. ["git", "commit", "-m", "my message"]
|
||||
Args() []string
|
||||
|
||||
// Set a string to be used as stdin for the command.
|
||||
SetStdin(input string) ICmdObj
|
||||
|
||||
AddEnvVars(...string) ICmdObj
|
||||
GetEnvVars() []string
|
||||
|
||||
|
@ -131,6 +134,12 @@ func (self *CmdObj) Args() []string {
|
|||
return self.cmd.Args
|
||||
}
|
||||
|
||||
func (self *CmdObj) SetStdin(input string) ICmdObj {
|
||||
self.cmd.Stdin = strings.NewReader(input)
|
||||
|
||||
return self
|
||||
}
|
||||
|
||||
func (self *CmdObj) AddEnvVars(vars ...string) ICmdObj {
|
||||
self.cmd.Env = append(self.cmd.Env, vars...)
|
||||
|
||||
|
|
|
@ -244,6 +244,9 @@ type GitConfig struct {
|
|||
AutoFetch bool `yaml:"autoFetch"`
|
||||
// If true, periodically refresh files and submodules
|
||||
AutoRefresh bool `yaml:"autoRefresh"`
|
||||
// If not "none", lazygit will automatically forward branches to their upstream after fetching. Applies to branches that are not the currently checked out branch, and only to those that are strictly behind their upstream (as opposed to diverged).
|
||||
// Possible values: 'none' | 'onlyMainBranches' | 'allBranches'
|
||||
AutoForwardBranches string `yaml:"autoForwardBranches" jsonschema:"enum=none,enum=onlyMainBranches,enum=allBranches"`
|
||||
// If true, pass the --all arg to git fetch
|
||||
FetchAll bool `yaml:"fetchAll"`
|
||||
// If true, lazygit will automatically stage files that used to have merge
|
||||
|
@ -822,6 +825,7 @@ func GetDefaultConfig() *UserConfig {
|
|||
MainBranches: []string{"master", "main"},
|
||||
AutoFetch: true,
|
||||
AutoRefresh: true,
|
||||
AutoForwardBranches: "onlyMainBranches",
|
||||
FetchAll: true,
|
||||
AutoStageResolvedConflicts: true,
|
||||
BranchLogCmd: "git log --graph --color=always --abbrev-commit --decorate --date=relative --pretty=medium {{branchName}} --",
|
||||
|
|
|
@ -19,6 +19,10 @@ func (config *UserConfig) Validate() error {
|
|||
[]string{"none", "onlyArrow", "arrowAndNumber"}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateEnum("git.autoForwardBranches", config.Git.AutoForwardBranches,
|
||||
[]string{"none", "onlyMainBranches", "allBranches"}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := validateKeybindings(config.Keybinding); err != nil {
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -125,7 +125,11 @@ func (self *BackgroundRoutineMgr) goEvery(interval time.Duration, stop chan stru
|
|||
func (self *BackgroundRoutineMgr) backgroundFetch() (err error) {
|
||||
err = self.gui.git.Sync.FetchBackground()
|
||||
|
||||
_ = self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.ASYNC})
|
||||
_ = self.gui.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.SYNC})
|
||||
|
||||
if err == nil {
|
||||
err = self.gui.helpers.BranchesHelper.AutoForwardBranches()
|
||||
}
|
||||
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -1189,24 +1189,21 @@ func (self *FilesController) onClickMain(opts gocui.ViewMouseBindingOpts) error
|
|||
|
||||
func (self *FilesController) fetch() error {
|
||||
return self.c.WithWaitingStatus(self.c.Tr.FetchingStatus, func(task gocui.Task) error {
|
||||
if err := self.fetchAux(task); err != nil {
|
||||
return err
|
||||
}
|
||||
return self.c.Refresh(types.RefreshOptions{Mode: types.ASYNC})
|
||||
})
|
||||
}
|
||||
|
||||
func (self *FilesController) fetchAux(task gocui.Task) (err error) {
|
||||
self.c.LogAction("Fetch")
|
||||
err = self.c.Git().Sync.Fetch(task)
|
||||
err := self.c.Git().Sync.Fetch(task)
|
||||
|
||||
if err != nil && strings.Contains(err.Error(), "exit status 128") {
|
||||
return errors.New(self.c.Tr.PassUnameWrong)
|
||||
}
|
||||
|
||||
_ = self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.ASYNC})
|
||||
_ = self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES, types.COMMITS, types.REMOTES, types.TAGS}, Mode: types.SYNC})
|
||||
|
||||
if err == nil {
|
||||
err = self.c.Helpers().BranchesHelper.AutoForwardBranches()
|
||||
}
|
||||
|
||||
return err
|
||||
})
|
||||
}
|
||||
|
||||
// Couldn't think of a better term than 'normalised'. Alas.
|
||||
|
|
|
@ -2,6 +2,7 @@ package helpers
|
|||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/gocui"
|
||||
|
@ -263,3 +264,34 @@ func (self *BranchesHelper) deleteRemoteBranches(remoteBranches []*models.Remote
|
|||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *BranchesHelper) AutoForwardBranches() error {
|
||||
if self.c.UserConfig().Git.AutoForwardBranches == "none" {
|
||||
return nil
|
||||
}
|
||||
|
||||
allBranches := self.c.UserConfig().Git.AutoForwardBranches == "allBranches"
|
||||
branches := self.c.Model().Branches
|
||||
updateCommands := ""
|
||||
// The first branch is the currently checked out branch; skip it
|
||||
for _, branch := range branches[1:] {
|
||||
if branch.RemoteBranchStoredLocally() && (allBranches || lo.Contains(self.c.UserConfig().Git.MainBranches, branch.Name)) {
|
||||
isStrictlyBehind := branch.IsBehindForPull() && !branch.IsAheadForPull()
|
||||
if isStrictlyBehind {
|
||||
updateCommands += fmt.Sprintf("update %s %s %s\n", branch.FullRefName(), branch.FullUpstreamRefName(), branch.CommitHash)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if updateCommands == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
self.c.LogAction(self.c.Tr.Actions.AutoForwardBranches)
|
||||
self.c.LogCommand(strings.TrimRight(updateCommands, "\n"), false)
|
||||
err := self.c.Git().Branch.UpdateBranchRefs(updateCommands)
|
||||
|
||||
_ = self.c.Refresh(types.RefreshOptions{Scope: []types.RefreshableView{types.BRANCHES}, Mode: types.SYNC})
|
||||
|
||||
return err
|
||||
}
|
||||
|
|
|
@ -931,6 +931,7 @@ type Actions struct {
|
|||
RenameBranch string
|
||||
CreateBranch string
|
||||
FastForwardBranch string
|
||||
AutoForwardBranches string
|
||||
CherryPick string
|
||||
CheckoutFile string
|
||||
DiscardOldFileChange string
|
||||
|
@ -2059,6 +2060,7 @@ func EnglishTranslationSet() *TranslationSet {
|
|||
MixedReset: "Mixed reset",
|
||||
HardReset: "Hard reset",
|
||||
FastForwardBranch: "Fast forward branch",
|
||||
AutoForwardBranches: "Auto-forward branches",
|
||||
Undo: "Undo",
|
||||
Redo: "Redo",
|
||||
CopyPullRequestURL: "Copy pull request URL",
|
||||
|
@ -2137,6 +2139,12 @@ gui:
|
|||
"0.44.0": `- The gui.branchColors config option is deprecated; it will be removed in a future version. Please use gui.branchColorPatterns instead.
|
||||
- The automatic coloring of branches starting with "feature/", "bugfix/", or "hotfix/" has been removed; if you want this, it's easy to set up using the new gui.branchColorPatterns option.`,
|
||||
"0.49.0": `- Executing shell commands (with the ':' prompt) no longer uses an interactive shell, which means that if you want to use your shell aliases in this prompt, you need to do a little bit of setup work. See https://github.com/jesseduffield/lazygit/blob/master/docs/Config.md#using-aliases-or-functions-in-shell-commands for details.`,
|
||||
"0.50.0": `- After fetching, main branches now get auto-forwarded to their upstream if they fall behind. This is useful for keeping your main or master branch up to date automatically. If you don't want this, you can disable it by setting the following in your config:
|
||||
|
||||
git:
|
||||
autoForwardBranches: none
|
||||
|
||||
If, on the other hand, you want this even for feature branches, you can set it to 'allBranches' instead.`,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,54 @@
|
|||
package sync
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var FetchAndAutoForwardBranchesAllBranches = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Fetch from remote and auto-forward branches with config set to 'allBranches'",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {
|
||||
config.GetUserConfig().Git.AutoForwardBranches = "allBranches"
|
||||
},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateNCommits(3)
|
||||
shell.NewBranch("feature")
|
||||
shell.NewBranch("diverged")
|
||||
shell.CloneIntoRemote("origin")
|
||||
shell.SetBranchUpstream("master", "origin/master")
|
||||
shell.SetBranchUpstream("feature", "origin/feature")
|
||||
shell.SetBranchUpstream("diverged", "origin/diverged")
|
||||
shell.Checkout("master")
|
||||
shell.HardReset("HEAD^")
|
||||
shell.Checkout("feature")
|
||||
shell.HardReset("HEAD~2")
|
||||
shell.Checkout("diverged")
|
||||
shell.HardReset("HEAD~2")
|
||||
shell.EmptyCommit("local")
|
||||
shell.NewBranch("checked-out")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Branches().
|
||||
Lines(
|
||||
Contains("checked-out").IsSelected(),
|
||||
Contains("diverged ↓2↑1"),
|
||||
Contains("feature ↓2").DoesNotContain("↑"),
|
||||
Contains("master ↓1").DoesNotContain("↑"),
|
||||
)
|
||||
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
Press(keys.Files.Fetch)
|
||||
|
||||
// AutoForwardBranches is "allBranches": both master and feature get forwarded
|
||||
t.Views().Branches().
|
||||
Lines(
|
||||
Contains("checked-out").IsSelected(),
|
||||
Contains("diverged ↓2↑1"),
|
||||
Contains("feature ✓"),
|
||||
Contains("master ✓"),
|
||||
)
|
||||
},
|
||||
})
|
|
@ -0,0 +1,54 @@
|
|||
package sync
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var FetchAndAutoForwardBranchesNone = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Fetch from remote and auto-forward branches with config set to 'none'",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {
|
||||
config.GetUserConfig().Git.AutoForwardBranches = "none"
|
||||
},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateNCommits(3)
|
||||
shell.NewBranch("feature")
|
||||
shell.NewBranch("diverged")
|
||||
shell.CloneIntoRemote("origin")
|
||||
shell.SetBranchUpstream("master", "origin/master")
|
||||
shell.SetBranchUpstream("feature", "origin/feature")
|
||||
shell.SetBranchUpstream("diverged", "origin/diverged")
|
||||
shell.Checkout("master")
|
||||
shell.HardReset("HEAD^")
|
||||
shell.Checkout("feature")
|
||||
shell.HardReset("HEAD~2")
|
||||
shell.Checkout("diverged")
|
||||
shell.HardReset("HEAD~2")
|
||||
shell.EmptyCommit("local")
|
||||
shell.NewBranch("checked-out")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Branches().
|
||||
Lines(
|
||||
Contains("checked-out").IsSelected(),
|
||||
Contains("diverged ↓2↑1"),
|
||||
Contains("feature ↓2").DoesNotContain("↑"),
|
||||
Contains("master ↓1").DoesNotContain("↑"),
|
||||
)
|
||||
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
Press(keys.Files.Fetch)
|
||||
|
||||
// AutoForwardBranches is "none": nothing should happen
|
||||
t.Views().Branches().
|
||||
Lines(
|
||||
Contains("checked-out").IsSelected(),
|
||||
Contains("diverged ↓2↑1"),
|
||||
Contains("feature ↓2").DoesNotContain("↑"),
|
||||
Contains("master ↓1").DoesNotContain("↑"),
|
||||
)
|
||||
},
|
||||
})
|
|
@ -0,0 +1,54 @@
|
|||
package sync
|
||||
|
||||
import (
|
||||
"github.com/jesseduffield/lazygit/pkg/config"
|
||||
. "github.com/jesseduffield/lazygit/pkg/integration/components"
|
||||
)
|
||||
|
||||
var FetchAndAutoForwardBranchesOnlyMainBranches = NewIntegrationTest(NewIntegrationTestArgs{
|
||||
Description: "Fetch from remote and auto-forward branches with config set to 'onlyMainBranches'",
|
||||
ExtraCmdArgs: []string{},
|
||||
Skip: false,
|
||||
SetupConfig: func(config *config.AppConfig) {
|
||||
config.GetUserConfig().Git.AutoForwardBranches = "onlyMainBranches"
|
||||
},
|
||||
SetupRepo: func(shell *Shell) {
|
||||
shell.CreateNCommits(3)
|
||||
shell.NewBranch("feature")
|
||||
shell.NewBranch("diverged")
|
||||
shell.CloneIntoRemote("origin")
|
||||
shell.SetBranchUpstream("master", "origin/master")
|
||||
shell.SetBranchUpstream("feature", "origin/feature")
|
||||
shell.SetBranchUpstream("diverged", "origin/diverged")
|
||||
shell.Checkout("master")
|
||||
shell.HardReset("HEAD^")
|
||||
shell.Checkout("feature")
|
||||
shell.HardReset("HEAD~2")
|
||||
shell.Checkout("diverged")
|
||||
shell.HardReset("HEAD~2")
|
||||
shell.EmptyCommit("local")
|
||||
shell.NewBranch("checked-out")
|
||||
},
|
||||
Run: func(t *TestDriver, keys config.KeybindingConfig) {
|
||||
t.Views().Branches().
|
||||
Lines(
|
||||
Contains("checked-out").IsSelected(),
|
||||
Contains("diverged ↓2↑1"),
|
||||
Contains("feature ↓2").DoesNotContain("↑"),
|
||||
Contains("master ↓1").DoesNotContain("↑"),
|
||||
)
|
||||
|
||||
t.Views().Files().
|
||||
IsFocused().
|
||||
Press(keys.Files.Fetch)
|
||||
|
||||
// AutoForwardBranches is "onlyMainBranches": master gets forwarded, but feature doesn't
|
||||
t.Views().Branches().
|
||||
Lines(
|
||||
Contains("checked-out").IsSelected(),
|
||||
Contains("diverged ↓2↑1"),
|
||||
Contains("feature ↓2").DoesNotContain("↑"),
|
||||
Contains("master ✓"),
|
||||
)
|
||||
},
|
||||
})
|
|
@ -360,6 +360,9 @@ var tests = []*components.IntegrationTest{
|
|||
submodule.RemoveNested,
|
||||
submodule.Reset,
|
||||
submodule.ResetFolder,
|
||||
sync.FetchAndAutoForwardBranchesAllBranches,
|
||||
sync.FetchAndAutoForwardBranchesNone,
|
||||
sync.FetchAndAutoForwardBranchesOnlyMainBranches,
|
||||
sync.FetchPrune,
|
||||
sync.FetchWhenSortedByDate,
|
||||
sync.ForcePush,
|
||||
|
|
|
@ -325,6 +325,16 @@
|
|||
"description": "If true, periodically refresh files and submodules",
|
||||
"default": true
|
||||
},
|
||||
"autoForwardBranches": {
|
||||
"type": "string",
|
||||
"enum": [
|
||||
"none",
|
||||
"onlyMainBranches",
|
||||
"allBranches"
|
||||
],
|
||||
"description": "If not \"none\", lazygit will automatically forward branches to their upstream after fetching. Applies to branches that are not the currently checked out branch, and only to those that are strictly behind their upstream (as opposed to diverged).\nPossible values: 'none' | 'onlyMainBranches' | 'allBranches'",
|
||||
"default": "onlyMainBranches"
|
||||
},
|
||||
"fetchAll": {
|
||||
"type": "boolean",
|
||||
"description": "If true, pass the --all arg to git fetch",
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue