upload: add a --ignore-hooks option

When upload hooks fail, people are forced to use --no-verify to upload
CLs anyways.  When projects have flaky hooks, this trains people to
always use that option.  This is obviously bad: hooks might get fixed,
or some of the hooks are always good & people should review.

Lets add an --ignore-hooks option.  This still runs the hooks, but any
failures will be ignored and allow the user to upload anyways.

Bug: https://crbug.com/gerrit/12230
Change-Id: Ide2ac8a40a656bfcd6aae20c3ce8118e06bf909b
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/254452
Tested-by: Mike Frysinger <vapier@google.com>
Reviewed-by: David Pursehouse <dpursehouse@collab.net>
diff --git a/subcmds/upload.py b/subcmds/upload.py
index 5c12aae..f463331 100644
--- a/subcmds/upload.py
+++ b/subcmds/upload.py
@@ -168,6 +168,9 @@
                  type='string', action='store', dest='dest_branch',
                  metavar='BRANCH',
                  help='Submit for review on this target branch.')
+    p.add_option('--no-cert-checks',
+                 dest='validate_certs', action='store_false', default=True,
+                 help='Disable verifying ssl certs (unsafe).')
 
     # Options relating to upload hook.  Note that verify and no-verify are NOT
     # opposites of each other, which is why they store to different locations.
@@ -185,15 +188,16 @@
     #   Never run upload hooks, but upload anyway (AKA bypass hooks).
     # - no-verify=True, verify=True:
     #   Invalid
-    p.add_option('--no-cert-checks',
-                 dest='validate_certs', action='store_false', default=True,
-                 help='Disable verifying ssl certs (unsafe).')
-    p.add_option('--no-verify',
+    g = p.add_option_group('Upload hooks')
+    g.add_option('--no-verify',
                  dest='bypass_hooks', action='store_true',
                  help='Do not run the upload hook.')
-    p.add_option('--verify',
+    g.add_option('--verify',
                  dest='allow_all_hooks', action='store_true',
                  help='Run the upload hook without prompting.')
+    g.add_option('--ignore-hooks',
+                 dest='ignore_hooks', action='store_true',
+                 help='Do not abort uploading if upload hooks fail.')
 
   def _SingleBranch(self, opt, branch, people):
     project = branch.project
@@ -488,12 +492,24 @@
                       abort_if_user_denies=True)
       pending_proj_names = [project.name for (project, available) in pending]
       pending_worktrees = [project.worktree for (project, available) in pending]
+      passed = True
       try:
         hook.Run(opt.allow_all_hooks, project_list=pending_proj_names,
                  worktree_list=pending_worktrees)
+      except SystemExit:
+        passed = False
+        if not opt.ignore_hooks:
+          raise
       except HookError as e:
+        passed = False
         print("ERROR: %s" % str(e), file=sys.stderr)
-        return
+
+      if not passed:
+        if opt.ignore_hooks:
+          print('\nWARNING: pre-upload hooks failed, but uploading anyways.',
+                file=sys.stderr)
+        else:
+          return
 
     if opt.reviewers:
       reviewers = _SplitEmails(opt.reviewers)