Skip to content

Commit 5ef1b46

Browse files
authored
Merge pull request #593 from Extra-Chill/refactor/active-cleanup-revalidation
Refactor active cleanup revalidation
2 parents 89ff426 + 80de813 commit 5ef1b46

1 file changed

Lines changed: 117 additions & 126 deletions

File tree

inc/Workspace/Workspace.php

Lines changed: 117 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -2343,53 +2343,24 @@ private function build_current_remote_tracking_clean_cleanup_evidence( string $h
23432343
}
23442344
}
23452345

2346-
if ( in_array($branch, $this->protected_base_branch_names(), true) ) {
2347-
return new \WP_Error('primary_protected_branch', 'refusing to auto-finalize a protected primary branch worktree');
2348-
}
2349-
2350-
$validation = $this->validate_containment($path, $this->workspace_path);
2351-
if ( ! $validation['valid'] ) {
2352-
return new \WP_Error('external_worktree', 'worktree path is outside the workspace root');
2353-
}
2354-
2355-
$real_path = (string) ( $validation['real_path'] ?? '' );
2356-
if ( '' === $real_path || ! is_dir($real_path) ) {
2357-
return new \WP_Error('missing_worktree', 'worktree path no longer exists');
2358-
}
2359-
2360-
$git_marker = rtrim($real_path, '/') . '/.git';
2361-
if ( is_dir($git_marker) ) {
2362-
return new \WP_Error('primary_checkout', 'refusing to mark a primary checkout cleanup_eligible');
2363-
}
2364-
if ( ! is_file($git_marker) ) {
2365-
return new \WP_Error('not_a_worktree', 'worktree marker missing');
2366-
}
2367-
2368-
$current_branch = $this->resolve_worktree_branch_from_head_file($real_path);
2369-
if ( $branch !== $current_branch ) {
2370-
return new \WP_Error('branch_identity_mismatch', 'worktree branch identity changed before apply');
2371-
}
2372-
2373-
$dirty = $this->probe_worktree_dirty_count($real_path, self::CLEANUP_GIT_PROBE_TIMEOUT);
2374-
if ( is_wp_error($dirty) ) {
2375-
return $dirty;
2376-
}
2377-
if ( 0 !== (int) $dirty ) {
2378-
return new \WP_Error('dirty_worktree', 'worktree is dirty');
2379-
}
2380-
2381-
$unpushed = $this->count_unpushed_commits($real_path, self::CLEANUP_GIT_PROBE_TIMEOUT);
2382-
if ( is_wp_error($unpushed) ) {
2383-
return $unpushed;
2384-
}
2385-
if ( 0 !== (int) $unpushed ) {
2386-
return new \WP_Error('unpushed_commits', 'worktree has unpushed commits');
2346+
$facts = $this->validate_current_cleanup_worktree(
2347+
$repo,
2348+
$path,
2349+
$branch,
2350+
array(
2351+
'require_clean' => true,
2352+
'missing_primary_code' => 'primary_missing',
2353+
'dirty_error_message' => 'worktree is dirty',
2354+
'unpushed_error_message' => 'worktree has unpushed commits',
2355+
)
2356+
);
2357+
if ( is_wp_error($facts) ) {
2358+
return $facts;
23872359
}
23882360

2389-
$primary_path = $this->get_primary_path($repo);
2390-
if ( '' === $primary_path || ! is_dir($primary_path . '/.git') ) {
2391-
return new \WP_Error('primary_missing', 'primary checkout missing');
2392-
}
2361+
$dirty = (int) $facts['dirty'];
2362+
$unpushed = (int) $facts['unpushed'];
2363+
$primary_path = (string) $facts['primary_path'];
23932364

23942365
$remote_ref = 'refs/remotes/origin/' . $branch;
23952366
$remote = $this->run_git($primary_path, sprintf('rev-parse --verify --quiet %s', escapeshellarg($remote_ref)), self::CLEANUP_GIT_PROBE_TIMEOUT);
@@ -2420,50 +2391,22 @@ private function build_current_remote_tracking_clean_cleanup_evidence( string $h
24202391
* @return array<string,mixed>|\WP_Error
24212392
*/
24222393
private function build_current_effective_clean_cleanup_evidence( string $repo, string $wt_path ): array|\WP_Error {
2423-
$validation = $this->validate_containment($wt_path, $this->workspace_path);
2424-
if ( ! $validation['valid'] ) {
2425-
return new \WP_Error('external_worktree', 'worktree path is outside the workspace root');
2394+
$facts = $this->validate_current_cleanup_worktree($repo, $wt_path);
2395+
if ( is_wp_error($facts) ) {
2396+
return $facts;
24262397
}
24272398

2428-
$real_path = (string) ( $validation['real_path'] ?? '' );
2429-
if ( '' === $real_path || ! is_dir($real_path) ) {
2430-
return new \WP_Error('missing_worktree', 'worktree path no longer exists');
2431-
}
2399+
$real_path = (string) $facts['real_path'];
2400+
$primary_path = (string) $facts['primary_path'];
2401+
$dirty = (int) $facts['dirty'];
2402+
$unpushed = (int) $facts['unpushed'];
2403+
$branch = (string) $facts['branch'];
24322404

2433-
$git_marker = rtrim($real_path, '/') . '/.git';
2434-
if ( is_dir($git_marker) ) {
2435-
return new \WP_Error('primary_checkout', 'refusing to mark a primary checkout cleanup_eligible');
2436-
}
2437-
if ( ! is_file($git_marker) ) {
2438-
return new \WP_Error('not_a_worktree', 'worktree marker missing');
2439-
}
2440-
2441-
$primary_path = $this->get_primary_path($repo);
2442-
if ( ! is_dir($primary_path . '/.git') ) {
2443-
return new \WP_Error('missing_primary', 'primary checkout missing');
2444-
}
2445-
2446-
$dirty = $this->probe_worktree_dirty_count($real_path, self::CLEANUP_GIT_PROBE_TIMEOUT);
2447-
if ( is_wp_error($dirty) ) {
2448-
return $dirty;
2449-
}
2450-
$unpushed = $this->count_unpushed_commits($real_path, self::CLEANUP_GIT_PROBE_TIMEOUT);
2451-
if ( is_wp_error($unpushed) ) {
2452-
return $unpushed;
2453-
}
24542405
$default_ref = $this->resolve_remote_default_ref($primary_path, self::CLEANUP_GIT_PROBE_TIMEOUT);
24552406
if ( ! is_string($default_ref) || '' === $default_ref ) {
24562407
return new \WP_Error('missing_default_ref', 'primary checkout default ref could not be resolved');
24572408
}
24582409

2459-
$branch = (string) $this->resolve_worktree_branch_from_head_file($real_path);
2460-
if ( '' === $branch ) {
2461-
return new \WP_Error('missing_branch_identity', 'worktree branch identity could not be resolved');
2462-
}
2463-
if ( in_array($branch, $this->protected_base_branch_names(), true) ) {
2464-
return new \WP_Error('primary_protected_branch', 'refusing to auto-finalize a protected primary branch worktree');
2465-
}
2466-
24672410
$upstream_equivalence = ( 0 === (int) $dirty && 0 === (int) $unpushed )
24682411
? $this->build_clean_upstream_equivalence_evidence($primary_path, $real_path, $default_ref, $branch)
24692412
: $this->build_dirty_unpushed_upstream_equivalence_evidence($primary_path, $real_path, $default_ref);
@@ -2501,53 +2444,24 @@ private function build_current_merged_to_default_cleanup_evidence( string $handl
25012444
}
25022445
}
25032446

2504-
if ( in_array($branch, $this->protected_base_branch_names(), true) ) {
2505-
return new \WP_Error('primary_protected_branch', 'refusing to auto-finalize a protected primary branch worktree');
2506-
}
2507-
2508-
$validation = $this->validate_containment($path, $this->workspace_path);
2509-
if ( ! $validation['valid'] ) {
2510-
return new \WP_Error('external_worktree', 'worktree path is outside the workspace root');
2511-
}
2512-
2513-
$real_path = (string) ( $validation['real_path'] ?? '' );
2514-
if ( '' === $real_path || ! is_dir($real_path) ) {
2515-
return new \WP_Error('missing_worktree', 'worktree path no longer exists');
2516-
}
2517-
2518-
$git_marker = rtrim($real_path, '/') . '/.git';
2519-
if ( is_dir($git_marker) ) {
2520-
return new \WP_Error('primary_checkout', 'refusing to mark a primary checkout cleanup_eligible');
2521-
}
2522-
if ( ! is_file($git_marker) ) {
2523-
return new \WP_Error('not_a_worktree', 'worktree marker missing');
2524-
}
2525-
2526-
$current_branch = $this->resolve_worktree_branch_from_head_file($real_path);
2527-
if ( $branch !== $current_branch ) {
2528-
return new \WP_Error('branch_identity_mismatch', 'worktree branch identity changed before apply');
2529-
}
2530-
2531-
$primary_path = $this->get_primary_path($repo);
2532-
if ( ! is_dir($primary_path . '/.git') ) {
2533-
return new \WP_Error('missing_primary', 'primary checkout missing');
2534-
}
2535-
2536-
$dirty = $this->probe_worktree_dirty_count($real_path, self::CLEANUP_GIT_PROBE_TIMEOUT);
2537-
if ( is_wp_error($dirty) ) {
2538-
return $dirty;
2539-
}
2540-
if ( (int) $dirty > 0 ) {
2541-
return new \WP_Error('dirty_worktree', 'refusing to mark dirty worktree cleanup_eligible from merged-to-default evidence');
2447+
$facts = $this->validate_current_cleanup_worktree(
2448+
$repo,
2449+
$path,
2450+
$branch,
2451+
array(
2452+
'require_clean' => true,
2453+
'dirty_error_message' => 'refusing to mark dirty worktree cleanup_eligible from merged-to-default evidence',
2454+
'unpushed_error_message' => 'refusing to mark worktree with unpushed commits cleanup_eligible from merged-to-default evidence',
2455+
)
2456+
);
2457+
if ( is_wp_error($facts) ) {
2458+
return $facts;
25422459
}
25432460

2544-
$unpushed = $this->count_unpushed_commits($real_path, self::CLEANUP_GIT_PROBE_TIMEOUT);
2545-
if ( is_wp_error($unpushed) ) {
2546-
return $unpushed;
2547-
}
2548-
if ( (int) $unpushed > 0 ) {
2549-
return new \WP_Error('unpushed_commits', 'refusing to mark worktree with unpushed commits cleanup_eligible from merged-to-default evidence');
2550-
}
2461+
$real_path = (string) $facts['real_path'];
2462+
$primary_path = (string) $facts['primary_path'];
2463+
$dirty = (int) $facts['dirty'];
2464+
$unpushed = (int) $facts['unpushed'];
25512465

25522466
$default_ref = $this->resolve_remote_default_ref($primary_path, self::CLEANUP_GIT_PROBE_TIMEOUT);
25532467
if ( ! is_string($default_ref) || '' === $default_ref ) {
@@ -2597,6 +2511,83 @@ private function build_current_merged_to_default_cleanup_evidence( string $handl
25972511
);
25982512
}
25992513

2514+
/**
2515+
* Revalidate the current worktree state before writing cleanup metadata.
2516+
*
2517+
* @param string $repo Repository name.
2518+
* @param string $path Worktree path.
2519+
* @param string|null $expected_branch Expected branch, or null to resolve it from the worktree.
2520+
* @param array<string,mixed> $opts Validation options.
2521+
* @return array<string,mixed>|\WP_Error
2522+
*/
2523+
private function validate_current_cleanup_worktree( string $repo, string $path, ?string $expected_branch = null, array $opts = array() ): array|\WP_Error {
2524+
if ( null !== $expected_branch && in_array($expected_branch, $this->protected_base_branch_names(), true) ) {
2525+
return new \WP_Error('primary_protected_branch', 'refusing to auto-finalize a protected primary branch worktree');
2526+
}
2527+
2528+
$validation = $this->validate_containment($path, $this->workspace_path);
2529+
if ( ! $validation['valid'] ) {
2530+
return new \WP_Error('external_worktree', 'worktree path is outside the workspace root');
2531+
}
2532+
2533+
$real_path = (string) ( $validation['real_path'] ?? '' );
2534+
if ( '' === $real_path || ! is_dir($real_path) ) {
2535+
return new \WP_Error('missing_worktree', 'worktree path no longer exists');
2536+
}
2537+
2538+
$git_marker = rtrim($real_path, '/') . '/.git';
2539+
if ( is_dir($git_marker) ) {
2540+
return new \WP_Error('primary_checkout', 'refusing to mark a primary checkout cleanup_eligible');
2541+
}
2542+
if ( ! is_file($git_marker) ) {
2543+
return new \WP_Error('not_a_worktree', 'worktree marker missing');
2544+
}
2545+
2546+
$current_branch = (string) $this->resolve_worktree_branch_from_head_file($real_path);
2547+
if ( null !== $expected_branch && $expected_branch !== $current_branch ) {
2548+
return new \WP_Error('branch_identity_mismatch', 'worktree branch identity changed before apply');
2549+
}
2550+
if ( null === $expected_branch && '' === $current_branch ) {
2551+
return new \WP_Error('missing_branch_identity', 'worktree branch identity could not be resolved');
2552+
}
2553+
if ( null === $expected_branch && in_array($current_branch, $this->protected_base_branch_names(), true) ) {
2554+
return new \WP_Error('primary_protected_branch', 'refusing to auto-finalize a protected primary branch worktree');
2555+
}
2556+
2557+
$primary_path = $this->get_primary_path($repo);
2558+
$missing_primary_code = (string) ( $opts['missing_primary_code'] ?? 'missing_primary' );
2559+
if ( '' === $primary_path || ! is_dir($primary_path . '/.git') ) {
2560+
return new \WP_Error($missing_primary_code, 'primary checkout missing');
2561+
}
2562+
2563+
$dirty = $this->probe_worktree_dirty_count($real_path, self::CLEANUP_GIT_PROBE_TIMEOUT);
2564+
if ( is_wp_error($dirty) ) {
2565+
return $dirty;
2566+
}
2567+
2568+
$unpushed = $this->count_unpushed_commits($real_path, self::CLEANUP_GIT_PROBE_TIMEOUT);
2569+
if ( is_wp_error($unpushed) ) {
2570+
return $unpushed;
2571+
}
2572+
2573+
if ( ! empty($opts['require_clean']) ) {
2574+
if ( 0 !== (int) $dirty ) {
2575+
return new \WP_Error('dirty_worktree', (string) ( $opts['dirty_error_message'] ?? 'worktree is dirty' ));
2576+
}
2577+
if ( 0 !== (int) $unpushed ) {
2578+
return new \WP_Error('unpushed_commits', (string) ( $opts['unpushed_error_message'] ?? 'worktree has unpushed commits' ));
2579+
}
2580+
}
2581+
2582+
return array(
2583+
'real_path' => $real_path,
2584+
'primary_path' => $primary_path,
2585+
'branch' => null !== $expected_branch ? $expected_branch : $current_branch,
2586+
'dirty' => (int) $dirty,
2587+
'unpushed' => (int) $unpushed,
2588+
);
2589+
}
2590+
26002591
/**
26012592
* Build a skip row for finalized active/no-signal apply.
26022593
*

0 commit comments

Comments
 (0)