diff options
Diffstat (limited to 'subcmds/gc.py')
-rw-r--r-- | subcmds/gc.py | 127 |
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 | |||
15 | import os | ||
16 | from typing import Set | ||
17 | |||
18 | from command import Command | ||
19 | import platform_utils | ||
20 | from progress import Progress | ||
21 | |||
22 | |||
23 | class 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() | ||