sync: refactor use of self.manifest

We need to iterate over multiple manifests, and generally use the
outer_client.manifest for multi-manifest support.  This refactors the
use of self.manifest into a chosen manifest.

Change-Id: I992f21d610c929675e99555ece9c38df4b635839
Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/334699
Tested-by: LaMont Jones <lamontjones@google.com>
Reviewed-by: Mike Frysinger <vapier@google.com>
diff --git a/subcmds/sync.py b/subcmds/sync.py
index 3451ab6..9a66e48 100644
--- a/subcmds/sync.py
+++ b/subcmds/sync.py
@@ -170,9 +170,9 @@
   PARALLEL_JOBS = 1
 
   def _CommonOptions(self, p):
-    if self.manifest:
+    if self.outer_client and self.outer_client.manifest:
       try:
-        self.PARALLEL_JOBS = self.manifest.default.sync_j
+        self.PARALLEL_JOBS = self.outer_client.manifest.default.sync_j
       except ManifestParseError:
         pass
     super()._CommonOptions(p)
@@ -270,25 +270,32 @@
                  dest='repo_upgraded', action='store_true',
                  help=SUPPRESS_HELP)
 
-  def _GetBranch(self):
-    """Returns the branch name for getting the approved manifest."""
-    p = self.manifest.manifestProject
-    b = p.GetBranch(p.CurrentBranch)
+  def _GetBranch(self, manifest_project):
+    """Returns the branch name for getting the approved smartsync manifest.
+
+    Args:
+      manifest_project: the manifestProject to query.
+    """
+    b = manifest_project.GetBranch(manifest_project.CurrentBranch)
     branch = b.merge
     if branch.startswith(R_HEADS):
       branch = branch[len(R_HEADS):]
     return branch
 
-  def _GetCurrentBranchOnly(self, opt):
+  def _GetCurrentBranchOnly(self, opt, manifest):
     """Returns whether current-branch or use-superproject options are enabled.
 
+    Args:
+      opt: Program options returned from optparse.  See _Options().
+      manifest: The manifest to use.
+
     Returns:
       True if a superproject is requested, otherwise the value of the
       current_branch option (True, False or None).
     """
-    return git_superproject.UseSuperproject(opt.use_superproject, self.manifest) or opt.current_branch_only
+    return git_superproject.UseSuperproject(opt.use_superproject, manifest) or opt.current_branch_only
 
-  def _UpdateProjectsRevisionId(self, opt, args, load_local_manifests, superproject_logging_data):
+  def _UpdateProjectsRevisionId(self, opt, args, load_local_manifests, superproject_logging_data, manifest):
     """Update revisionId of every project with the SHA from superproject.
 
     This function updates each project's revisionId with SHA from superproject.
@@ -300,6 +307,7 @@
           docstring for details.
       load_local_manifests: Whether to load local manifests.
       superproject_logging_data: A dictionary of superproject data that is to be logged.
+      manifest: The manifest to use.
 
     Returns:
       Returns path to the overriding manifest file instead of None.
@@ -312,7 +320,7 @@
     if opt.local_only:
       manifest_path = superproject.manifest_path
       if manifest_path:
-        self._ReloadManifest(manifest_path, load_local_manifests)
+        self._ReloadManifest(manifest_path, manifest, load_local_manifests)
       return manifest_path
 
     all_projects = self.GetProjects(args,
@@ -323,7 +331,7 @@
     manifest_path = update_result.manifest_path
     superproject_logging_data['updatedrevisionid'] = bool(manifest_path)
     if manifest_path:
-      self._ReloadManifest(manifest_path, load_local_manifests)
+      self._ReloadManifest(manifest_path, manifest, load_local_manifests)
     else:
       if print_messages:
         print('warning: Update of revisionId from superproject has failed, '
@@ -366,16 +374,16 @@
           quiet=opt.quiet,
           verbose=opt.verbose,
           output_redir=buf,
-          current_branch_only=self._GetCurrentBranchOnly(opt),
+          current_branch_only=self._GetCurrentBranchOnly(opt, project.manifest),
           force_sync=opt.force_sync,
           clone_bundle=opt.clone_bundle,
-          tags=opt.tags, archive=self.manifest.IsArchive,
+          tags=opt.tags, archive=project.manifest.IsArchive,
           optimized_fetch=opt.optimized_fetch,
           retry_fetches=opt.retry_fetches,
           prune=opt.prune,
           ssh_proxy=self.ssh_proxy,
-          clone_filter=self.manifest.CloneFilter,
-          partial_clone_exclude=self.manifest.PartialCloneExclude)
+          clone_filter=project.manifest.CloneFilter,
+          partial_clone_exclude=project.manifest.PartialCloneExclude)
 
       output = buf.getvalue()
       if (opt.verbose or not success) and output:
@@ -472,13 +480,13 @@
     pm.end()
     self._fetch_times.Save()
 
-    if not self.manifest.IsArchive:
+    if not self.outer_client.manifest.IsArchive:
       self._GCProjects(projects, opt, err_event)
 
     return (ret, fetched)
 
   def _FetchMain(self, opt, args, all_projects, err_event, manifest_name,
-                 load_local_manifests, ssh_proxy):
+                 load_local_manifests, ssh_proxy, manifest):
     """The main network fetch loop.
 
     Args:
