From 26ad23a8d2fff80099094f1a391d47141dfaca99 Mon Sep 17 00:00:00 2001 From: Stefan Haller Date: Thu, 24 Sep 2026 08:53:46 +0200 Subject: [PATCH] Offer to update the branches stacked below the current one when pulling When somebody else rebases a stack and force-pushes it, pulling the topmost branch leaves the branches below it diverged. They can be updated with `f`, but pushing the stack offers to push the branches below along with the current one, and pulling should be symmetric. When pulling, find the branches stacked below the current one that can be updated without losing anything. These are those that are behind their upstream branches, and those that diverged from them only because they were rewritten. If there are any, show a menu that lists them and offers to update them in addition to pulling the current branch, or to pull only the current branch. The current branch is pulled as before. The decision is based on the last fetch, not on a new one, so that the menu appears right away. If the remote has changed since then, a branch might not be offered although it could be updated; it then shows as diverged after the pull, and pulling again offers it. The update itself fetches the upstream branches and checks them again, so it never loses commits. Branches that are checked out in another worktree are not offered, since updating them would change the files there. The branches below are updated before the current branch is pulled. If one of them pointed into the commits that a rebasing pull rebases, the pull would move it when rebase.updateRefs is set, and updating it afterwards would fail. If updating them fails, the current branch isn't pulled. Co-Authored-By: Claude Opus 5.5 (1M context) --- docs-master/Stacked_Branches.md | 8 ++ docs-master/keybindings/Keybindings_en.md | 2 +- docs-master/keybindings/Keybindings_ko.md | 2 +- docs-master/keybindings/Keybindings_ru.md | 2 +- pkg/gui/controllers/sync_controller.go | 98 ++++++++++++++++++- pkg/i18n/english.go | 8 +- .../tests/sync/pull_stacked_branches.go | 56 +++++++++++ ..._branches_checked_out_in_other_worktree.go | 47 +++++++++ .../pull_stacked_branches_only_current.go | 36 +++++++ .../pull_stacked_branches_update_fails.go | 44 +++++++++ ...ull_stacked_branches_with_local_commits.go | 47 +++++++++ pkg/integration/tests/test_list.go | 5 + 12 files changed, 346 insertions(+), 9 deletions(-) create mode 100644 pkg/integration/tests/sync/pull_stacked_branches.go create mode 100644 pkg/integration/tests/sync/pull_stacked_branches_checked_out_in_other_worktree.go create mode 100644 pkg/integration/tests/sync/pull_stacked_branches_only_current.go create mode 100644 pkg/integration/tests/sync/pull_stacked_branches_update_fails.go create mode 100644 pkg/integration/tests/sync/pull_stacked_branches_with_local_commits.go diff --git a/docs-master/Stacked_Branches.md b/docs-master/Stacked_Branches.md index c9b080e11..f961e74fd 100644 --- a/docs-master/Stacked_Branches.md +++ b/docs-master/Stacked_Branches.md @@ -41,3 +41,11 @@ this work. whole stack and bring all of it back in sync at once. If any of the selected branches can't be updated, none of them is, so that you don't end up with half of the stack updated. + +Alternatively, check out the topmost branch of the stack and pull it with `p`. +If branches below it can be updated this way, or are simply behind their +upstream, lazygit offers to update them along with it. Lazygit decides this +from the last fetch, so a branch whose changes on the remote haven't been +fetched yet isn't offered; with auto-fetch turned off, pull a second time after +the first pull has fetched them. Branches that are checked out in another +worktree are left alone. The topmost branch itself is pulled as usual. diff --git a/docs-master/keybindings/Keybindings_en.md b/docs-master/keybindings/Keybindings_en.md index 5010a4f75..f19d74964 100644 --- a/docs-master/keybindings/Keybindings_en.md +++ b/docs-master/keybindings/Keybindings_en.md @@ -11,7 +11,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` , J, (fn+down/shift+j) `` | Scroll down main window | | | `` @ `` | View command log options | View options for the command log e.g. show/hide the command log and focus the command log. | | `` P `` | Push | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. If other branches are stacked below the current one and have commits to push, you are offered to push those too. | -| `` p `` | Pull | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. | +| `` p `` | Pull | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. If other branches are stacked below the current one and have changed on the remote, you are offered to update those too. | | `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename.

The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | | `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename.

The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | | `` } `` | Increase diff context size | Increase the amount of the context shown around changes in the diff view.

The default can be changed in the config file with the key 'git.diffContextSize'. | diff --git a/docs-master/keybindings/Keybindings_ko.md b/docs-master/keybindings/Keybindings_ko.md index 918f15d55..085c8e3fc 100644 --- a/docs-master/keybindings/Keybindings_ko.md +++ b/docs-master/keybindings/Keybindings_ko.md @@ -11,7 +11,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` , J, (fn+down/shift+j) `` | 메인 패널을 아래로로 스크롤 | | | `` @ `` | 명령어 로그 메뉴 열기 | View options for the command log e.g. show/hide the command log and focus the command log. | | `` P `` | 푸시 | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. If other branches are stacked below the current one and have commits to push, you are offered to push those too. | -| `` p `` | 업데이트 | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. | +| `` p `` | 업데이트 | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. If other branches are stacked below the current one and have changed on the remote, you are offered to update those too. | | `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename.

The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | | `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename.

The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | | `` } `` | Diff 보기의 변경 사항 주위에 표시되는 컨텍스트의 크기를 늘리기 | Increase the amount of the context shown around changes in the diff view.

The default can be changed in the config file with the key 'git.diffContextSize'. | diff --git a/docs-master/keybindings/Keybindings_ru.md b/docs-master/keybindings/Keybindings_ru.md index 716f11f0e..f6304dda9 100644 --- a/docs-master/keybindings/Keybindings_ru.md +++ b/docs-master/keybindings/Keybindings_ru.md @@ -11,7 +11,7 @@ _This file is auto-generated. To update, make the changes in the pkg/i18n direct | `` , J, (fn+down/shift+j) `` | Прокрутить вниз главную панель | | | `` @ `` | Открыть меню журнала команд | View options for the command log e.g. show/hide the command log and focus the command log. | | `` P `` | Отправить изменения | Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. If other branches are stacked below the current one and have commits to push, you are offered to push those too. | -| `` p `` | Получить и слить изменения | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. | +| `` p `` | Получить и слить изменения | Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. If other branches are stacked below the current one and have changed on the remote, you are offered to update those too. | | `` ) `` | Increase rename similarity threshold | Increase the similarity threshold for a deletion and addition pair to be treated as a rename.

The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | | `` ( `` | Decrease rename similarity threshold | Decrease the similarity threshold for a deletion and addition pair to be treated as a rename.

The default can be changed in the config file with the key 'git.renameSimilarityThreshold'. | | `` } `` | Увеличить размер контекста, отображаемого вокруг изменений в просмотрщике сравнении | Increase the amount of the context shown around changes in the diff view.

