Fix issue develop repeated invocation with named branches
This commit is contained in:
parent
a2b8b687c0
commit
b38f6772e5
2 changed files with 274 additions and 13 deletions
|
|
@ -4,6 +4,8 @@ import (
|
|||
ctx "context"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
|
||||
"github.com/MakeNowJust/heredoc"
|
||||
"github.com/cli/cli/v2/api"
|
||||
|
|
@ -174,7 +176,6 @@ func developRun(opts *DevelopOptions) error {
|
|||
|
||||
func developRunCreate(opts *DevelopOptions, apiClient *api.Client, issueRepo ghrepo.Interface, issue *api.Issue) error {
|
||||
branchRepo := issueRepo
|
||||
var repoID string
|
||||
if opts.BranchRepo != "" {
|
||||
var err error
|
||||
branchRepo, err = ghrepo.FromFullName(opts.BranchRepo)
|
||||
|
|
@ -183,24 +184,66 @@ func developRunCreate(opts *DevelopOptions, apiClient *api.Client, issueRepo ghr
|
|||
}
|
||||
}
|
||||
|
||||
opts.IO.StartProgressIndicator()
|
||||
repoID, branchID, err := api.FindRepoBranchID(apiClient, branchRepo, opts.BaseBranch)
|
||||
opts.IO.StopProgressIndicator()
|
||||
if err != nil {
|
||||
return err
|
||||
branchName := ""
|
||||
reusedExisting := false
|
||||
if opts.Name != "" {
|
||||
opts.IO.StartProgressIndicator()
|
||||
branches, err := api.ListLinkedBranches(apiClient, issueRepo, issue.Number)
|
||||
opts.IO.StopProgressIndicator()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
branchName = findExistingLinkedBranchName(branches, branchRepo, opts.Name)
|
||||
reusedExisting = branchName != ""
|
||||
}
|
||||
|
||||
opts.IO.StartProgressIndicator()
|
||||
branchName, err := api.CreateLinkedBranch(apiClient, branchRepo.RepoHost(), repoID, issue.ID, branchID, opts.Name)
|
||||
opts.IO.StopProgressIndicator()
|
||||
if err != nil {
|
||||
return err
|
||||
repoID := ""
|
||||
branchID := ""
|
||||
baseValidated := false
|
||||
if opts.BaseBranch != "" {
|
||||
opts.IO.StartProgressIndicator()
|
||||
foundRepoID, foundBranchID, err := api.FindRepoBranchID(apiClient, branchRepo, opts.BaseBranch)
|
||||
opts.IO.StopProgressIndicator()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
repoID = foundRepoID
|
||||
branchID = foundBranchID
|
||||
baseValidated = true
|
||||
}
|
||||
|
||||
if branchName == "" {
|
||||
if !baseValidated {
|
||||
opts.IO.StartProgressIndicator()
|
||||
foundRepoID, foundBranchID, err := api.FindRepoBranchID(apiClient, branchRepo, opts.BaseBranch)
|
||||
opts.IO.StopProgressIndicator()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
repoID = foundRepoID
|
||||
branchID = foundBranchID
|
||||
}
|
||||
|
||||
opts.IO.StartProgressIndicator()
|
||||
createdBranchName, err := api.CreateLinkedBranch(apiClient, branchRepo.RepoHost(), repoID, issue.ID, branchID, opts.Name)
|
||||
opts.IO.StopProgressIndicator()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
branchName = createdBranchName
|
||||
}
|
||||
|
||||
if branchName == "" {
|
||||
return fmt.Errorf("failed to create linked branch: API returned empty branch name")
|
||||
}
|
||||
|
||||
if reusedExisting && opts.IO.IsStdoutTTY() {
|
||||
fmt.Fprintf(opts.IO.ErrOut, "Using existing linked branch %q\n", branchName)
|
||||
}
|
||||
|
||||
// Remember which branch to target when creating a PR.
|
||||
if opts.BaseBranch != "" {
|
||||
err = opts.GitClient.SetBranchConfig(ctx.Background(), branchName, git.MergeBaseConfig, opts.BaseBranch)
|
||||
if err != nil {
|
||||
if err := opts.GitClient.SetBranchConfig(ctx.Background(), branchName, git.MergeBaseConfig, opts.BaseBranch); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
|
@ -210,6 +253,35 @@ func developRunCreate(opts *DevelopOptions, apiClient *api.Client, issueRepo ghr
|
|||
return checkoutBranch(opts, branchRepo, branchName)
|
||||
}
|
||||
|
||||
func findExistingLinkedBranchName(branches []api.LinkedBranch, branchRepo ghrepo.Interface, branchName string) string {
|
||||
for _, branch := range branches {
|
||||
if branch.BranchName != branchName {
|
||||
continue
|
||||
}
|
||||
linkedRepo, err := linkedBranchRepoFromURL(branch.URL)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if ghrepo.IsSame(linkedRepo, branchRepo) {
|
||||
return branch.BranchName
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func linkedBranchRepoFromURL(branchURL string) (ghrepo.Interface, error) {
|
||||
u, err := url.Parse(branchURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
pathParts := strings.SplitN(strings.Trim(u.Path, "/"), "/", 3)
|
||||
if len(pathParts) < 2 {
|
||||
return nil, fmt.Errorf("invalid linked branch URL: %q", branchURL)
|
||||
}
|
||||
u.Path = "/" + strings.Join(pathParts[0:2], "/")
|
||||
return ghrepo.FromURL(u)
|
||||
}
|
||||
|
||||
func developRunList(opts *DevelopOptions, apiClient *api.Client, issueRepo ghrepo.Interface, issue *api.Issue) error {
|
||||
opts.IO.StartProgressIndicator()
|
||||
branches, err := api.ListLinkedBranches(apiClient, issueRepo, issue.Number)
|
||||
|
|
|
|||
|
|
@ -353,6 +353,16 @@ func TestDevelopRun(t *testing.T) {
|
|||
reg.Register(
|
||||
httpmock.GraphQL(`query FindRepoBranchID\b`),
|
||||
httpmock.StringResponse(`{"data":{"repository":{"id":"REPOID","ref":{"target":{"oid":"OID"}}}}}`))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query ListLinkedBranches\b`),
|
||||
httpmock.GraphQLQuery(`
|
||||
{"data":{"repository":{"issue":{"linkedBranches":{"nodes":[]}}}}}
|
||||
`, func(query string, inputs map[string]interface{}) {
|
||||
assert.Equal(t, float64(123), inputs["number"])
|
||||
assert.Equal(t, "OWNER", inputs["owner"])
|
||||
assert.Equal(t, "REPO", inputs["name"])
|
||||
}),
|
||||
)
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`mutation CreateLinkedBranch\b`),
|
||||
httpmock.GraphQLMutation(`{"data":{"createLinkedBranch":{"linkedBranch":{"id":"2","ref":{"name":"my-branch"}}}}}`,
|
||||
|
|
@ -370,6 +380,165 @@ func TestDevelopRun(t *testing.T) {
|
|||
},
|
||||
expectedOut: "github.com/OWNER/REPO/tree/my-branch\n",
|
||||
},
|
||||
{
|
||||
name: "develop existing linked branch with name and checkout",
|
||||
opts: &DevelopOptions{
|
||||
Name: "my-branch",
|
||||
BaseBranch: "main",
|
||||
IssueNumber: 123,
|
||||
Checkout: true,
|
||||
},
|
||||
remotes: map[string]string{
|
||||
"origin": "OWNER/REPO",
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry, t *testing.T) {
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query LinkedBranchFeature\b`),
|
||||
httpmock.StringResponse(featureEnabledPayload),
|
||||
)
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query IssueByNumber\b`),
|
||||
httpmock.StringResponse(`{"data":{"repository":{ "hasIssuesEnabled":true,"issue":{"id":"SOMEID","number":123,"title":"my issue"}}}}`),
|
||||
)
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query ListLinkedBranches\b`),
|
||||
httpmock.GraphQLQuery(`
|
||||
{"data":{"repository":{"issue":{"linkedBranches":{"nodes":[{"ref":{"name":"my-branch","repository":{"url":"https://github.com/OWNER/REPO"}}}]}}}}}
|
||||
`, func(query string, inputs map[string]interface{}) {
|
||||
assert.Equal(t, float64(123), inputs["number"])
|
||||
assert.Equal(t, "OWNER", inputs["owner"])
|
||||
assert.Equal(t, "REPO", inputs["name"])
|
||||
}),
|
||||
)
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query FindRepoBranchID\b`),
|
||||
httpmock.StringResponse(`{"data":{"repository":{"id":"REPOID","ref":{"target":{"oid":"OID"}}}}}`))
|
||||
},
|
||||
runStubs: func(cs *run.CommandStubber) {
|
||||
cs.Register(`git config branch\.my-branch\.gh-merge-base main`, 0, "")
|
||||
cs.Register(`git fetch origin \+refs/heads/my-branch:refs/remotes/origin/my-branch`, 0, "")
|
||||
cs.Register(`git rev-parse --verify refs/heads/my-branch`, 0, "")
|
||||
cs.Register(`git checkout my-branch`, 0, "")
|
||||
cs.Register(`git pull --ff-only origin my-branch`, 0, "")
|
||||
},
|
||||
expectedOut: "github.com/OWNER/REPO/tree/my-branch\n",
|
||||
},
|
||||
{
|
||||
name: "develop existing linked branch with name in tty shows reuse message",
|
||||
opts: &DevelopOptions{
|
||||
Name: "my-branch",
|
||||
BaseBranch: "main",
|
||||
IssueNumber: 123,
|
||||
},
|
||||
tty: true,
|
||||
remotes: map[string]string{
|
||||
"origin": "OWNER/REPO",
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry, t *testing.T) {
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query LinkedBranchFeature\b`),
|
||||
httpmock.StringResponse(featureEnabledPayload),
|
||||
)
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query IssueByNumber\b`),
|
||||
httpmock.StringResponse(`{"data":{"repository":{ "hasIssuesEnabled":true,"issue":{"id":"SOMEID","number":123,"title":"my issue"}}}}`),
|
||||
)
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query ListLinkedBranches\b`),
|
||||
httpmock.GraphQLQuery(`
|
||||
{"data":{"repository":{"issue":{"linkedBranches":{"nodes":[{"ref":{"name":"my-branch","repository":{"url":"https://github.com/OWNER/REPO"}}}]}}}}}
|
||||
`, func(query string, inputs map[string]interface{}) {
|
||||
assert.Equal(t, float64(123), inputs["number"])
|
||||
assert.Equal(t, "OWNER", inputs["owner"])
|
||||
assert.Equal(t, "REPO", inputs["name"])
|
||||
}),
|
||||
)
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query FindRepoBranchID\b`),
|
||||
httpmock.StringResponse(`{"data":{"repository":{"id":"REPOID","ref":{"target":{"oid":"OID"}}}}}`))
|
||||
},
|
||||
runStubs: func(cs *run.CommandStubber) {
|
||||
cs.Register(`git config branch\.my-branch\.gh-merge-base main`, 0, "")
|
||||
cs.Register(`git fetch origin \+refs/heads/my-branch:refs/remotes/origin/my-branch`, 0, "")
|
||||
},
|
||||
expectedOut: "github.com/OWNER/REPO/tree/my-branch\n",
|
||||
expectedErrOut: "Using existing linked branch \"my-branch\"\n",
|
||||
},
|
||||
{
|
||||
name: "develop existing linked branch with invalid base branch returns an error",
|
||||
opts: &DevelopOptions{
|
||||
Name: "my-branch",
|
||||
BaseBranch: "does-not-exist-branch",
|
||||
IssueNumber: 123,
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry, t *testing.T) {
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query LinkedBranchFeature\b`),
|
||||
httpmock.StringResponse(featureEnabledPayload),
|
||||
)
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query IssueByNumber\b`),
|
||||
httpmock.StringResponse(`{"data":{"repository":{ "hasIssuesEnabled":true,"issue":{"id":"SOMEID","number":123,"title":"my issue"}}}}`),
|
||||
)
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query ListLinkedBranches\b`),
|
||||
httpmock.GraphQLQuery(`
|
||||
{"data":{"repository":{"issue":{"linkedBranches":{"nodes":[{"ref":{"name":"my-branch","repository":{"url":"https://github.com/OWNER/REPO"}}}]}}}}}
|
||||
`, func(query string, inputs map[string]interface{}) {
|
||||
assert.Equal(t, float64(123), inputs["number"])
|
||||
assert.Equal(t, "OWNER", inputs["owner"])
|
||||
assert.Equal(t, "REPO", inputs["name"])
|
||||
}),
|
||||
)
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query FindRepoBranchID\b`),
|
||||
httpmock.StringResponse(`{"data":{"repository":{"id":"REPOID","defaultBranchRef":{"target":{"oid":"DEFAULTOID"}},"ref":null}}}`),
|
||||
)
|
||||
},
|
||||
wantErr: `could not find branch "does-not-exist-branch" in OWNER/REPO`,
|
||||
},
|
||||
{
|
||||
name: "develop with empty linked branch name response returns an error",
|
||||
opts: &DevelopOptions{
|
||||
Name: "my-branch",
|
||||
BaseBranch: "main",
|
||||
IssueNumber: 123,
|
||||
},
|
||||
httpStubs: func(reg *httpmock.Registry, t *testing.T) {
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query LinkedBranchFeature\b`),
|
||||
httpmock.StringResponse(featureEnabledPayload),
|
||||
)
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query IssueByNumber\b`),
|
||||
httpmock.StringResponse(`{"data":{"repository":{ "hasIssuesEnabled":true,"issue":{"id":"SOMEID","number":123,"title":"my issue"}}}}`),
|
||||
)
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query ListLinkedBranches\b`),
|
||||
httpmock.GraphQLQuery(`
|
||||
{"data":{"repository":{"issue":{"linkedBranches":{"nodes":[]}}}}}
|
||||
`, func(query string, inputs map[string]interface{}) {
|
||||
assert.Equal(t, float64(123), inputs["number"])
|
||||
assert.Equal(t, "OWNER", inputs["owner"])
|
||||
assert.Equal(t, "REPO", inputs["name"])
|
||||
}),
|
||||
)
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query FindRepoBranchID\b`),
|
||||
httpmock.StringResponse(`{"data":{"repository":{"id":"REPOID","ref":{"target":{"oid":"OID"}}}}}`))
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`mutation CreateLinkedBranch\b`),
|
||||
httpmock.GraphQLMutation(`{"data":{"createLinkedBranch":{"linkedBranch":{"id":"2","ref":{"name":""}}}}}`,
|
||||
func(inputs map[string]interface{}) {
|
||||
assert.Equal(t, "REPOID", inputs["repositoryId"])
|
||||
assert.Equal(t, "SOMEID", inputs["issueId"])
|
||||
assert.Equal(t, "OID", inputs["oid"])
|
||||
assert.Equal(t, "my-branch", inputs["name"])
|
||||
}),
|
||||
)
|
||||
},
|
||||
wantErr: "failed to create linked branch: API returned empty branch name",
|
||||
},
|
||||
{
|
||||
name: "develop new branch outside of local git repo",
|
||||
opts: &DevelopOptions{
|
||||
|
|
@ -426,6 +595,16 @@ func TestDevelopRun(t *testing.T) {
|
|||
httpmock.GraphQL(`query FindRepoBranchID\b`),
|
||||
httpmock.StringResponse(`{"data":{"repository":{"id":"REPOID","ref":{"target":{"oid":"OID"}}}}}`),
|
||||
)
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query ListLinkedBranches\b`),
|
||||
httpmock.GraphQLQuery(`
|
||||
{"data":{"repository":{"issue":{"linkedBranches":{"nodes":[]}}}}}
|
||||
`, func(query string, inputs map[string]interface{}) {
|
||||
assert.Equal(t, float64(123), inputs["number"])
|
||||
assert.Equal(t, "OWNER", inputs["owner"])
|
||||
assert.Equal(t, "REPO", inputs["name"])
|
||||
}),
|
||||
)
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`mutation CreateLinkedBranch\b`),
|
||||
httpmock.GraphQLMutation(`{"data":{"createLinkedBranch":{"linkedBranch":{"id":"2","ref":{"name":"my-branch"}}}}}`,
|
||||
|
|
@ -468,6 +647,16 @@ func TestDevelopRun(t *testing.T) {
|
|||
httpmock.GraphQL(`query FindRepoBranchID\b`),
|
||||
httpmock.StringResponse(`{"data":{"repository":{"id":"REPOID","ref":{"target":{"oid":"OID"}}}}}`),
|
||||
)
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`query ListLinkedBranches\b`),
|
||||
httpmock.GraphQLQuery(`
|
||||
{"data":{"repository":{"issue":{"linkedBranches":{"nodes":[]}}}}}
|
||||
`, func(query string, inputs map[string]interface{}) {
|
||||
assert.Equal(t, float64(123), inputs["number"])
|
||||
assert.Equal(t, "OWNER", inputs["owner"])
|
||||
assert.Equal(t, "REPO", inputs["name"])
|
||||
}),
|
||||
)
|
||||
reg.Register(
|
||||
httpmock.GraphQL(`mutation CreateLinkedBranch\b`),
|
||||
httpmock.GraphQLMutation(`{"data":{"createLinkedBranch":{"linkedBranch":{"id":"2","ref":{"name":"my-branch"}}}}}`,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue