Find push remote using branch.<name>.pushRemote and remote.pushDefault
When using a push.default = current triangular workflow, apart from
using @{push} to determine the remote branch name, we should also follow
the
1. branch.<name>.pushRemote
2. remote.pushDefault
3. branch.<name>.remote
...list to determine which remote Git pushes to.
This commit is contained in:
parent
7fc35fd47d
commit
4254818dbd
8 changed files with 215 additions and 37 deletions
|
|
@ -379,7 +379,7 @@ func (c *Client) lookupCommit(ctx context.Context, sha, format string) ([]byte,
|
|||
// ReadBranchConfig parses the `branch.BRANCH.(remote|merge|gh-merge-base)` part of git config.
|
||||
func (c *Client) ReadBranchConfig(ctx context.Context, branch string) (cfg BranchConfig) {
|
||||
prefix := regexp.QuoteMeta(fmt.Sprintf("branch.%s.", branch))
|
||||
args := []string{"config", "--get-regexp", fmt.Sprintf("^%s(remote|merge|%s)$", prefix, MergeBaseConfig)}
|
||||
args := []string{"config", "--get-regexp", fmt.Sprintf("^%s(remote|merge|pushremote|%s)$", prefix, MergeBaseConfig)}
|
||||
cmd, err := c.Command(ctx, args...)
|
||||
if err != nil {
|
||||
return
|
||||
|
|
@ -397,21 +397,22 @@ func (c *Client) ReadBranchConfig(ctx context.Context, branch string) (cfg Branc
|
|||
keys := strings.Split(parts[0], ".")
|
||||
switch keys[len(keys)-1] {
|
||||
case "remote":
|
||||
if strings.Contains(parts[1], ":") {
|
||||
u, err := ParseURL(parts[1])
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
cfg.RemoteURL = u
|
||||
} else if !isFilesystemPath(parts[1]) {
|
||||
cfg.RemoteName = parts[1]
|
||||
}
|
||||
parseRemoteURLOrName(parts[1], &cfg.RemoteURL, &cfg.RemoteName)
|
||||
case "pushremote":
|
||||
parseRemoteURLOrName(parts[1], &cfg.PushRemoteURL, &cfg.PushRemoteName)
|
||||
case "merge":
|
||||
cfg.MergeRef = parts[1]
|
||||
case MergeBaseConfig:
|
||||
cfg.MergeBase = parts[1]
|
||||
}
|
||||
}
|
||||
if cfg.PushRemoteURL == nil && cfg.PushRemoteName == "" {
|
||||
if conf, err := c.Config(ctx, "remote.pushDefault"); err == nil && conf != "" {
|
||||
parseRemoteURLOrName(conf, &cfg.PushRemoteURL, &cfg.PushRemoteName)
|
||||
} else {
|
||||
cfg.PushRemoteName = cfg.RemoteName
|
||||
}
|
||||
}
|
||||
if out, err = c.revParse(ctx, "--verify", "--quiet", "--abbrev-ref", branch+"@{push}"); err == nil {
|
||||
cfg.Push = strings.TrimSuffix(string(out), "\n")
|
||||
}
|
||||
|
|
@ -776,6 +777,16 @@ func parseRemotes(remotesStr []string) RemoteSet {
|
|||
return remotes
|
||||
}
|
||||
|
||||
func parseRemoteURLOrName(value string, remoteURL **url.URL, remoteName *string) {
|
||||
if strings.Contains(value, ":") {
|
||||
if u, err := ParseURL(value); err == nil {
|
||||
*remoteURL = u
|
||||
}
|
||||
} else if !isFilesystemPath(value) {
|
||||
*remoteName = value
|
||||
}
|
||||
}
|
||||
|
||||
func populateResolvedRemotes(remotes RemoteSet, resolved []string) {
|
||||
for _, l := range resolved {
|
||||
parts := strings.SplitN(l, " ", 2)
|
||||
|
|
|
|||
|
|
@ -725,31 +725,160 @@ func TestClientCommitBody(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestClientReadBranchConfig(t *testing.T) {
|
||||
type cmdTest struct {
|
||||
exitStatus int
|
||||
stdOut string
|
||||
stdErr string
|
||||
wantArgs string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
cmdExitStatus []int
|
||||
cmdStdout []string
|
||||
cmdStderr []string
|
||||
wantCmdArgs []string
|
||||
cmds []cmdTest
|
||||
wantBranchConfig BranchConfig
|
||||
}{
|
||||
{
|
||||
name: "read branch config",
|
||||
cmdExitStatus: []int{0, 0},
|
||||
cmdStdout: []string{"branch.trunk.remote origin\nbranch.trunk.merge refs/heads/trunk\nbranch.trunk.gh-merge-base trunk", "origin/trunk"},
|
||||
cmdStderr: []string{"", ""},
|
||||
wantCmdArgs: []string{`path/to/git config --get-regexp ^branch\.trunk\.(remote|merge|gh-merge-base)$`, `path/to/git rev-parse --verify --quiet --abbrev-ref trunk@{push}`},
|
||||
wantBranchConfig: BranchConfig{RemoteName: "origin", MergeRef: "refs/heads/trunk", MergeBase: "trunk", Push: "origin/trunk"},
|
||||
name: "read branch config, central",
|
||||
cmds: []cmdTest{
|
||||
{
|
||||
stdOut: "branch.trunk.remote origin\nbranch.trunk.merge refs/heads/trunk\nbranch.trunk.gh-merge-base trunk",
|
||||
wantArgs: `path/to/git config --get-regexp ^branch\.trunk\.(remote|merge|pushremote|gh-merge-base)$`,
|
||||
},
|
||||
{
|
||||
wantArgs: `path/to/git config remote.pushDefault`,
|
||||
},
|
||||
{
|
||||
stdOut: "origin/trunk",
|
||||
wantArgs: `path/to/git rev-parse --verify --quiet --abbrev-ref trunk@{push}`,
|
||||
},
|
||||
},
|
||||
wantBranchConfig: BranchConfig{RemoteName: "origin", MergeRef: "refs/heads/trunk", MergeBase: "trunk", PushRemoteName: "origin", Push: "origin/trunk"},
|
||||
},
|
||||
{
|
||||
name: "read branch config, central, push.default = upstream",
|
||||
cmds: []cmdTest{
|
||||
{
|
||||
stdOut: "branch.trunk.remote origin\nbranch.trunk.merge refs/heads/trunk-remote",
|
||||
wantArgs: `path/to/git config --get-regexp ^branch\.trunk\.(remote|merge|pushremote|gh-merge-base)$`,
|
||||
},
|
||||
{
|
||||
wantArgs: `path/to/git config remote.pushDefault`,
|
||||
},
|
||||
{
|
||||
stdOut: "origin/trunk-remote",
|
||||
wantArgs: `path/to/git rev-parse --verify --quiet --abbrev-ref trunk@{push}`,
|
||||
},
|
||||
},
|
||||
wantBranchConfig: BranchConfig{RemoteName: "origin", MergeRef: "refs/heads/trunk-remote", PushRemoteName: "origin", Push: "origin/trunk-remote"},
|
||||
},
|
||||
{
|
||||
name: "read branch config, central, push.default = current",
|
||||
cmds: []cmdTest{
|
||||
{
|
||||
stdOut: "branch.trunk.remote origin\nbranch.trunk.merge refs/heads/main",
|
||||
wantArgs: `path/to/git config --get-regexp ^branch\.trunk\.(remote|merge|pushremote|gh-merge-base)$`,
|
||||
},
|
||||
{
|
||||
wantArgs: `path/to/git config remote.pushDefault`,
|
||||
},
|
||||
{
|
||||
stdOut: "origin/trunk",
|
||||
wantArgs: `path/to/git rev-parse --verify --quiet --abbrev-ref trunk@{push}`,
|
||||
},
|
||||
},
|
||||
wantBranchConfig: BranchConfig{RemoteName: "origin", MergeRef: "refs/heads/main", PushRemoteName: "origin", Push: "origin/trunk"},
|
||||
},
|
||||
{
|
||||
name: "read branch config, central, push.default = current, target branch not pushed, no existing remote branch",
|
||||
cmds: []cmdTest{
|
||||
{
|
||||
stdOut: "branch.trunk.remote .\nbranch.trunk.merge refs/heads/trunk-middle",
|
||||
wantArgs: `path/to/git config --get-regexp ^branch\.trunk\.(remote|merge|pushremote|gh-merge-base)$`,
|
||||
},
|
||||
{
|
||||
stdOut: "origin",
|
||||
wantArgs: `path/to/git config remote.pushDefault`,
|
||||
},
|
||||
{
|
||||
exitStatus: 1,
|
||||
wantArgs: `path/to/git rev-parse --verify --quiet --abbrev-ref trunk@{push}`,
|
||||
},
|
||||
},
|
||||
wantBranchConfig: BranchConfig{MergeRef: "refs/heads/trunk-middle", PushRemoteName: "origin"},
|
||||
},
|
||||
{
|
||||
name: "read branch config, triangular, push.default = current, has existing remote branch, branch.trunk.pushremote effective",
|
||||
cmds: []cmdTest{
|
||||
{
|
||||
stdOut: "branch.trunk.remote upstream\nbranch.trunk.merge refs/heads/main\nbranch.trunk.pushremote origin",
|
||||
wantArgs: `path/to/git config --get-regexp ^branch\.trunk\.(remote|merge|pushremote|gh-merge-base)$`,
|
||||
},
|
||||
{
|
||||
stdOut: "origin/trunk",
|
||||
wantArgs: `path/to/git rev-parse --verify --quiet --abbrev-ref trunk@{push}`,
|
||||
},
|
||||
},
|
||||
wantBranchConfig: BranchConfig{RemoteName: "upstream", MergeRef: "refs/heads/main", PushRemoteName: "origin", Push: "origin/trunk"},
|
||||
},
|
||||
{
|
||||
name: "read branch config, triangular, push.default = current, has existing remote branch, remote.pushDefault effective",
|
||||
cmds: []cmdTest{
|
||||
{
|
||||
stdOut: "branch.trunk.remote upstream\nbranch.trunk.merge refs/heads/main",
|
||||
wantArgs: `path/to/git config --get-regexp ^branch\.trunk\.(remote|merge|pushremote|gh-merge-base)$`,
|
||||
},
|
||||
{
|
||||
stdOut: "origin",
|
||||
wantArgs: `path/to/git config remote.pushDefault`,
|
||||
},
|
||||
{
|
||||
stdOut: "origin/trunk",
|
||||
wantArgs: `path/to/git rev-parse --verify --quiet --abbrev-ref trunk@{push}`,
|
||||
},
|
||||
},
|
||||
wantBranchConfig: BranchConfig{RemoteName: "upstream", MergeRef: "refs/heads/main", PushRemoteName: "origin", Push: "origin/trunk"},
|
||||
},
|
||||
{
|
||||
name: "read branch config, triangular, push.default = current, no existing remote branch, branch.trunk.pushremote effective",
|
||||
cmds: []cmdTest{
|
||||
{
|
||||
stdOut: "branch.trunk.remote upstream\nbranch.trunk.merge refs/heads/main\nbranch.trunk.pushremote origin",
|
||||
wantArgs: `path/to/git config --get-regexp ^branch\.trunk\.(remote|merge|pushremote|gh-merge-base)$`,
|
||||
},
|
||||
{
|
||||
exitStatus: 1,
|
||||
wantArgs: `path/to/git rev-parse --verify --quiet --abbrev-ref trunk@{push}`,
|
||||
},
|
||||
},
|
||||
wantBranchConfig: BranchConfig{RemoteName: "upstream", MergeRef: "refs/heads/main", PushRemoteName: "origin"},
|
||||
},
|
||||
{
|
||||
name: "read branch config, triangular, push.default = current, no existing remote branch, remote.pushDefault effective",
|
||||
cmds: []cmdTest{
|
||||
{
|
||||
stdOut: "branch.trunk.remote upstream\nbranch.trunk.merge refs/heads/main",
|
||||
wantArgs: `path/to/git config --get-regexp ^branch\.trunk\.(remote|merge|pushremote|gh-merge-base)$`,
|
||||
},
|
||||
{
|
||||
stdOut: "origin",
|
||||
wantArgs: `path/to/git config remote.pushDefault`,
|
||||
},
|
||||
{
|
||||
exitStatus: 1,
|
||||
wantArgs: `path/to/git rev-parse --verify --quiet --abbrev-ref trunk@{push}`,
|
||||
},
|
||||
},
|
||||
wantBranchConfig: BranchConfig{RemoteName: "upstream", MergeRef: "refs/heads/main", PushRemoteName: "origin"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
var cmds []*exec.Cmd
|
||||
var cmdCtxs []commandCtx
|
||||
for i := 0; i < len(tt.cmdExitStatus); i++ {
|
||||
cmd, cmdCtx := createCommandContext(t, tt.cmdExitStatus[i], tt.cmdStdout[i], tt.cmdStderr[i])
|
||||
for _, c := range tt.cmds {
|
||||
cmd, cmdCtx := createCommandContext(t, c.exitStatus, c.stdOut, c.stdErr)
|
||||
cmds = append(cmds, cmd)
|
||||
cmdCtxs = append(cmdCtxs, cmdCtx)
|
||||
|
||||
}
|
||||
i := -1
|
||||
client := Client{
|
||||
|
|
@ -761,8 +890,8 @@ func TestClientReadBranchConfig(t *testing.T) {
|
|||
},
|
||||
}
|
||||
branchConfig := client.ReadBranchConfig(context.Background(), "trunk")
|
||||
for i := 0; i < len(tt.cmdExitStatus); i++ {
|
||||
assert.Equal(t, tt.wantCmdArgs[i], strings.Join(cmds[i].Args[3:], " "))
|
||||
for i := 0; i < len(tt.cmds); i++ {
|
||||
assert.Equal(t, tt.cmds[i].wantArgs, strings.Join(cmds[i].Args[3:], " "))
|
||||
}
|
||||
assert.Equal(t, tt.wantBranchConfig, branchConfig)
|
||||
})
|
||||
|
|
|
|||
|
|
@ -64,7 +64,9 @@ type BranchConfig struct {
|
|||
RemoteName string
|
||||
RemoteURL *url.URL
|
||||
// MergeBase is the optional base branch to target in a new PR if `--base` is not specified.
|
||||
MergeBase string
|
||||
MergeRef string
|
||||
Push string
|
||||
MergeBase string
|
||||
MergeRef string
|
||||
PushRemoteURL *url.URL
|
||||
PushRemoteName string
|
||||
Push string
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue