diff options
-rw-r--r-- | man/repo-gc.1 | 43 | ||||
-rw-r--r-- | man/repo-manifest.1 | 20 | ||||
-rw-r--r-- | man/repo.1 | 5 | ||||
-rw-r--r-- | subcmds/gc.py | 127 |
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 | ||
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. | ||
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 |
4 | repo \- repo manifest - manual page for repo manifest | 4 | repo \- 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`. | |||
495 | Attribute `upstream`: If specified, overrides the upstream of the original | 497 | Attribute `upstream`: If specified, overrides the upstream of the original |
496 | project. Same syntax as the corresponding element of `project`. | 498 | project. Same syntax as the corresponding element of `project`. |
497 | .PP | 499 | .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 | ||
498 | Element annotation | 508 | Element annotation |
499 | .PP | 509 | .PP |
500 | Zero or more annotation elements may be specified as children of a project or | 510 | 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. | |||
556 | Attribute `optional`: Set to true to ignore remove\-project elements with no | 566 | Attribute `optional`: Set to true to ignore remove\-project elements with no |
557 | matching `project` element. | 567 | matching `project` element. |
558 | .PP | 568 | .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 | ||
559 | Element repo\-hooks | 577 | Element repo\-hooks |
560 | .PP | 578 | .PP |
561 | NB: See the [practical documentation](./repo\-hooks.md) for using repo hooks. | 579 | NB: See the [practical documentation](./repo\-hooks.md) for using repo hooks. |
@@ -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 |
4 | repo \- repository management tool built on top of git | 4 | repo \- repository management tool built on top of git |
5 | .SH SYNOPSIS | 5 | .SH SYNOPSIS |
@@ -79,6 +79,9 @@ Download and checkout a change | |||
79 | forall | 79 | forall |
80 | Run a shell command in each project | 80 | Run a shell command in each project |
81 | .TP | 81 | .TP |
82 | gc | ||
83 | Cleaning up internal repo state. | ||
84 | .TP | ||
82 | grep | 85 | grep |
83 | Print lines matching a pattern | 86 | Print 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 | |||
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() | ||