Skip to content

Commit b3ff8ad

Browse files
authored
fix: batch dirty path classification (#378)
1 parent 5e6789f commit b3ff8ad

2 files changed

Lines changed: 49 additions & 1 deletion

File tree

inc/Workspace/Workspace.php

Lines changed: 48 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6950,8 +6950,9 @@ private function build_dirty_unpushed_upstream_equivalence_evidence( string $pri
69506950
$evidence['path_inspection_truncated'] = count( $paths ) > count( $inspect_paths );
69516951

69526952
$classification_started = microtime( true );
6953+
$classifications = $this->classify_dirty_paths_against_default( $primary_path, $wt_path, $default_ref, $inspect_paths );
69536954
foreach ( $inspect_paths as $path ) {
6954-
$classification = $this->classify_dirty_path_against_default( $primary_path, $wt_path, $default_ref, $path );
6955+
$classification = $classifications[ $path ] ?? $this->classify_dirty_path_against_default( $primary_path, $wt_path, $default_ref, $path );
69556956
$bucket = $classification['bucket'];
69566957
if ( isset( $evidence['dirty_paths'][ $bucket ] ) ) {
69576958
++$evidence['dirty_paths'][ $bucket ];
@@ -6975,6 +6976,52 @@ private function build_dirty_unpushed_upstream_equivalence_evidence( string $pri
69756976
return $evidence;
69766977
}
69776978

6979+
/**
6980+
* Classify dirty paths against the remote default branch with batched git probes.
6981+
*
6982+
* @param string $primary_path Primary checkout path.
6983+
* @param string $wt_path Worktree path.
6984+
* @param string $default_ref Remote default ref.
6985+
* @param array<int,string> $paths Repository-relative paths.
6986+
* @return array<string,array<string,string>> Classifications keyed by path.
6987+
*/
6988+
private function classify_dirty_paths_against_default( string $primary_path, string $wt_path, string $default_ref, array $paths ): array {
6989+
$paths = array_values( array_unique( array_filter( array_map( 'strval', $paths ), fn( $path ) => '' !== $path ) ) );
6990+
if ( array() === $paths ) {
6991+
return array();
6992+
}
6993+
6994+
$path_args = implode( ' ', array_map( 'escapeshellarg', $paths ) );
6995+
$existing = $this->run_git( $primary_path, sprintf( 'ls-tree -r --name-only %s -- %s', escapeshellarg( $default_ref ), $path_args ), self::CLEANUP_GIT_PROBE_TIMEOUT );
6996+
$changed = $this->run_git( $wt_path, sprintf( 'diff --name-only %s -- %s', escapeshellarg( $default_ref ), $path_args ), self::CLEANUP_GIT_PROBE_TIMEOUT );
6997+
if ( is_wp_error( $existing ) || is_wp_error( $changed ) || $this->is_git_timeout_error( $existing ) || $this->is_git_timeout_error( $changed ) ) {
6998+
return array();
6999+
}
7000+
7001+
$existing_set = array_fill_keys( array_values( array_filter( array_map( 'trim', explode( "\n", (string) ( $existing['output'] ?? '' ) ) ) ) ), true );
7002+
$changed_set = array_fill_keys( array_values( array_filter( array_map( 'trim', explode( "\n", (string) ( $changed['output'] ?? '' ) ) ) ) ), true );
7003+
$out = array();
7004+
foreach ( $paths as $path ) {
7005+
$kind = $this->is_generated_or_artifact_path( $path ) ? 'generated_or_artifact' : 'source_like';
7006+
if ( ! isset( $existing_set[ $path ] ) ) {
7007+
$out[ $path ] = array(
7008+
'path' => $path,
7009+
'bucket' => 'absent_on_default',
7010+
'kind' => $kind,
7011+
);
7012+
continue;
7013+
}
7014+
7015+
$out[ $path ] = array(
7016+
'path' => $path,
7017+
'bucket' => isset( $changed_set[ $path ] ) ? 'different_from_default' : 'identical_to_default',
7018+
'kind' => $kind,
7019+
);
7020+
}
7021+
7022+
return $out;
7023+
}
7024+
69787025
/**
69797026
* Derive an operator-facing effective status for dirty/unpushed evidence.
69807027
*

tests/smoke-worktree-metadata-reconcile.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -526,6 +526,7 @@ function size_format( $bytes ): string {
526526
$assert( true, isset( $active_rows['demo@dirty-active']['probe_timings_ms']['upstream_equivalence'] ), 'active/no-signal rows include upstream equivalence probe timing' );
527527
$assert( true, isset( $active_rows['demo@dirty-active']['upstream_equivalence']['probe_timings_ms']['git_cherry'] ), 'upstream equivalence includes git cherry probe timing' );
528528
$assert( true, isset( $active_rows['demo@dirty-active']['upstream_equivalence']['probe_timings_ms']['dirty_path_classification'] ), 'upstream equivalence includes dirty path classification timing' );
529+
$assert( true, (int) ( $active_rows['demo@dirty-active']['upstream_equivalence']['dirty_paths']['inspected'] ?? 0 ) >= 1, 'batched dirty path classification preserves inspected path count' );
529530
$budgeted_active_report = $ws->worktree_active_no_signal_report( array( 'limit' => 20, 'offset' => 0, 'internal_budget_label' => '1s', 'internal_budget_seconds' => 1, 'internal_budget_started' => microtime( true ) - 1 ) );
530531
$assert( true, ! is_wp_error( $budgeted_active_report ) && ( $budgeted_active_report['success'] ?? false ), 'budgeted active/no-signal report succeeds' );
531532
$assert( true, (bool) ( $budgeted_active_report['pagination']['partial'] ?? false ), 'budgeted active/no-signal report returns partial pagination' );

0 commit comments

Comments
 (0)