Archived
Hard restore with X drops staged pins at every depth
X restores the selection to the committed pins without the staged or committed question, and resets every restored repository's index to HEAD, so staged pins below are dropped too. Dropped staged pins alone don't make it ask; they are listed if something else is lost. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+3
-1
@@ -448,7 +448,9 @@ func (m Model) handleKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) {
|
||||
m.layout()
|
||||
return m, m.filter.Focus()
|
||||
case key.Matches(msg, k.Restore):
|
||||
return m.startRestore()
|
||||
return m.startRestore(false)
|
||||
case key.Matches(msg, k.HardRestore):
|
||||
return m.startRestore(true)
|
||||
case key.Matches(msg, k.Switch):
|
||||
return m.startSwitch()
|
||||
case key.Matches(msg, k.Update):
|
||||
|
||||
@@ -10,6 +10,7 @@ type keyMap struct {
|
||||
Home, End, PageUp, PageDown key.Binding
|
||||
Fetch, Reload, Filter, Issues key.Binding
|
||||
Copy, Restore, Switch, Update key.Binding
|
||||
HardRestore key.Binding
|
||||
Help, Quit, QuitUpdate key.Binding
|
||||
}
|
||||
|
||||
@@ -31,6 +32,7 @@ func defaultKeys() keyMap {
|
||||
Filter: key.NewBinding(key.WithKeys("/"), key.WithHelp("/", "filter")),
|
||||
Issues: key.NewBinding(key.WithKeys("!"), key.WithHelp("!", "problems only")),
|
||||
Restore: key.NewBinding(key.WithKeys("x"), key.WithHelp("x", "restore")),
|
||||
HardRestore: key.NewBinding(key.WithKeys("X"), key.WithHelp("X", "hard restore")),
|
||||
Switch: key.NewBinding(key.WithKeys("b"), key.WithHelp("b", "switch branch")),
|
||||
Update: key.NewBinding(key.WithKeys("u"), key.WithHelp("u", "update pin")),
|
||||
Copy: key.NewBinding(key.WithKeys("c"), key.WithHelp("c", "copy path")),
|
||||
@@ -58,7 +60,7 @@ func (k keyMap) Navigation() []key.Binding {
|
||||
// Submodule returns the bindings that act on the selected submodule. They
|
||||
// are shown in the detail pane, or in the footer if the pane can't show them.
|
||||
func (k keyMap) Submodule() []key.Binding {
|
||||
return []key.Binding{k.Copy, k.Update, k.Switch, k.Restore}
|
||||
return []key.Binding{k.Copy, k.Update, k.Switch, k.Restore, k.HardRestore}
|
||||
}
|
||||
|
||||
// Global returns the bindings that don't depend on the selection.
|
||||
@@ -90,7 +92,7 @@ func (k keyMap) FullHelp() [][]key.Binding {
|
||||
{k.Up, k.Down, k.Left, k.Right, k.Toggle},
|
||||
{k.CollapseAll, k.ExpandAll},
|
||||
{k.Home, k.End, k.PageUp, k.PageDown},
|
||||
{k.Copy, k.Update, k.Switch, k.Restore},
|
||||
{k.Copy, k.Update, k.Switch, k.Restore, k.HardRestore},
|
||||
{k.Fetch, k.Reload, k.Filter, k.Issues},
|
||||
append([]key.Binding{k.Help, k.Quit}, k.fullUpdate()...),
|
||||
}
|
||||
|
||||
+22
-8
@@ -47,8 +47,9 @@ type restoreDoneMsg struct {
|
||||
|
||||
// startRestore checks what restoring the selection would discard. If nothing
|
||||
// would be lost it restores at once, else it asks first. A submodule with a
|
||||
// staged pin first asks which pin to restore to.
|
||||
func (m Model) startRestore() (tea.Model, tea.Cmd) {
|
||||
// staged pin first asks which pin to restore to; a hard restore doesn't: it
|
||||
// goes to the committed pins, at every depth.
|
||||
func (m Model) startRestore(hard bool) (tea.Model, tea.Cmd) {
|
||||
s := m.tree.Selected()
|
||||
_, ok := m.provider.(submodule.Restorer)
|
||||
var flash string
|
||||
@@ -67,6 +68,10 @@ func (m Model) startRestore() (tea.Model, tea.Cmd) {
|
||||
}
|
||||
|
||||
m.restore = &restoreState{path: s.Path, name: restoreName(s)}
|
||||
if hard {
|
||||
m.restore.to = submodule.RestoreHard
|
||||
return m.checkRestore()
|
||||
}
|
||||
if !s.IsRoot() && s.Status != nil && s.Status.PinStaged {
|
||||
m.restore.phase = restoreChoosing
|
||||
m.layout()
|
||||
@@ -130,7 +135,13 @@ func (m Model) lossChecked(msg lossMsg) (tea.Model, tea.Cmd) {
|
||||
m.restore.branch = msg.branch
|
||||
}
|
||||
m.restore.loss = msg.loss
|
||||
if msg.loss.Empty() {
|
||||
// a hard restore is asked for to drop staged pins: alone they are no
|
||||
// reason to ask, but are listed if something else is lost
|
||||
lost := msg.loss
|
||||
if m.restore.to == submodule.RestoreHard {
|
||||
lost.StagedPins, lost.Modules = 0, 0
|
||||
}
|
||||
if lost.Empty() {
|
||||
return m.runRestore()
|
||||
}
|
||||
m.restore.phase = restoreAsking
|
||||
@@ -199,13 +210,16 @@ func (m Model) restoreDone(msg restoreDoneMsg) (tea.Model, tea.Cmd) {
|
||||
return m, tea.Batch(cmds...)
|
||||
}
|
||||
|
||||
// toText is " to origin/feat" for a switch, " to the committed pin" if that was chosen; the staged pin is the
|
||||
// usual target and needs no words.
|
||||
// toText is " to origin/feat" for a switch, " to the committed pin" if that
|
||||
// was chosen, " to the committed pins" for a hard restore; the staged pin is
|
||||
// the usual target and needs no words.
|
||||
func (r *restoreState) toText() string {
|
||||
if r.branch != nil {
|
||||
switch {
|
||||
case r.branch != nil:
|
||||
return " to " + r.branch.Name
|
||||
}
|
||||
if r.to == submodule.RestoreCommitted {
|
||||
case r.to == submodule.RestoreHard:
|
||||
return " to the committed pins"
|
||||
case r.to == submodule.RestoreCommitted:
|
||||
return " to the committed pin"
|
||||
}
|
||||
return ""
|
||||
|
||||
@@ -3,6 +3,7 @@ package tui
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
tea "github.com/charmbracelet/bubbletea"
|
||||
@@ -187,3 +188,47 @@ func TestRestoreOfAStagedPinAsksWhichPin(t *testing.T) {
|
||||
t.Fatalf("restored %q to %v, want %s to the committed pin", f.restored, f.to, staged.Path)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHardRestoreOfAStagedPinDoesNotAsk(t *testing.T) {
|
||||
m, f := newRestoreModel(t, submodule.Loss{StagedPins: 2, Modules: 2})
|
||||
mm := m.(Model)
|
||||
var staged *submodule.Submodule
|
||||
submodule.Walk(mm.repo.Submodules, func(s *submodule.Submodule) {
|
||||
if staged == nil && s.Children == nil {
|
||||
staged = s
|
||||
}
|
||||
})
|
||||
staged.Status = &submodule.Status{PinStaged: true, Pin: submodule.PinInfo{Pushed: true}}
|
||||
mm.tree.Select(staged.Path)
|
||||
m = mm
|
||||
|
||||
// neither which pin, nor y/n for staged pins alone
|
||||
m, cmd := press(m, "X")
|
||||
var next tea.Cmd
|
||||
for _, msg := range run(cmd) {
|
||||
if l, ok := msg.(lossMsg); ok {
|
||||
m, next = m.Update(l)
|
||||
}
|
||||
}
|
||||
feed(m, next)
|
||||
if len(f.to) != 1 || f.to[0] != submodule.RestoreHard || f.restored[0] != staged.Path {
|
||||
t.Fatalf("restored %q to %v, want %s hard", f.restored, f.to, staged.Path)
|
||||
}
|
||||
}
|
||||
|
||||
func TestHardRestoreAsksWhenWorkIsLost(t *testing.T) {
|
||||
m, _ := newRestoreModel(t, submodule.Loss{Files: 1, StagedPins: 1, Modules: 1})
|
||||
m, cmd := press(m, "X")
|
||||
for _, msg := range run(cmd) {
|
||||
if l, ok := msg.(lossMsg); ok {
|
||||
m, _ = m.Update(l)
|
||||
}
|
||||
}
|
||||
r := m.(Model).restore
|
||||
if r == nil || r.phase != restoreAsking {
|
||||
t.Fatalf("want a question, got %+v", r)
|
||||
}
|
||||
if got := lossText(r.loss); !strings.Contains(got, "1 staged pin") {
|
||||
t.Errorf("question %q doesn't list the staged pin", got)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user