@@ -489,11 +497,12 @@
       manifest_name: Manifest file to be reloaded.
       load_local_manifests: Whether to load local manifests.
       ssh_proxy: SSH manager for clients & masters.
+      manifest: The manifest to use.
 
     Returns:
       List of all projects that should be checked out.
     """
-    rp = self.manifest.repoProject
+    rp = manifest.repoProject
 
     to_fetch = []
     now = time.time()
@@ -517,7 +526,7 @@
     # Iteratively fetch missing and/or nested unregistered submodules
     previously_missing_set = set()
     while True:
-      self._ReloadManifest(manifest_name, load_local_manifests)
+      self._ReloadManifest(manifest_name, self.manifest, load_local_manifests)
       all_projects = self.GetProjects(args,
                                       missing_ok=True,
                                       submodules_ok=opt.fetch_submodules)
@@ -552,7 +561,7 @@
       Whether the fetch was successful.
     """
     start = time.time()
-    syncbuf = SyncBuffer(self.manifest.manifestProject.config,
+    syncbuf = SyncBuffer(project.manifest.manifestProject.config,
                          detach_head=detach_head)
     success = False
     try:
@@ -689,28 +698,29 @@
       t.join()
     pm.end()
 
-  def _ReloadManifest(self, manifest_name=None, load_local_manifests=True):
+  def _ReloadManifest(self, manifest_name, manifest, load_local_manifests=True):
     """Reload the manfiest from the file specified by the |manifest_name|.
 
     It unloads the manifest if |manifest_name| is None.
 
     Args:
       manifest_name: Manifest file to be reloaded.
+      manifest: The manifest to use.
       load_local_manifests: Whether to load local manifests.
     """
     if manifest_name:
       # Override calls Unload already
-      self.manifest.Override(manifest_name, load_local_manifests=load_local_manifests)
+      manifest.Override(manifest_name, load_local_manifests=load_local_manifests)
     else:
-      self.manifest.Unload()
+      manifest.Unload()
 
-  def UpdateProjectList(self, opt):
+  def UpdateProjectList(self, opt, manifest):
     new_project_paths = []
     for project in self.GetProjects(None, missing_ok=True):
       if project.relpath:
         new_project_paths.append(project.relpath)
     file_name = 'project.list'
-    file_path = os.path.join(self.manifest.subdir, file_name)
+    file_path = os.path.join(manifest.subdir, file_name)
     old_project_paths = []
 
     if os.path.exists(file_path):
@@ -722,16 +732,16 @@
           continue
         if path not in new_project_paths:
           # If the path has already been deleted, we don't need to do it
-          gitdir = os.path.join(self.manifest.topdir, path, '.git')
+          gitdir = os.path.join(manifest.topdir, path, '.git')
           if os.path.exists(gitdir):
             project = Project(
-                manifest=self.manifest,
+                manifest=manifest,
                 name=path,
                 remote=RemoteSpec('origin'),
                 gitdir=gitdir,
                 objdir=gitdir,
                 use_git_worktrees=os.path.isfile(gitdir),
-                worktree=os.path.join(self.manifest.topdir, path),
+                worktree=os.path.join(manifest.topdir, path),
                 relpath=path,
                 revisionExpr='HEAD',
                 revisionId=None,
@@ -747,7 +757,7 @@
       fd.write('\n')
     return 0
 
-  def UpdateCopyLinkfileList(self):
+  def UpdateCopyLinkfileList(self, manifest):
     """Save all dests of copyfile and linkfile, and update them if needed.
 
     Returns:
@@ -766,7 +776,7 @@
     }
 
     copylinkfile_name = 'copy-link-files.json'
-    copylinkfile_path = os.path.join(self.manifest.subdir, copylinkfile_name)
+    copylinkfile_path = os.path.join(manifest.subdir, copylinkfile_name)
     old_copylinkfile_paths = {}
 
     if os.path.exists(copylinkfile_path):
@@ -797,13 +807,13 @@
       json.dump(new_paths, fp)
     return True
 
-  def _SmartSyncSetup(self, opt, smart_sync_manifest_path):
-    if not self.manifest.manifest_server:
+  def _SmartSyncSetup(self, opt, smart_sync_manifest_path, manifest):
+    if not manifest.manifest_server:
       print('error: cannot smart sync: no manifest server defined in '
             'manifest', file=sys.stderr)
       sys.exit(1)
 
-    manifest_server = self.manifest.manifest_server
+    manifest_server = manifest.manifest_server
     if not opt.quiet:
       print('Using manifest server %s' % manifest_server)
 
@@ -844,7 +854,7 @@
     try:
       server = xmlrpc.client.Server(manifest_server, transport=transport)
       if opt.smart_sync:
-        branch = self._GetBranch()
+        branch = self._GetBranch(manifest.manifestProject)
 
         if 'SYNC_TARGET' in os.environ:
           target = os.environ['SYNC_TARGET']
@@ -870,18 +880,18 @@
                 % (smart_sync_manifest_path, e),
                 file=sys.stderr)
           sys.exit(1)
-        self._ReloadManifest(manifest_name)
+        self._ReloadManifest(manifest_name, manifest)
       else:
         print('error: manifest server RPC call failed: %s' %
               manifest_str, file=sys.stderr)
         sys.exit(1)
     except (socket.error, IOError, xmlrpc.client.Fault) as e:
       print('error: cannot connect to manifest server %s:\n%s'
-            % (self.manifest.manifest_server, e), file=sys.stderr)
+            % (manifest.manifest_server, e), file=sys.stderr)
       sys.exit(1)
     except xmlrpc.client.ProtocolError as e:
       print('error: cannot connect to manifest server %s:\n%d %s'
-            % (self.manifest.manifest_server, e.errcode, e.errmsg),
+            % (manifest.manifest_server, e.errcode, e.errmsg),
             file=sys.stderr)
       sys.exit(1)
 
@@ -892,14 +902,14 @@
     if not opt.local_only:
       start = time.time()
       success = mp.Sync_NetworkHalf(quiet=opt.quiet, verbose=opt.verbose,
-                                    current_branch_only=self._GetCurrentBranchOnly(opt),
+                                    current_branch_only=self._GetCurrentBranchOnly(opt, mp.manifest),
                                     force_sync=opt.force_sync,
                                     tags=opt.tags,
                                     optimized_fetch=opt.optimized_fetch,
                                     retry_fetches=opt.retry_fetches,
-                                    submodules=self.manifest.HasSubmodules,
-                                    clone_filter=self.manifest.CloneFilter,
-                                    partial_clone_exclude=self.manifest.PartialCloneExclude)
+                                    submodules=mp.manifest.HasSubmodules,
+                                    clone_filter=mp.manifest.CloneFilter,
+                                    partial_clone_exclude=mp.manifest.PartialCloneExclude)
       finish = time.time()
       self.event_log.AddSync(mp, event_log.TASK_SYNC_NETWORK,
                              start, finish, success)
@@ -907,15 +917,15 @@
     if mp.HasChanges:
       syncbuf = SyncBuffer(mp.config)
       start = time.time()
-      mp.Sync_LocalHalf(syncbuf, submodules=self.manifest.HasSubmodules)
+      mp.Sync_LocalHalf(syncbuf, submodules=mp.manifest.HasSubmodules)
       clean = syncbuf.Finish()
       self.event_log.AddSync(mp, event_log.TASK_SYNC_LOCAL,
                              start, time.time(), clean)
       if not clean:
         sys.exit(1)
-      self._ReloadManifest(manifest_name)
+      self._ReloadManifest(manifest_name, mp.manifest)
       if opt.jobs is None:
-        self.jobs = self.manifest.default.sync_j
+        self.jobs = mp.manifest.default.sync_j
 
   def ValidateOptions(self, opt, args):
     if opt.force_broken:
@@ -938,7 +948,7 @@
     if opt.prune is None:
       opt.prune = True
 
-    if self.manifest.is_multimanifest and not opt.this_manifest_only and args:
+    if self.outer_client.manifest.is_multimanifest and not opt.this_manifest_only and args:
       self.OptionParser.error('partial syncs must use --this-manifest-only')
 
   def Execute(self, opt, args):
@@ -948,18 +958,22 @@
       soft_limit, _ = _rlimit_nofile()
       self.jobs = min(self.jobs, (soft_limit - 5) // 3)
 
+    manifest = self.outer_manifest
+    if opt.this_manifest_only or not opt.outer_manifest:
+      manifest = self.manifest
+
     if opt.manifest_name:
-      self.manifest.Override(opt.manifest_name)
+      manifest.Override(opt.manifest_name)
 
     manifest_name = opt.manifest_name
     smart_sync_manifest_path = os.path.join(
-        self.manifest.manifestProject.worktree, 'smart_sync_override.xml')
+        manifest.manifestProject.worktree, 'smart_sync_override.xml')
 
     if opt.clone_bundle is None:
-      opt.clone_bundle = self.manifest.CloneBundle
+      opt.clone_bundle = manifest.CloneBundle
 
     if opt.smart_sync or opt.smart_tag:
-      manifest_name = self._SmartSyncSetup(opt, smart_sync_manifest_path)
+      manifest_name = self._SmartSyncSetup(opt, smart_sync_manifest_path, manifest)
     else:
       if os.path.isfile(smart_sync_manifest_path):
         try:
@@ -970,7 +984,7 @@
 
     err_event = multiprocessing.Event()
 
-    rp = self.manifest.repoProject
+    rp = manifest.repoProject
     rp.PreSync()
     cb = rp.CurrentBranch
     if cb:
@@ -980,35 +994,35 @@
               'receive updates; run `repo init --repo-rev=stable` to fix.',
               file=sys.stderr)
 
-    mp = self.manifest.manifestProject
+    mp = manifest.manifestProject
     is_standalone_manifest = bool(mp.standalone_manifest_url)
     if not is_standalone_manifest:
       mp.PreSync()
 
     if opt.repo_upgraded:
-      _PostRepoUpgrade(self.manifest, quiet=opt.quiet)
+      _PostRepoUpgrade(manifest, quiet=opt.quiet)
 
     if not opt.mp_update:
       print('Skipping update of local manifest project.')
     elif not is_standalone_manifest:
       self._UpdateManifestProject(opt, mp, manifest_name)
 
-    load_local_manifests = not self.manifest.HasLocalManifests
-    use_superproject = git_superproject.UseSuperproject(opt.use_superproject,
-                                                        self.manifest)
-    if use_superproject and (self.manifest.IsMirror or self.manifest.IsArchive):
+    load_local_manifests = not manifest.HasLocalManifests
+    use_superproject = git_superproject.UseSuperproject(opt.use_superproject, manifest)
+    if use_superproject and (manifest.IsMirror or manifest.IsArchive):
       # Don't use superproject, because we have no working tree.
       use_superproject = False
       if opt.use_superproject is not None:
         print('Defaulting to no-use-superproject because there is no working tree.')
     superproject_logging_data = {
         'superproject': use_superproject,
-        'haslocalmanifests': bool(self.manifest.HasLocalManifests),
-        'hassuperprojecttag': bool(self.manifest.superproject),
+        'haslocalmanifests': bool(manifest.HasLocalManifests),
+        'hassuperprojecttag': bool(manifest.superproject),
     }
     if use_superproject:
       manifest_name = self._UpdateProjectsRevisionId(
-          opt, args, load_local_manifests, superproject_logging_data) or opt.manifest_name
+          opt, args, load_local_manifests, superproject_logging_data,
+          manifest) or opt.manifest_name
 
     if self.gitc_manifest:
       gitc_manifest_projects = self.GetProjects(args,
@@ -1031,7 +1045,7 @@
         if manifest_name:
           manifest.Override(manifest_name)
         else:
-          manifest.Override(self.manifest.manifestFile)
+          manifest.Override(manifest.manifestFile)
         gitc_utils.generate_gitc_manifest(self.gitc_manifest,
                                           manifest,
                                           gitc_projects)
@@ -1041,7 +1055,7 @@
       # generate a new args list to represent the opened projects.
       # TODO: make this more reliable -- if there's a project name/path overlap,
       # this may choose the wrong project.
-      args = [os.path.relpath(self.manifest.paths[path].worktree, os.getcwd())
+      args = [os.path.relpath(manifest.paths[path].worktree, os.getcwd())
               for path in opened_projects]
       if not args:
         return
@@ -1052,7 +1066,7 @@
     err_network_sync = False
     err_update_projects = False
 
-    self._fetch_times = _FetchTimes(self.manifest)
+    self._fetch_times = _FetchTimes(manifest)
     if not opt.local_only:
       with multiprocessing.Manager() as manager:
         with ssh.ProxyManager(manager) as ssh_proxy:
@@ -1060,7 +1074,7 @@
           ssh_proxy.sock()
           all_projects = self._FetchMain(opt, args, all_projects, err_event,
                                          manifest_name, load_local_manifests,
-                                         ssh_proxy)
+                                         ssh_proxy, manifest)
 
       if opt.network_only:
         return
@@ -1076,18 +1090,18 @@
                 file=sys.stderr)
           sys.exit(1)
 
-    if self.manifest.IsMirror or self.manifest.IsArchive:
+    if manifest.IsMirror or manifest.IsArchive:
       # bail out now, we have no working tree
       return
 
-    if self.UpdateProjectList(opt):
+    if self.UpdateProjectList(opt, manifest):
       err_event.set()
       err_update_projects = True
       if opt.fail_fast:
         print('\nerror: Local checkouts *not* updated.', file=sys.stderr)
         sys.exit(1)
 
-    err_update_linkfiles = not self.UpdateCopyLinkfileList()
+    err_update_linkfiles = not self.UpdateCopyLinkfileList(manifest)
     if err_update_linkfiles:
       err_event.set()
       if opt.fail_fast:
@@ -1102,8 +1116,8 @@
 
     # If there's a notice that's supposed to print at the end of the sync, print
     # it now...
-    if self.manifest.notice:
-      print(self.manifest.notice)
+    if manifest.notice:
+      print(manifest.notice)
 
     # If we saw an error, exit with code 1 so that other scripts can check.
     if err_event.is_set():
diff --git a/tests/test_subcmds_sync.py b/tests/test_subcmds_sync.py
index c1d1758..aad713f 100644
--- a/tests/test_subcmds_sync.py
+++ b/tests/test_subcmds_sync.py
@@ -42,4 +42,4 @@
   opts, _ = cmd.OptionParser.parse_args(cli_args)
 
   with mock.patch('git_superproject.UseSuperproject', return_value=use_superproject):
-    assert cmd._GetCurrentBranchOnly(opts) == result
+    assert cmd._GetCurrentBranchOnly(opts, cmd.manifest) == result