diff --git a/experiment/autobumper/bumper/bumper.go b/experiment/autobumper/bumper/bumper.go index 602df4588434..3f4f8dbc426f 100644 --- a/experiment/autobumper/bumper/bumper.go +++ b/experiment/autobumper/bumper/bumper.go @@ -286,6 +286,13 @@ func Run(o *Options) error { } } + // Check to see if the proper fork exists and if it does not, create one. + // TODO(mpherman): Handle case where account has repo of same name as one we are trying to autobump but is not a fork of it (e.g. test-infra) + _, err := gc.EnsureFork(o.GitHubLogin, o.GitHubOrg, o.GitHubRepo) + if err != nil { + return fmt.Errorf("fork needed for autobump does not exist. unable to create new fork. %v", err) + } + stdout := HideSecretsWriter{Delegate: os.Stdout, Censor: &sa} stderr := HideSecretsWriter{Delegate: os.Stderr, Censor: &sa} if err := MakeGitCommit(fmt.Sprintf("git@github.com:%s/%s.git", o.GitHubLogin, o.RemoteName), o.HeadBranchName, o.GitName, o.GitEmail, o.Prefixes, stdout, stderr, versions); err != nil { diff --git a/prow/external-plugins/cherrypicker/server.go b/prow/external-plugins/cherrypicker/server.go index 14836a95790b..99bc650d37aa 100644 --- a/prow/external-plugins/cherrypicker/server.go +++ b/prow/external-plugins/cherrypicker/server.go @@ -51,6 +51,7 @@ type githubClient interface { CreateFork(org, repo string) (string, error) CreatePullRequest(org, repo, title, body, head, base string, canModify bool) (int, error) CreateIssue(org, repo, title, body string, milestone int, labels, assignees []string) (int, error) + EnsureFork(forkingUser, org, repo string) (string, error) GetPullRequest(org, repo string, number int) (*github.PullRequest, error) GetPullRequestPatch(org, repo string, number int) ([]byte, error) GetPullRequests(org, repo string) ([]github.PullRequest, error) @@ -516,61 +517,15 @@ func (s *Server) createIssue(org, repo, title, body string, num int, comment *gi func (s *Server) ensureForkExists(org, repo string) (string, error) { s.repoLock.Lock() defer s.repoLock.Unlock() - - // Fork repo if it doesn't exist. fork := s.botUser.Login + "/" + repo - if !repoExists(fork, s.repos) { - if name, err := s.ghc.CreateFork(org, repo); err != nil { - return repo, fmt.Errorf("cannot fork %s/%s: %v", org, repo, err) - } else { - // we got a fork but it may be named differently - repo = name - } - if err := waitForRepo(s.botUser.Login, repo, s.ghc); err != nil { - return repo, fmt.Errorf("fork of %s/%s cannot show up on GitHub: %v", org, repo, err) - } - s.repos = append(s.repos, github.Repo{FullName: fork, Fork: true}) - } - return repo, nil -} -func waitForRepo(owner, name string, ghc githubClient) error { - // Wait for at most 5 minutes for the fork to appear on GitHub. - // The documentation instructs us to contact support if this - // takes longer than five minutes. - after := time.After(6 * time.Minute) - tick := time.Tick(30 * time.Second) - - var ghErr string - for { - select { - case <-tick: - repo, err := ghc.GetRepo(owner, name) - if err != nil { - ghErr = fmt.Sprintf(": %v", err) - logrus.WithError(err).Warn("Error getting bot repository.") - continue - } - ghErr = "" - if repoExists(owner+"/"+name, []github.Repo{repo.Repo}) { - return nil - } - case <-after: - return fmt.Errorf("timed out waiting for %s to appear on GitHub%s", owner+"/"+name, ghErr) - } + // fork repo if it doesn't exsit + if _, err := s.ghc.EnsureFork(s.botUser.Login, org, repo); err != nil { + return repo, err } -} -func repoExists(repo string, repos []github.Repo) bool { - for _, r := range repos { - if !r.Fork { - continue - } - if r.FullName == repo { - return true - } - } - return false + s.repos = append(s.repos, github.Repo{FullName: fork, Fork: true}) + return repo, nil } // getPatch gets the patch for the provided PR and creates a local diff --git a/prow/external-plugins/cherrypicker/server_test.go b/prow/external-plugins/cherrypicker/server_test.go index 243ddbe3fb32..46191fce7022 100644 --- a/prow/external-plugins/cherrypicker/server_test.go +++ b/prow/external-plugins/cherrypicker/server_test.go @@ -99,6 +99,10 @@ func (f *fghc) GetRepo(owner, name string) (github.FullRepo, error) { return github.FullRepo{}, nil } +func (f *fghc) EnsureFork(forkingUser, org, repo string) (string, error) { + return "", nil +} + var expectedFmt = `title=%q body=%q head=%s base=%s labels=%v` func prToString(pr github.PullRequest) string { diff --git a/prow/github/client.go b/prow/github/client.go index fce9f2be66b8..24f590e445a7 100644 --- a/prow/github/client.go +++ b/prow/github/client.go @@ -164,6 +164,7 @@ type RepositoryClient interface { IsCollaborator(org, repo, user string) (bool, error) ListCollaborators(org, repo string) ([]User, error) CreateFork(owner, repo string) (string, error) + EnsureFork(forkingUser, org, repo string) (string, error) ListRepoTeams(org, repo string) ([]Team, error) CreateRepo(owner string, isUser bool, repo RepoCreateRequest) (*FullRepo, error) UpdateRepo(owner, name string, repo RepoUpdateRequest) (*FullRepo, error) @@ -3561,6 +3562,71 @@ func (c *client) CreateFork(owner, repo string) (string, error) { return resp.Name, err } +// EnsureFork checks to see that there is a fork of org/repo in the forkedUsers repositories. +// If there is not, it makes one, and waits for the fork to be created before returning. +// The return value is the name of the repo that was created +// (This may be different then the one that is forked due to naming conflict) +func (c *client) EnsureFork(forkingUser, org, repo string) (string, error) { + // Fork repo if it doesn't exist. + fork := forkingUser + "/" + repo + repos, err := c.GetRepos(forkingUser, true) + if err != nil { + return repo, fmt.Errorf("could not fetch all existing repos: %v", err) + } + if !repoExists(fork, repos) { + if name, err := c.CreateFork(org, repo); err != nil { + return repo, fmt.Errorf("cannot fork %s/%s: %v", org, repo, err) + } else { + // we got a fork but it may be named differently + repo = name + } + if err := c.waitForRepo(forkingUser, repo); err != nil { + return repo, fmt.Errorf("fork of %s/%s cannot show up on GitHub: %v", org, repo, err) + } + } + return repo, nil + +} + +func (c *client) waitForRepo(owner, name string) error { + // Wait for at most 5 minutes for the fork to appear on GitHub. + // The documentation instructs us to contact support if this + // takes longer than five minutes. + after := time.After(6 * time.Minute) + tick := time.Tick(30 * time.Second) + + var ghErr string + for { + select { + case <-tick: + repo, err := c.GetRepo(owner, name) + if err != nil { + ghErr = fmt.Sprintf(": %v", err) + logrus.WithError(err).Warn("Error getting bot repository.") + continue + } + ghErr = "" + if repoExists(owner+"/"+name, []Repo{repo.Repo}) { + return nil + } + case <-after: + return fmt.Errorf("timed out waiting for %s to appear on GitHub%s", owner+"/"+name, ghErr) + } + } +} + +func repoExists(repo string, repos []Repo) bool { + for _, r := range repos { + if !r.Fork { + continue + } + if r.FullName == repo { + return true + } + } + return false +} + // ListRepoTeams gets a list of all the teams with access to a repository // See https://developer.github.com/v3/repos/#list-teams func (c *client) ListRepoTeams(org, repo string) ([]Team, error) {