Skip to content

Commit 13d6588

Browse files
sokcevicGLUCI
authored and
LUCI
committed
gc: Introduce new command to remove old projects
When projects are removed from manifest, they are only removed from worktree and not from .repo/projects and .repo/project-objects. Keeping data under .repo can be desired if user expects deleted projects to be restored (e.g. checking out a release branch). Android has ongoing effort to remove many stale projects and this change allows users to easily free-up their disk space. Bug: b/344018971 Bug: 40013312 Change-Id: Id23c7524a88082ee6db908f9fd69dcd5d0c4f681 Reviewed-on: https://gerrit-review.googlesource.com/c/git-repo/+/445921 Reviewed-by: Mike Frysinger <[email protected]> Commit-Queue: Josip Sokcevic <[email protected]> Reviewed-by: Gavin Mak <[email protected]> Tested-by: Josip Sokcevic <[email protected]>
1 parent 9500aca commit 13d6588

File tree

4 files changed

+193
-2
lines changed

4 files changed

+193
-2
lines changed

man/repo-gc.1

+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
2+
.TH REPO "1" "December 2024" "repo gc" "Repo Manual"
3+
.SH NAME
4+
repo \- repo gc - manual page for repo gc
5+
.SH SYNOPSIS
6+
.B repo
7+
\fI\,gc\/\fR
8+
.SH DESCRIPTION
9+
Summary
10+
.PP
11+
Cleaning up internal repo state.
12+
.SH OPTIONS
13+
.TP
14+
\fB\-h\fR, \fB\-\-help\fR
15+
show this help message and exit
16+
.TP
17+
\fB\-n\fR, \fB\-\-dry\-run\fR
18+
do everything except actually delete
19+
.TP
20+
\fB\-y\fR, \fB\-\-yes\fR
21+
answer yes to all safe prompts
22+
.SS Logging options:
23+
.TP
24+
\fB\-v\fR, \fB\-\-verbose\fR
25+
show all output
26+
.TP
27+
\fB\-q\fR, \fB\-\-quiet\fR
28+
only show errors
29+
.SS Multi\-manifest options:
30+
.TP
31+
\fB\-\-outer\-manifest\fR
32+
operate starting at the outermost manifest
33+
.TP
34+
\fB\-\-no\-outer\-manifest\fR
35+
do not operate on outer manifests
36+
.TP
37+
\fB\-\-this\-manifest\-only\fR
38+
only operate on this (sub)manifest
39+
.TP
40+
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
41+
operate on this manifest and its submanifests
42+
.PP
43+
Run `repo help gc` to view the detailed manual.

man/repo-manifest.1

+19-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
2-
.TH REPO "1" "April 2024" "repo manifest" "Repo Manual"
2+
.TH REPO "1" "December 2024" "repo manifest" "Repo Manual"
33
.SH NAME
44
repo \- repo manifest - manual page for repo manifest
55
.SH SYNOPSIS
@@ -192,11 +192,13 @@ CDATA #IMPLIED>
192192
<!ATTLIST extend\-project remote CDATA #IMPLIED>
193193
<!ATTLIST extend\-project dest\-branch CDATA #IMPLIED>
194194
<!ATTLIST extend\-project upstream CDATA #IMPLIED>
195+
<!ATTLIST extend\-project base\-rev CDATA #IMPLIED>
195196
.IP
196197
<!ELEMENT remove\-project EMPTY>
197198
<!ATTLIST remove\-project name CDATA #IMPLIED>
198199
<!ATTLIST remove\-project path CDATA #IMPLIED>
199200
<!ATTLIST remove\-project optional CDATA #IMPLIED>
201+
<!ATTLIST remove\-project base\-rev CDATA #IMPLIED>
200202
.IP
201203
<!ELEMENT repo\-hooks EMPTY>
202204
<!ATTLIST repo\-hooks in\-project CDATA #REQUIRED>
@@ -495,6 +497,14 @@ project. Same syntax as the corresponding element of `project`.
495497
Attribute `upstream`: If specified, overrides the upstream of the original
496498
project. Same syntax as the corresponding element of `project`.
497499
.PP
500+
Attribute `base\-rev`: If specified, adds a check against the revision to be
501+
extended. Manifest parse will fail and give a list of mismatch extends if the
502+
revisions being extended have changed since base\-rev was set. Intended for use
503+
with layered manifests using hash revisions to prevent patch branches hiding
504+
newer upstream revisions. Also compares named refs like branches or tags but is
505+
misleading if branches are used as base\-rev. Same syntax as the corresponding
506+
element of `project`.
507+
.PP
498508
Element annotation
499509
.PP
500510
Zero or more annotation elements may be specified as children of a project or
@@ -556,6 +566,14 @@ Logic otherwise behaves like both are specified.
556566
Attribute `optional`: Set to true to ignore remove\-project elements with no
557567
matching `project` element.
558568
.PP
569+
Attribute `base\-rev`: If specified, adds a check against the revision to be
570+
removed. Manifest parse will fail and give a list of mismatch removes if the
571+
revisions being removed have changed since base\-rev was set. Intended for use
572+
with layered manifests using hash revisions to prevent patch branches hiding
573+
newer upstream revisions. Also compares named refs like branches or tags but is
574+
misleading if branches are used as base\-rev. Same syntax as the corresponding
575+
element of `project`.
576+
.PP
559577
Element repo\-hooks
560578
.PP
561579
NB: See the [practical documentation](./repo\-hooks.md) for using repo hooks.

man/repo.1

+4-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
2-
.TH REPO "1" "April 2024" "repo" "Repo Manual"
2+
.TH REPO "1" "December 2024" "repo" "Repo Manual"
33
.SH NAME
44
repo \- repository management tool built on top of git
55
.SH SYNOPSIS
@@ -79,6 +79,9 @@ Download and checkout a change
7979
forall
8080
Run a shell command in each project
8181
.TP
82+
gc
83+
Cleaning up internal repo state.
84+
.TP
8285
grep
8386
Print lines matching a pattern
8487
.TP

subcmds/gc.py

+127
Original file line numberDiff line numberDiff line change
@@ -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()

0 commit comments

Comments
 (0)