[autoroll] If rebasing CL fails due to a merge conflict, abandon it

Change-Id: I5d7f470e7dce565a4f773b17396da434f102c00d
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/430622
Reviewed-by: Ravi Mistry <rmistry@google.com>
Commit-Queue: Eric Boren <borenet@google.com>
diff --git a/autoroll/go/codereview/roll.go b/autoroll/go/codereview/roll.go
index 67023d0..7919460 100644
--- a/autoroll/go/codereview/roll.go
+++ b/autoroll/go/codereview/roll.go
@@ -5,6 +5,7 @@
 	"errors"
 	"fmt"
 	"net/http"
+	"strings"
 	"time"
 
 	github_api "github.com/google/go-github/v29/github"
@@ -25,6 +26,10 @@
 	// GitHubPRDurationForChecks is the duration after a PR is created that
 	// checks should be looked at.
 	GitHubPRDurationForChecks = time.Minute * 15
+
+	// ErrMergeConflict as a substring of an error message indicates that a
+	// merge conflict occurred.
+	ErrMergeConflict = "conflict during merge"
 )
 
 // RollImpl describes the behavior of an autoroll CL.
@@ -250,6 +255,11 @@
 	} else if rollCommit.Parents[0].Commit != head.Hash {
 		sklog.Infof("HEAD is %s and CL is based on %s; attempting rebase.", head.Hash, rollCommit.Parents[0].Commit)
 		if err := r.g.Rebase(ctx, r.ci, "", false); err != nil {
+			if strings.Contains(err.Error(), ErrMergeConflict) {
+				if err2 := r.g.Abandon(ctx, r.ci, "Failed to rebase due to merge conflict; closing CL."); err2 != nil {
+					return skerr.Wrapf(err, "failed to rebase due to merge conflict and failed to abandon CL with: %s", err2)
+				}
+			}
 			return skerr.Wrap(err)
 		}
 		return r.Update(ctx)
diff --git a/autoroll/go/state_machine/state_machine.go b/autoroll/go/state_machine/state_machine.go
index 06343a7..dfd9086 100644
--- a/autoroll/go/state_machine/state_machine.go
+++ b/autoroll/go/state_machine/state_machine.go
@@ -326,7 +326,7 @@
 		return nil
 	})
 	f(F_CLOSE_DRY_RUN_FAILED, func(ctx context.Context, roll RollCLImpl) error {
-		if err := roll.Close(ctx, autoroll.ROLL_RESULT_DRY_RUN_FAILURE, fmt.Sprintf("Commit queue failed; closing this roll.")); err != nil {
+		if err := roll.Close(ctx, autoroll.ROLL_RESULT_DRY_RUN_FAILURE, fmt.Sprintf("Dry run failed; closing this roll.")); err != nil {
 			return err
 		}
 		n.SendIssueUpdate(ctx, roll.IssueID(), roll.IssueURL(), "This CL was abandoned because the commit queue dry run failed and there are new commits to try.")
@@ -377,9 +377,7 @@
 		return nil
 	})
 	f(F_RETRY_FAILED_NORMAL, func(ctx context.Context, roll RollCLImpl) error {
-		// TODO(borenet): The CQ will fail forever in the case of a
-		// merge conflict; we should really patch in the CL, rebase and
-		// upload again.
+		sklog.Infof("CQ failed but no new commits; retrying CQ.")
 		if err := roll.RetryCQ(ctx); err != nil {
 			return err
 		}
@@ -388,9 +386,6 @@
 	})
 	f(F_RETRY_FAILED_DRY_RUN, func(ctx context.Context, roll RollCLImpl) error {
 		sklog.Infof("Dry run failed but no new commits; retrying CQ.")
-		// TODO(borenet): The CQ will fail forever in the case of a
-		// merge conflict; we should really patch in the CL, rebase and
-		// upload again.
 		if err := roll.RetryDryRun(ctx); err != nil {
 			return err
 		}