Skip to content

Commit 357064e

Browse files
committed
fixup! Make moving todo commits more robust
1 parent 1f739f6 commit 357064e

File tree

4 files changed

+147
-42
lines changed

4 files changed

+147
-42
lines changed

pkg/utils/rebaseTodo.go

Lines changed: 40 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -52,42 +52,6 @@ func MoveTodoDown(fileName string, sha string, action todo.TodoCommand) error {
5252
return WriteRebaseTodoFile(fileName, rearrangedTodos)
5353
}
5454

55-
func moveTodoDown(todos []todo.Todo, sha string, action todo.TodoCommand) ([]todo.Todo, error) {
56-
for i, t := range todos {
57-
// Comparing just the sha is not enough; we need to compare both the
58-
// action and the sha, as the sha could appear multiple times (e.g. in a
59-
// pick and later in a merge)
60-
if t.Command == action && equalShas(t.Commit, sha) {
61-
// The todos are ordered backwards compared to our model commits, so
62-
// actually move the commit _up_ in the todos slice (i.e. towards 0)
63-
64-
// Skip over any entries that we don't show in lazygit's view (e.g.
65-
// label, reset, or comment lines).
66-
j := i
67-
for ; j > 0 && todos[j-1].Commit == "" && todos[j-1].Command != todo.UpdateRef; j -= 1 {
68-
}
69-
70-
if j == 0 {
71-
// We expect callers to guard against this
72-
return []todo.Todo{}, fmt.Errorf("Destination position for moving todo out of range")
73-
}
74-
75-
// Need to allocate a new slice so that we don't overwrite the
76-
// original one while we're picking things from it
77-
rearrangedTodos := make([]todo.Todo, 0, len(todos))
78-
rearrangedTodos = append(rearrangedTodos, todos[0:j-1]...) // everything up to the target position
79-
rearrangedTodos = append(rearrangedTodos, todos[i]) // the commit we are moving
80-
rearrangedTodos = append(rearrangedTodos, todos[j-1:i]...) // the commits between src and dest that we skipped above
81-
rearrangedTodos = append(rearrangedTodos, todos[i+1:]...) // the remaining commits
82-
83-
return rearrangedTodos, nil
84-
}
85-
}
86-
87-
// Should never get here
88-
return []todo.Todo{}, fmt.Errorf("Todo %s not found in git-rebase-todo", sha)
89-
}
90-
9155
func MoveTodoUp(fileName string, sha string, action todo.TodoCommand) error {
9256
todos, err := ReadRebaseTodoFile(fileName)
9357
if err != nil {
@@ -100,7 +64,45 @@ func MoveTodoUp(fileName string, sha string, action todo.TodoCommand) error {
10064
return WriteRebaseTodoFile(fileName, rearrangedTodos)
10165
}
10266

103-
func moveTodoUp(todos []todo.Todo, sha string, action todo.TodoCommand) ([]todo.Todo, error) {
104-
rearrangedTodos, err := moveTodoDown(lo.Reverse(todos), sha, action)
67+
func moveTodoDown(todos []todo.Todo, sha string, action todo.TodoCommand) ([]todo.Todo, error) {
68+
rearrangedTodos, err := moveTodoUp(lo.Reverse(todos), sha, action)
10569
return lo.Reverse(rearrangedTodos), err
10670
}
71+
72+
func moveTodoUp(todos []todo.Todo, sha string, action todo.TodoCommand) ([]todo.Todo, error) {
73+
_, sourceIdx, ok := lo.FindIndexOf(todos, func(t todo.Todo) bool {
74+
// Comparing just the sha is not enough; we need to compare both the
75+
// action and the sha, as the sha could appear multiple times (e.g. in a
76+
// pick and later in a merge)
77+
return t.Command == action && equalShas(t.Commit, sha)
78+
})
79+
80+
if !ok {
81+
// Should never happen
82+
return []todo.Todo{}, fmt.Errorf("Todo %s not found in git-rebase-todo", sha)
83+
}
84+
85+
// The todos are ordered backwards compared to our model commits, so
86+
// actually move the commit _down_ in the todos slice (i.e. towards
87+
// the end of the slice)
88+
89+
// Find the next todo that we show in lazygit's commits view (skipping the rest)
90+
_, skip, ok := lo.FindIndexOf(todos[sourceIdx+1:], isRenderedTodo)
91+
92+
if !ok {
93+
// We expect callers to guard against this
94+
return []todo.Todo{}, fmt.Errorf("Destination position for moving todo is out of range")
95+
}
96+
97+
destinationIdx := sourceIdx + 1 + skip
98+
99+
rearrangedTodos := MoveElement(todos, sourceIdx, destinationIdx)
100+
101+
return rearrangedTodos, nil
102+
}
103+
104+
// We render a todo in the commits view if it's a commit or if it's an
105+
// update-ref. We don't render label, reset, or comment lines.
106+
func isRenderedTodo(t todo.Todo) bool {
107+
return t.Commit != "" || t.Command == todo.UpdateRef
108+
}

pkg/utils/rebaseTodo_test.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ func TestRebaseCommands_moveTodoDown(t *testing.T) {
8787
{Command: todo.Pick, Commit: "abcd"},
8888
},
8989
shaToMoveDown: "1234",
90-
expectedErr: "Destination position for moving todo out of range",
90+
expectedErr: "Destination position for moving todo is out of range",
9191
expectedTodos: []todo.Todo{},
9292
},
9393
{
@@ -99,7 +99,7 @@ func TestRebaseCommands_moveTodoDown(t *testing.T) {
9999
{Command: todo.Pick, Commit: "5678"},
100100
},
101101
shaToMoveDown: "1234",
102-
expectedErr: "Destination position for moving todo out of range",
102+
expectedErr: "Destination position for moving todo is out of range",
103103
expectedTodos: []todo.Todo{},
104104
},
105105
}
@@ -198,7 +198,7 @@ func TestRebaseCommands_moveTodoUp(t *testing.T) {
198198
{Command: todo.Pick, Commit: "abcd"},
199199
},
200200
shaToMoveDown: "abcd",
201-
expectedErr: "Destination position for moving todo out of range",
201+
expectedErr: "Destination position for moving todo is out of range",
202202
expectedTodos: []todo.Todo{},
203203
},
204204
{
@@ -210,7 +210,7 @@ func TestRebaseCommands_moveTodoUp(t *testing.T) {
210210
{Command: todo.Reset, Label: "otherlabel"},
211211
},
212212
shaToMoveDown: "5678",
213-
expectedErr: "Destination position for moving todo out of range",
213+
expectedErr: "Destination position for moving todo is out of range",
214214
expectedTodos: []todo.Todo{},
215215
},
216216
}

