# Copyright (C) 2024 The Android Open Source Project # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, software # distributed under the License is distributed on an "AS IS" BASIS, # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # See the License for the specific language governing permissions and # limitations under the License. import os from typing import Set from command import Command import platform_utils from progress import Progress class Gc(Command): COMMON = True helpSummary = "Cleaning up internal repo state." helpUsage = """ %prog """ def _Options(self, p): p.add_option( "-n", "--dry-run", dest="dryrun", default=False, action="store_true", help="do everything except actually delete", ) p.add_option( "-y", "--yes", default=False, action="store_true", help="answer yes to all safe prompts", ) def _find_git_to_delete( self, to_keep: Set[str], start_dir: str ) -> Set[str]: """Searches no longer needed ".git" directories. Scans the file system starting from `start_dir` and removes all directories that end with ".git" that are not in the `to_keep` set. """ to_delete = set() for root, dirs, _ in platform_utils.walk(start_dir): for directory in dirs: if not directory.endswith(".git"): continue path = os.path.join(root, directory) if path not in to_keep: to_delete.add(path) return to_delete def Execute(self, opt, args): projects = self.GetProjects( args, all_manifests=not opt.this_manifest_only ) print(f"Scanning filesystem under {self.repodir}...") project_paths = set() project_object_paths = set() for project in projects: project_paths.add(project.gitdir) project_object_paths.add(project.objdir) to_delete = self._find_git_to_delete( project_paths, os.path.join(self.repodir, "projects") ) to_delete.update( self._find_git_to_delete( project_object_paths, os.path.join(self.repodir, "project-objects"), ) ) if not to_delete: print("Nothing to clean up.") return print("Identified the following projects are no longer used:") print("\n".join(to_delete)) print("\n") if not opt.yes: print( "If you proceed, any local commits in those projects will be " "destroyed!" ) ask = input("Proceed? [y/N] ") if ask.lower() != "y": return 1 pm = Progress( "Deleting", len(to_delete), delay=False, quiet=opt.quiet, show_elapsed=True, elide=True, ) for path in to_delete: if opt.dryrun: print(f"\nWould have deleted ${path}") else: tmp_path = os.path.join( os.path.dirname(path), f"to_be_deleted_{os.path.basename(path)}", ) platform_utils.rename(path, tmp_path) platform_utils.rmtree(tmp_path) pm.update(msg=path) pm.end()