Skip to content

Commit 981c3c1

Browse files
authored
fix: accept branch slugs in cleanup plan revalidation (#498)
1 parent 0419a74 commit 981c3c1

3 files changed

Lines changed: 178 additions & 0 deletions

File tree

inc/Workspace/Workspace.php

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3228,6 +3228,13 @@ private function scope_worktree_cleanup_to_plan( array $planned_candidates, arra
32283228
foreach ( array( 'repo', 'branch', 'path', 'signal' ) as $field ) {
32293229
$planned = (string) ( $plan_row[ $field ] ?? '' );
32303230
$actual = (string) ( $current[ $field ] ?? '' );
3231+
if ( 'branch' === $field ) {
3232+
$planned_slug = $this->slugify_branch($planned);
3233+
$actual_slug = $this->slugify_branch($actual);
3234+
if ( '' !== $planned_slug && $planned_slug === $actual_slug ) {
3235+
continue;
3236+
}
3237+
}
32313238
if ( $planned !== $actual ) {
32323239
$mismatches[] = sprintf('%s planned=%s current=%s', $field, $planned, $actual);
32333240
}
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
<?php
2+
/**
3+
* Smoke test for cleanup apply-plan revalidation branch/slug matching.
4+
*
5+
* Run: php tests/smoke-worktree-cleanup-plan-scope.php
6+
*/
7+
8+
declare( strict_types=1 );
9+
10+
namespace DataMachine\Core\FilesRepository {
11+
if ( ! class_exists(__NAMESPACE__ . '\\FilesystemHelper') ) {
12+
class FilesystemHelper {
13+
public static function get() {
14+
return null;
15+
}
16+
}
17+
}
18+
}
19+
20+
namespace {
21+
if ( ! defined('ABSPATH') ) {
22+
define('ABSPATH', __DIR__ . '/');
23+
}
24+
25+
if ( ! class_exists('WP_Error') ) {
26+
class WP_Error {
27+
public string $code;
28+
public string $message;
29+
public array $data;
30+
public function __construct( $code = '', $message = '', $data = array() ) {
31+
$this->code = (string) $code;
32+
$this->message = (string) $message;
33+
$this->data = (array) $data;
34+
}
35+
public function get_error_message(): string {
36+
return $this->message;
37+
}
38+
}
39+
}
40+
41+
if ( ! function_exists('is_wp_error') ) {
42+
function is_wp_error( $thing ): bool {
43+
return $thing instanceof \WP_Error;
44+
}
45+
}
46+
47+
if ( ! function_exists('apply_filters') ) {
48+
function apply_filters( string $hook_name, $value ) {
49+
return $value;
50+
}
51+
}
52+
53+
if ( ! function_exists('wp_json_encode') ) {
54+
function wp_json_encode( $value, int $flags = 0, int $depth = 512 ): string|false {
55+
return json_encode($value, $flags, $depth);
56+
}
57+
}
58+
59+
include __DIR__ . '/../inc/Support/GitHubRemote.php';
60+
include __DIR__ . '/../inc/Support/GitRunner.php';
61+
include __DIR__ . '/../inc/Support/PathSecurity.php';
62+
include __DIR__ . '/../inc/Workspace/WorkspaceMutationLock.php';
63+
include __DIR__ . '/../inc/Workspace/WorktreeDiskBudget.php';
64+
include __DIR__ . '/../inc/Workspace/WorktreeContextInjector.php';
65+
include __DIR__ . '/../inc/Workspace/Workspace.php';
66+
67+
$failures = 0;
68+
$total = 0;
69+
$assert = function ( $expected, $actual, string $message ) use ( &$failures, &$total ): void {
70+
++$total;
71+
if ( $expected === $actual ) {
72+
echo "{$message}\n";
73+
return;
74+
}
75+
76+
++$failures;
77+
echo "{$message}\n";
78+
echo ' expected: ' . var_export($expected, true) . "\n";
79+
echo ' actual: ' . var_export($actual, true) . "\n";
80+
};
81+
82+
$ws = new \DataMachineCode\Workspace\Workspace();
83+
$scope_reflection = new \ReflectionMethod(\DataMachineCode\Workspace\Workspace::class, 'scope_worktree_cleanup_to_plan');
84+
85+
$scoped = $scope_reflection->invoke(
86+
$ws,
87+
array(
88+
array(
89+
'handle' => 'demo@fix-foo',
90+
'repo' => 'demo',
91+
'branch' => 'fix-foo',
92+
'path' => '/workspace/demo@fix-foo',
93+
'signal' => 'pr-merged',
94+
),
95+
),
96+
array(
97+
array(
98+
'handle' => 'demo@fix-foo',
99+
'repo' => 'demo',
100+
'branch' => 'fix/foo',
101+
'path' => '/workspace/demo@fix-foo',
102+
'signal' => 'pr-merged',
103+
),
104+
),
105+
array()
106+
);
107+
108+
$assert(1, count($scoped['candidates'] ?? array()), 'slugified planned branch matches current slash branch');
109+
$assert(array(), $scoped['skipped'] ?? array(), 'matching branch slug is not skipped as plan_mismatch');
110+
111+
$scoped_mismatch = $scope_reflection->invoke(
112+
$ws,
113+
array(
114+
array(
115+
'handle' => 'demo@fix-foo',
116+
'repo' => 'demo',
117+
'branch' => 'fix-foo',
118+
'path' => '/workspace/demo@fix-foo',
119+
'signal' => 'pr-merged',
120+
),
121+
),
122+
array(
123+
array(
124+
'handle' => 'demo@fix-foo',
125+
'repo' => 'demo',
126+
'branch' => 'other/foo',
127+
'path' => '/workspace/demo@fix-foo',
128+
'signal' => 'pr-merged',
129+
),
130+
),
131+
array()
132+
);
133+
134+
$assert(0, count($scoped_mismatch['candidates'] ?? array()), 'different branch slug remains blocked');
135+
$assert('plan_mismatch', $scoped_mismatch['skipped'][0]['reason_code'] ?? '', 'different branch slug reports plan_mismatch');
136+
137+
echo "\nResult: " . ( $total - $failures ) . "/{$total} passed\n";
138+
exit($failures > 0 ? 1 : 0);
139+
}

tests/smoke-worktree-cleanup.php

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,6 +113,13 @@ function size_format( $bytes ): string
113113
}
114114
}
115115

116+
if (! function_exists('wp_json_encode') ) {
117+
function wp_json_encode( $value, int $flags = 0, int $depth = 512 ): string|false
118+
{
119+
return json_encode($value, $flags, $depth);
120+
}
121+
}
122+
116123
include __DIR__ . '/../inc/Support/GitHubRemote.php';
117124
include __DIR__ . '/../inc/Support/GitRunner.php';
118125
include __DIR__ . '/../inc/Support/PathSecurity.php';
@@ -607,6 +614,31 @@ public function worktree_list( ?string $repo = null, ?string $state = null, arra
607614
$assert(0, count($scoped_mismatch['candidates'] ?? array()), 'plan mismatch does not leave a removable candidate');
608615
$assert('plan_mismatch', $scoped_mismatch['skipped'][0]['reason_code'] ?? '', 'plan mismatch reports a stable reason code');
609616

617+
$scoped_slug_branch = $scope_reflection->invoke(
618+
$ws,
619+
array(
620+
array(
621+
'handle' => 'demo@fix-foo',
622+
'repo' => 'demo',
623+
'branch' => 'fix-foo',
624+
'path' => $tmp . '/demo@fix-foo',
625+
'signal' => 'pr-merged',
626+
),
627+
),
628+
array(
629+
array(
630+
'handle' => 'demo@fix-foo',
631+
'repo' => 'demo',
632+
'branch' => 'fix/foo',
633+
'path' => $tmp . '/demo@fix-foo',
634+
'signal' => 'pr-merged',
635+
),
636+
),
637+
array()
638+
);
639+
$assert(1, count($scoped_slug_branch['candidates'] ?? array()), 'plan revalidation accepts slugged branch matching slash branch');
640+
$assert(array(), $scoped_slug_branch['skipped'] ?? array(), 'slugged branch match is not reported as plan_mismatch');
641+
610642
// External worktrees are reported with routing metadata, but never owned by cleanup.
611643
$external_skips = array_filter($plan['skipped'] ?? array(), fn( $s ) => ( $s['path'] ?? '' ) === $external_real);
612644
$assert(1, count($external_skips), 'external worktree skipped with exactly one entry');

0 commit comments

Comments
 (0)