pkg/utils/slice.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,3 +92,24 @@ func MuiltiGroupBy[T any, K comparable](slice []T, f func(T) []K) map[K][]T {
9292
}
9393
return result
9494
}
95+
96+
// Returns a new slice with the element at index 'from' moved to index 'to'.
97+
// Does not mutate original slice.
98+
func MoveElement[T any](slice []T, from int, to int) []T {
99+
newSlice := make([]T, len(slice))
100+
copy(newSlice, slice)
101+
102+
if from == to {
103+
return newSlice
104+
}
105+
106+
if from < to {
107+
copy(newSlice[from:to+1], newSlice[from+1:to+1])
108+
} else {
109+
copy(newSlice[to+1:from+1], newSlice[to:from])
110+
}
111+
112+
newSlice[to] = slice[from]
113+
114+
return newSlice
115+
}

pkg/utils/slice_test.go

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -233,3 +233,85 @@ func TestLimitStr(t *testing.T) {
233233
}
234234
}
235235
}
236+
237+
func TestMoveElement(t *testing.T) {
238+
type scenario struct {
239+
testName string
240+
list []int
241+
from int
242+
to int
243+
expected []int
244+
}
245+
246+
scenarios := []scenario{
247+
{
248+
"no elements",
249+
[]int{},
250+
0,
251+
0,
252+
[]int{},
253+
},
254+
{
255+
"one element",
256+
[]int{1},
257+
0,
258+
0,
259+
[]int{1},
260+
},
261+
{
262+
"two elements, moving first to second",
263+
[]int{1, 2},
264+
0,
265+
1,
266+
[]int{2, 1},
267+
},
268+
{
269+
"two elements, moving second to first",
270+
[]int{1, 2},
271+
1,
272+
0,
273+
[]int{2, 1},
274+
},
275+
{
276+
"three elements, moving first to second",
277+
[]int{1, 2, 3},
278+
0,
279+
1,
280+
[]int{2, 1, 3},
281+
},
282+
{
283+
"three elements, moving second to first",
284+
[]int{1, 2, 3},
285+
1,
286+
0,
287+
[]int{2, 1, 3},
288+
},
289+
{
290+
"three elements, moving second to third",
291+
[]int{1, 2, 3},
292+
1,
293+
2,
294+
[]int{1, 3, 2},
295+
},
296+
{
297+
"three elements, moving third to second",
298+
[]int{1, 2, 3},
299+
2,
300+
1,
301+
[]int{1, 3, 2},
302+
},
303+
}
304+
305+
for _, s := range scenarios {
306+
s := s
307+
t.Run(s.testName, func(t *testing.T) {
308+
assert.EqualValues(t, s.expected, MoveElement(s.list, s.from, s.to))
309+
})
310+
}
311+
312+
t.Run("from out of bounds", func(t *testing.T) {
313+
assert.Panics(t, func() {
314+
MoveElement([]int{1, 2, 3}, 3, 0)
315+
})
316+
})
317+
}

0 commit comments

Comments
 (0)