summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJosip Sokcevic <sokcevic@chromium.org>2024-12-16 22:30:07 +0000
committerLUCI <gerrit-scoped@luci-project-accounts.iam.gserviceaccount.com>2024-12-18 09:23:49 -0800
commit13d6588bf60f0980ffa3d178441fa707655fee95 (patch)
tree38d1bd9e2ad3988739576d82d4d6a62ffd69364b
parent9500aca754058bff18ddf35db62852ca4f722c63 (diff)
downloadgit-repo-13d6588bf60f0980ffa3d178441fa707655fee95.tar.gz
gc: Introduce new command to remove old projectsv2.50.1
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 <vapier@google.com> Commit-Queue: Josip Sokcevic <sokcevic@chromium.org> Reviewed-by: Gavin Mak <gavinmak@google.com> Tested-by: Josip Sokcevic <sokcevic@chromium.org>
-rw-r--r--man/repo-gc.143
-rw-r--r--man/repo-manifest.120
-rw-r--r--man/repo.15
-rw-r--r--subcmds/gc.py127
4 files changed, 193 insertions, 2 deletions
diff --git a/man/repo-gc.1 b/man/repo-gc.1
new file mode 100644
index 00000000..e465a253
--- /dev/null
+++ b/man/repo-gc.1
@@ -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
4repo \- repo gc - manual page for repo gc
5.SH SYNOPSIS
6.B repo
7\fI\,gc\/\fR
8.SH DESCRIPTION
9Summary
10.PP
11Cleaning up internal repo state.
12.SH OPTIONS
13.TP
14\fB\-h\fR, \fB\-\-help\fR
15show this help message and exit
16.TP
17\fB\-n\fR, \fB\-\-dry\-run\fR
18do everything except actually delete
19.TP
20\fB\-y\fR, \fB\-\-yes\fR
21answer yes to all safe prompts
22.SS Logging options:
23.TP
24\fB\-v\fR, \fB\-\-verbose\fR
25show all output
26.TP
27\fB\-q\fR, \fB\-\-quiet\fR
28only show errors
29.SS Multi\-manifest options:
30.TP
31\fB\-\-outer\-manifest\fR
32operate starting at the outermost manifest
33.TP
34\fB\-\-no\-outer\-manifest\fR
35do not operate on outer manifests
36.TP
37\fB\-\-this\-manifest\-only\fR
38only operate on this (sub)manifest
39.TP
40\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
41operate on this manifest and its submanifests
42.PP
43Run `repo help gc` to view the detailed manual.
diff --git a/man/repo-manifest.1 b/man/repo-manifest.1
index 10ec2e75..2ee23e64 100644
--- a/man/repo-manifest.1
+++ b/man/repo-manifest.1
@@ -1,5 +1,5 @@
1.\" DO NOT MODIFY THIS FILE! It was generated by help2man. 1.\" 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"
3.SH NAME 3.SH NAME
4repo \- repo manifest - manual page for repo manifest 4repo \- repo manifest - manual page for repo manifest
5.SH SYNOPSIS 5.SH SYNOPSIS
@@ -192,11 +192,13 @@ CDATA #IMPLIED>
192<!ATTLIST extend\-project remote CDATA #IMPLIED> 192<!ATTLIST extend\-project remote CDATA #IMPLIED>
193<!ATTLIST extend\-project dest\-branch CDATA #IMPLIED> 193<!ATTLIST extend\-project dest\-branch CDATA #IMPLIED>
194<!ATTLIST extend\-project upstream CDATA #IMPLIED> 194<!ATTLIST extend\-project upstream CDATA #IMPLIED>
195<!ATTLIST extend\-project base\-rev CDATA #IMPLIED>
195.IP 196.IP
196<!ELEMENT remove\-project EMPTY> 197<!ELEMENT remove\-project EMPTY>
197<!ATTLIST remove\-project name CDATA #IMPLIED> 198<!ATTLIST remove\-project name CDATA #IMPLIED>
198<!ATTLIST remove\-project path CDATA #IMPLIED> 199<!ATTLIST remove\-project path CDATA #IMPLIED>
199<!ATTLIST remove\-project optional CDATA #IMPLIED> 200<!ATTLIST remove\-project optional CDATA #IMPLIED>
201<!ATTLIST remove\-project base\-rev CDATA #IMPLIED>
200.IP 202.IP
201<!ELEMENT repo\-hooks EMPTY> 203<!ELEMENT repo\-hooks EMPTY>
202<!ATTLIST repo\-hooks in\-project CDATA #REQUIRED> 204<!ATTLIST repo\-hooks in\-project CDATA #REQUIRED>
@@ -495,6 +497,14 @@ project. Same syntax as the corresponding element of `project`.
495Attribute `upstream`: If specified, overrides the upstream of the original 497Attribute `upstream`: If specified, overrides the upstream of the original
496project. Same syntax as the corresponding element of `project`. 498project. Same syntax as the corresponding element of `project`.
497.PP 499.PP
500Attribute `base\-rev`: If specified, adds a check against the revision to be
501extended. Manifest parse will fail and give a list of mismatch extends if the
502revisions being extended have changed since base\-rev was set. Intended for use
503with layered manifests using hash revisions to prevent patch branches hiding
504newer upstream revisions. Also compares named refs like branches or tags but is
505misleading if branches are used as base\-rev. Same syntax as the corresponding
506element of `project`.
507.PP
498Element annotation 508Element annotation
499.PP 509.PP
500Zero or more annotation elements may be specified as children of a project or 510Zero or more annotation elements may be specified as children of a project or
@@ -556,6 +566,14 @@ Logic otherwise behaves like both are specified.
556Attribute `optional`: Set to true to ignore remove\-project elements with no 566Attribute `optional`: Set to true to ignore remove\-project elements with no
557matching `project` element. 567matching `project` element.
558.PP 568.PP
569Attribute `base\-rev`: If specified, adds a check against the revision to be
570removed. Manifest parse will fail and give a list of mismatch removes if the
571revisions being removed have changed since base\-rev was set. Intended for use
572with layered manifests using hash revisions to prevent patch branches hiding
573newer upstream revisions. Also compares named refs like branches or tags but is
574misleading if branches are used as base\-rev. Same syntax as the corresponding
575element of `project`.
576.PP
559Element repo\-hooks 577Element repo\-hooks
560.PP 578.PP
561NB: See the [practical documentation](./repo\-hooks.md) for using repo hooks. 579NB: See the [practical documentation](./repo\-hooks.md) for using repo hooks.
diff --git a/man/repo.1 b/man/repo.1
index bda68c39..1c05dcfc 100644
--- a/man/repo.1
+++ b/man/repo.1
@@ -1,5 +1,5 @@
1.\" DO NOT MODIFY THIS FILE! It was generated by help2man. 1.\" 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"
3.SH NAME 3.SH NAME
4repo \- repository management tool built on top of git 4repo \- repository management tool built on top of git
5.SH SYNOPSIS 5.SH SYNOPSIS
@@ -79,6 +79,9 @@ Download and checkout a change
79forall 79forall
80Run a shell command in each project 80Run a shell command in each project
81.TP 81.TP
82gc
83Cleaning up internal repo state.
84.TP
82grep 85grep
83Print lines matching a pattern 86Print lines matching a pattern
84.TP 87.TP
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()