@osdlc/repo-cleaner 0.19.0 → 0.20.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/README.md +18 -11
- package/dist/reference.js +2 -1
- package/dist/resolve.d.ts +4 -3
- package/dist/resolve.js +4 -3
- package/dist/sweep.d.ts +3 -1
- package/dist/sweep.js +33 -9
- package/dist/version.generated.d.ts +1 -1
- package/dist/version.generated.js +1 -1
- package/package.json +4 -4
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @osdlc/repo-cleaner
|
|
2
2
|
|
|
3
|
+
## 0.20.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- 9171850: Run with no target from a subdirectory of a checkout, `repo-cleaner` now sweeps that checkout instead of finding nothing and exiting 10. It widens only when the search below finds no checkout and the checkout around tracks files in the directory, so `~/Developer` under a home directory kept in git still sweeps the checkouts below it. A subdirectory named as a target is read as written, as before.
|
|
8
|
+
|
|
9
|
+
## 0.19.1
|
|
10
|
+
|
|
11
|
+
### Patch Changes
|
|
12
|
+
|
|
13
|
+
- bfac1ae: The note that suggests `--provider none` now names what the provider lists ("if this remote has no PRs", or "no merge requests" for GitLab) instead of "if this remote has no forge", and the README drops "forge" the same way.
|
|
14
|
+
|
|
3
15
|
## 0.19.0
|
|
4
16
|
|
|
5
17
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -115,8 +115,15 @@ A target is what to sweep:
|
|
|
115
115
|
|
|
116
116
|
No target is the same as naming the current directory: at a checkout's root
|
|
117
117
|
it sweeps that repository, and in a directory of checkouts it sweeps each
|
|
118
|
-
one. In a subdirectory of a checkout it
|
|
119
|
-
|
|
118
|
+
one. In a subdirectory of a checkout, such as `src/`, it sweeps the checkout
|
|
119
|
+
around it. That happens when the search below finds no checkout and the
|
|
120
|
+
checkout around tracks files in the directory (or git cannot say, in which
|
|
121
|
+
case the report gives git's reason). So a directory of checkouts inside a
|
|
122
|
+
checkout of its own, such as `~/Developer` under a home directory kept in
|
|
123
|
+
git, still sweeps the checkouts below it, or reports that it found none, as
|
|
124
|
+
long as the home repository tracks no file there. A search that finds
|
|
125
|
+
checkouts it cannot read is reported as it is. A subdirectory named as a
|
|
126
|
+
target is read as written and searched.
|
|
120
127
|
What a target names is read off the disk rather than from how it is written:
|
|
121
128
|
a path that is itself a checkout names that repository, and a path that is
|
|
122
129
|
not is searched for the checkouts under it, descending through directories
|
|
@@ -597,7 +604,7 @@ a failed fetch costs the whole repository.
|
|
|
597
604
|
| What failed | What you get |
|
|
598
605
|
| --- | --- |
|
|
599
606
|
| `gh pr list` / `glab api` (the PR listing) | Every row is judged on git evidence. Remote branches with no known PR need `--allow confirm-open-pr` to delete. One note on the repository. |
|
|
600
|
-
| `gh` / `glab` not installed or not signed in | The provider is never asked, so this is treated like a failed listing: every row is judged on git evidence, remote branches with no known PR need `--allow confirm-open-pr` to delete, and a note on the repository says why. Pass `--provider none` if the remote has no
|
|
607
|
+
| `gh` / `glab` not installed or not signed in | The provider is never asked, so this is treated like a failed listing: every row is judged on git evidence, remote branches with no known PR need `--allow confirm-open-pr` to delete, and a note on the repository says why. Pass `--provider none` if the remote has no pull requests, so there is nothing to check. |
|
|
601
608
|
| `git ls-remote`, after a fetch that worked | Remote branches are read from the refs that fetch just wrote. A note on the repository and on each remote row. |
|
|
602
609
|
| `git ls-remote`, in `clean`'s re-check of an `--offline` plan | The run is refused before anything runs. The refs on disk are what the re-check is there to replace, and `clean` does not delete on a remote it cannot ask. |
|
|
603
610
|
| `git fetch` | The repository is an error with no rows. Every row would otherwise be judged against an older default branch than the one you asked for. |
|
|
@@ -630,7 +637,7 @@ $ repo-cleaner --remote --offline
|
|
|
630
637
|
2 to delete, 0 to review, across 1 repository.
|
|
631
638
|
```
|
|
632
639
|
|
|
633
|
-
When the remote may
|
|
640
|
+
When the remote may have no pull requests at all, the note also suggests
|
|
634
641
|
`--provider none`: for a host `auto` could only guess was GitHub, or a local
|
|
635
642
|
path swept with `--provider github`. Under `auto`, a local path gets no
|
|
636
643
|
provider in the first place.
|
|
@@ -656,8 +663,8 @@ status: complete
|
|
|
656
663
|
The local branch went; the remote branch stayed. To go on, pick one:
|
|
657
664
|
|
|
658
665
|
- Fix the lookup (install `gh`, sign in, check the remote URL) and run again.
|
|
659
|
-
- Pass `--provider none` if this remote has no
|
|
660
|
-
|
|
666
|
+
- Pass `--provider none` if this remote has no pull requests at all, so there
|
|
667
|
+
is none to lose.
|
|
661
668
|
- Check for open pull requests yourself, then pass `--allow confirm-open-pr`.
|
|
662
669
|
|
|
663
670
|
The same happens when `gh` is not installed or not signed in. Nothing was
|
|
@@ -675,8 +682,8 @@ $ repo-cleaner --remote --offline
|
|
|
675
682
|
2 to delete, 0 to review, across 1 repository.
|
|
676
683
|
```
|
|
677
684
|
|
|
678
|
-
`--provider none` is how you say the remote has no
|
|
679
|
-
lookup, no note and no gate.
|
|
685
|
+
`--provider none` is how you say the remote has no pull requests: then there
|
|
686
|
+
is no lookup, no note and no gate.
|
|
680
687
|
|
|
681
688
|
### `--offline` reads what is already on disk
|
|
682
689
|
|
|
@@ -707,7 +714,7 @@ $ repo-cleaner --remote --offline --provider none # now
|
|
|
707
714
|
`--provider none` to judge with no network at all.
|
|
708
715
|
|
|
709
716
|
The refs on disk can be stale. A remote-tracking ref stays until the next
|
|
710
|
-
`git fetch --prune`, so a branch the
|
|
717
|
+
`git fetch --prune`, so a branch the host deleted after its pull request
|
|
711
718
|
merged still shows in the report, and a branch someone pushed to since shows
|
|
712
719
|
its old tip. That is fine for a report, but `clean` deletes on the remote, so
|
|
713
720
|
before it does, its re-check asks the remote itself with `git ls-remote`. A
|
|
@@ -779,7 +786,7 @@ is allowed.
|
|
|
779
786
|
|
|
780
787
|
A `PrProvider` you write reports a failure by returning `null` or by
|
|
781
788
|
throwing; either becomes `listError` and a note. Return an empty list only
|
|
782
|
-
when the
|
|
789
|
+
when the provider answered and there are no pull requests.
|
|
783
790
|
|
|
784
791
|
The decision behind this is
|
|
785
792
|
[D-UVUH](docs/planning/decisions/D-UVUH-failed-lookups.md).
|
|
@@ -967,7 +974,7 @@ Not done yet:
|
|
|
967
974
|
|
|
968
975
|
`git` on `PATH`, and `gh` (or `glab` for GitLab) for pull request evidence.
|
|
969
976
|
Without it, deleting a remote branch needs `--allow confirm-open-pr`, or
|
|
970
|
-
`--provider none` for a remote with no
|
|
977
|
+
`--provider none` for a remote with no pull requests (see
|
|
971
978
|
[When a lookup fails](#when-a-lookup-fails)). Authentication is the CLI's
|
|
972
979
|
own — this package reads no token.
|
|
973
980
|
|
package/dist/reference.js
CHANGED
|
@@ -9,7 +9,8 @@ const TARGETS = `Targets
|
|
|
9
9
|
a directory every checkout found under it, three levels deep at most
|
|
10
10
|
a remote URL its remote branches only, through a clone kept in the cache
|
|
11
11
|
directory (https://, ssh://, file://, or user@host:path)
|
|
12
|
-
No target sweeps the directory you are in, read the same way
|
|
12
|
+
No target sweeps the directory you are in, read the same way; in a subdirectory
|
|
13
|
+
the checkout around it tracks, where the search finds no checkout, that checkout.`;
|
|
13
14
|
const SWEEP = `Sweep options (the report, plan and clean)
|
|
14
15
|
--remote Also judge remote branches. Turns on the network: fetches
|
|
15
16
|
each remote first and asks GitHub or GitLab about pull
|
package/dist/resolve.d.ts
CHANGED
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
* covers everything under it — including a per-owner or per-client
|
|
9
9
|
* directory — without listing any of them.
|
|
10
10
|
*
|
|
11
|
-
* A caller that passes no entries gets no repositories.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* A caller that passes no entries gets no repositories. `sweep` with no
|
|
12
|
+
* targets passes the directory it stands in, which resolves like any other
|
|
13
|
+
* entry; when that finds nothing in a directory the checkout around it tracks,
|
|
14
|
+
* `sweep` widens it to that checkout.
|
|
14
15
|
*/
|
|
15
16
|
import { type Git } from '@osdlc/typed-git';
|
|
16
17
|
import { type ScanStats, type UnresolvedEntry } from './types.ts';
|
package/dist/resolve.js
CHANGED
|
@@ -8,9 +8,10 @@
|
|
|
8
8
|
* covers everything under it — including a per-owner or per-client
|
|
9
9
|
* directory — without listing any of them.
|
|
10
10
|
*
|
|
11
|
-
* A caller that passes no entries gets no repositories.
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* A caller that passes no entries gets no repositories. `sweep` with no
|
|
12
|
+
* targets passes the directory it stands in, which resolves like any other
|
|
13
|
+
* entry; when that finds nothing in a directory the checkout around it tracks,
|
|
14
|
+
* `sweep` widens it to that checkout.
|
|
14
15
|
*/
|
|
15
16
|
import { homedir } from 'node:os';
|
|
16
17
|
import { isAbsolute, join, resolve as resolvePath } from 'node:path';
|
package/dist/sweep.d.ts
CHANGED
|
@@ -67,7 +67,9 @@ export declare function sweptWith(targets: readonly string[], opts: Omit<SweepOp
|
|
|
67
67
|
/**
|
|
68
68
|
* Sweep `targets`: checkouts, directories to search for checkouts, or remote
|
|
69
69
|
* URLs (swept through a clone kept in `cacheDir`, remote branches only). No
|
|
70
|
-
* targets means `[cwd]`, resolved the same way
|
|
70
|
+
* targets means `[cwd]`, resolved the same way, except that a `cwd` where the
|
|
71
|
+
* search finds no checkout, and whose checkout tracks files in it, sweeps
|
|
72
|
+
* that checkout.
|
|
71
73
|
*
|
|
72
74
|
* Never throws for a repository it cannot read: that one comes back with
|
|
73
75
|
* `error` set, because a fan-out must not end on its first bad entry. Throws a
|
package/dist/sweep.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { existsSync, realpathSync } from 'node:fs';
|
|
10
10
|
import { isAbsolute, resolve } from 'node:path';
|
|
11
|
-
import { Git } from '@osdlc/typed-git';
|
|
11
|
+
import { Git, checkoutRoot } from '@osdlc/typed-git';
|
|
12
12
|
import { Gh } from '@osdlc/typed-gh';
|
|
13
13
|
import { HELD_BY_WORKTREE, gatherLocalBranches, gatherRemoteBranches, resolveBranch, } from "./branches.js";
|
|
14
14
|
import { discoverRepo } from "./discover.js";
|
|
@@ -103,10 +103,21 @@ export function sweptWith(targets, opts) {
|
|
|
103
103
|
urls,
|
|
104
104
|
};
|
|
105
105
|
}
|
|
106
|
+
/**
|
|
107
|
+
* Whether the checkout `dir` is in tracks any file under it. True as well when
|
|
108
|
+
* git cannot say: then widening to that checkout reports why it cannot be
|
|
109
|
+
* read, which says more than "no git checkouts under it".
|
|
110
|
+
*/
|
|
111
|
+
function tracksFilesIn(dir, git) {
|
|
112
|
+
const listed = git.at(dir).try.lsFiles({ cached: true });
|
|
113
|
+
return !listed.ok || listed.value.length > 0;
|
|
114
|
+
}
|
|
106
115
|
/**
|
|
107
116
|
* Sweep `targets`: checkouts, directories to search for checkouts, or remote
|
|
108
117
|
* URLs (swept through a clone kept in `cacheDir`, remote branches only). No
|
|
109
|
-
* targets means `[cwd]`, resolved the same way
|
|
118
|
+
* targets means `[cwd]`, resolved the same way, except that a `cwd` where the
|
|
119
|
+
* search finds no checkout, and whose checkout tracks files in it, sweeps
|
|
120
|
+
* that checkout.
|
|
110
121
|
*
|
|
111
122
|
* Never throws for a repository it cannot read: that one comes back with
|
|
112
123
|
* `error` set, because a fan-out must not end on its first bad entry. Throws a
|
|
@@ -131,13 +142,26 @@ export function sweep(targets, opts = {}) {
|
|
|
131
142
|
}
|
|
132
143
|
}
|
|
133
144
|
}
|
|
134
|
-
const
|
|
145
|
+
const resolveOpts = {
|
|
146
|
+
git: new Git(cwd, { runner: opts.git }),
|
|
147
|
+
baseDir: cwd,
|
|
148
|
+
...(opts.onWarn === undefined ? {} : { onWarn: opts.onWarn }),
|
|
149
|
+
};
|
|
150
|
+
let resolution = entries.length === 0
|
|
135
151
|
? { repos: [], unresolved: [], scan: emptyScan() }
|
|
136
|
-
: resolveEntries(entries,
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
152
|
+
: resolveEntries(entries, resolveOpts);
|
|
153
|
+
// With no target, a directory the checkout around it tracks files in means
|
|
154
|
+
// that checkout, as it does for git itself. The search comes first, and a
|
|
155
|
+
// directory the checkout tracks nothing in is not part of it: a home
|
|
156
|
+
// directory kept in git does not track ~/Developer, so a search there that
|
|
157
|
+
// finds nothing is reported as it is. So is a search that found checkouts
|
|
158
|
+
// it could not read.
|
|
159
|
+
const around = targets.length === 0 && resolution.repos.length === 0 && resolution.scan.checkoutsFound === 0
|
|
160
|
+
? checkoutRoot(cwd)
|
|
161
|
+
: null;
|
|
162
|
+
if (around !== null && around !== resolve(cwd) && tracksFilesIn(cwd, resolveOpts.git)) {
|
|
163
|
+
resolution = resolveEntries([around], resolveOpts);
|
|
164
|
+
}
|
|
141
165
|
if (branch !== undefined && resolution.repos.length > 1) {
|
|
142
166
|
throw new RepoCleanerError('conflict', `--branch sweeps one repository; ${resolution.repos.length} found under ${entries[0]}`);
|
|
143
167
|
}
|
|
@@ -182,7 +206,7 @@ function providerNote(repo, kinds, choice) {
|
|
|
182
206
|
// is a forge: there the cause is access, not the provider.
|
|
183
207
|
const hint = status.host === null ||
|
|
184
208
|
(choice === 'auto' && status.name === 'github' && status.host !== 'github.com')
|
|
185
|
-
?
|
|
209
|
+
? ` Pass \`--provider none\` if this remote has no ${status.noun}s.`
|
|
186
210
|
: '';
|
|
187
211
|
if (unchecked.kind === 'list-failed') {
|
|
188
212
|
return `Listing ${status.noun}s with \`${status.listing}\` failed (${status.listError}); judged on git evidence alone.${hint}`;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
/** This package's version, from its package.json when it was built. */
|
|
2
|
-
export declare const VERSION = "0.
|
|
2
|
+
export declare const VERSION = "0.20.0";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@osdlc/repo-cleaner",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.20.0",
|
|
4
4
|
"description": "What has been merged into the default branch, and what can go away. Inventories local branches, worktrees and remote branches across one repository or many, classifying each with the evidence behind the verdict and the command that would act on it. Plans and applies approved deletes, compare-and-swap.",
|
|
5
5
|
"license": "MIT OR Apache-2.0",
|
|
6
6
|
"author": "sksizer",
|
|
@@ -50,9 +50,9 @@
|
|
|
50
50
|
"dependencies": {
|
|
51
51
|
"zod": "^4.6.6",
|
|
52
52
|
"@osdlc/cli-tool": "^0.17.0",
|
|
53
|
+
"@osdlc/command-seam": "^0.14.0",
|
|
53
54
|
"@osdlc/path-guard": "^0.3.1",
|
|
54
55
|
"@osdlc/typed-gh": "^0.16.1",
|
|
55
|
-
"@osdlc/command-seam": "^0.14.0",
|
|
56
56
|
"@osdlc/ux": "^0.1.1",
|
|
57
57
|
"@osdlc/typed-git": "^0.13.0"
|
|
58
58
|
},
|
|
@@ -63,8 +63,8 @@
|
|
|
63
63
|
"prettier": "3.9.6",
|
|
64
64
|
"typescript": "~6.0.3",
|
|
65
65
|
"vitest": "^5.0.5",
|
|
66
|
-
"@osdlc/
|
|
67
|
-
"@osdlc/
|
|
66
|
+
"@osdlc/tsconfig": "^0.4.1",
|
|
67
|
+
"@osdlc/prettier-config": "^0.3.1"
|
|
68
68
|
},
|
|
69
69
|
"scripts": {
|
|
70
70
|
"build": "cli-tool-stamp-version && tsc -p tsconfig.build.json",
|