command: make --verbose/--quiet available to all subcommands

Add new CommonOptions entry points to move the existing --jobs to,
and relocate all --verbose/--quiet options to that.  This provides
both a consistent interface for users as well as for code.

Change-Id: Ifaf83b88872421f4749b073c472b4a67ca6c0437
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/303224
Reviewed-by: Raman Tenneti <rtenneti@google.com>
Tested-by: Mike Frysinger <vapier@google.com>
diff --git a/command.py b/command.py
index f708832..be2d6a6 100644
--- a/command.py
+++ b/command.py
@@ -84,18 +84,34 @@
         usage = 'repo %s' % self.NAME
       epilog = 'Run `repo help %s` to view the detailed manual.' % self.NAME
       self._optparse = optparse.OptionParser(usage=usage, epilog=epilog)
+      self._CommonOptions(self._optparse)
       self._Options(self._optparse)
     return self._optparse
 
-  def _Options(self, p):
-    """Initialize the option parser.
+  def _CommonOptions(self, p, opt_v=True):
+    """Initialize the option parser with common options.
+
+    These will show up for *all* subcommands, so use sparingly.
+    NB: Keep in sync with repo:InitParser().
     """
+    g = p.add_option_group('Logging options')
+    opts = ['-v'] if opt_v else []
+    g.add_option(*opts, '--verbose',
+                 dest='output_mode', action='store_true',
+                 help='show all output')
+    g.add_option('-q', '--quiet',
+                 dest='output_mode', action='store_false',
+                 help='only show errors')
+
     if self.PARALLEL_JOBS is not None:
       p.add_option(
           '-j', '--jobs',
           type=int, default=self.PARALLEL_JOBS,
           help='number of jobs to run in parallel (default: %s)' % self.PARALLEL_JOBS)
 
+  def _Options(self, p):
+    """Initialize the option parser with subcommand-specific options."""
+
   def _RegisteredEnvironmentOptions(self):
     """Get options that can be set from environment variables.
 
@@ -120,6 +136,11 @@
     self.OptionParser.print_usage()
     sys.exit(1)
 
+  def CommonValidateOptions(self, opt, args):
+    """Validate common options."""
+    opt.quiet = opt.output_mode is False
+    opt.verbose = opt.output_mode is True
+
   def ValidateOptions(self, opt, args):
     """Validate the user options & arguments before executing.
 
diff --git a/main.py b/main.py
index 9abda6a..8aba2ec 100755
--- a/main.py
+++ b/main.py
@@ -257,6 +257,7 @@
     git_trace2_event_log.CommandEvent(name='repo', subcommands=[name])
 
     try:
+      cmd.CommonValidateOptions(copts, cargs)
       cmd.ValidateOptions(copts, cargs)
       result = cmd.Execute(copts, cargs)
     except (DownloadError, ManifestInvalidRevisionError,
diff --git a/repo b/repo
index 604f800..cf9f186 100755
--- a/repo
+++ b/repo
@@ -281,6 +281,8 @@
 
 def InitParser(parser, gitc_init=False):
   """Setup the CLI parser."""
+  # NB: Keep in sync with command.py:_CommonOptions().
+
   # Logging.
   group = parser.add_option_group('Logging options')
   group.add_option('-v', '--verbose',
diff --git a/subcmds/abandon.py b/subcmds/abandon.py
index b82a2db..ea3f4ed 100644
--- a/subcmds/abandon.py
+++ b/subcmds/abandon.py
@@ -37,10 +37,6 @@
   PARALLEL_JOBS = DEFAULT_LOCAL_JOBS
 
   def _Options(self, p):
-    super()._Options(p)
-    p.add_option('-q', '--quiet',
-                 action='store_true', default=False,
-                 help='be quiet')
     p.add_option('--all',
                  dest='all', action='store_true',
                  help='delete all branches in all projects')
diff --git a/subcmds/cherry_pick.py b/subcmds/cherry_pick.py
index 4b7f141..fc4998c 100644
--- a/subcmds/cherry_pick.py
+++ b/subcmds/cherry_pick.py
@@ -32,9 +32,6 @@
 change id will be added.
 """
 
-  def _Options(self, p):
-    pass
-
   def ValidateOptions(self, opt, args):
     if len(args) != 1:
       self.Usage()
diff --git a/subcmds/diff.py b/subcmds/diff.py
index 8186817..cdc262e 100644
--- a/subcmds/diff.py
+++ b/subcmds/diff.py
@@ -32,7 +32,6 @@
   PARALLEL_JOBS = DEFAULT_LOCAL_JOBS
 
   def _Options(self, p):
-    super()._Options(p)
     p.add_option('-u', '--absolute',
                  dest='absolute', action='store_true',
                  help='Paths are relative to the repository root')
diff --git a/subcmds/forall.py b/subcmds/forall.py
index f0ce97c..4a631fb 100644
--- a/subcmds/forall.py
+++ b/subcmds/forall.py
@@ -129,8 +129,6 @@
       del parser.rargs[0]
 
   def _Options(self, p):
-    super()._Options(p)
-
     p.add_option('-r', '--regex',
                  dest='regex', action='store_true',
                  help="Execute the command only on projects matching regex or wildcard expression")
@@ -153,13 +151,10 @@
                  help='Silently skip & do not exit non-zero due missing '
                       'checkouts')
 
-    g = p.add_option_group('Output')
+    g = p.get_option_group('--quiet')
     g.add_option('-p',
                  dest='project_header', action='store_true',
                  help='Show project headers before output')
-    g.add_option('-v', '--verbose',
-                 dest='verbose', action='store_true',
-                 help='Show command error messages')
     p.add_option('--interactive',
                  action='store_true',
                  help='force interactive usage')
