// Copyright IBM Corp. 2016, 2025 // SPDX-License-Identifier: BUSL-1.1 package github import ( "errors" "fmt" "strconv" "strings" ) // copyBranchPrefix is the prefix for copied pull request branches. It is used // both for copying pull requests and for closing copied pull requests. const copyBranchPrefix = "copy" // encodeCopyPullRequestBranch returns the branch name to use for our PR copy operation. // The branch name is important because we trigger post-merge automation based // on the branch prefix. We also encode origin PR information for use in // post-merge operations. // // The format is: //// // eg: copy/hashicorp/vault/99999/my-feature-branch // // This informs the post-merge close operations that the Pull Request was originally // copied from hashicorp/vault's Pull Request number 99999 and that the branch // name of the pull request is my-feature-branch. // // It's imporant that both the encodeCopyPullRequestBranch and decodeCopyPullRequestBranch // stay in sync. func encodeCopyPullRequestBranch( owner string, repo string, number uint, prBranch string, ) string { prNumber := strconv.Itoa(int(number)) name := strings.Join([]string{ copyBranchPrefix, owner, repo, prNumber, prBranch, }, "/") if len(name) > 250 { // Handle Githubs branch name max length name = name[:250] } return name } // decodeCopyPullRequestBranch returns the encoded origin PR details from the // copied Pull Request branch name. // // The format must be the same as what is described in encodeCopyPullRequestBranch() func decodeCopyPullRequestBranch(ref string) ( owner string, repo string, number uint, branch string, err error, ) { if ref == "" { err = errors.New("no copy branch provided") return owner, repo, number, branch, err } if !strings.HasPrefix(ref, copyBranchPrefix) { err = fmt.Errorf("invalid copy branch: branch does not start with %s", copyBranchPrefix) return owner, repo, number, branch, err } // eg: copy/hashicorp/vault/99999/my-feature-branch parts := strings.SplitN(ref, "/", 5) if len(parts) < 5 { err = fmt.Errorf("invalid copy branch: expected 5 parts, got %d", len(parts)) return owner, repo, number, branch, err } owner = parts[1] repo = parts[2] var signedNumber int signedNumber, err = strconv.Atoi(parts[3]) if err != nil { err = fmt.Errorf("invalid copy branch: pull request number is not a number: %w", err) return owner, repo, number, branch, err } if signedNumber < 0 { err = fmt.Errorf("invalid copy branch: number must be positive, got %d", signedNumber) return owner, repo, number, branch, err } number = uint(signedNumber) branch = parts[4] return owner, repo, number, branch, err } func closingIssueRefEqual(a, b *ClosingIssueRef) bool { if a == nil && b == nil { return true } if (a == nil && b != nil) || (a != nil && b == nil) { return false } if a.URL != b.URL { return false } if a.Number != b.Number { return false } if a.Title != b.Title { return false } if a.Repository.NameWithOwner != b.Repository.NameWithOwner { return false } return true }