summaryrefslogtreecommitdiffstats
path: root/subcmds/gc.py
diff options
context:
space:
mode:
Diffstat (limited to 'subcmds/gc.py')
-rw-r--r--subcmds/gc.py127
1 files changed, 127 insertions, 0 deletions
diff --git a/subcmds/gc.py b/subcmds/gc.py
new file mode 100644
index 00000000..f12f56f1
--- /dev/null
+++ b/subcmds/gc.py
@@ -0,0 +1,127 @@
1# Copyright (C) 2024 The Android Open Source Project
2#
3# Licensed under the Apache License, Version 2.0 (the "License");
4# you may not use this file except in compliance with the License.
5# You may obtain a copy of the License at
6#
7# http://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS,
11# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12# See the License for the specific language governing permissions and
13# limitations under the License.
14
15import os
16from typing import Set
17
18from command import Command
19import platform_utils
20from progress import Progress
21
22
23class Gc(Command):
24 COMMON = True
25 helpSummary = "Cleaning up internal repo state."
26 helpUsage = """
27%prog
28"""
29
30 def _Options(self, p):
31 p.add_option(
32 "-n",
33 "--dry-run",
34 dest="dryrun",
35 default=False,
36 action="store_true",
37 help="do everything except actually delete",
38 )
39 p.add_option(
40 "-y",
41 "--yes",
42 default=False,
43 action="store_true",
44 help="answer yes to all safe prompts",
45 )
46
47 def _find_git_to_delete(
48 self, to_keep: Set[str], start_dir: str
49 ) -> Set[str]:
50 """Searches no longer needed ".git" directories.
51
52 Scans the file system starting from `start_dir` and removes all
53 directories that end with ".git" that are not in the `to_keep` set.
54 """
55 to_delete = set()
56 for root, dirs, _ in platform_utils.walk(start_dir):
57 for directory in dirs:
58 if not directory.endswith(".git"):
59 continue
60
61 path = os.path.join(root, directory)
62 if path not in to_keep:
63 to_delete.add(path)
64
65 return to_delete
66
67 def Execute(self, opt, args):
68 projects = self.GetProjects(
69 args, all_manifests=not opt.this_manifest_only
70 )
71 print(f"Scanning filesystem under {self.repodir}...")
72
73 project_paths = set()
74 project_object_paths = set()
75
76 for project in projects:
77 project_paths.add(project.gitdir)
78 project_object_paths.add(project.objdir)
79
80 to_delete = self._find_git_to_delete(
81 project_paths, os.path.join(self.repodir, "projects")
82 )
83
84 to_delete.update(
85 self._find_git_to_delete(
86 project_object_paths,
87 os.path.join(self.repodir, "project-objects"),
88 )
89 )
90
91 if not to_delete:
92 print("Nothing to clean up.")
93 return
94
95 print("Identified the following projects are no longer used:")
96 print("\n".join(to_delete))
97 print("\n")
98 if not opt.yes:
99 print(
100 "If you proceed, any local commits in those projects will be "
101 "destroyed!"
102 )
103 ask = input("Proceed? [y/N] ")
104 if ask.lower() != "y":
105 return 1
106
107 pm = Progress(
108 "Deleting",
109 len(to_delete),
110 delay=False,
111 quiet=opt.quiet,
112 show_elapsed=True,
113 elide=True,
114 )
115
116 for path in to_delete:
117 if opt.dryrun:
118 print(f"\nWould have deleted ${path}")
119 else:
120 tmp_path = os.path.join(
121 os.path.dirname(path),
122 f"to_be_deleted_{os.path.basename(path)}",
123 )
124 platform_utils.rename(path, tmp_path)
125 platform_utils.rmtree(tmp_path)
126 pm.update(msg=path)
127 pm.end()