[autoroll] Flesh out commit message comparson in mock-commit-msg

Change-Id: I02a20c24e9b7c51b76aa959104d4c2f67a6bff7c
Reviewed-on: https://skia-review.googlesource.com/c/buildbot/+/290134
Reviewed-by: Ravi Mistry <rmistry@google.com>
Commit-Queue: Eric Boren <borenet@google.com>
diff --git a/autoroll/go/commit_msg/cmd/mock-commit-msg/main.go b/autoroll/go/commit_msg/cmd/mock-commit-msg/main.go
index 4da5625..6fec323 100644
--- a/autoroll/go/commit_msg/cmd/mock-commit-msg/main.go
+++ b/autoroll/go/commit_msg/cmd/mock-commit-msg/main.go
@@ -1,21 +1,44 @@
 package main
 
 import (
+	"context"
 	"flag"
 	"fmt"
 	"io"
+	"io/ioutil"
 	"log"
+	"os/user"
+	"path/filepath"
+	"strings"
 
+	"cloud.google.com/go/datastore"
+	"cloud.google.com/go/storage"
 	"github.com/flynn/json5"
+	"github.com/pmezard/go-difflib/difflib"
 	"go.skia.org/infra/autoroll/go/commit_msg"
+	"go.skia.org/infra/autoroll/go/config_vars"
+	"go.skia.org/infra/autoroll/go/revision"
 	"go.skia.org/infra/autoroll/go/roller"
+	"go.skia.org/infra/autoroll/go/status"
+	"go.skia.org/infra/go/auth"
+	"go.skia.org/infra/go/chrome_branch"
 	"go.skia.org/infra/go/common"
+	"go.skia.org/infra/go/ds"
+	"go.skia.org/infra/go/gcs/gcsclient"
+	"go.skia.org/infra/go/gerrit"
+	"go.skia.org/infra/go/github"
+	"go.skia.org/infra/go/httputils"
+	"go.skia.org/infra/go/repo_root"
 	"go.skia.org/infra/go/util"
+	"golang.org/x/oauth2"
+	"google.golang.org/api/option"
 )
 
 var (
 	configFile = flag.String("config", "", "Config file to parse. Required.")
+	compare    = flag.Bool("compare", false, "Compare the generated commit message against the most recent actual commit message.")
 	serverURL  = flag.String("server_url", "", "Server URL. Optional.")
+	workdir    = flag.String("workdir", "", "Working directory. If not set, a temporary directory is created.")
 )
 
 func main() {
@@ -39,11 +62,169 @@
 		*serverURL = fmt.Sprintf("https://autoroll.skia.org/r/%s", cfg.RollerName)
 	}
 
