From b8402f08399628166c25c178863be218b32f1141 Mon Sep 17 00:00:00 2001 From: Brian Samek Date: Thu, 5 Dec 2024 09:43:40 -0500 Subject: [PATCH] DEVPROD-9181 Remove commit queue CLI (#8524) --- cmd/evergreen/evergreen.go | 1 - config.go | 2 +- docs/CLI.md | 3 - operations/before.go | 10 - operations/commit_queue.go | 794 ------------------------ operations/commit_queue_test.go | 369 ----------- operations/flags.go | 76 ++- operations/patch_util.go | 60 -- scripts/verify-client-version-update.sh | 2 +- 9 files changed, 37 insertions(+), 1280 deletions(-) delete mode 100644 operations/commit_queue.go delete mode 100644 operations/commit_queue_test.go diff --git a/cmd/evergreen/evergreen.go b/cmd/evergreen/evergreen.go index e48ce22980c..dfef2c37d16 100644 --- a/cmd/evergreen/evergreen.go +++ b/cmd/evergreen/evergreen.go @@ -58,7 +58,6 @@ func buildApp() *cli.App { operations.List(), operations.LastGreen(), operations.Subscriptions(), - operations.CommitQueue(), operations.Scheduler(), operations.Client(), diff --git a/config.go b/config.go index 733cd308c92..ff319b8fe3c 100644 --- a/config.go +++ b/config.go @@ -33,7 +33,7 @@ var ( // ClientVersion is the commandline version string used to control updating // the CLI. The format is the calendar date (YYYY-MM-DD). - ClientVersion = "2024-11-07" + ClientVersion = "2024-12-04" // Agent version to control agent rollover. The format is the calendar date // (YYYY-MM-DD). diff --git a/docs/CLI.md b/docs/CLI.md index 123968e366f..bcaa543f8e8 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -414,9 +414,6 @@ evergreen task build TaskLogs --task_id --execution --type evergreen task build TestLogs --task_id --execution --log_path ``` -#### Commit Queue -The command `evergreen commit-queue` contains subcommands for interacting with the commit queue. See [Commit Queue](Project-Configuration/Commit-Queue). - ### Server Side (for Evergreen admins) To enable auto-updating of client binaries, add a section like this to the settings file for your server: diff --git a/operations/before.go b/operations/before.go index 4d6677e5d68..562acb4382a 100644 --- a/operations/before.go +++ b/operations/before.go @@ -61,16 +61,6 @@ var ( return c.Set(hostFlagName, host) } - checkCommitMessageFlag = func(c *cli.Context) error { - message := c.String(commitMessageFlag) - if message != "" { - if c.NArg() > 1 { - return errors.New("multiple arguments passed in for commit message, please consider using quotations") - } - } - return nil - } - requireProjectFlag = func(c *cli.Context) error { if c.String(projectFlagName) == "" { return errors.New("must specify a project") diff --git a/operations/commit_queue.go b/operations/commit_queue.go deleted file mode 100644 index 7f5b2925118..00000000000 --- a/operations/commit_queue.go +++ /dev/null @@ -1,794 +0,0 @@ -package operations - -import ( - "context" - "fmt" - "net/http" - "strings" - - "github.com/evergreen-ci/evergreen" - "github.com/evergreen-ci/evergreen/model" - "github.com/evergreen-ci/evergreen/model/commitqueue" - "github.com/evergreen-ci/evergreen/model/patch" - "github.com/evergreen-ci/evergreen/rest/client" - restModel "github.com/evergreen-ci/evergreen/rest/model" - "github.com/evergreen-ci/utility" - "github.com/mongodb/grip" - "github.com/pkg/errors" - "github.com/urfave/cli" -) - -const ( - itemFlagName = "item" - pauseFlagName = "pause" - resumeFlagName = "resume" - commitsFlagName = "commits" - existingPatchFlag = "existing-patch" - backportProjectFlag = "backport-project" - commitShaFlag = "commit-sha" - commitMessageFlag = "commit-message" - githubAuthorFlag = "author" - patchAuthorFlag = "author" - - noCommits = "No Commits Added" - commitQueuePatchLabel = "Commit Queue Merge:" - commitFmtString = "'%s' into '%s/%s:%s'" -) - -func CommitQueue() cli.Command { - return cli.Command{ - Name: "commit-queue", - Usage: "interact with the commit queue", - Subcommands: []cli.Command{ - listQueue(), - deleteItem(), - mergeCommand(), - setModuleCommand(), - enqueuePatch(), - backport(), - }, - } -} - -func listQueue() cli.Command { - return cli.Command{ - Name: "list", - Usage: "list the contents of a project's commit queue", - Flags: addProjectFlag(), - Before: mergeBeforeFuncs( - requireStringFlag(projectFlagName), - setPlainLogger, - ), - Action: func(c *cli.Context) error { - confPath := c.Parent().Parent().String(confFlagName) - projectID := c.String(projectFlagName) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - conf, err := NewClientSettings(confPath) - if err != nil { - return errors.Wrap(err, "loading configuration") - } - client, err := conf.setupRestCommunicator(ctx, true) - if err != nil { - return errors.Wrap(err, "setting up REST communicator") - } - defer client.Close() - - ac, _, err := conf.getLegacyClients() - if err != nil { - return errors.Wrap(err, "setting up legacy Evergreen client") - } - - return listCommitQueue(ctx, client, ac, projectID, conf.UIServerHost) - }, - } -} - -func deleteItem() cli.Command { - return cli.Command{ - Name: "delete", - Usage: "delete a patch from a project's commit queue", - Flags: []cli.Flag{ - cli.StringFlag{ - Name: joinFlagNames(itemFlagName, "i"), - Usage: "specify the patch ID or PR number to delete", - }, - }, - Before: mergeBeforeFuncs( - requireStringFlag(itemFlagName), - setPlainLogger, - ), - Action: func(c *cli.Context) error { - confPath := c.Parent().Parent().String(confFlagName) - item := c.String(itemFlagName) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - conf, err := NewClientSettings(confPath) - if err != nil { - return errors.Wrap(err, "loading configuration") - } - client, err := conf.setupRestCommunicator(ctx, true) - if err != nil { - return errors.Wrap(err, "setting up REST communicator") - } - defer client.Close() - - if err != nil { - return errors.Wrap(err, "setting up legacy Evergreen client") - } - - showCQMessageForPatch(ctx, client, item) - return deleteCommitQueueItem(ctx, client, item) - }, - } -} - -func mergeCommand() cli.Command { - return cli.Command{ - Name: "merge", - Usage: "test and merge a feature branch", - Flags: mergeFlagSlices(addProjectFlag(), addLargeFlag(), addRefFlag(), addCommitsFlag(), addSkipConfirmFlag( - cli.StringFlag{ - Name: joinFlagNames(resumeFlagName, "r", patchFinalizeFlagName, "f"), - Usage: "resume testing a preexisting item with `ID`", - }, - cli.BoolFlag{ - Name: pauseFlagName, - Usage: "wait to enqueue an item until finalized", - }, - cli.BoolFlag{ - Name: forceFlagName, - Usage: "force item to front of queue", - }, - cli.StringFlag{ - Name: githubAuthorFlag, - Usage: "optionally define the patch author by providing a GitHub username (this will only work if the " + - "submitter has permission and the provided user has provided the username in their Evergreen settings)", - }, - )), - Before: mergeBeforeFuncs( - autoUpdateCLI, - setPlainLogger, - mutuallyExclusiveArgs(false, refFlagName, commitsFlagName), - ), - Action: func(c *cli.Context) error { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - ref := c.String(refFlagName) - commits := c.String(commitsFlagName) - if commits != "" { - ref = "" - } - - params := mergeParams{ - project: c.String(projectFlagName), - ref: ref, - commits: commits, - id: c.String(resumeFlagName), - pause: c.Bool(pauseFlagName), - skipConfirm: c.Bool(skipConfirmFlagName), - large: c.Bool(largeFlagName), - force: c.Bool(forceFlagName), - githubAuthor: c.String(githubAuthorFlag), - } - if params.force && !params.skipConfirm && !confirm( - "Forcing item to front of queue will be reported. Continue?", false) { - return errors.New("Merge aborted.") - } - conf, err := NewClientSettings(c.Parent().Parent().String(confFlagName)) - if err != nil { - return errors.Wrap(err, "loading configuration") - } - - ac, _, err := conf.getLegacyClients() - if err != nil { - return errors.Wrap(err, "setting up legacy Evergreen client") - } - - client, err := conf.setupRestCommunicator(ctx, true) - if err != nil { - return errors.Wrap(err, "setting up REST communicator") - } - defer client.Close() - - return params.mergeBranch(ctx, conf, client, ac) - }, - } -} - -func setModuleCommand() cli.Command { - return cli.Command{ - Name: "set-module", - Usage: "update or add module to an existing merge patch", - Flags: mergeFlagSlices(addLargeFlag(), addPatchIDFlag(), addModuleFlag(), addSkipConfirmFlag(), addRefFlag(), addCommitsFlag()), - Before: mergeBeforeFuncs( - autoUpdateCLI, - requirePatchIDFlag, - requireModuleFlag, - setPlainLogger, - mutuallyExclusiveArgs(false, refFlagName, commitsFlagName), - ), - Action: func(c *cli.Context) error { - params := moduleParams{ - patchID: c.String(patchIDFlagName), - module: c.String(moduleFlagName), - ref: c.String(refFlagName), - commits: c.String(commitsFlagName), - large: c.Bool(largeFlagName), - skipConfirm: c.Bool(skipConfirmFlagName), - } - - conf, err := NewClientSettings(c.Parent().Parent().String(confFlagName)) - if err != nil { - return errors.Wrap(err, "loading configuration") - } - ac, rc, err := conf.getLegacyClients() - if err != nil { - return errors.Wrap(err, "setting up legacy Evergreen client") - } - ctx := context.Background() - client, err := conf.setupRestCommunicator(ctx, true) - if err != nil { - return errors.Wrap(err, "setting up REST communicator") - } - defer client.Close() - showCQMessageForPatch(ctx, client, params.patchID) - - return errors.WithStack(params.addModule(ac, rc)) - }, - } -} - -func enqueuePatch() cli.Command { - return cli.Command{ - Name: "enqueue-patch", - Usage: "enqueue an existing patch on the commit queue", - Flags: mergeFlagSlices(addSkipConfirmFlag(), addPatchIDFlag( - cli.BoolFlag{ - Name: forceFlagName, - Usage: "force item to front of queue", - }, - cli.StringFlag{ - Name: commitMessageFlag, - Usage: "commit message for the new commit (default is the existing patch description)", - }, - )), - Before: mergeBeforeFuncs( - autoUpdateCLI, - checkCommitMessageFlag, - requirePatchIDFlag, - setPlainLogger, - ), - Action: func(c *cli.Context) error { - confPath := c.Parent().Parent().String(confFlagName) - patchID := c.String(patchIDFlagName) - commitMessage := c.String(commitMessageFlag) - force := c.Bool(forceFlagName) - skipConfirm := c.Bool(skipConfirmFlagName) - - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - conf, err := NewClientSettings(confPath) - if err != nil { - return errors.Wrap(err, "loading configuration") - } - client, err := conf.setupRestCommunicator(ctx, true) - if err != nil { - return errors.Wrap(err, "setting up REST communicator") - } - defer client.Close() - - ac, _, err := conf.getLegacyClients() - if err != nil { - return errors.Wrap(err, "setting up legacy Evergreen client") - } - - // verify the patch can be enqueued - existingPatch, err := ac.GetPatch(patchID) - if err != nil { - return errors.Wrapf(err, "getting patch '%s'", patchID) - } - if !existingPatch.HasValidGitInfo() { - return errors.Errorf("patch '%s' is not eligible to be enqueued", patchID) - } - - // confirm multiple commits - multipleCommits := false - for _, p := range existingPatch.Patches { - if len(p.PatchSet.CommitMessages) > 1 { - multipleCommits = true - } - } - if multipleCommits && !skipConfirm && - !confirm("Original patch has multiple commits (these will be tested together but merged separately). Continue?", false) { - return errors.New("enqueue aborted") - } - - showCQMessageForPatch(ctx, client, patchID) - - if commitMessage == "" { - commitMessage = existingPatch.Description - } - - // create the new merge patch - mergePatch, err := client.CreatePatchForMerge(ctx, patchID, commitMessage) - if err != nil { - return errors.Wrap(err, "creating commit queue patch") - } - uiV2, err := client.GetUiV2URL(ctx) - if err != nil { - return errors.Wrap(err, "getting UI v2 URL") - } - patchDisp, err := getAPICommitQueuePatchDisplay(ac, mergePatch, false, uiV2) - if err != nil { - grip.Errorf("can't print patch display for new patch '%s'", mergePatch.Id) - } - grip.Info("Patch successfully created.") - grip.Info(patchDisp) - - // enqueue the patch - position, err := client.EnqueueItem(ctx, utility.FromStringPtr(mergePatch.Id), force) - if err != nil { - return errors.Wrap(err, "enqueueing new patch") - } - grip.Infof("Queue position is %d.", position) - - return nil - }, - } -} - -func backport() cli.Command { - return cli.Command{ - Name: "backport", - Usage: "Backport low-risk commits -- note the patch created from this will create a regular patch to validate " + - "the backport, and if the patch succeeds then the changes are automatically added to the commit queue.", - Flags: mergeFlagSlices( - addPatchFinalizeFlag(), - addPatchBrowseFlag( - cli.StringSliceFlag{ - Name: joinFlagNames(tasksFlagName, "t"), - Usage: "tasks to validate the backport", - }, - cli.StringSliceFlag{ - Name: joinFlagNames(variantsFlagName, "v"), - Usage: "variants to validate the backport", - }, - cli.StringFlag{ - Name: joinFlagNames(patchAliasFlagName, "a"), - Usage: "patch alias to select tasks/variants to validate the backport", - }, - cli.StringFlag{ - Name: joinFlagNames(existingPatchFlag, "e"), - Usage: "existing commit queue patch usually from another project or branch", - }, - cli.StringFlag{ - Name: joinFlagNames(commitShaFlag, "s"), - Usage: "existing commit SHA from the same repository to backport", - }, - cli.StringFlag{ - Name: joinFlagNames(backportProjectFlag, "b"), - Usage: "project to backport onto", - }, - )), - Before: mergeBeforeFuncs( - autoUpdateCLI, - setPlainLogger, - requireStringFlag(backportProjectFlag), - mutuallyExclusiveArgs(true, existingPatchFlag, commitShaFlag), - ), - Action: func(c *cli.Context) error { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - - confPath := c.Parent().Parent().String(confFlagName) - patchParams := &patchParams{ - Tasks: c.StringSlice(tasksFlagName), - Variants: c.StringSlice(variantsFlagName), - Alias: c.String(patchAliasFlagName), - Project: c.String(backportProjectFlag), - Browse: c.Bool(patchBrowseFlagName), - BackportOf: patch.BackportInfo{ - PatchID: c.String(existingPatchFlag), - SHA: c.String(commitShaFlag), - }, - } - shouldFinalize := c.Bool(patchFinalizeFlagName) - conf, err := NewClientSettings(confPath) - if err != nil { - return errors.Wrap(err, "loading configuration") - } - ac, rc, err := conf.getLegacyClients() - if err != nil { - return errors.Wrap(err, "setting up legacy Evergreen client") - } - showCQMessageForProject(ac, patchParams.Project) - client, err := conf.setupRestCommunicator(ctx, true) - if err != nil { - return errors.Wrap(err, "setting up REST communicator") - } - defer client.Close() - - if _, err = patchParams.validatePatchCommand(ctx, conf, ac, client); err != nil { - return err - } - - if len(patchParams.BackportOf.PatchID) > 0 { - var existingPatch *patch.Patch - existingPatch, err = ac.GetPatch(patchParams.BackportOf.PatchID) - if err != nil { - return errors.Wrapf(err, "getting existing patch '%s'", patchParams.BackportOf.PatchID) - } - if !existingPatch.IsCommitQueuePatch() { - return errors.Errorf("patch '%s' is not a commit queue patch", patchParams.BackportOf.PatchID) - } - } - - uiV2, err := client.GetUiV2URL(ctx) - if err != nil { - return errors.Wrap(err, "getting UI v2 URL") - } - latestVersions, err := client.GetRecentVersionsForProject(ctx, patchParams.Project, evergreen.RepotrackerVersionRequester) - if err != nil { - return errors.Wrapf(err, "getting latest repotracker version for project '%s'", patchParams.Project) - } - if len(latestVersions) == 0 { - return errors.Errorf("no repotracker versions exist in project '%s'", patchParams.Project) - } - gitMetadata, err := getGitConfigMetadata() - if err != nil { - return errors.Wrap(err, "getting git metadata") - } - var backportPatch *patch.Patch - backportPatch, err = patchParams.createPatch(ac, &localDiff{base: utility.FromStringPtr(latestVersions[0].Revision), gitMetadata: gitMetadata}) - if err != nil { - return errors.Wrap(err, "uploading backport patch") - } - if shouldFinalize { - patchId := backportPatch.Id.Hex() - shouldContinue, err := checkForLargeNumFinalizedTasks(ctx, client, rc, patchParams, patchId) - if err != nil { - return err - } - if shouldContinue { - if err = ac.FinalizePatch(patchId); err != nil { - return errors.Wrapf(err, "finalizing patch '%s'", patchId) - } - backportPatch.Activated = true - } - } - - params := outputPatchParams{ - patches: []patch.Patch{*backportPatch}, - uiHost: uiV2, - } - - if err = patchParams.displayPatch(ac, params); err != nil { - return errors.Wrap(err, "getting result display") - } - - return nil - }, - } -} - -func listCommitQueue(ctx context.Context, client client.Communicator, ac *legacyClient, projectID string, uiServerHost string) error { - projectRef, err := ac.GetProjectRef(projectID) - if err != nil { - return errors.Wrapf(err, "finding project '%s' for commit queue", projectID) - } - cq, err := client.GetCommitQueue(ctx, projectRef.Id) - if err != nil { - return err - } - grip.Infof("Project: %s\n", projectID) - if projectRef.CommitQueue.Message != "" { - grip.Infof("Message: %s\n", projectRef.CommitQueue.Message) - } - - grip.Infof("Queue Length: %d\n", len(cq.Queue)) - for i, item := range cq.Queue { - grip.Infof("%d:", i) - if utility.FromStringPtr(item.Source) == commitqueue.SourcePullRequest { - listPRCommitQueueItem(item, projectRef, uiServerHost) - } else if utility.FromStringPtr(item.Source) == commitqueue.SourceDiff { - listCLICommitQueueItem(item, ac, uiServerHost) - } - listModules(item) - } - - return nil -} - -func listPRCommitQueueItem(item restModel.APICommitQueueItem, projectRef *model.ProjectRef, uiServerHost string) { - issue := utility.FromStringPtr(item.Issue) - prDisplay := ` - PR # : %s - URL : %s -` - url := fmt.Sprintf("https://github.com/%s/%s/pull/%s", projectRef.Owner, projectRef.Repo, issue) - grip.Infof(prDisplay, issue, url) - - prDisplayVersion := " Build : %s/version/%s" - if utility.FromStringPtr(item.Version) != "" { - grip.Infof(prDisplayVersion, uiServerHost, utility.FromStringPtr(item.Version)) - } - - grip.Info("\n") -} - -func listCLICommitQueueItem(item restModel.APICommitQueueItem, ac *legacyClient, uiServerHost string) { - issue := utility.FromStringPtr(item.Issue) - p, err := ac.GetPatch(issue) - if err != nil { - grip.Error(errors.Wrapf(err, "getting patch for issue '%s'", issue)) - return - } - - if p.Author != "" { - grip.Infof("Author: %s", p.Author) - } - params := outputPatchParams{ - patches: []patch.Patch{*p}, - uiHost: uiServerHost, - } - disp, err := getPatchDisplay(ac, params) - if err != nil { - grip.Error(errors.Wrapf(err, "getting patch display summary for patch '%s'", p.Id.Hex())) - return - } - grip.Info(disp) -} - -func listModules(item restModel.APICommitQueueItem) { - if len(item.Modules) > 0 { - grip.Infof("\tModules :") - - for j, module := range item.Modules { - grip.Infof("\t\t%d: %s (%s)\n", j+1, utility.FromStringPtr(module.Module), utility.FromStringPtr(module.Issue)) - } - grip.Info("\n") - } -} - -func deleteCommitQueueItem(ctx context.Context, client client.Communicator, item string) error { - err := client.DeleteCommitQueueItem(ctx, item) - if err != nil { - return err - } - - grip.Infof("Item '%s' deleted\n", item) - - return nil -} - -type mergeParams struct { - project string - commits string - ref string - id string - pause bool - skipConfirm bool - large bool - force bool - githubAuthor string -} - -func (p *mergeParams) mergeBranch(ctx context.Context, conf *ClientSettings, client client.Communicator, ac *legacyClient) error { - if p.id == "" { - showCQMessageForProject(ac, p.project) - uiV2, err := client.GetUiV2URL(ctx) - if err != nil { - return errors.Wrap(err, "getting UI v2 URL") - } - if err := p.uploadMergePatch(conf, ac, uiV2); err != nil { - return err - } - } else { - showCQMessageForPatch(ctx, client, p.id) - } - if p.pause { - return nil - } - position, err := client.EnqueueItem(ctx, p.id, p.force) - if err != nil { - return err - } - grip.Infof("Queue position is %d.", position) - - return nil -} - -func (p *mergeParams) uploadMergePatch(conf *ClientSettings, ac *legacyClient, uiV2Url string) error { - patchParams := &patchParams{ - Project: p.project, - SkipConfirm: p.skipConfirm, - Large: p.large, - Alias: evergreen.CommitQueueAlias, - GithubAuthor: p.githubAuthor, - } - - if err := patchParams.loadProject(conf); err != nil { - return errors.Wrap(err, "invalid project ID") - } - - ref, err := ac.GetProjectRef(patchParams.Project) - if err != nil { - if apiErr, ok := err.(APIError); ok && apiErr.code == http.StatusNotFound { - err = errors.WithStack(err) - } - return errors.Wrap(err, "getting project ref") - } - if !ref.CommitQueue.IsEnabled() { - return errors.New("commit queue not enabled for project") - } - if ref.CommitQueue.MergeQueue == model.MergeQueueGitHub { - return errors.New("Commit queue merge method is set to GitHub, not Evergreen, in the project settings. You must merge from a GitHub PR.") - } - - commitCount, err := gitCommitCount(ref.Branch, p.ref, p.commits) - if err != nil { - return errors.Wrap(err, "getting commit count") - } - if commitCount > 1 && !p.skipConfirm && - !confirm("Commit queue patch has multiple commits (these will be tested together but merged separately). Continue?", false) { - return errors.New("patch aborted") - } - - if err = isValidCommitsFormat(p.commits); err != nil { - return err - } - - diffData, err := loadGitData("", "", ref.Branch, p.ref, p.commits, true) - if err != nil { - return errors.Wrap(err, "generating patches") - } - - commits := noCommits - if commitCount > 0 { - var commitMessages string - commitMessages, err = gitCommitMessages(ref.Branch, p.ref, p.commits) - if err != nil { - return errors.Wrap(err, "getting commit messages") - } - commits = fmt.Sprintf(commitFmtString, commitMessages, ref.Owner, ref.Repo, ref.Branch) - } - patchParams.Description = fmt.Sprintf("%s %s", commitQueuePatchLabel, commits) - - if err = patchParams.validateSubmission(diffData); err != nil { - return err - } - newPatch, err := patchParams.createPatch(ac, diffData) - if err != nil { - return err - } - params := outputPatchParams{ - patches: []patch.Patch{*newPatch}, - uiHost: uiV2Url, - } - if err = patchParams.displayPatch(ac, params); err != nil { - grip.Error("Patch information cannot be displayed.") - } - - p.id = newPatch.Id.Hex() - patchParams.setDefaultProject(conf) - - return nil -} - -type moduleParams struct { - patchID string - module string - ref string - commits string - large bool - skipConfirm bool -} - -func (p *moduleParams) addModule(ac *legacyClient, rc *legacyClient) error { - if err := isValidCommitsFormat(p.commits); err != nil { - return err - } - - proj, err := rc.GetPatchedConfig(p.patchID) - if err != nil { - return err - } - module, err := proj.GetModuleByName(p.module) - if err != nil { - return errors.Wrapf(err, "finding module '%s'", p.module) - } - - commitCount, err := gitCommitCount(module.Branch, p.ref, p.commits) - if err != nil { - return errors.Wrap(err, "getting commit count") - } - if commitCount == 0 { - return errors.New("no commits for module") - } - if commitCount > 1 && !p.skipConfirm && - !confirm("Commit queue module patch has multiple commits (these will be tested together but merged separately). Continue?", false) { - return errors.New("module patch aborted") - } - - owner, repo, err := module.GetOwnerAndRepo() - if err != nil { - return errors.Wrapf(err, "getting owner and repo for '%s'", module.Name) - } - - patch, err := rc.GetPatch(p.patchID) - if err != nil { - return errors.Wrapf(err, "get patch '%s'", p.patchID) - } - - commitMessages, err := gitCommitMessages(module.Branch, p.ref, p.commits) - if err != nil { - return errors.Wrap(err, "getting module commit messages") - } - commits := fmt.Sprintf(commitFmtString, commitMessages, owner, repo, module.Branch) - message := fmt.Sprintf("%s || %s", patch.Description, commits) - // replace the description if the original patch was empty - if strings.HasSuffix(patch.Description, noCommits) { - message = fmt.Sprintf("%s %s", commitQueuePatchLabel, commits) - } - - diffData, err := loadGitData("", "", module.Branch, p.ref, p.commits, true) - if err != nil { - return errors.Wrap(err, "getting patch data") - } - if err = validatePatchSize(diffData, p.large); err != nil { - return err - } - - if !p.skipConfirm { - grip.InfoWhen(diffData.patchSummary != "", diffData.patchSummary) - grip.InfoWhen(diffData.log != "", diffData.log) - if !confirm("This is a summary of the patch to be submitted. Continue?", true) { - return nil - } - } - - params := UpdatePatchModuleParams{ - patchID: p.patchID, - module: p.module, - patch: diffData.fullPatch, - base: diffData.base, - message: message, - } - err = ac.UpdatePatchModule(params) - if err != nil { - return errors.WithStack(err) - } - grip.Info("Module updated.") - return nil -} - -func showCQMessageForProject(ac *legacyClient, projectID string) { - projectRef, _ := ac.GetProjectRef(projectID) - if projectRef != nil && projectRef.CommitQueue.Message != "" { - grip.Info(projectRef.CommitQueue.Message) - } -} - -func showCQMessageForPatch(ctx context.Context, comm client.Communicator, patchID string) { - message, _ := comm.GetMessageForPatch(ctx, patchID) - if message != "" { - grip.Info(message) - } -} - -func getAPICommitQueuePatchDisplay(ac *legacyClient, apiPatch *restModel.APIPatch, summarize bool, uiHost string) (string, error) { - servicePatch, err := apiPatch.ToService() - if err != nil { - return "", errors.Wrap(err, "converting patch to service model") - } - - params := outputPatchParams{ - patches: []patch.Patch{servicePatch}, - summarize: summarize, - uiHost: uiHost, - } - return getPatchDisplay(ac, params) -} diff --git a/operations/commit_queue_test.go b/operations/commit_queue_test.go deleted file mode 100644 index 65fa7f68282..00000000000 --- a/operations/commit_queue_test.go +++ /dev/null @@ -1,369 +0,0 @@ -package operations - -import ( - "context" - "fmt" - "io" - "os" - "testing" - "time" - - "github.com/evergreen-ci/evergreen" - "github.com/evergreen-ci/evergreen/db" - "github.com/evergreen-ci/evergreen/db/mgo/bson" - "github.com/evergreen-ci/evergreen/model" - "github.com/evergreen-ci/evergreen/model/commitqueue" - "github.com/evergreen-ci/evergreen/model/patch" - "github.com/evergreen-ci/evergreen/rest/client" - "github.com/evergreen-ci/evergreen/service" - "github.com/evergreen-ci/evergreen/testutil" - "github.com/evergreen-ci/utility" - "github.com/mongodb/grip" - "github.com/mongodb/grip/send" - "github.com/stretchr/testify/require" - "github.com/stretchr/testify/suite" - "gopkg.in/yaml.v3" -) - -type CommitQueueSuite struct { - client client.Communicator - conf *ClientSettings - ctx context.Context - settingsFile string - server *service.TestServer - suite.Suite -} - -func TestCommitQueueSuite(t *testing.T) { - ctx, cancel := context.WithCancel(context.Background()) - defer cancel() - originalEnv := evergreen.GetEnvironment() - env := testutil.NewEnvironment(ctx, t) - evergreen.SetEnvironment(env) - defer func() { - evergreen.SetEnvironment(originalEnv) - }() - testutil.ConfigureIntegrationTest(t, testConfig) - require.NoError(t, testConfig.Set(ctx)) - suite.Run(t, new(CommitQueueSuite)) -} - -func (s *CommitQueueSuite) SetupSuite() { - s.ctx = context.Background() - testutil.DisablePermissionsForTests() - - var err error - s.server, err = service.CreateTestServer(s.ctx, testConfig, nil, false) - s.Require().NoError(err) - - settings := ClientSettings{ - APIServerHost: s.server.URL + "/api", - UIServerHost: "http://dev-evg.mongodb.com", - APIKey: "testapikey", - User: "testuser", - } - settingsFile, err := os.CreateTemp("", "settings") - s.Require().NoError(err) - s.settingsFile = settingsFile.Name() - settingsBytes, err := yaml.Marshal(settings) - s.Require().NoError(err) - _, err = settingsFile.Write(settingsBytes) - s.Require().NoError(err) - s.Require().NoError(settingsFile.Close()) - s.conf, err = NewClientSettings(settingsFile.Name()) - s.Require().NoError(err) - s.client, err = s.conf.setupRestCommunicator(s.ctx, true) - s.Require().NoError(err) -} - -func (s *CommitQueueSuite) TearDownSuite() { - s.NoError(os.RemoveAll(s.settingsFile)) - testutil.EnablePermissionsForTests() - s.server.Close() - s.client.Close() -} - -func (s *CommitQueueSuite) TestListContentsForCLI() { - s.Require().NoError(db.ClearCollections(commitqueue.Collection, patch.Collection, model.ProjectRefCollection)) - now := time.Now() - p1 := patch.Patch{ - Id: bson.NewObjectId(), - Project: "mci", - Author: "annie.black", - Activated: true, - Description: "fix things", - CreateTime: now, - Status: evergreen.TaskDispatched, - } - s.NoError(p1.Insert()) - p2 := patch.Patch{ - Id: bson.NewObjectId(), - Author: "annie.black", - Project: "mci", - Description: "do things", - } - s.NoError(p2.Insert()) - p3 := patch.Patch{ - Id: bson.NewObjectId(), - Author: "john.liu", - Project: "mci", - Description: "no things", - } - s.NoError(p3.Insert()) - - pRef := &model.ProjectRef{ - Id: "mci", - Identifier: "mci", - } - s.Require().NoError(pRef.Insert()) - - cq := &commitqueue.CommitQueue{ProjectID: "mci"} - s.Require().NoError(commitqueue.InsertQueue(cq)) - - pos, err := cq.Enqueue(commitqueue.CommitQueueItem{Issue: p1.Id.Hex(), Source: commitqueue.SourceDiff}) - s.NoError(err) - s.Equal(0, pos) - pos, err = cq.Enqueue(commitqueue.CommitQueueItem{Issue: p2.Id.Hex(), Source: commitqueue.SourceDiff}) - s.NoError(err) - s.Equal(1, pos) - pos, err = cq.Enqueue(commitqueue.CommitQueueItem{Issue: p3.Id.Hex(), Source: commitqueue.SourceDiff}) - s.NoError(err) - s.Equal(2, pos) - - origStdout := os.Stdout - r, w, _ := os.Pipe() - os.Stdout = w - s.NoError(grip.SetSender(send.MakePlainLogger())) - ac, _, err := s.conf.getLegacyClients() - s.NoError(err) - - s.NoError(listCommitQueue(s.ctx, s.client, ac, "mci", s.conf.UIServerHost)) - s.NoError(w.Close()) - os.Stdout = origStdout - out, _ := io.ReadAll(r) - stringOut := string(out[:]) - - s.Contains(stringOut, "Project: mci") - s.Contains(stringOut, "Description : do things") - s.Contains(stringOut, "0:") - s.Contains(stringOut, fmt.Sprintf("ID : %s", p1.Id.Hex())) - s.Contains(stringOut, fmt.Sprintf("ID : %s", p2.Id.Hex())) - s.Contains(stringOut, fmt.Sprintf("ID : %s", p3.Id.Hex())) - s.Contains(stringOut, fmt.Sprintf("Author: %s", p1.Author)) - s.Contains(stringOut, fmt.Sprintf("Author: %s", p3.Author)) - versionURL := fmt.Sprintf("Build : %s/version/%s", s.conf.UIServerHost, p1.Id.Hex()) - patchURL := fmt.Sprintf("Build : %s/patch/%s", s.conf.UIServerHost, p2.Id.Hex()) - s.Contains(stringOut, versionURL) - s.Contains(stringOut, patchURL) -} - -func (s *CommitQueueSuite) TestListContentsMissingPatch() { - s.Require().NoError(db.ClearCollections(commitqueue.Collection, model.ProjectRefCollection, patch.Collection)) - p1 := patch.Patch{ - Id: bson.NewObjectId(), - Project: "mci", - Author: "annie.black", - Activated: true, - Description: "fix things", - CreateTime: time.Now(), - Status: evergreen.TaskDispatched, - } - s.NoError(p1.Insert()) - pRef := &model.ProjectRef{ - Id: "mci", - Identifier: "mci", - } - s.Require().NoError(pRef.Insert()) - - fakeIssue := "not-a-real-issue" - cq := &commitqueue.CommitQueue{ - ProjectID: "mci", - Queue: []commitqueue.CommitQueueItem{ - {Issue: fakeIssue, Source: commitqueue.SourceDiff}, - {Issue: p1.Id.Hex(), Source: commitqueue.SourceDiff}, - }, - } - s.Require().NoError(commitqueue.InsertQueue(cq)) - - origStdout := os.Stdout - r, w, _ := os.Pipe() - os.Stdout = w - s.NoError(grip.SetSender(send.MakePlainLogger())) - ac, _, err := s.conf.getLegacyClients() - s.NoError(err) - s.NoError(listCommitQueue(s.ctx, s.client, ac, "mci", s.conf.UIServerHost)) - s.NoError(w.Close()) - os.Stdout = origStdout - out, _ := io.ReadAll(r) - stringOut := string(out[:]) - - s.Contains(stringOut, "Project: mci") - s.Contains(stringOut, "0:") - s.Contains(stringOut, fmt.Sprintf("getting patch for issue '%s'", fakeIssue)) - s.Contains(stringOut, fmt.Sprintf("ID : %s", p1.Id.Hex())) -} - -func (s *CommitQueueSuite) TestListContentsForPRs() { - s.Require().NoError(db.ClearCollections(commitqueue.Collection, model.ProjectRefCollection)) - cq := &commitqueue.CommitQueue{ - ProjectID: "mci", - Queue: []commitqueue.CommitQueueItem{ - { - Issue: "123", - Source: commitqueue.SourcePullRequest, - }, - { - Issue: "456", - Source: commitqueue.SourcePullRequest, - }, - { - Issue: "789", - Source: commitqueue.SourcePullRequest, - }, - }, - } - s.Require().NoError(commitqueue.InsertQueue(cq)) - cq.Queue[0].Version = "my_version" - s.NoError(cq.UpdateVersion(&cq.Queue[0])) - pRef := &model.ProjectRef{ - Id: "mci", - Identifier: "mci", - Owner: "evergreen-ci", - Repo: "evergreen", - } - s.Require().NoError(pRef.Insert()) - - origStdout := os.Stdout - r, w, _ := os.Pipe() - os.Stdout = w - s.NoError(grip.SetSender(send.MakePlainLogger())) - ac, _, err := s.conf.getLegacyClients() - s.NoError(err) - - s.NoError(listCommitQueue(s.ctx, s.client, ac, "mci", s.conf.UIServerHost)) - s.NoError(w.Close()) - os.Stdout = origStdout - out, _ := io.ReadAll(r) - stringOut := string(out[:]) - - s.Contains(stringOut, "Project: mci") - s.Contains(stringOut, "0:") - s.Contains(stringOut, "PR # : 123") - s.Contains(stringOut, "PR # : 456") - s.Contains(stringOut, "PR # : 789") - url := fmt.Sprintf("URL : https://github.com/%s/%s/pull/%s", pRef.Owner, pRef.Repo, "456") - versionURL := fmt.Sprintf("Build : %s/version/%s", s.conf.UIServerHost, "my_version") - s.Contains(stringOut, url) - s.Contains(stringOut, versionURL) -} - -func (s *CommitQueueSuite) TestListContentsWithModule() { - s.Require().NoError(db.ClearCollections(commitqueue.Collection, model.ProjectRefCollection)) - cq := &commitqueue.CommitQueue{ - ProjectID: "mci", - Queue: []commitqueue.CommitQueueItem{ - { - Issue: "123", - Source: commitqueue.SourcePullRequest, - Modules: []commitqueue.Module{ - { - Module: "test_module", - Issue: "1234", - }, - }, - }, - { - Issue: "456", - Source: commitqueue.SourcePullRequest, - }, - { - Issue: "789", - Source: commitqueue.SourcePullRequest, - }, - }, - } - s.Require().NoError(commitqueue.InsertQueue(cq)) - - pRef := &model.ProjectRef{ - Id: "mci", - Identifier: "mci", - Owner: "me", - Repo: "evergreen", - } - s.Require().NoError(pRef.Insert()) - - origStdout := os.Stdout - r, w, _ := os.Pipe() - os.Stdout = w - s.NoError(grip.SetSender(send.MakePlainLogger())) - - ac, _, err := s.conf.getLegacyClients() - s.NoError(err) - s.NoError(listCommitQueue(s.ctx, s.client, ac, "mci", s.conf.UIServerHost)) - s.NoError(w.Close()) - os.Stdout = origStdout - out, _ := io.ReadAll(r) - stringOut := string(out[:]) - - s.Contains(stringOut, "Project: mci") - s.Contains(stringOut, "PR # : 123") - s.Contains(stringOut, "Modules :") - s.Contains(stringOut, "1: test_module (1234)") - s.Contains(stringOut, "PR # : 456") - s.Contains(stringOut, "PR # : 789") -} - -func (s *CommitQueueSuite) TestDeleteCommitQueueItem() { - s.Require().NoError(db.ClearCollections(commitqueue.Collection, model.ProjectRefCollection, patch.Collection)) - validId := bson.NewObjectId().Hex() - cq := &commitqueue.CommitQueue{ - ProjectID: "mci", - Queue: []commitqueue.CommitQueueItem{ - { - Issue: validId, - PatchId: validId, - Source: commitqueue.SourceDiff, - }, - { - Issue: bson.NewObjectId().Hex(), - PatchId: bson.NewObjectId().Hex(), - Source: commitqueue.SourceDiff, - }, - { - Issue: bson.NewObjectId().Hex(), - PatchId: bson.NewObjectId().Hex(), - Source: commitqueue.SourceDiff, - }, - }, - } - s.Require().NoError(commitqueue.InsertQueue(cq)) - projectRef := model.ProjectRef{ - Id: "mci", - Admins: []string{"testuser"}, - CommitQueue: model.CommitQueueParams{ - Enabled: utility.TruePtr(), - }, - } - - s.NoError(projectRef.Insert()) - - patch := patch.Patch{ - Id: patch.NewId(validId), - Project: "mci", - } - s.NoError(patch.Insert()) - - s.Error(deleteCommitQueueItem(s.ctx, s.client, "not_here")) - - origStdout := os.Stdout - r, w, _ := os.Pipe() - os.Stdout = w - s.NoError(grip.SetSender(send.MakePlainLogger())) - s.NoError(deleteCommitQueueItem(s.ctx, s.client, validId)) - s.NoError(w.Close()) - os.Stdout = origStdout - out, _ := io.ReadAll(r) - stringOut := string(out[:]) - - s.Contains(stringOut, fmt.Sprintf("Item '%s' deleted", validId)) -} diff --git a/operations/flags.go b/operations/flags.go index 9627ed8e926..2336296673a 100644 --- a/operations/flags.go +++ b/operations/flags.go @@ -9,53 +9,54 @@ import ( ) const ( - confFlagName = "conf" - versionIDFlagName = "version_id" clientS3BucketFlagName = "client_s3_bucket" - traceEndpointFlagName = "trace_endpoint" - overwriteConfFlagName = "overwrite" - pathFlagName = "path" - projectFlagName = "project" - patchIDFlagName = "patch" - moduleFlagName = "module" + confFlagName = "conf" + dirFlagName = "dir" + displayNameFlagName = "name" + errorOnWarningsFlagName = "error-on-warnings" + forceFlagName = "force" + hostFlagName = "host" + largeFlagName = "large" + limitFlagName = "limit" localModulesFlagName = "local_modules" - skipConfirmFlagName = "skip_confirm" - yesFlagName = "yes" - variantsFlagName = "variants" - regexVariantsFlagName = "regex_variants" - tasksFlagName = "tasks" - regexTasksFlagName = "regex_tasks" + longFlagName = "long" + moduleFlagName = "module" + overwriteConfFlagName = "overwrite" parameterFlagName = "param" patchAliasFlagName = "alias" - patchFinalizeFlagName = "finalize" + patchAuthorFlag = "author" patchBrowseFlagName = "browse" + patchFinalizeFlagName = "finalize" + patchIDFlagName = "patch" + pathFlagName = "path" + preserveCommitsFlag = "preserve-commits" + projectFlagName = "project" + quietFlagName = "quiet" + refFlagName = "ref" + regexTasksFlagName = "regex_tasks" + regexVariantsFlagName = "regex_variants" + regionFlagName = "region" + skipConfirmFlagName = "skip_confirm" + startTimeFlagName = "time" + subscriptionTypeFlag = "subscription-type" syncBuildVariantsFlagName = "sync_variants" + syncStatusesFlagName = "sync_statuses" syncTasksFlagName = "sync_tasks" syncTimeoutFlagName = "sync_timeout" - syncStatusesFlagName = "sync_statuses" - largeFlagName = "large" - hostFlagName = "host" - displayNameFlagName = "name" - regionFlagName = "region" - startTimeFlagName = "time" - limitFlagName = "limit" - forceFlagName = "force" - refFlagName = "ref" - quietFlagName = "quiet" - longFlagName = "long" - dirFlagName = "dir" + tasksFlagName = "tasks" + traceEndpointFlagName = "trace_endpoint" uncommittedChangesFlag = "uncommitted" - preserveCommitsFlag = "preserve-commits" - subscriptionTypeFlag = "subscription-type" - errorOnWarningsFlagName = "error-on-warnings" + variantsFlagName = "variants" + versionIDFlagName = "version_id" + yesFlagName = "yes" - dbUrlFlagName = "url" - sharedDBUrlFlagName = "shared-db-url" dbAWSAuthFlagName = "mongo-aws-auth" dbNameFlagName = "db" - dbWriteNumFlagName = "w" - dbWmodeFlagName = "wmode" dbRmodeFlagName = "rmode" + dbUrlFlagName = "url" + dbWmodeFlagName = "wmode" + dbWriteNumFlagName = "w" + sharedDBUrlFlagName = "shared-db-url" jsonFlagName = "json" ) @@ -262,13 +263,6 @@ func addRefFlag(flags ...cli.Flag) []cli.Flag { }) } -func addCommitsFlag(flags ...cli.Flag) []cli.Flag { - return append(flags, cli.StringFlag{ - Name: joinFlagNames(commitsFlagName, "c"), - Usage: "specify commit hash (can also be a range .., where hash1 is excluded)", - }) -} - func addUncommittedChangesFlag(flags ...cli.Flag) []cli.Flag { return append(flags, cli.BoolFlag{ Name: joinFlagNames(uncommittedChangesFlag, "u"), diff --git a/operations/patch_util.go b/operations/patch_util.go index f0eefc447da..5ff536a134a 100644 --- a/operations/patch_util.go +++ b/operations/patch_util.go @@ -11,7 +11,6 @@ import ( "path/filepath" "regexp" "runtime" - "strconv" "strings" "text/template" "time" @@ -650,25 +649,6 @@ func getFeatureBranch(ref, commits string) string { return "HEAD" } -func isValidCommitsFormat(commits string) error { - errToReturn := errors.New("Invalid commit format: verify input is of the form ` OR `..` (where hash1 is an ancestor of hash2)") - if commits == "" || !isCommitRange(commits) { - return nil - } - - commitsList := strings.Split(commits, "..") - if len(commitsList) != 2 { // extra check - return errToReturn - } - - if _, err := gitIsAncestor(commitsList[0], strings.Trim(commitsList[1], ".")); err != nil { - // suppressing given error bc it's not helpful - return errToReturn - } - - return nil -} - func confirmUncommittedChanges(dir string, preserveCommits, includeUncommitedChanges bool) (bool, error) { uncommittedChanges, err := gitUncommittedChanges(dir) if err != nil { @@ -791,11 +771,6 @@ func gitMergeBase(dir, branch1, ref, commits string) (string, error) { return strings.TrimSpace(out), err } -func gitIsAncestor(commit1, commit2 string) (string, error) { - args := []string{"--is-ancestor", commit1, commit2} - return gitCmd("merge-base", args...) -} - // gitDiff runs "git diff " and returns the output of the command as a string, // where ref and commits are mutually exclusive (and not required). If dir is specified, runs the command // in the specified directory. @@ -829,23 +804,6 @@ func gitLog(dir, base, ref, commits string) (string, error) { return gitCmdWithDir("log", dir, revisionRange, "--oneline") } -func gitCommitMessages(base, ref, commits string) (string, error) { - input := fmt.Sprintf("%s@{upstream}..%s", base, ref) - if commits != "" { - input = formatCommitRange(commits) - } - args := []string{"--no-show-signature", "--pretty=format:%s", "--reverse", input} - msg, err := gitCmd("log", args...) - if err != nil { - return "", errors.Wrap(err, "getting git log messages") - } - // separate multiple commits with <- - msg = strings.TrimSpace(msg) - msg = strings.Replace(msg, "\n", " <- ", -1) - - return msg, nil -} - // assumes base includes @{upstream} func gitLastCommitMessage() (string, error) { args := []string{"HEAD", "--no-show-signature", "--pretty=format:%s", "-n 1"} @@ -874,24 +832,6 @@ func getDefaultDescription() (string, error) { return fmt.Sprintf("%s: %s", branch, desc), nil } -func gitCommitCount(base, ref, commits string) (int, error) { - input := fmt.Sprintf("%s@{upstream}..%s", base, ref) - if commits != "" { - input = formatCommitRange(commits) - } - out, err := gitCmd("rev-list", input, "--count") - if err != nil { - return 0, errors.Wrap(err, "getting git commit count") - } - - count, err := strconv.Atoi(strings.TrimSpace(out)) - if err != nil { - return 0, errors.Wrapf(err, "parsing git commit count from git command output '%s'", out) - } - - return count, nil -} - func gitUncommittedChanges(dir string) (bool, error) { args := "--porcelain" out, err := gitCmdWithDir("status", dir, args) diff --git a/scripts/verify-client-version-update.sh b/scripts/verify-client-version-update.sh index 4c4d571901f..d79e4f4915b 100755 --- a/scripts/verify-client-version-update.sh +++ b/scripts/verify-client-version-update.sh @@ -33,5 +33,5 @@ if [[ "${last_commit_client_version_updated}" == "000000000000000000000000000000 fi echo -e "Files affected by client version update:\n${files_changed}" >&2 -echo "Evergreen CLI has been changed but client version has not been updated. Please update the client version." >&2 +echo "The CLI has been changed, but the ClientVersion has not been updated. Please update the ClientVersion in config.go." >&2 exit 1