|
6 | 6 | "fmt" |
7 | 7 | "io" |
8 | 8 | "net/http" |
| 9 | + "strings" |
9 | 10 |
|
10 | 11 | "github.com/go-viper/mapstructure/v2" |
11 | 12 | "github.com/google/go-github/v89/github" |
@@ -647,6 +648,45 @@ var pullRequestUpdateFormParams = map[string]struct{}{ |
647 | 648 | "_ui_submitted": {}, |
648 | 649 | } |
649 | 650 |
|
| 651 | +// findExistingPullRequest checks whether the Create error was GitHub's |
| 652 | +// "a pull request already exists" 422, and if so, looks up and returns |
| 653 | +// that existing PR so callers can surface a useful link instead of the |
| 654 | +// raw validation error. |
| 655 | +func findExistingPullRequest(ctx context.Context, client *github.Client, owner, repo, head, base string, createErr error) (*github.PullRequest, error) { |
| 656 | + ghErr, ok := createErr.(*github.ErrorResponse) |
| 657 | + if !ok { |
| 658 | + return nil, fmt.Errorf("not a github.ErrorResponse") |
| 659 | + } |
| 660 | + |
| 661 | + matched := false |
| 662 | + for _, e := range ghErr.Errors { |
| 663 | + if e.Code == "custom" && strings.Contains(e.Message, "A pull request already exists") { |
| 664 | + matched = true |
| 665 | + break |
| 666 | + } |
| 667 | + } |
| 668 | + if !matched { |
| 669 | + return nil, fmt.Errorf("error is not an 'already exists' validation failure") |
| 670 | + } |
| 671 | + |
| 672 | + // head may or may not already be qualified with an owner (fork case); |
| 673 | + // the List API wants "owner:branch" form. |
| 674 | + headFilter := head |
| 675 | + if !strings.Contains(head, ":") { |
| 676 | + headFilter = fmt.Sprintf("%s:%s", owner, head) |
| 677 | + } |
| 678 | + |
| 679 | + prs, _, err := client.PullRequests.List(ctx, owner, repo, &github.PullRequestListOptions{ |
| 680 | + Head: headFilter, |
| 681 | + Base: base, |
| 682 | + State: "all", |
| 683 | + }) |
| 684 | + if err != nil || len(prs) == 0 { |
| 685 | + return nil, fmt.Errorf("could not find existing pull request: %w", err) |
| 686 | + } |
| 687 | + return prs[0], nil |
| 688 | +} |
| 689 | + |
650 | 690 | // CreatePullRequest creates a tool to create a new pull request. |
651 | 691 | func CreatePullRequest(t translations.TranslationHelperFunc) inventory.ServerTool { |
652 | 692 | return NewTool( |
@@ -796,6 +836,25 @@ func CreatePullRequest(t translations.TranslationHelperFunc) inventory.ServerToo |
796 | 836 | } |
797 | 837 | pr, resp, err := client.PullRequests.Create(ctx, owner, repo, newPR) |
798 | 838 | if err != nil { |
| 839 | + if existingPR, findErr := findExistingPullRequest(ctx, client, owner, repo, head, base, err); findErr == nil && existingPR != nil { |
| 840 | + minimalResponse := MinimalResponse{ |
| 841 | + ID: fmt.Sprintf("%d", existingPR.GetID()), |
| 842 | + URL: existingPR.GetHTMLURL(), |
| 843 | + } |
| 844 | + r, marshalErr := json.Marshal(struct { |
| 845 | + MinimalResponse |
| 846 | + Message string `json:"message"` |
| 847 | + }{ |
| 848 | + MinimalResponse: minimalResponse, |
| 849 | + Message: fmt.Sprintf( |
| 850 | + "An open pull request already exists for %q into %q: #%d, titled %q at %s", |
| 851 | + head, base, existingPR.GetNumber(), existingPR.GetTitle(), existingPR.GetHTMLURL(), |
| 852 | + ), |
| 853 | + }) |
| 854 | + if marshalErr == nil { |
| 855 | + return utils.NewToolResultText(string(r)), nil, nil |
| 856 | + } |
| 857 | + } |
799 | 858 | return ghErrors.NewGitHubAPIErrorResponse(ctx, |
800 | 859 | "failed to create pull request", |
801 | 860 | resp, |
|
0 commit comments