-	// Obtain fake data to use for the commit message.
-	from, to, revs, _ := commit_msg.FakeCommitMsgInputs()
-	reviewers, err := roller.GetSheriff(cfg.RollerName, cfg.Sheriff, cfg.SheriffBackup)
-	if err != nil {
-		log.Fatalf("Failed to retrieve sheriff: %s", err)
+	// Obtain data to use for the commit message. If --compare was provided, use
+	// the most recent actual commit message.
+	var from, to *revision.Revision
+	var revs []*revision.Revision
+	var reviewers []string
+	var realCommitMsg string
+	var realRollURL string
+	if *compare {
+		// Create the working directory.
+		if *workdir == "" {
+			wd, err := ioutil.TempDir("", "")
+			if err != nil {
+				log.Fatal(err)
+			}
+			*workdir = wd
+		}
+
+		// Create the RepoManager.
+		ctx := context.Background()
+		ts, err := auth.NewDefaultTokenSource(true, auth.SCOPE_USERINFO_EMAIL, auth.SCOPE_GERRIT, datastore.ScopeDatastore, "https://www.googleapis.com/auth/devstorage.read_only")
+		if err != nil {
+			log.Fatal(err)
+		}
+		client := httputils.DefaultClientConfig().WithTokenSource(ts).With2xxOnly().Client()
+
+		s, err := storage.NewClient(ctx)
+		if err != nil {
+			log.Fatal(err)
+		}
+		gcsClient := gcsclient.New(s, "skia-autoroll")
+
+		var gerritClient *gerrit.Gerrit
+		var githubClient *github.GitHub
+		if cfg.Gerrit != nil {
+			gc, err := cfg.Gerrit.GetConfig()
+			if err != nil {
+				log.Fatalf("Failed to get Gerrit config: %s", err)
+			}
+			gerritClient, err = gerrit.NewGerritWithConfig(gc, cfg.Gerrit.URL, client)
+			if err != nil {
+				log.Fatalf("Failed to create Gerrit client: %s", err)
+			}
+		} else if cfg.Github != nil {
+			user, err := user.Current()
+			if err != nil {
+				log.Fatal(err)
+			}
+			pathToGithubToken := filepath.Join(user.HomeDir, github.GITHUB_TOKEN_FILENAME)
+			// Instantiate githubClient using the github token secret.
+			gBody, err := ioutil.ReadFile(pathToGithubToken)
+			if err != nil {
+				log.Fatalf("Couldn't find githubToken in %s: %s.", pathToGithubToken, err)
+			}
+			gToken := strings.TrimSpace(string(gBody))
+			githubHttpClient := oauth2.NewClient(ctx, oauth2.StaticTokenSource(&oauth2.Token{AccessToken: gToken}))
+			githubClient, err = github.NewGitHub(ctx, cfg.Github.RepoOwner, cfg.Github.RepoName, githubHttpClient)
+			if err != nil {
+				log.Fatalf("Could not create Github client: %s", err)
+			}
+		}
+
+		cr, err := cfg.CodeReview().Init(gerritClient, githubClient)
+		if err != nil {
+			log.Fatalf("Failed to initialize code review: %s", err)
+		}
+		reg, err := config_vars.NewRegistry(ctx, chrome_branch.NewClient(client))
+		if err != nil {
+			log.Fatalf("Failed to create config var registry: %s", err)
+		}
+
+		repoRoot, err := repo_root.Get()
+		if err != nil {
+			log.Fatal(err)
+		}
+		recipesCfgFile := filepath.Join(repoRoot, "infra", "config", "recipes.cfg")
+
+		rm, err := cfg.CreateRepoManager(ctx, cr, reg, gerritClient, githubClient, *workdir, recipesCfgFile, *serverURL, cfg.RollerName, gcsClient, client, true)
+		if err != nil {
+			log.Fatal(err)
+		}
+
+		// Read the most recent roll from the roller.
+		namespace := ds.AUTOROLL_NS
+		if cfg.IsInternal {
+			namespace = ds.AUTOROLL_INTERNAL_NS
+		}
+		if err := ds.InitWithOpt(common.PROJECT_ID, namespace, option.WithTokenSource(ts)); err != nil {
+			log.Fatal(err)
+		}
+
+		st, err := status.Get(ctx, cfg.RollerName)
+		if err != nil {
+			log.Fatal(err)
+		}
+		if len(st.Recent) == 0 {
+			log.Fatal("No recent commit messages to compare against!")
+		}
+		lastRoll := st.Recent[0]
+		fromRev, err := rm.GetRevision(ctx, lastRoll.RollingFrom)
+		if err != nil {
+			log.Fatal(err)
+		}
+		from = fromRev
+		toRev, err := rm.GetRevision(ctx, lastRoll.RollingTo)
+		if err != nil {
+			log.Fatal(err)
+		}
+		to = toRev
+		// TODO(borenet): We don't have a RepoManager.Log(from, to) method to
+		// return a slice of revisions, so we can't form an actual list here.
+		revs = []*revision.Revision{to}
+		if cfg.Gerrit != nil {
+			ci, err := gerritClient.GetIssueProperties(ctx, lastRoll.Issue)
+			if err != nil {
+				log.Fatalf("Failed to get change: %s", err)
+			}
+			commit, err := gerritClient.GetCommit(ctx, ci.Issue, ci.Patchsets[len(ci.Patchsets)-1].ID)
+			if err != nil {
+				log.Fatalf("Failed to get commit: %s", err)
+			}
+			var realCommitMsgLines []string
+			for _, line := range strings.Split(commit.Message, "\n") {
+				// Filter out automatically-added lines.
+				if strings.HasPrefix(line, "Change-Id") ||
+					strings.HasPrefix(line, "Reviewed-on") ||
+					strings.HasPrefix(line, "Reviewed-by") ||
+					strings.HasPrefix(line, "Commit-Queue") ||
+					strings.HasPrefix(line, "Cr-Commit-Position") ||
+					strings.HasPrefix(line, "Cr-Branched-From") {
+					continue
+				}
+				realCommitMsgLines = append(realCommitMsgLines, line)
+			}
+			realCommitMsg = strings.Join(realCommitMsgLines, "\n")
+			for _, reviewer := range ci.Reviewers.Reviewer {
+				// Exclude automatically-added reviewers.
+				if strings.Contains(reviewer.Email, "gserviceaccount") ||
+					strings.Contains(reviewer.Email, "commit-bot") {
+					continue
+				}
+				reviewers = append(reviewers, reviewer.Email)
+			}
+			realRollURL = gerritClient.Url(ci.Issue)
+		} else if cfg.Github != nil {
+			pr, err := githubClient.GetPullRequest(int(lastRoll.Issue))
+			if err != nil {
+				log.Fatalf("Failed to get pull request: %s", err)
+			}
+			realCommitMsg = *pr.Title + "\n" + *pr.Body
+			for _, reviewer := range pr.RequestedReviewers {
+				reviewers = append(reviewers, *reviewer.Email)
+			}
+			realRollURL = *pr.HTMLURL
+		} else {
+			log.Fatal("Either Gerrit or Github is required.")
+		}
+	} else {
+		from, to, revs, _ = commit_msg.FakeCommitMsgInputs()
+		var err error
+		reviewers, err = roller.GetSheriff(cfg.RollerName, cfg.Sheriff, cfg.SheriffBackup)
+		if err != nil {
+			log.Fatalf("Failed to retrieve sheriff: %s", err)
+		}
 	}
 
 	// Create the commit message builder.
@@ -57,5 +238,36 @@
 	if err != nil {
 		log.Fatalf("Failed to build commit message: %s", err)
 	}
-	fmt.Println(genCommitMsg)
+	if *compare {
+		diff, err := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{
+			A:        difflib.SplitLines(string(realCommitMsg)),
+			B:        difflib.SplitLines(string(genCommitMsg)),
+			FromFile: "Generated",
+			ToFile:   "Actual",
+			Context:  3,
+			Eol:      "\n",
+		})
+		if err != nil {
+			log.Fatal(err)
+		}
+		fmt.Println("Found most recent real roll:")
+		fmt.Println(realRollURL)
+		if diff != "" {
+			fmt.Println("Generated commit message differs from most recent actual commit message (note that revision lists generated by this tool will be incomplete):")
+			fmt.Println(diff)
+			fmt.Println("=====================================================")
+			fmt.Println("Full old commit message:")
+			fmt.Println("=====================================================")
+			fmt.Println(realCommitMsg)
+			fmt.Println("=====================================================")
+			fmt.Println("Full new commit message:")
+			fmt.Println("=====================================================")
+			fmt.Println(genCommitMsg)
+		} else {
+			fmt.Println("Generated commit message is identical to most recent actual commit message:")
+			fmt.Println(genCommitMsg)
+		}
+	} else {
+		fmt.Println(genCommitMsg)
+	}
 }