diff --git a/subcmds/grep.py b/subcmds/grep.py
index 49feaf6..9a4a8a3 100644
--- a/subcmds/grep.py
+++ b/subcmds/grep.py
@@ -82,8 +82,11 @@
     if value is not None:
       pt.append(value)
 
+  def _CommonOptions(self, p):
+    """Override common options slightly."""
+    super()._CommonOptions(p, opt_v=False)
+
   def _Options(self, p):
-    super()._Options(p)
     g = p.add_option_group('Sources')
     g.add_option('--cached',
                  action='callback', callback=self._carry_option,
diff --git a/subcmds/init.py b/subcmds/init.py
index a23e529..4182262 100644
--- a/subcmds/init.py
+++ b/subcmds/init.py
@@ -79,6 +79,9 @@
 to update the working directory files.
 """
 
+  def _CommonOptions(self, p):
+    """Disable due to re-use of Wrapper()."""
+
   def _Options(self, p, gitc_init=False):
     Wrapper().InitParser(p, gitc_init=gitc_init)
 
@@ -436,9 +439,6 @@
             % ('.'.join(str(x) for x in MIN_GIT_VERSION_SOFT),),
             file=sys.stderr)
 
-    opt.quiet = opt.output_mode is False
-    opt.verbose = opt.output_mode is True
-
     rp = self.manifest.repoProject
 
     # Handle new --repo-url requests.
diff --git a/subcmds/rebase.py b/subcmds/rebase.py
index cf536e9..e0186d4 100644
--- a/subcmds/rebase.py
+++ b/subcmds/rebase.py
@@ -39,7 +39,8 @@
 """
 
   def _Options(self, p):
-    p.add_option('-i', '--interactive',
+    g = p.get_option_group('--quiet')
+    g.add_option('-i', '--interactive',
                  dest="interactive", action="store_true",
                  help="interactive rebase (single project only)")
 
@@ -52,9 +53,6 @@
     p.add_option('--no-ff',
                  dest='ff', default=True, action='store_false',
                  help='Pass --no-ff to git rebase')
-    p.add_option('-q', '--quiet',
-                 dest='quiet', action='store_true',
-                 help='Pass --quiet to git rebase')
     p.add_option('--autosquash',
                  dest='autosquash', action='store_true',
                  help='Pass --autosquash to git rebase')
diff --git a/subcmds/stage.py b/subcmds/stage.py
index 98b3022..ff0f173 100644
--- a/subcmds/stage.py
+++ b/subcmds/stage.py
@@ -38,7 +38,8 @@
 """
 
   def _Options(self, p):
-    p.add_option('-i', '--interactive',
+    g = p.get_option_group('--quiet')
+    g.add_option('-i', '--interactive',
                  dest='interactive', action='store_true',
                  help='use interactive staging')
 
diff --git a/subcmds/start.py b/subcmds/start.py
index 04589fb..2593ace 100644
--- a/subcmds/start.py
+++ b/subcmds/start.py
@@ -38,7 +38,6 @@
   PARALLEL_JOBS = DEFAULT_LOCAL_JOBS
 
   def _Options(self, p):
-    super()._Options(p)
     p.add_option('--all',
                  dest='all', action='store_true',
                  help='begin branch in all projects')
diff --git a/subcmds/status.py b/subcmds/status.py
index 6c8e22e..dc223a0 100644
--- a/subcmds/status.py
+++ b/subcmds/status.py
@@ -80,12 +80,9 @@
   PARALLEL_JOBS = DEFAULT_LOCAL_JOBS
 
   def _Options(self, p):
-    super()._Options(p)
     p.add_option('-o', '--orphans',
                  dest='orphans', action='store_true',
                  help="include objects in working directory outside of repo projects")
-    p.add_option('-q', '--quiet', action='store_true',
-                 help="only print the name of modified projects")
 
   def _StatusHelper(self, quiet, project):
     """Obtains the status for a specific project.
diff --git a/subcmds/sync.py b/subcmds/sync.py
index b8abb1a..e707987 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -167,13 +167,14 @@
 """
   PARALLEL_JOBS = 1
 
-  def _Options(self, p, show_smart=True):
+  def _CommonOptions(self, p):
     try:
       self.PARALLEL_JOBS = self.manifest.default.sync_j
     except ManifestParseError:
       pass
-    super()._Options(p)
+    super()._CommonOptions(p)
 
+  def _Options(self, p, show_smart=True):
     p.add_option('--jobs-network', default=None, type=int, metavar='JOBS',
                  help='number of network jobs to run in parallel (defaults to --jobs)')
     p.add_option('--jobs-checkout', default=None, type=int, metavar='JOBS',
@@ -211,12 +212,6 @@
     p.add_option('-c', '--current-branch',
                  dest='current_branch_only', action='store_true',
                  help='fetch only current branch from server')
-    p.add_option('-v', '--verbose',
-                 dest='output_mode', action='store_true',
-                 help='show all sync output')
-    p.add_option('-q', '--quiet',
-                 dest='output_mode', action='store_false',
-                 help='only show errors')
     p.add_option('-m', '--manifest-name',
                  dest='manifest_name',
                  help='temporary manifest to use for this sync', metavar='NAME.xml')
@@ -770,9 +765,6 @@
       soft_limit, _ = _rlimit_nofile()
       self.jobs = min(self.jobs, (soft_limit - 5) // 3)
 
-    opt.quiet = opt.output_mode is False
-    opt.verbose = opt.output_mode is True
-
     if opt.manifest_name:
       self.manifest.Override(opt.manifest_name)