summaryrefslogtreecommitdiffstats
path: root/subcmds/gc.py
blob: f12f56f101607973ad3e0ed21f4d477000c8cc66 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
# 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()