Skip to content

Commit c867516

Browse files
authored
Merge pull request #682 from Extra-Chill/fix/issue-679-cleanup-plan
Improve cleanup plan summary
2 parents 8ce5ebc + 559e949 commit c867516

5 files changed

Lines changed: 463 additions & 21 deletions

File tree

inc/Cli/Commands/WorkspaceCommand.php

Lines changed: 126 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -602,6 +602,10 @@ public function adopt_repo( array $args, array $assoc_args ): void {
602602
* [--older-than=<duration>]
603603
* : Pass an age gate such as 7d or 24h into cleanup task params.
604604
*
605+
* [--top=<count>]
606+
* : For `plan`, number of largest reclaimable paths to show in the upfront
607+
* summary. Defaults to 10.
608+
*
605609
* [--limit=<count>]
606610
* : For DB-backed `apply` / `resume`, maximum pending rows to process in this
607611
* invocation (default 25, max 100). For `--mode=artifacts` pages, maximum
@@ -973,6 +977,9 @@ private function cleanup_plan_input( string $mode, array $assoc_args ): array {
973977
if ( isset($assoc_args['older-than']) && '' !== trim( (string) $assoc_args['older-than']) ) {
974978
$input['worktree_older_than'] = trim( (string) $assoc_args['older-than']);
975979
}
980+
if ( isset($assoc_args['top']) ) {
981+
$input['top_n'] = (int) $assoc_args['top'];
982+
}
976983
if ( isset($assoc_args['force']) ) {
977984
$input['force_artifact_cleanup'] = (bool) $assoc_args['force'];
978985
}
@@ -1428,12 +1435,131 @@ private function render_cleanup_plan_result( array $result, array $assoc_args ):
14281435
WP_CLI::log(sprintf('Rows: %d', (int) ( $summary['total_rows'] ?? 0 )));
14291436
WP_CLI::log(sprintf('Bytes: %s', $this->format_bytes($summary['total_size_bytes'] ?? 0)));
14301437
WP_CLI::log(sprintf('Apply: wp datamachine-code workspace cleanup apply %s', (string) ( $result['run_id'] ?? '' )));
1438+
$this->render_cleanup_plan_category_totals( (array) ( $summary['category_totals'] ?? array() ) );
1439+
$this->render_cleanup_plan_top_reclaimable( (array) ( $summary['top_reclaimable'] ?? array() ) );
1440+
$this->render_cleanup_plan_blockers( (array) ( $summary['blockers'] ?? array() ) );
1441+
$this->render_cleanup_plan_recommended_commands( (array) ( $summary['recommended_commands'] ?? array() ), (string) ( $result['run_id'] ?? '' ) );
14311442
$inputs = (array) ( $result['inputs'] ?? array() );
14321443
if ( empty($inputs['include_artifacts']) ) {
14331444
WP_CLI::log('Artifacts: skipped for bounded retention planning; run `wp datamachine-code workspace cleanup plan --mode=artifacts` when you want artifact rows.');
14341445
}
14351446
}
14361447

1448+
/**
1449+
* Render reclaimable cleanup bytes by category.
1450+
*
1451+
* @param array<string,int> $totals Category totals.
1452+
*/
1453+
private function render_cleanup_plan_category_totals( array $totals ): void {
1454+
if ( array() === $totals ) {
1455+
return;
1456+
}
1457+
1458+
WP_CLI::log('');
1459+
WP_CLI::log('Reclaimable space by category:');
1460+
$labels = array(
1461+
'whole_worktrees' => 'whole worktrees',
1462+
'dependency_artifacts' => 'dependency artifacts',
1463+
'build_outputs' => 'build outputs',
1464+
'caches' => 'caches',
1465+
);
1466+
$rows = array();
1467+
foreach ( $labels as $category => $label ) {
1468+
$rows[] = array(
1469+
'category' => $label,
1470+
'bytes' => $this->format_bytes($totals[ $category ] ?? 0),
1471+
);
1472+
}
1473+
$this->format_items($rows, array( 'category', 'bytes' ), array( 'format' => 'table' ), 'category');
1474+
}
1475+
1476+
/**
1477+
* Render largest reclaimable paths.
1478+
*
1479+
* @param array<int,array<string,mixed>> $paths Top paths.
1480+
*/
1481+
private function render_cleanup_plan_top_reclaimable( array $paths ): void {
1482+
if ( array() === $paths ) {
1483+
return;
1484+
}
1485+
1486+
WP_CLI::log('');
1487+
WP_CLI::log('Top reclaimable paths:');
1488+
$rows = array_map(
1489+
fn( $row ) => array(
1490+
'size' => $this->format_bytes($row['size_bytes'] ?? 0),
1491+
'category' => (string) ( $row['category'] ?? '' ),
1492+
'risk' => (string) ( $row['safety_class'] ?? '' ),
1493+
'handle' => (string) ( $row['handle'] ?? '' ),
1494+
'path' => (string) ( $row['path'] ?? '' ),
1495+
),
1496+
$paths
1497+
);
1498+
$this->format_items($rows, array( 'size', 'category', 'risk', 'handle', 'path' ), array( 'format' => 'table' ), 'size');
1499+
}
1500+
1501+
/**
1502+
* Render blockers grouped by reason and repo.
1503+
*
1504+
* @param array<string,array<string,mixed>> $blockers Blocker buckets.
1505+
*/
1506+
private function render_cleanup_plan_blockers( array $blockers ): void {
1507+
if ( array() === $blockers ) {
1508+
return;
1509+
}
1510+
1511+
WP_CLI::log('');
1512+
WP_CLI::log('Blockers by reason and repo:');
1513+
$rows = array();
1514+
foreach ( $blockers as $reason => $bucket ) {
1515+
$bucket = (array) $bucket;
1516+
$repos = array();
1517+
foreach ( (array) ( $bucket['repos'] ?? array() ) as $repo => $repo_bucket ) {
1518+
$repo_bucket = (array) $repo_bucket;
1519+
$repos[] = sprintf('%s=%d', (string) $repo, (int) ( $repo_bucket['count'] ?? 0 ));
1520+
}
1521+
$rows[] = array(
1522+
'reason' => (string) $reason,
1523+
'count' => (int) ( $bucket['count'] ?? 0 ),
1524+
'bytes' => $this->format_bytes($bucket['size_bytes'] ?? 0),
1525+
'repos' => implode(', ', array_slice($repos, 0, 5)),
1526+
'examples' => implode(', ', array_slice(array_map('strval', (array) ( $bucket['examples'] ?? array() )), 0, 5)),
1527+
);
1528+
}
1529+
$this->format_items($rows, array( 'reason', 'count', 'bytes', 'repos', 'examples' ), array( 'format' => 'table' ), 'reason');
1530+
}
1531+
1532+
/**
1533+
* Render directly executable recommended cleanup commands.
1534+
*
1535+
* @param array<int,array<string,string>> $commands Recommended commands.
1536+
* @param string $run_id Cleanup run ID.
1537+
*/
1538+
private function render_cleanup_plan_recommended_commands( array $commands, string $run_id ): void {
1539+
if ( array() === $commands ) {
1540+
return;
1541+
}
1542+
1543+
WP_CLI::log('');
1544+
WP_CLI::log('Recommended commands:');
1545+
$rows = array_map(
1546+
function ( $row ) use ( $run_id ): array {
1547+
$row = (array) $row;
1548+
$command = (string) ( $row['command'] ?? '' );
1549+
if ( '' !== $run_id ) {
1550+
$command = str_replace('<run-id>', $run_id, $command);
1551+
}
1552+
return array(
1553+
'label' => (string) ( $row['label'] ?? '' ),
1554+
'risk' => (string) ( $row['risk'] ?? '' ),
1555+
'command' => $command,
1556+
);
1557+
},
1558+
$commands
1559+
);
1560+
$this->format_items($rows, array( 'label', 'risk', 'command' ), array( 'format' => 'table' ), 'label');
1561+
}
1562+
14371563
private function cleanup_run_id( int $job_id ): string {
14381564
return 'cleanup-run-' . $job_id;
14391565
}

inc/Workspace/CleanupRunService.php

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,8 @@ public function plan( array $opts = array() ): array|\WP_Error {
5353
if ( $run_id instanceof \WP_Error ) {
5454
return $run_id;
5555
}
56+
$plan['summary'] = $this->materialize_plan_recommended_commands( (array) ( $plan['summary'] ?? array() ), $run_id );
57+
$this->repository->update_run($run_id, array( 'summary' => $plan['summary'] ));
5658

5759
$inserted = $this->repository->add_items($run_id, $items);
5860
if ( $inserted instanceof \WP_Error ) {
@@ -70,6 +72,26 @@ public function plan( array $opts = array() ): array|\WP_Error {
7072
return $plan;
7173
}
7274

75+
/**
76+
* Replace run-id placeholders in persisted plan command recommendations.
77+
*
78+
* @param array<string,mixed> $summary Plan summary.
79+
* @param string $run_id Cleanup run ID.
80+
* @return array<string,mixed>
81+
*/
82+
private function materialize_plan_recommended_commands( array $summary, string $run_id ): array {
83+
$commands = array();
84+
foreach ( (array) ( $summary['recommended_commands'] ?? array() ) as $row ) {
85+
if ( ! is_array($row) ) {
86+
continue;
87+
}
88+
$row['command'] = str_replace('<run-id>', $run_id, (string) ( $row['command'] ?? '' ));
89+
$commands[] = $row;
90+
}
91+
$summary['recommended_commands'] = $commands;
92+
return $summary;
93+
}
94+
7395
/**
7496
* Apply pending rows from a DB-backed run.
7597
*
@@ -115,13 +137,12 @@ public function apply( string $run_id, array $opts = array() ): array|\WP_Error
115137
'limit' => count($artifact_batch),
116138
)
117139
);
118-
$this->record_apply_result($artifact_batch, $results['artifact_cleanup'], 'removed');
119-
if ( ! is_wp_error($results['artifact_cleanup']) ) {
120-
$applied_rows += count( (array) ( $results['artifact_cleanup']['removed'] ?? array() ) );
121-
$skipped_rows += count( (array) ( $results['artifact_cleanup']['skipped'] ?? array() ) );
122-
} else {
123-
$skipped_rows += count($artifact_batch);
140+
if ( $results['artifact_cleanup'] instanceof \WP_Error ) {
141+
return $results['artifact_cleanup'];
124142
}
143+
$this->record_apply_result($artifact_batch, $results['artifact_cleanup'], 'removed');
144+
$applied_rows += count( (array) ( $results['artifact_cleanup']['removed'] ?? array() ) );
145+
$skipped_rows += count( (array) ( $results['artifact_cleanup']['skipped'] ?? array() ) );
125146
}
126147

127148
$remaining_capacity = max(0, $limit - $processed_rows);
@@ -138,13 +159,12 @@ public function apply( string $run_id, array $opts = array() ): array|\WP_Error
138159
'stale_liveness_only' => $stale_worktrees_only,
139160
)
140161
);
141-
$this->record_apply_result($worktree_batch, $results['worktree_removal'], 'removed');
142-
if ( ! is_wp_error($results['worktree_removal']) ) {
143-
$applied_rows += count( (array) ( $results['worktree_removal']['removed'] ?? array() ) );
144-
$skipped_rows += count( (array) ( $results['worktree_removal']['skipped'] ?? array() ) );
145-
} else {
146-
$skipped_rows += count($worktree_batch);
162+
if ( $results['worktree_removal'] instanceof \WP_Error ) {
163+
return $results['worktree_removal'];
147164
}
165+
$this->record_apply_result($worktree_batch, $results['worktree_removal'], 'removed');
166+
$applied_rows += count( (array) ( $results['worktree_removal']['removed'] ?? array() ) );
167+
$skipped_rows += count( (array) ( $results['worktree_removal']['skipped'] ?? array() ) );
148168
}
149169

150170
$status = $this->status($run_id);

0 commit comments

Comments
 (0)