mirror of
https://github.com/jesseduffield/lazygit.git
synced 2026-09-14 09:36:24 -04:00
Add runGitCmdOnPaths utility
Useful when we need to call git with potentially tons of arguments that might exceed the OS' command-line length limit.
This commit is contained in:
parent
f987b35a9e
commit
e434f5b5e9
|
|
@ -2,6 +2,8 @@ package git_commands
|
|||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
)
|
||||
|
||||
// convenience struct for building git commands. Especially useful when
|
||||
|
|
@ -106,3 +108,30 @@ func (self *GitCommandBuilder) ToArgv() []string {
|
|||
func (self *GitCommandBuilder) ToString() string {
|
||||
return strings.Join(self.ToArgv(), " ")
|
||||
}
|
||||
|
||||
// runGitCmdOnPaths runs `git <subcommand> -- <paths...>`, splitting into
|
||||
// multiple calls if needed to stay under the OS command-line length limit.
|
||||
// Windows CreateProcess has a ~32 KB limit; we use 30 KB as a safe threshold.
|
||||
func runGitCmdOnPaths(subcommand string, paths []string, cmd oscommands.ICmdObjBuilder) error {
|
||||
const maxArgBytes = 30_000
|
||||
|
||||
start := 0
|
||||
for start < len(paths) {
|
||||
end := start
|
||||
total := 0
|
||||
for end < len(paths) {
|
||||
total += len(paths[end]) + 1 // +1 for the separating space
|
||||
if total > maxArgBytes && end > start {
|
||||
break
|
||||
}
|
||||
end++
|
||||
}
|
||||
if err := cmd.New(NewGitCmd(subcommand).Arg("--").
|
||||
Arg(paths[start:end]...).
|
||||
ToArgv()).Run(); err != nil {
|
||||
return err
|
||||
}
|
||||
start = end
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,8 +1,10 @@
|
|||
package git_commands
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
|
|
@ -54,3 +56,44 @@ func TestGitCommandBuilder(t *testing.T) {
|
|||
assert.Equal(t, s.input, s.expected)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRunGitCmdOnPaths(t *testing.T) {
|
||||
// Each path is 9000 bytes. Three fit within the 30 KB limit (27001 bytes
|
||||
// including spaces), four do not (36002 bytes), so a four-path slice must
|
||||
// be split into two calls of three and one.
|
||||
longPath := func(ch string) string { return strings.Repeat(ch, 9_000) }
|
||||
p1, p2, p3, p4 := longPath("a"), longPath("b"), longPath("c"), longPath("d")
|
||||
|
||||
scenarios := []struct {
|
||||
name string
|
||||
paths []string
|
||||
runner *oscommands.FakeCmdObjRunner
|
||||
}{
|
||||
{
|
||||
name: "empty list makes no calls",
|
||||
paths: []string{},
|
||||
runner: oscommands.NewFakeRunner(t),
|
||||
},
|
||||
{
|
||||
name: "paths that fit in one batch make a single call",
|
||||
paths: []string{p1, p2, p3},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs(append([]string{"checkout", "--"}, p1, p2, p3), "", nil),
|
||||
},
|
||||
{
|
||||
name: "paths that exceed the limit are split across multiple calls",
|
||||
paths: []string{p1, p2, p3, p4},
|
||||
runner: oscommands.NewFakeRunner(t).
|
||||
ExpectGitArgs(append([]string{"checkout", "--"}, p1, p2, p3), "", nil).
|
||||
ExpectGitArgs(append([]string{"checkout", "--"}, p4), "", nil),
|
||||
},
|
||||
}
|
||||
|
||||
for _, s := range scenarios {
|
||||
t.Run(s.name, func(t *testing.T) {
|
||||
cmd := oscommands.NewDummyCmdObjBuilder(s.runner)
|
||||
assert.NoError(t, runGitCmdOnPaths("checkout", s.paths, cmd))
|
||||
s.runner.CheckForMissingCalls()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue