hooks: pass yes flag when available

Pass the -y flag as a keyword argument "yes" to the hook main function.
This allows upload hooks (such as auto-fixers) to automatically
apply fixes when the -y flag is passed, rather than prompting the user.

Bug: 498893733
Change-Id: I12096029aa3af471ba9175314d749deb0ebd1007
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/605261
Commit-Queue: Rahul Yadav <yadavrah@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Rahul Yadav <yadavrah@google.com>
diff --git a/docs/repo-hooks.md b/docs/repo-hooks.md
index 696ad5a..c3adee8 100644
--- a/docs/repo-hooks.md
+++ b/docs/repo-hooks.md
@@ -83,6 +83,13 @@
 the user.  Although user interaction is discouraged in the common case, it can
 be useful when deploying automatic fixes.
 
+### Safe Prompts
+
+If the repo command that triggered the hook supports a "yes" option (e.g.,
+`repo upload --yes`), this option is propagated to the hook's `main` function
+as `yes` parameter (defaulting to `False`).  Hooks can use this to bypass
+interactive confirmation prompts when they can automatically fix issues.
+
 ### Shebang Handling
 
 *** note
@@ -119,7 +126,7 @@
 The `pre-upload.py` file should be defined like:
 
 ```py
-def main(project_list, worktree_list=None, **kwargs):
+def main(project_list, worktree_list=None, yes=False, **kwargs):
     """Main function invoked directly by repo.
 
     We must use the name "main" as that is what repo requires.
@@ -130,6 +137,8 @@
           project_list, so that each entry in project_list matches with a
           directory in worktree_list.  If None, we will attempt to calculate
           the directories automatically.
+      yes: Whether to answer yes to all safe prompts (see
+          [Safe Prompts](#safe-prompts)).
       kwargs: Leave this here for forward-compatibility.
     """
 ```
diff --git a/hooks.py b/hooks.py
index 0637dce..5c763db 100644
--- a/hooks.py
+++ b/hooks.py
@@ -68,6 +68,7 @@
         allow_all_hooks=False,
         ignore_hooks=False,
         abort_if_user_denies=False,
+        yes=False,
     ):
         """RepoHook constructor.
 
@@ -89,6 +90,7 @@
             ignore_hooks: If True, then 'Do not abort action if hooks fail'.
             abort_if_user_denies: If True, we'll abort running the hook if the
                 user doesn't allow us to run the hook.
+            yes: If True, then 'Yes' is assumed for any prompts.
         """
         self._hook_type = hook_type
         self._hooks_project = hooks_project
@@ -99,6 +101,7 @@
         self._allow_all_hooks = allow_all_hooks
         self._ignore_hooks = ignore_hooks
         self._abort_if_user_denies = abort_if_user_denies
+        self._yes = yes
 
         # Store the full path to the script for convenience.
         self._script_fullpath = None
@@ -374,8 +377,11 @@
             #   def main(project_list, **kwargs):
             #
             # This allows us to later expand the API without breaking old hooks.
-            kwargs = kwargs.copy()
-            kwargs["hook_should_take_kwargs"] = True
+            kwargs = {
+                **kwargs,
+                "hook_should_take_kwargs": True,
+                "yes": self._yes,
+            }
 
             # See what version of python the hook has been written against.
             data = open(self._script_fullpath).read()
@@ -497,6 +503,7 @@
                     "origin"
                 ).url,
                 "bug_url": manifest.contactinfo.bugurl,
+                "yes": getattr(opt, "yes", False),
             }
         )
         return cls(*args, **kwargs)
diff --git a/tests/test_hooks.py b/tests/test_hooks.py
index 14c226c..33ccba7 100644
--- a/tests/test_hooks.py
+++ b/tests/test_hooks.py
@@ -105,3 +105,37 @@
 
     finally:
         sys.stderr = old_stderr
+
+
+@pytest.mark.parametrize("yes_val", (True, False))
+def test_repo_upload_yes_arg(tmp_path, yes_val: bool) -> None:
+    """Test that yes is passed in kwargs during hook execution."""
+
+    class FakeProject:
+        def __init__(self, worktree):
+            self.worktree = worktree
+            self.enabled_repo_hooks = ["pre-upload"]
+            self.config = None
+
+    hook_file = tmp_path / "pre-upload.py"
+
+    hook_content = """
+def main(project_list, **kwargs):
+    project_list.append(kwargs.get("yes"))
+"""
+    hook_file.write_text(hook_content)
+
+    hook = hooks.RepoHook(
+        hook_type="pre-upload",
+        hooks_project=FakeProject(str(tmp_path)),
+        repo_topdir=str(tmp_path),
+        manifest_url="https://gerrit",
+        allow_all_hooks=True,
+        yes=yes_val,
+    )
+
+    project_list = []
+    res = hook.Run(project_list=project_list, worktree_list=[])
+
+    assert res is True
+    assert project_list == [yes_val]