Add PushUrls field to models.Remote

Add some tests for RemoteLoader while we're at it; we didn't have any.
This commit is contained in:
Stefan Haller
2026-05-04 13:09:47 +02:00
parent 59b9b5959d
commit d0ed65e0fd
3 changed files with 134 additions and 6 deletions
+21 -4
View File
@@ -69,7 +69,7 @@ func (self *RemoteLoader) GetRemotes() ([]*models.Remote, error) {
func (self *RemoteLoader) getRemotesFromConfig() []*models.Remote {
cmdArgs := NewGitCmd("config").
Arg("--local", "--get-regexp", `^remote\.[^.]+\.url$`).ToArgv()
Arg("--local", "--get-regexp", `^remote\.[^.]+\.(url|pushurl)$`).ToArgv()
output, err := self.cmd.New(cmdArgs).DontLog().RunWithOutput()
if err != nil {
// exit code 1 means no matching keys (no remotes configured)
@@ -83,12 +83,29 @@ func (self *RemoteLoader) getRemotesFromConfig() []*models.Remote {
if !found {
continue
}
// key is "remote.<name>.url"; strip prefix and suffix to get the name
remoteName := strings.TrimSuffix(strings.TrimPrefix(key, "remote."), ".url")
// key is "remote.<name>.url" or "remote.<name>.pushurl";
// strip prefix and suffix to get the name
rest, ok := strings.CutPrefix(key, "remote.")
if !ok {
continue
}
var remoteName string
var isPushUrl bool
if name, ok := strings.CutSuffix(rest, ".pushurl"); ok {
remoteName, isPushUrl = name, true
} else if name, ok := strings.CutSuffix(rest, ".url"); ok {
remoteName, isPushUrl = name, false
} else {
continue
}
if _, ok := remotesByName[remoteName]; !ok {
remotesByName[remoteName] = &models.Remote{Name: remoteName}
}
remotesByName[remoteName].Urls = append(remotesByName[remoteName].Urls, url)
if isPushUrl {
remotesByName[remoteName].PushUrls = append(remotesByName[remoteName].PushUrls, url)
} else {
remotesByName[remoteName].Urls = append(remotesByName[remoteName].Urls, url)
}
}
return slices.Collect(maps.Values(remotesByName))
@@ -0,0 +1,108 @@
package git_commands
import (
"errors"
"testing"
"github.com/jesseduffield/lazygit/pkg/commands/models"
"github.com/jesseduffield/lazygit/pkg/commands/oscommands"
"github.com/jesseduffield/lazygit/pkg/common"
"github.com/stretchr/testify/assert"
)
func TestGetRemotesFromConfig(t *testing.T) {
configArgs := []string{"config", "--local", "--get-regexp", `^remote\.[^.]+\.(url|pushurl)$`}
scenarios := []struct {
testName string
runner *oscommands.FakeCmdObjRunner
expectedRemotes []*models.Remote
}{
{
testName: "no remotes configured",
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs(configArgs, "", errors.New("exit status 1")),
expectedRemotes: nil,
},
{
testName: "single remote with one url",
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs(configArgs,
"remote.origin.url https://github.com/foo/bar.git\n",
nil),
expectedRemotes: []*models.Remote{
{Name: "origin", Urls: []string{"https://github.com/foo/bar.git"}},
},
},
{
testName: "mirror remote with multiple urls",
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs(configArgs,
"remote.origin.url https://github.com/foo/bar.git\n"+
"remote.origin.url git@github.com:foo/bar.git\n",
nil),
expectedRemotes: []*models.Remote{
{Name: "origin", Urls: []string{
"https://github.com/foo/bar.git",
"git@github.com:foo/bar.git",
}},
},
},
{
testName: "remote with both url and pushurl",
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs(configArgs,
"remote.origin.url https://github.com/foo/bar.git\n"+
"remote.origin.pushurl git@github.com:foo/bar.git\n",
nil),
expectedRemotes: []*models.Remote{
{
Name: "origin",
Urls: []string{"https://github.com/foo/bar.git"},
PushUrls: []string{"git@github.com:foo/bar.git"},
},
},
},
{
testName: "multiple remotes",
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs(configArgs,
"remote.origin.url https://github.com/foo/bar.git\n"+
"remote.upstream.url https://github.com/baz/bar.git\n"+
"remote.upstream.pushurl git@github.com:baz/bar.git\n",
nil),
expectedRemotes: []*models.Remote{
{Name: "origin", Urls: []string{"https://github.com/foo/bar.git"}},
{
Name: "upstream",
Urls: []string{"https://github.com/baz/bar.git"},
PushUrls: []string{"git@github.com:baz/bar.git"},
},
},
},
{
testName: "remote name containing dots is preserved",
runner: oscommands.NewFakeRunner(t).
ExpectGitArgs(configArgs,
"remote.my.fork.url https://github.com/foo/bar.git\n",
nil),
expectedRemotes: []*models.Remote{
{Name: "my.fork", Urls: []string{"https://github.com/foo/bar.git"}},
},
},
}
for _, scenario := range scenarios {
t.Run(scenario.testName, func(t *testing.T) {
loader := &RemoteLoader{
Common: common.NewDummyCommon(),
cmd: oscommands.NewDummyCmdObjBuilder(scenario.runner),
}
// map iteration order is non-deterministic, so compare unordered
assert.ElementsMatch(t, scenario.expectedRemotes, loader.getRemotesFromConfig())
scenario.runner.CheckForMissingCalls()
})
}
}
+5 -2
View File
@@ -2,8 +2,11 @@ package models
// Remote : A git remote
type Remote struct {
Name string
Urls []string
Name string
Urls []string
// PushUrls is empty unless the remote has explicit `remote.<name>.pushurl`
// entries; when empty, pushes go to Urls.
PushUrls []string
Branches []*RemoteBranch
}