Archived
Update a pin that is behind with u; switch fast-forwards
`u` moves the pin of the selection to the tip of its ref when the pin is only behind it: a switch to that branch with exactly the tip the row shows, nothing fetched; if the ref moved meanwhile, nothing happens. On the superproject it fast-forwards the branch to its upstream and restores the submodules (git pull --ff-only from the last fetch). Switch now fast-forwards a local branch that is only behind the chosen remote branch, unless another worktree has it checked out, and keeps a checked-out branch that tracks it under another name. A branch with commits of its own is still never moved. Stage pin is dropped: switching to the branch already stages it. The detail pane's palette wraps instead of cutting off keys. Add D29 and F59. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,143 @@
|
||||
package tui
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
|
||||
"submarine/internal/submodule"
|
||||
)
|
||||
|
||||
// newPinUpdateModel is the switch model with the first submodule selected
|
||||
// and given st as its status.
|
||||
func newPinUpdateModel(t *testing.T, loss submodule.Loss, st *submodule.Status) (tea.Model, *fakeSwitcher, string) {
|
||||
t.Helper()
|
||||
m, f := newSwitchModel(t, loss)
|
||||
repo, err := f.Tree(context.Background())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
path := repo.Submodules[0].Path
|
||||
m, _ = m.Update(statusLoadedMsg{path: path, status: st})
|
||||
mm := m.(Model)
|
||||
mm.tree.Select(path)
|
||||
return mm, f, path
|
||||
}
|
||||
|
||||
// behind is a pin two commits behind origin/feat, whose tip is bbbbbbb.
|
||||
func behind() *submodule.Status {
|
||||
return &submodule.Status{
|
||||
Pin: submodule.PinInfo{Ref: "origin/feat", Behind: 2, Pushed: true, Source: submodule.RefFromGitmodules},
|
||||
Tip: &submodule.Commit{SHA: "bbbb"}, // abbreviated, like the git provider's
|
||||
}
|
||||
}
|
||||
|
||||
func TestPinUpdateSwitchesToTheTipOfTheRef(t *testing.T) {
|
||||
m, f, path := newPinUpdateModel(t, submodule.Loss{}, behind())
|
||||
m, cmd := press(m, "u")
|
||||
m = settle(m, cmd)
|
||||
if len(f.switched) != 1 || f.switched[0] != path+" refs/remotes/origin/feat" {
|
||||
t.Fatalf("switched %q", f.switched)
|
||||
}
|
||||
if v := m.View(); !strings.Contains(v, "updated "+path+" to feat") {
|
||||
t.Errorf("no flash:\n%s", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPinUpdateAsksWhenSomethingIsLost(t *testing.T) {
|
||||
m, f, path := newPinUpdateModel(t, submodule.Loss{Files: 1, Modules: 1}, behind())
|
||||
m, cmd := press(m, "u")
|
||||
for _, msg := range run(cmd) {
|
||||
if l, ok := msg.(lossMsg); ok {
|
||||
m, _ = m.Update(l)
|
||||
}
|
||||
}
|
||||
if v := m.View(); !strings.Contains(v, "Update "+path+" to origin/feat?") || !strings.Contains(v, "y update") {
|
||||
t.Errorf("question not shown:\n%s", v)
|
||||
}
|
||||
m, cmd = press(m, "y")
|
||||
feed(m, cmd)
|
||||
if len(f.switched) != 1 {
|
||||
t.Fatalf("switched %q", f.switched)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPinUpdateAbortsWhenTheRefMoved(t *testing.T) {
|
||||
st := behind()
|
||||
st.Tip.SHA = "ccccccc" // the row showed another tip than the branch has now
|
||||
m, f, _ := newPinUpdateModel(t, submodule.Loss{}, st)
|
||||
m, cmd := press(m, "u")
|
||||
m = settle(m, cmd)
|
||||
if len(f.switched) != 0 {
|
||||
t.Fatalf("switched %q", f.switched)
|
||||
}
|
||||
if v := m.View(); !strings.Contains(v, "origin/feat moved") {
|
||||
t.Errorf("no notice:\n%s", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPinUpdateOnlyWhenThePinIsBehind(t *testing.T) {
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
edit func(*submodule.Status)
|
||||
want string
|
||||
}{
|
||||
{"up to date", func(s *submodule.Status) { s.Pin.Behind = 0 }, "is up to date with origin/feat"},
|
||||
{"ahead", func(s *submodule.Status) { s.Pin.Behind, s.Pin.Ahead = 0, 1 }, "is ahead of origin/feat"},
|
||||
{"diverged", func(s *submodule.Status) { s.Pin.Ahead = 1 }, "has diverged from origin/feat"},
|
||||
{"tag", func(s *submodule.Status) { s.Pin.Source = submodule.RefFromTag }, "is not on a branch"},
|
||||
{"missing", func(s *submodule.Status) { s.Presence = submodule.NotInitialized }, "is not initialized"},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
st := behind()
|
||||
tc.edit(st)
|
||||
m, f, _ := newPinUpdateModel(t, submodule.Loss{}, st)
|
||||
m, _ = press(m, "u") // the command is only the flash's timer
|
||||
if len(f.switched) != 0 || m.(Model).restore != nil {
|
||||
t.Fatalf("updated anyway: %q", f.switched)
|
||||
}
|
||||
if v := m.View(); !strings.Contains(v, tc.want) {
|
||||
t.Errorf("flash lacks %q:\n%s", tc.want, v)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestPinUpdateOfTheSuperprojectFastForwardsItsBranch(t *testing.T) {
|
||||
m, f := newSwitchModel(t, submodule.Loss{}) // the superproject is selected
|
||||
st := behind()
|
||||
st.Root = true
|
||||
m, _ = m.Update(statusLoadedMsg{path: "", status: st})
|
||||
m, cmd := press(m, "u")
|
||||
m = settle(m, cmd)
|
||||
if len(f.switched) != 1 || f.switched[0] != " refs/remotes/origin/feat" {
|
||||
t.Fatalf("switched %q", f.switched)
|
||||
}
|
||||
if v := m.View(); !strings.Contains(v, "updated the superproject to feat") {
|
||||
t.Errorf("no flash:\n%s", v)
|
||||
}
|
||||
}
|
||||
|
||||
func TestPinUpdateOfTheSuperprojectWithoutUpstreamSaysSo(t *testing.T) {
|
||||
m, f := newSwitchModel(t, submodule.Loss{})
|
||||
st := behind()
|
||||
st.Root, st.Pin.Source, st.Tip = true, submodule.RefFromSHA, nil
|
||||
m, _ = m.Update(statusLoadedMsg{path: "", status: st})
|
||||
m, _ = press(m, "u")
|
||||
if len(f.switched) != 0 || !strings.Contains(m.View(), "the superproject has no upstream branch") {
|
||||
t.Errorf("switched %q, view:\n%s", f.switched, m.View())
|
||||
}
|
||||
}
|
||||
|
||||
func TestPinUpdateRefusesALocalRef(t *testing.T) {
|
||||
st := behind()
|
||||
st.Pin.Ref, st.Tip.SHA = "main", "aaaa" // upstream is a local branch
|
||||
m, f, _ := newPinUpdateModel(t, submodule.Loss{}, st)
|
||||
m, cmd := press(m, "u")
|
||||
m = settle(m, cmd)
|
||||
if len(f.switched) != 0 || !strings.Contains(m.View(), "main is a local branch") {
|
||||
t.Errorf("switched %q, view:\n%s", f.switched, m.View())
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user