mirror of
https://gerrit.googlesource.com/git-repo
synced 2024-12-21 07:16:21 +00:00
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 <vapier@google.com> Commit-Queue: Josip Sokcevic <sokcevic@chromium.org> Reviewed-by: Gavin Mak <gavinmak@google.com> Tested-by: Josip Sokcevic <sokcevic@chromium.org>
This commit is contained in:
parent
9500aca754
commit
13d6588bf6
43
man/repo-gc.1
Normal file
43
man/repo-gc.1
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
|
||||||
|
.TH REPO "1" "December 2024" "repo gc" "Repo Manual"
|
||||||
|
.SH NAME
|
||||||
|
repo \- repo gc - manual page for repo gc
|
||||||
|
.SH SYNOPSIS
|
||||||
|
.B repo
|
||||||
|
\fI\,gc\/\fR
|
||||||
|
.SH DESCRIPTION
|
||||||
|
Summary
|
||||||
|
.PP
|
||||||
|
Cleaning up internal repo state.
|
||||||
|
.SH OPTIONS
|
||||||
|
.TP
|
||||||
|
\fB\-h\fR, \fB\-\-help\fR
|
||||||
|
show this help message and exit
|
||||||
|
.TP
|
||||||
|
\fB\-n\fR, \fB\-\-dry\-run\fR
|
||||||
|
do everything except actually delete
|
||||||
|
.TP
|
||||||
|
\fB\-y\fR, \fB\-\-yes\fR
|
||||||
|
answer yes to all safe prompts
|
||||||
|
.SS Logging options:
|
||||||
|
.TP
|
||||||
|
\fB\-v\fR, \fB\-\-verbose\fR
|
||||||
|
show all output
|
||||||
|
.TP
|
||||||
|
\fB\-q\fR, \fB\-\-quiet\fR
|
||||||
|
only show errors
|
||||||
|
.SS Multi\-manifest options:
|
||||||
|
.TP
|
||||||
|
\fB\-\-outer\-manifest\fR
|
||||||
|
operate starting at the outermost manifest
|
||||||
|
.TP
|
||||||
|
\fB\-\-no\-outer\-manifest\fR
|
||||||
|
do not operate on outer manifests
|
||||||
|
.TP
|
||||||
|
\fB\-\-this\-manifest\-only\fR
|
||||||
|
only operate on this (sub)manifest
|
||||||
|
.TP
|
||||||
|
\fB\-\-no\-this\-manifest\-only\fR, \fB\-\-all\-manifests\fR
|
||||||
|
operate on this manifest and its submanifests
|
||||||
|
.PP
|
||||||
|
Run `repo help gc` to view the detailed manual.
|
@ -1,5 +1,5 @@
|
|||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
|
||||||
.TH REPO "1" "April 2024" "repo manifest" "Repo Manual"
|
.TH REPO "1" "December 2024" "repo manifest" "Repo Manual"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
repo \- repo manifest - manual page for repo manifest
|
repo \- repo manifest - manual page for repo manifest
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
@ -192,11 +192,13 @@ CDATA #IMPLIED>
|
|||||||
<!ATTLIST extend\-project remote CDATA #IMPLIED>
|
<!ATTLIST extend\-project remote CDATA #IMPLIED>
|
||||||
<!ATTLIST extend\-project dest\-branch CDATA #IMPLIED>
|
<!ATTLIST extend\-project dest\-branch CDATA #IMPLIED>
|
||||||
<!ATTLIST extend\-project upstream CDATA #IMPLIED>
|
<!ATTLIST extend\-project upstream CDATA #IMPLIED>
|
||||||
|
<!ATTLIST extend\-project base\-rev CDATA #IMPLIED>
|
||||||
.IP
|
.IP
|
||||||
<!ELEMENT remove\-project EMPTY>
|
<!ELEMENT remove\-project EMPTY>
|
||||||
<!ATTLIST remove\-project name CDATA #IMPLIED>
|
<!ATTLIST remove\-project name CDATA #IMPLIED>
|
||||||
<!ATTLIST remove\-project path CDATA #IMPLIED>
|
<!ATTLIST remove\-project path CDATA #IMPLIED>
|
||||||
<!ATTLIST remove\-project optional CDATA #IMPLIED>
|
<!ATTLIST remove\-project optional CDATA #IMPLIED>
|
||||||
|
<!ATTLIST remove\-project base\-rev CDATA #IMPLIED>
|
||||||
.IP
|
.IP
|
||||||
<!ELEMENT repo\-hooks EMPTY>
|
<!ELEMENT repo\-hooks EMPTY>
|
||||||
<!ATTLIST repo\-hooks in\-project CDATA #REQUIRED>
|
<!ATTLIST repo\-hooks in\-project CDATA #REQUIRED>
|
||||||
@ -495,6 +497,14 @@ project. Same syntax as the corresponding element of `project`.
|
|||||||
Attribute `upstream`: If specified, overrides the upstream of the original
|
Attribute `upstream`: If specified, overrides the upstream of the original
|
||||||
project. Same syntax as the corresponding element of `project`.
|
project. Same syntax as the corresponding element of `project`.
|
||||||
.PP
|
.PP
|
||||||
|
Attribute `base\-rev`: If specified, adds a check against the revision to be
|
||||||
|
extended. Manifest parse will fail and give a list of mismatch extends if the
|
||||||
|
revisions being extended have changed since base\-rev was set. Intended for use
|
||||||
|
with layered manifests using hash revisions to prevent patch branches hiding
|
||||||
|
newer upstream revisions. Also compares named refs like branches or tags but is
|
||||||
|
misleading if branches are used as base\-rev. Same syntax as the corresponding
|
||||||
|
element of `project`.
|
||||||
|
.PP
|
||||||
Element annotation
|
Element annotation
|
||||||
.PP
|
.PP
|
||||||
Zero or more annotation elements may be specified as children of a project or
|
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.
|
|||||||
Attribute `optional`: Set to true to ignore remove\-project elements with no
|
Attribute `optional`: Set to true to ignore remove\-project elements with no
|
||||||
matching `project` element.
|
matching `project` element.
|
||||||
.PP
|
.PP
|
||||||
|
Attribute `base\-rev`: If specified, adds a check against the revision to be
|
||||||
|
removed. Manifest parse will fail and give a list of mismatch removes if the
|
||||||
|
revisions being removed have changed since base\-rev was set. Intended for use
|
||||||
|
with layered manifests using hash revisions to prevent patch branches hiding
|
||||||
|
newer upstream revisions. Also compares named refs like branches or tags but is
|
||||||
|
misleading if branches are used as base\-rev. Same syntax as the corresponding
|
||||||
|
element of `project`.
|
||||||
|
.PP
|
||||||
Element repo\-hooks
|
Element repo\-hooks
|
||||||
.PP
|
.PP
|
||||||
NB: See the [practical documentation](./repo\-hooks.md) for using repo hooks.
|
NB: See the [practical documentation](./repo\-hooks.md) for using repo hooks.
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
|
.\" DO NOT MODIFY THIS FILE! It was generated by help2man.
|
||||||
.TH REPO "1" "April 2024" "repo" "Repo Manual"
|
.TH REPO "1" "December 2024" "repo" "Repo Manual"
|
||||||
.SH NAME
|
.SH NAME
|
||||||
repo \- repository management tool built on top of git
|
repo \- repository management tool built on top of git
|
||||||
.SH SYNOPSIS
|
.SH SYNOPSIS
|
||||||
@ -79,6 +79,9 @@ Download and checkout a change
|
|||||||
forall
|
forall
|
||||||
Run a shell command in each project
|
Run a shell command in each project
|
||||||
.TP
|
.TP
|
||||||
|
gc
|
||||||
|
Cleaning up internal repo state.
|
||||||
|
.TP
|
||||||
grep
|
grep
|
||||||
Print lines matching a pattern
|
Print lines matching a pattern
|
||||||
.TP
|
.TP
|
||||||
|
127
subcmds/gc.py
Normal file
127
subcmds/gc.py
Normal file
@ -0,0 +1,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()
|
Loading…
Reference in New Issue
Block a user