@@ -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 *
0 commit comments