command: Respect smart sync override declaratively by default

Introduce a `RESPECT_SMART_SYNC_OVERRIDE` class attribute to the base
`Command` class, defaulting to `True`. This allows subcommands to
automatically respect the smart sync override manifest if it exists.

The override is applied in `CommonValidateOptions` before any
subcommand-specific validation or execution occurs. The `sync` and
`init` commands explicitly opt out.

This ensures all workspace-aware subcommands consistently align with the
active smart sync override manifest. It also fixes a bug in
multi-manifest setups where running a command from a submanifest would
not apply the override to the outer manifest, causing inconsistency when
resolving projects across all manifests.

Bug: 279204331
Change-Id: I9426e90a13a77ce6bd94b4a82efda4d485cbe116
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/585081
Reviewed-by: Mike Frysinger <vapier@google.com>
Tested-by: Gavin Mak <gavinmak@google.com>
Commit-Queue: Gavin Mak <gavinmak@google.com>
diff --git a/command.py b/command.py
index bfafcd4..8b6a4d1 100644
--- a/command.py
+++ b/command.py
@@ -66,6 +66,10 @@
     # command to show short-vs-full summaries.
     COMMON = False
 
+    # Whether this command should respect the smart sync override manifest if
+    # it exists.
+    RESPECT_SMART_SYNC_OVERRIDE = True
+
     # Whether this command supports running in parallel. If greater than 0,
     # it is the number of parallel jobs to default to.
     PARALLEL_JOBS = None
@@ -247,6 +251,12 @@
             # from the user's perspective.
             opt.outer_manifest = True
 
+        if self.RESPECT_SMART_SYNC_OVERRIDE:
+            if self.manifest:
+                self.TryOverrideManifestWithSmartSync(self.manifest)
+            if self.outer_manifest and self.outer_manifest != self.manifest:
+                self.TryOverrideManifestWithSmartSync(self.outer_manifest)
+
     def ValidateOptions(self, opt, args):
         """Validate the user options & arguments before executing.
 
diff --git a/subcmds/abandon.py b/subcmds/abandon.py
index 38f6ca4..f1688e7 100644
--- a/subcmds/abandon.py
+++ b/subcmds/abandon.py
@@ -94,7 +94,6 @@
 
     def Execute(self, opt, args):
         nb = args[0].split()
-        self.TryOverrideManifestWithSmartSync()
         err = collections.defaultdict(list)
         success = collections.defaultdict(list)
         aggregate_errors = []
diff --git a/subcmds/forall.py b/subcmds/forall.py
index 03d1270..982b1ba 100644
--- a/subcmds/forall.py
+++ b/subcmds/forall.py
@@ -243,8 +243,6 @@
 
         mirror = self.manifest.IsMirror
 
-        self.TryOverrideManifestWithSmartSync()
-
         if opt.regex:
             projects = self.FindProjects(args, all_manifests=all_trees)
         elif opt.inverse_regex:
diff --git a/subcmds/info.py b/subcmds/info.py
index 348a7c8..594e633 100644
--- a/subcmds/info.py
+++ b/subcmds/info.py
@@ -147,8 +147,6 @@
         if not opt.this_manifest_only:
             self.manifest = self.manifest.outer_client
 
-        self.TryOverrideManifestWithSmartSync()
-
         output_format = OutputFormat[opt.format.upper()]
         if output_format == OutputFormat.JSON:
             self._ExecuteJson(opt, args)
diff --git a/subcmds/init.py b/subcmds/init.py
index b60ef28..772002e 100644
--- a/subcmds/init.py
+++ b/subcmds/init.py
@@ -33,6 +33,7 @@
 
 class Init(InteractiveCommand, MirrorSafeCommand):
     COMMON = True
+    RESPECT_SMART_SYNC_OVERRIDE = False
     MULTI_MANIFEST_SUPPORT = True
     helpSummary = "Initialize a repo client checkout in the current directory"
     helpUsage = """
diff --git a/subcmds/start.py b/subcmds/start.py
index 9882cf0..73dddf3 100644
--- a/subcmds/start.py
+++ b/subcmds/start.py
@@ -104,7 +104,6 @@
 
     def Execute(self, opt, args):
         nb = args[0]
-        self.TryOverrideManifestWithSmartSync()
         err_projects = []
         err = []
         projects = []
diff --git a/subcmds/sync.py b/subcmds/sync.py
index 573c344..6a88530 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -312,6 +312,7 @@
 
 class Sync(Command, MirrorSafeCommand):
     COMMON = True
+    RESPECT_SMART_SYNC_OVERRIDE = False
     MULTI_MANIFEST_SUPPORT = True
     helpSummary = "Update working tree to the latest revision"
     helpUsage = """
diff --git a/tests/test_subcmds_gc.py b/tests/test_subcmds_gc.py
index 708f4a7..f5c415e 100644
--- a/tests/test_subcmds_gc.py
+++ b/tests/test_subcmds_gc.py
@@ -24,7 +24,7 @@
     """Tests for gc command."""
 
     def setUp(self):
-        self.cmd = gc.Gc()
+        self.cmd = gc.Gc(manifest=mock.MagicMock())
         self.opt, self.args = self.cmd.OptionParser.parse_args([])
         self.opt.this_manifest_only = False
         self.opt.repack = False