scripts: lint_package produces CLs for fixes

This CL adds support to the lint_package script for creating CLs with
the changes it applies in platform2.

BUG=b:300533086
TEST=lint_package -b=atlas --apply-fixes --fetch-only --create-cl

Change-Id: I76ae884c18bb06ac783fee6d8434ed5025f6ec3f
Reviewed-on: https://chromium-review.googlesource.com/c/chromiumos/chromite/+/4898927
Reviewed-by: George Burgess <gbiv@chromium.org>
Commit-Queue: Ryan Beltran <ryanbeltran@chromium.org>
Tested-by: Ryan Beltran <ryanbeltran@chromium.org>
diff --git a/scripts/lint_package.py b/scripts/lint_package.py
index 53fb2bb..01ed490 100644
--- a/scripts/lint_package.py
+++ b/scripts/lint_package.py
@@ -20,6 +20,7 @@
 from chromite.lib import commandline
 from chromite.lib import constants
 from chromite.lib import cros_build_lib
+from chromite.lib import git
 from chromite.lib import portage_util
 from chromite.lib import terminal
 from chromite.lib import workon_helper
@@ -31,6 +32,26 @@
 PLATFORM2_PATH = constants.CHROOT_SOURCE_ROOT / "src/platform2"
 
 
+def create_fixes_cl(formatted_fixes: Text, bug: Optional[Text]):
+    """Make a commit in src/platform2 with all changes."""
+    message = (
+        "Apply generated linter fixes\n\n"
+        "This CL was generated by the lint_package chromite script.\n"
+        f"The following lints should be fixed:\n\n{formatted_fixes}\n\n"
+        f"BUG={bug}\n"
+        "TEST=CQ\n"
+    )
+
+    git.RunGit(PLATFORM2_PATH, ["add", "--all"])
+    git.Commit(PLATFORM2_PATH, message)
+
+
+def check_plat2_diff() -> bool:
+    """Check if src/platform2 has changes in it's diff."""
+    diff = git.RawDiff(PLATFORM2_PATH, ".")
+    return bool(diff)
+
+
 def parse_packages(
     build_target: build_target_lib.BuildTarget, packages: List[str]
 ) -> List[package_info.PackageInfo]:
@@ -377,6 +398,16 @@
         help="Apply suggested fixes from linters.",
     )
     parser.add_argument(
+        "--create-cl",
+        action="store_true",
+        help="Generate a CL for fixes.",
+    )
+    parser.add_argument(
+        "--bug",
+        default="None",
+        help="Sets the tracking bug for the CL if --create_cl is used.",
+    )
+    parser.add_argument(
         "--filter-names",
         help="Only keep lints if the name contains one of the provided filters",
         action="append",
@@ -454,6 +485,12 @@
             "--restrict-fix-subdirs is meaningless if fixes aren't applied"
         )
 
+    if opts.create_cl and not opts.apply_fixes:
+        parser.error("--create-cl not allowed if fixes aren't applied")
+
+    if opts.bug != "None" and not opts.create_cl:
+        parser.error("--bug not allowed if a CL is not being created")
+
     return opts
 
 
@@ -480,6 +517,17 @@
         build_linter = toolchain.BuildLinter(
             packages, build_target.root, opts.differential
         )
+        create_cl = False
+        if opts.apply_fixes and opts.create_cl:
+            if not check_plat2_diff():
+                create_cl = True
+            else:
+                create_cl = cros_build_lib.BooleanPrompt(
+                    "Platform2 contains uncommited changes which will be "
+                    "added to the generated cl. Would you still like to "
+                    "create a CL from fixes?"
+                )
+
         if opts.fetch_only:
             if opts.apply_fixes:
                 logging.warning(
@@ -530,6 +578,14 @@
         else:
             formatted_fixes = "\n".join(format_lint(l) for l in fixed_lints)
 
+        if create_cl:
+            if fixed_lints:
+                create_fixes_cl(formatted_fixes, opts.bug)
+            else:
+                logging.warning(
+                    "Skipped creating CL since no fixes were applied."
+                )
+
     with file_util.Open(opts.output, "w") as output_file:
         output_file.write(formatted_output)
         if not opts.json: