This repository has been archived on 2026-10-25. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
simonfrankenandClaude Opus 5.5 fbd233f58d 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>
2026-10-24 22:39:18 +02:00

102 lines
2.9 KiB
Go

package tui
import (
"context"
"fmt"
"strings"
tea "github.com/charmbracelet/bubbletea"
"submarine/internal/submodule"
)
// startPinUpdate moves the pin of the selected submodule to the tip of its
// ref, if the pin is only behind it: a switch to that branch, with the tip
// the row shows. Nothing is fetched. For the superproject the pin is HEAD
// and the ref its upstream: a fast-forward of its branch.
func (m Model) startPinUpdate() (tea.Model, tea.Cmd) {
s := m.tree.Selected()
if s == nil {
return m, nil
}
_, ok := m.provider.(submodule.Switcher)
var flash string
switch {
case !ok:
flash = "this data source can't update pins"
case m.restore != nil:
flash = "wait for the restore of " + m.restore.name + " to finish"
default:
flash = pinUpdateBlocker(s)
}
if flash != "" {
cmd := m.footer.Flash(flash)
m.layout()
return m, cmd
}
st := s.Status
m.restore = &restoreState{path: s.Path, name: restoreName(s), pinUpdate: true, phase: restoreChecking}
m.layout()
ctx, path, ref, sha := m.ctx, s.Path, st.Pin.Ref, st.Tip.SHA
sw := m.provider.(submodule.Switcher)
check := func() tea.Msg {
b, err := pinUpdateBranch(ctx, sw, path, ref, sha)
if err != nil {
return lossMsg{path: path, err: err}
}
loss, err := sw.SwitchLoss(ctx, path, *b)
return lossMsg{path, loss, err, b}
}
return m, tea.Batch(check, m.tick())
}
// pinUpdateBlocker says why the pin of s can't be updated, "" if it can.
func pinUpdateBlocker(s *submodule.Submodule) string {
st, name := s.Status, restoreName(s)
pin := "the pin of " + name
if s.IsRoot() {
pin = name // its pin is HEAD
}
switch {
case st == nil:
return name + " is still loading"
case st.Presence != submodule.PresenceOK:
return name + " is not initialized; restore it first"
case st.Pin.Ref == "" || st.Tip == nil || st.Pin.Source == submodule.RefFromTag || st.Pin.Source == submodule.RefFromSHA:
if s.IsRoot() {
return name + " has no upstream branch"
}
return pin + " is not on a branch"
case st.Pin.Ahead > 0 && st.Pin.Behind > 0:
return pin + " has diverged from " + st.Pin.Ref + "; b switches the branch"
case st.Pin.Ahead > 0:
return pin + " is ahead of " + st.Pin.Ref
case st.Pin.Behind == 0:
return name + " is up to date with " + st.Pin.Ref
}
return ""
}
// pinUpdateBranch finds the branch ref among the branches of path. Its tip
// must still be sha, the tip the row showed (abbreviated, see Commit).
func pinUpdateBranch(ctx context.Context, sw submodule.Switcher, path, ref, sha string) (*submodule.Branch, error) {
bs, err := sw.Branches(ctx, path)
if err != nil {
return nil, err
}
for _, b := range bs {
if b.Name != ref {
continue
}
if !b.Remote {
return nil, fmt.Errorf("%s is a local branch; b switches to it", ref)
}
if !strings.HasPrefix(b.SHA, sha) {
return nil, fmt.Errorf("%s moved; check the new state", ref)
}
return &b, nil
}
return nil, fmt.Errorf("%s not found", ref)
}