The default can be changed in the config file with the key 'git.diffContextSize'. | diff --git a/pkg/gui/controllers/sync_controller.go b/pkg/gui/controllers/sync_controller.go index bd07e2c9a..260b5a2fb 100644 --- a/pkg/gui/controllers/sync_controller.go +++ b/pkg/gui/controllers/sync_controller.go @@ -8,7 +8,6 @@ import ( "github.com/jesseduffield/lazygit/pkg/commands/git_commands" "github.com/jesseduffield/lazygit/pkg/commands/models" "github.com/jesseduffield/lazygit/pkg/gocui" - "github.com/jesseduffield/lazygit/pkg/gui/context" "github.com/jesseduffield/lazygit/pkg/gui/controllers/helpers" "github.com/jesseduffield/lazygit/pkg/gui/style" "github.com/jesseduffield/lazygit/pkg/gui/types" @@ -241,7 +240,36 @@ func (self *SyncController) resolvePushOfCurrentBranch(currentBranch *models.Bra } func (self *SyncController) pull(currentBranch *models.Branch) error { - action := self.c.Tr.Actions.Pull + branchesBelow := self.updatableBranchesBelow(currentBranch) + if len(branchesBelow) == 0 { + return self.pullCurrentBranch(currentBranch, nil) + } + + branchName := map[string]string{"branchName": currentBranch.Name} + return self.c.Menu(types.CreateMenuOptions{ + Title: self.c.Tr.Pull, + Prompt: self.branchesBelowToUpdatePrompt(currentBranch, branchesBelow), + Items: []*types.MenuItem{ + { + Label: utils.ResolvePlaceholderString(self.c.Tr.PullBranchAndBranchesBelow, branchName), + OnPress: func() error { + return self.pullCurrentBranch(currentBranch, branchesBelow) + }, + }, + { + Label: utils.ResolvePlaceholderString(self.c.Tr.PullOnlyCurrentBranch, branchName), + OnPress: func() error { + return self.pullCurrentBranch(currentBranch, nil) + }, + }, + }, + }) +} + +// Pulls the current branch, after updating the given branches stacked below it +// to their upstream branches +func (self *SyncController) pullCurrentBranch(currentBranch *models.Branch, branchesBelow []*models.Branch) error { + opts := PullFilesOptions{Action: self.c.Tr.Actions.Pull, BranchesBelow: branchesBelow} // if we have no upstream branch we need to set that first if !currentBranch.IsTrackingRemote() { @@ -250,11 +278,46 @@ func (self *SyncController) pull(currentBranch *models.Branch) error { return err } - return self.PullAux(currentBranch, PullFilesOptions{Action: action}) + return self.PullAux(currentBranch, opts) }) } - return self.PullAux(currentBranch, PullFilesOptions{Action: action}) + return self.PullAux(currentBranch, opts) +} + +// The branches stacked below the current one that can be updated to their +// upstream branches without losing anything: those that are behind them, and +// those that diverged from them only because they were rewritten +func (self *SyncController) updatableBranchesBelow(currentBranch *models.Branch) []*models.Branch { + branchesBelow := helpers.BranchesBelowInStack( + self.c.Model().Commits, self.c.Model().Branches, currentBranch, self.c.UserConfig().Git.MainBranches) + + return lo.Filter(branchesBelow, func(branch *models.Branch, _ int) bool { + // Updating a branch that is checked out in another worktree changes + // the files there, which is more than pulling the current branch + // should do + if git_commands.CheckedOutByOtherWorktree(branch, self.c.Model().Worktrees) { + return false + } + + return branch.IsBehindForPull() && (!branch.IsAheadForPull() || branch.UpstreamRewritten.Load()) + }) +} + +func (self *SyncController) branchesBelowToUpdatePrompt(currentBranch *models.Branch, branchesBelow []*models.Branch) string { + intro := utils.ResolvePlaceholderString( + self.c.Tr.BranchesBelowHaveChangedOnRemote, + map[string]string{"branchName": currentBranch.Name}, + ) + lines := lo.Map(branchesBelow, func(branch *models.Branch, _ int) string { + divergence := "↓" + branch.BehindForPull + if branch.IsAheadForPull() { + divergence += "↑" + branch.AheadForPull + } + return fmt.Sprintf("%s %s", branch.Name, style.FgYellow.Sprint(divergence)) + }) + + return intro + "\n\n " + strings.Join(lines, "\n ") } func (self *SyncController) setCurrentBranchUpstream(upstream string) error { @@ -280,10 +343,35 @@ type PullFilesOptions struct { UpstreamBranch string FastForwardOnly bool Action string + + // Branches stacked below the current one, updated to their upstream + // branches before the current one is pulled + BranchesBelow []*models.Branch } func (self *SyncController) PullAux(currentBranch *models.Branch, opts PullFilesOptions) error { - return self.c.WithInlineStatus(currentBranch, types.ItemOperationPulling, context.LOCAL_BRANCHES_CONTEXT_KEY, func(task gocui.Task) error { + var updateBranchesBelow func(gocui.Task) error + if len(opts.BranchesBelow) > 0 { + var err error + updateBranchesBelow, err = self.c.Helpers().BranchesHelper.PrepareFastForward(opts.BranchesBelow) + if err != nil { + return err + } + } + + branches := append([]*models.Branch{currentBranch}, opts.BranchesBelow...) + return self.c.Helpers().BranchesHelper.WithInlineStatusOnBranches(branches, types.ItemOperationPulling, func(task gocui.Task) error { + // Update the branches below first. If one of them pointed into the + // commits that a rebasing pull rebases, the pull would move it when + // rebase.updateRefs is set, and updating it afterwards would fail. If + // updating them fails, the current branch isn't pulled either; the + // user can still choose to pull only the current branch. + if updateBranchesBelow != nil { + if err := updateBranchesBelow(task); err != nil { + return err + } + } + return self.pullWithLock(task, opts) }) } diff --git a/pkg/i18n/english.go b/pkg/i18n/english.go index 3cfe7bd78..8e0bcb017 100644 --- a/pkg/i18n/english.go +++ b/pkg/i18n/english.go @@ -244,6 +244,9 @@ type TranslationSet struct { BranchesBelowHaveCommitsToPush string PushBranchAndBranchesBelow string PushOnlyCurrentBranch string + BranchesBelowHaveChangedOnRemote string + PullBranchAndBranchesBelow string + PullOnlyCurrentBranch string UpdatesRejected string UpdatesRejectedAndForcePushDisabled string CheckForUpdate string @@ -1232,7 +1235,7 @@ func EnglishTranslationSet() *TranslationSet { Push: "Push", PushTooltip: "Push the current branch to its upstream branch. If no upstream is configured, you will be prompted to configure an upstream branch. If other branches are stacked below the current one and have commits to push, you are offered to push those too.", Pull: "Pull", - PullTooltip: "Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch.", + PullTooltip: "Pull changes from the remote for the current branch. If no upstream is configured, you will be prompted to configure an upstream branch. If other branches are stacked below the current one and have changed on the remote, you are offered to update those too.", MergeConflictsTitle: "Merge conflicts", MergeConflictDescription_DD: "Conflict: this file was moved or renamed both in the current and the incoming changes, but to different destinations. I don't know which ones, but they should both show up as conflicts too (marked 'AU' and 'UA', respectively). The most likely resolution is to delete this file, and pick one of the destinations and delete the other.", MergeConflictDescription_AU: "Conflict: this file is the destination of a move or rename in the current changes, but was moved or renamed to a different destination in the incoming changes. That other destination should also show up as a conflict (marked 'UA'), as well as the file that both were renamed from (marked 'DD').", @@ -1413,6 +1416,9 @@ func EnglishTranslationSet() *TranslationSet { BranchesBelowHaveCommitsToPush: "The following branches stacked below '{{.branchName}}' also have commits to push:", PushBranchAndBranchesBelow: "Push all these branches in addition to the current one", PushOnlyCurrentBranch: "Push only '{{.branchName}}'", + BranchesBelowHaveChangedOnRemote: "The following branches stacked below '{{.branchName}}' have also changed on the remote:", + PullBranchAndBranchesBelow: "Pull all these branches in addition to the current one", + PullOnlyCurrentBranch: "Pull only '{{.branchName}}'", UpdatesRejected: "Updates were rejected. Please fetch and examine the remote changes before pushing again.", UpdatesRejectedAndForcePushDisabled: "Updates were rejected and you have disabled force pushing", CheckForUpdate: "Check for update", diff --git a/pkg/integration/tests/sync/pull_stacked_branches.go b/pkg/integration/tests/sync/pull_stacked_branches.go new file mode 100644 index 000000000..2b45b63cd --- /dev/null +++ b/pkg/integration/tests/sync/pull_stacked_branches.go @@ -0,0 +1,56 @@ +package sync + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var PullStackedBranches = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Pull a stack of branches that was rebased on the remote, updating the branches below the current one too", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) { + config.GetUserConfig().Git.LocalBranchSortOrder = "alphabetical" + }, + SetupRepo: func(shell *Shell) { + createStackRewrittenOnTheRemote(shell) + shell.Checkout("branch3") + shell.SetConfig("pull.rebase", "true") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Branches(). + Lines( + Contains("branch3 ↓3↑3"), + Contains("branch1 ↓1↑1"), + Contains("branch2 ↓2↑2"), + Contains("master"), + ) + + t.Views().Files().IsFocused().Press(keys.Universal.Pull) + + t.ExpectPopup().Menu(). + Title(Equals("Pull")). + ContainsLines( + Contains(" branch2 ↓2↑2"), + Contains(" branch1 ↓1↑1"), + ). + Select(Contains("Pull all these branches in addition to the current one")). + Confirm() + + t.Views().Branches(). + Lines( + Contains("branch3 ✓"), + Contains("branch1 ✓"), + Contains("branch2 ✓"), + Contains("master"), + ) + + t.Views().Commits(). + Lines( + Contains("three-rewritten"), + Contains("two-rewritten"), + Contains("one-rewritten"), + Contains("base"), + ) + }, +}) diff --git a/pkg/integration/tests/sync/pull_stacked_branches_checked_out_in_other_worktree.go b/pkg/integration/tests/sync/pull_stacked_branches_checked_out_in_other_worktree.go new file mode 100644 index 000000000..592dc438c --- /dev/null +++ b/pkg/integration/tests/sync/pull_stacked_branches_checked_out_in_other_worktree.go @@ -0,0 +1,47 @@ +package sync + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var PullStackedBranchesCheckedOutInOtherWorktree = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Pull a stack that was rebased on the remote; a branch below the current one that is checked out in another worktree is not offered for updating", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) { + config.GetUserConfig().Git.LocalBranchSortOrder = "alphabetical" + }, + SetupRepo: func(shell *Shell) { + createStackRewrittenOnTheRemote(shell) + shell.Checkout("branch3") + shell.SetConfig("pull.rebase", "true") + + shell.AddWorktreeCheckout("branch1", "../linked-worktree") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Files().IsFocused().Press(keys.Universal.Pull) + + t.ExpectPopup().Menu(). + Title(Equals("Pull")). + Lines( + Contains("The following branches stacked below 'branch3' have also changed on the remote:"), + Equals(""), + Equals(" branch2 ↓2↑2"), + Equals(""), + Contains("Pull all these branches in addition to the current one"), + Contains("Pull only 'branch3'"), + Contains("Cancel"), + ). + Select(Contains("Pull all these branches in addition to the current one")). + Confirm() + + t.Views().Branches(). + Lines( + Contains("branch3 ✓"), + Contains("branch1 (worktree linked-worktree) ↓1↑1"), + Contains("branch2 ✓"), + Contains("master"), + ) + }, +}) diff --git a/pkg/integration/tests/sync/pull_stacked_branches_only_current.go b/pkg/integration/tests/sync/pull_stacked_branches_only_current.go new file mode 100644 index 000000000..f1cbfc6a2 --- /dev/null +++ b/pkg/integration/tests/sync/pull_stacked_branches_only_current.go @@ -0,0 +1,36 @@ +package sync + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var PullStackedBranchesOnlyCurrent = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Decline updating the branches below the current one in a stack that was rebased on the remote, pulling only the current branch", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) { + config.GetUserConfig().Git.LocalBranchSortOrder = "alphabetical" + }, + SetupRepo: func(shell *Shell) { + createStackRewrittenOnTheRemote(shell) + shell.Checkout("branch3") + shell.SetConfig("pull.rebase", "true") + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Files().IsFocused().Press(keys.Universal.Pull) + + t.ExpectPopup().Menu(). + Title(Equals("Pull")). + Select(Contains("Pull only 'branch3'")). + Confirm() + + t.Views().Branches(). + Lines( + Contains("branch3 ✓"), + Contains("branch1 ↓1↑1"), + Contains("branch2 ↓2↑2"), + Contains("master"), + ) + }, +}) diff --git a/pkg/integration/tests/sync/pull_stacked_branches_update_fails.go b/pkg/integration/tests/sync/pull_stacked_branches_update_fails.go new file mode 100644 index 000000000..cfadb051b --- /dev/null +++ b/pkg/integration/tests/sync/pull_stacked_branches_update_fails.go @@ -0,0 +1,44 @@ +package sync + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var PullStackedBranchesUpdateFails = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Pull a stack that was rebased on the remote, when updating the branches below the current one fails; the current branch is not pulled then", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) { + config.GetUserConfig().Git.LocalBranchSortOrder = "alphabetical" + }, + SetupRepo: func(shell *Shell) { + createStackRewrittenOnTheRemote(shell) + shell.Checkout("branch3") + shell.SetConfig("pull.rebase", "true") + + // Somebody deleted branch1 on the remote, which we haven't fetched yet + shell.RunCommand([]string{"git", "-C", "../origin", "branch", "-D", "branch1"}) + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Files().IsFocused().Press(keys.Universal.Pull) + + t.ExpectPopup().Menu(). + Title(Equals("Pull")). + Select(Contains("Pull all these branches in addition to the current one")). + Confirm() + + t.ExpectPopup().Alert(). + Title(Equals("Error")). + Content(Contains("couldn't find remote ref")). + Confirm() + + t.Views().Branches(). + Lines( + Contains("branch3 ↓3↑3"), + Contains("branch1 ↓1↑1"), + Contains("branch2 ↓2↑2"), + Contains("master"), + ) + }, +}) diff --git a/pkg/integration/tests/sync/pull_stacked_branches_with_local_commits.go b/pkg/integration/tests/sync/pull_stacked_branches_with_local_commits.go new file mode 100644 index 000000000..d9c426471 --- /dev/null +++ b/pkg/integration/tests/sync/pull_stacked_branches_with_local_commits.go @@ -0,0 +1,47 @@ +package sync + +import ( + "github.com/jesseduffield/lazygit/pkg/config" + . "github.com/jesseduffield/lazygit/pkg/integration/components" +) + +var PullStackedBranchesWithLocalCommits = NewIntegrationTest(NewIntegrationTestArgs{ + Description: "Pull a stack that was rebased on the remote; a branch below the current one that has a commit of its own is not offered for updating", + ExtraCmdArgs: []string{}, + Skip: false, + SetupConfig: func(config *config.AppConfig) { + config.GetUserConfig().Git.LocalBranchSortOrder = "alphabetical" + }, + SetupRepo: func(shell *Shell) { + createStackRewrittenOnTheRemote(shell) + + shell.Checkout("branch2") + shell.EmptyCommit("mine") + shell.Checkout("branch3") + shell.RunCommand([]string{"git", "rebase", "--onto", "branch2", "branch2~1"}) + }, + Run: func(t *TestDriver, keys config.KeybindingConfig) { + t.Views().Branches(). + Lines( + Contains("branch3 ↓3↑4"), + Contains("branch1 ↓1↑1"), + Contains("branch2 ↓2↑3"), + Contains("master"), + ) + + t.Views().Files().IsFocused().Press(keys.Universal.Pull) + + t.ExpectPopup().Menu(). + Title(Equals("Pull")). + Lines( + Contains("The following branches stacked below 'branch3' have also changed on the remote:"), + Equals(""), + Equals(" branch1 ↓1↑1"), + Equals(""), + Contains("Pull all these branches in addition to the current one"), + Contains("Pull only 'branch3'"), + Contains("Cancel"), + ). + Cancel() + }, +}) diff --git a/pkg/integration/tests/test_list.go b/pkg/integration/tests/test_list.go index d859b3bc7..7ac9055ab 100644 --- a/pkg/integration/tests/test_list.go +++ b/pkg/integration/tests/test_list.go @@ -509,6 +509,11 @@ var tests = []*components.IntegrationTest{ sync.PullRebaseConflict, sync.PullRebaseInteractiveConflict, sync.PullRebaseInteractiveConflictDrop, + sync.PullStackedBranches, + sync.PullStackedBranchesCheckedOutInOtherWorktree, + sync.PullStackedBranchesOnlyCurrent, + sync.PullStackedBranchesUpdateFails, + sync.PullStackedBranchesWithLocalCommits, sync.Push, sync.PushAndAutoSetUpstream, sync.PushAndSetUpstream,