Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion inc/Bundle/WorkspacePreloadArtifact.php
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ private function clone_repository( array $repository ): array|\WP_Error {
$input['full'] = $repository['full'];
}

$callback = $this->clone_callback ?: array( WorkspaceAbilities::class, 'cloneRepo' );
$callback = $this->clone_callback ? $this->clone_callback : array( WorkspaceAbilities::class, 'cloneRepo' );
$result = call_user_func( $callback, $input );

if ( is_wp_error( $result ) && 'repo_exists' === $result->get_error_code() ) {
Expand Down
19 changes: 10 additions & 9 deletions inc/Cleanup/DataMachineJobCleanupRunEvidenceStore.php
Original file line number Diff line number Diff line change
Expand Up @@ -158,10 +158,10 @@ private function aggregate_cleanup_child_jobs( array $child_jobs ): array {

$summary['artifact_cleanup']['freed_human'] = $this->format_bytes( $summary['artifact_cleanup']['bytes_reclaimed'] );
$summary['cleanup_items']['freed_human'] = $this->format_bytes( $summary['cleanup_items']['bytes_reclaimed'] );
$summary['children']['batch_job_ids'] = array_values( array_unique( $summary['children']['batch_job_ids'] ) );
$summary['children']['chunk_job_ids'] = array_values( array_unique( $summary['children']['chunk_job_ids'] ) );
$summary['children']['job_ids'] = array_values( array_unique( $summary['children']['job_ids'] ) );
$summary['children']['running'] = (int) $summary['children']['processing'];
$summary['children']['batch_job_ids'] = array_values( array_unique( $summary['children']['batch_job_ids'] ) );
$summary['children']['chunk_job_ids'] = array_values( array_unique( $summary['children']['chunk_job_ids'] ) );
$summary['children']['job_ids'] = array_values( array_unique( $summary['children']['job_ids'] ) );
$summary['children']['running'] = (int) $summary['children']['processing'];

return $summary;
}
Expand Down Expand Up @@ -440,11 +440,12 @@ private function cleanup_run_job_id( string $run_id ): int {
* @return string
*/
private function format_bytes( int $bytes ): string {
$bytes = max( 0, $bytes );
$units = array( 'B', 'KiB', 'MiB', 'GiB', 'TiB' );
$value = (float) $bytes;
$unit = 0;
while ( $value >= 1024 && $unit < count( $units ) - 1 ) {
$bytes = max( 0, $bytes );
$units = array( 'B', 'KiB', 'MiB', 'GiB', 'TiB' );
$max_unit = count( $units ) - 1;
$value = (float) $bytes;
$unit = 0;
while ( $value >= 1024 && $unit < $max_unit ) {
$value /= 1024;
++$unit;
}
Expand Down
52 changes: 26 additions & 26 deletions inc/Handlers/GitHub/GitHub.php
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ private function fetchActionsArtifactItems( array $config, ExecutionContext $con
return array();
}

$payload = $json_files[ $json_file ];
$payload = $json_files[ $json_file ];
$items_payload = $this->selectArtifactItems( $payload, (string) ( $config['items_path'] ?? '' ) );
if ( empty( $items_payload ) ) {
$context->log( 'info', sprintf( 'GitHub: Artifact JSON file %s contained no items.', $json_file ) );
Expand All @@ -414,7 +414,7 @@ private function fetchActionsArtifactItems( array $config, ExecutionContext $con
$head_sha,
$artifact_name,
$json_file,
hash( 'sha256', wp_json_encode( $item, JSON_UNESCAPED_SLASHES ) ?: (string) $index )
hash( 'sha256', wp_json_encode( $item, JSON_UNESCAPED_SLASHES ) ?: (string) $index ) // phpcs:ignore Universal.Operators.DisallowShortTernary.Found -- Avoids double-encoding side effect.
);

$title = (string) ( $item['title'] ?? $item['selector'] ?? $item['kind'] ?? '' );
Expand All @@ -426,18 +426,18 @@ private function fetchActionsArtifactItems( array $config, ExecutionContext $con
'title' => $title,
'content' => wp_json_encode( $item, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES ),
'metadata' => array(
'source_type' => 'github_actions_artifact',
'item_identifier' => $item_identifier,
'original_id' => $item_identifier,
'dedup_key' => $item_identifier,
'original_title' => $title,
'github_repo' => $repo,
'github_type' => 'actions_artifact_items',
'github_head_sha' => $head_sha,
'artifact_name' => $artifact_name,
'artifact_json_file' => $json_file,
'source_type' => 'github_actions_artifact',
'item_identifier' => $item_identifier,
'original_id' => $item_identifier,
'dedup_key' => $item_identifier,
'original_title' => $title,
'github_repo' => $repo,
'github_type' => 'actions_artifact_items',
'github_head_sha' => $head_sha,
'artifact_name' => $artifact_name,
'artifact_json_file' => $json_file,
'artifact_item_index' => (int) $index,
'artifact_item' => $item,
'artifact_item' => $item,
),
);
}
Expand Down Expand Up @@ -579,7 +579,7 @@ private function fetchFiles( array $config, ExecutionContext $context, string $r
$context->log( 'debug', sprintf( 'GitHub: Skipped %s — no file content returned.', $file['path'] ) );
continue;
}
$guid = sprintf( 'github_%s_files_%s', $repo, $file['sha'] );
$guid = sprintf( 'github_%s_files_%s', $repo, $file['sha'] );

$eligible_items[] = array(
'title' => $file_data['path'],
Expand Down Expand Up @@ -619,10 +619,10 @@ private function fetchFiles( array $config, ExecutionContext $context, string $r
* @return array DataPacket-compatible array or empty on no data.
*/
private function fetchIssuesOrPulls( array $config, ExecutionContext $context, string $repo, string $data_source ): array {
$state = $config['state'] ?? 'open';
$labels = $config['labels'] ?? '';
$issue_number = (int) ( $config['issue_number'] ?? 0 );
$pull_number = (int) ( $config['pull_number'] ?? 0 );
$state = $config['state'] ?? 'open';
$labels = $config['labels'] ?? '';
$issue_number = (int) ( $config['issue_number'] ?? 0 );
$pull_number = (int) ( $config['pull_number'] ?? 0 );

// Targeted single-item fetch by issue_number / pull_number.
if ( $issue_number > 0 || $pull_number > 0 ) {
Expand Down Expand Up @@ -659,11 +659,11 @@ private function fetchIssuesOrPulls( array $config, ExecutionContext $context, s

$context->log( 'info', sprintf( 'GitHub: Found %d %s.', count( $items ), $data_source ) );

$search = $config['search'] ?? '';
$exclude_keywords = $config['exclude_keywords'] ?? '';
$exclude_labels_raw = $config['exclude_labels'] ?? '';
$timeframe_limit = $config['timeframe_limit'] ?? 'all_time';
$eligible_items = array();
$search = $config['search'] ?? '';
$exclude_keywords = $config['exclude_keywords'] ?? '';
$exclude_labels_raw = $config['exclude_labels'] ?? '';
$timeframe_limit = $config['timeframe_limit'] ?? 'all_time';
$eligible_items = array();

$exclude_labels = array();
if ( ! empty( $exclude_labels_raw ) ) {
Expand Down Expand Up @@ -691,7 +691,7 @@ private function fetchIssuesOrPulls( array $config, ExecutionContext $context, s
static fn( $label ) => strtolower( (string) $label ),
(array) $item['labels']
);
$hit = array_values( array_intersect( $item_labels_lower, $exclude_labels ) );
$hit = array_values( array_intersect( $item_labels_lower, $exclude_labels ) );
if ( ! empty( $hit ) ) {
$context->log( 'debug', sprintf(
'GitHub: skipping #%d — excluded by label(s): %s',
Expand Down Expand Up @@ -802,8 +802,8 @@ private function fetchSingleIssueOrPull(
$context->log( 'info', 'GitHub: targeted fetch ignores list filters: ' . implode( ', ', $ignored_fields ) );
}

$number = $issue_number > 0 ? $issue_number : $pull_number;
$expected_state = $config['state'] ?? 'open';
$number = $issue_number > 0 ? $issue_number : $pull_number;
$expected_state = $config['state'] ?? 'open';

if ( 'pulls' === $data_source ) {
$result = GitHubAbilities::getPull( array(
Expand Down
10 changes: 7 additions & 3 deletions inc/Runtime/WordPressRuntimeInspector.php
Original file line number Diff line number Diff line change
Expand Up @@ -135,10 +135,14 @@ public function read( array $input ): array|\WP_Error {

$size = (int) filesize( $resolved['real_path'] );
if ( $size > $max_size ) {
return new \WP_Error( 'datamachine_runtime_file_too_large', 'File exceeds the requested max_size.', array( 'path' => $resolved['relative_path'], 'size' => $size, 'max_size' => $max_size ) );
return new \WP_Error( 'datamachine_runtime_file_too_large', 'File exceeds the requested max_size.', array(
'path' => $resolved['relative_path'],
'size' => $size,
'max_size' => $max_size,
) );
}

$sample = @file_get_contents( $resolved['real_path'], false, null, 0, min( $size, 8192 ) );
$sample = @file_get_contents( $resolved['real_path'], false, null, 0, min( $size, 8192 ) ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents,WordPress.PHP.NoSilencedErrors.Discouraged -- Path is validated by resolveReadablePath().
if ( false === $sample ) {
return new \WP_Error( 'datamachine_runtime_unreadable', 'File is not readable.' );
}
Expand All @@ -147,7 +151,7 @@ public function read( array $input ): array|\WP_Error {
return new \WP_Error( 'datamachine_runtime_binary_file', 'Binary file reading is denied.', array( 'path' => $resolved['relative_path'] ) );
}

$lines = @file( $resolved['real_path'], FILE_IGNORE_NEW_LINES );
$lines = @file( $resolved['real_path'], FILE_IGNORE_NEW_LINES ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_file,WordPress.PHP.NoSilencedErrors.Discouraged -- Path is validated by resolveReadablePath().
if ( false === $lines ) {
return new \WP_Error( 'datamachine_runtime_unreadable', 'File is not readable.' );
}
Expand Down
4 changes: 4 additions & 0 deletions inc/Storage/CleanupRunRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,9 @@ public function add_items( string $run_id, array $items ): int|\WP_Error {
public function get_run( string $run_id ): ?array {
global $wpdb;

// phpcs:disable WordPress.DB.PreparedSQL -- Table name from $wpdb->prefix, not user input.
$row = $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM ' . CleanupSchema::runs_table() . ' WHERE run_id = %s', $run_id ), ARRAY_A );
// phpcs:enable WordPress.DB.PreparedSQL
return is_array( $row ) ? $this->decode_run( $row ) : null;
}

Expand All @@ -117,7 +119,9 @@ public function get_run( string $run_id ): ?array {
public function get_items( string $run_id ): array {
global $wpdb;

// phpcs:disable WordPress.DB.PreparedSQL -- Table name from $wpdb->prefix, not user input.
$rows = $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM ' . CleanupSchema::items_table() . ' WHERE run_id = %s ORDER BY id ASC', $run_id ), ARRAY_A );
// phpcs:enable WordPress.DB.PreparedSQL
return array_map( fn( $row ) => $this->decode_item( (array) $row ), is_array( $rows ) ? $rows : array() );
}

Expand Down
4 changes: 3 additions & 1 deletion inc/Storage/WorktreeInventoryRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -166,7 +166,9 @@ public function list( ?string $repo = null ): array {

$table = self::table_name();
if ( null !== $repo && '' !== trim( $repo ) && method_exists( $wpdb, 'prepare' ) ) {
// phpcs:disable WordPress.DB.PreparedSQL -- Table name from $wpdb->prefix, not user input.
$sql = $wpdb->prepare( "SELECT * FROM {$table} WHERE repo = %s ORDER BY handle ASC", $repo );
// phpcs:enable WordPress.DB.PreparedSQL
} else {
$sql = "SELECT * FROM {$table} ORDER BY handle ASC";
}
Expand Down Expand Up @@ -253,7 +255,7 @@ private function normalize_row( array $row ): array {
* @return array<string,mixed>
*/
private function decode_row( array $row ): array {
$decoded = isset( $row['metadata'] ) && is_string( $row['metadata'] ) ? json_decode( $row['metadata'], true ) : null;
$decoded = isset( $row['metadata'] ) && is_string( $row['metadata'] ) ? json_decode( $row['metadata'], true ) : null;
$row['metadata'] = is_array( $decoded ) ? $decoded : null;
foreach ( array( 'id', 'is_primary', 'dirty_count', 'unpushed_count', 'artifact_count', 'artifact_size_bytes', 'size_bytes', 'missing_path' ) as $key ) {
if ( isset( $row[ $key ] ) ) {
Expand Down
36 changes: 25 additions & 11 deletions inc/Workspace/CleanupRunService.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public function __construct(
private ?Workspace $workspace = null
) {
$this->repository ??= new CleanupRunRepository();
$this->workspace ??= new Workspace();
$this->workspace ??= new Workspace();
}

/**
Expand Down Expand Up @@ -55,9 +55,9 @@ public function plan( array $opts = array() ): array|\WP_Error {

$plan['run_id'] = $run_id;
$plan['cleanup_storage'] = array(
'type' => 'database',
'item_count' => $inserted,
'plan_id' => $plan['plan_id'] ?? null,
'type' => 'database',
'item_count' => $inserted,
'plan_id' => $plan['plan_id'] ?? null,
'escape_hatch' => 'filesystem apply-plan import remains available on lower-level worktree commands only',
);

Expand All @@ -77,7 +77,10 @@ public function apply( string $run_id, array $opts = array() ): array|\WP_Error
return new \WP_Error( 'cleanup_run_not_found', sprintf( 'Cleanup run not found: %s', $run_id ), array( 'status' => 404 ) );
}

$this->repository->update_run( $run_id, array( 'status' => 'applying', 'started_at' => gmdate( 'Y-m-d H:i:s' ) ) );
$this->repository->update_run( $run_id, array(
'status' => 'applying',
'started_at' => gmdate( 'Y-m-d H:i:s' ),
) );

$items = $this->repository->get_items( $run_id );
$artifact_rows = $this->pending_rows_of_type( $items, 'artifact_cleanup' );
Expand Down Expand Up @@ -105,7 +108,11 @@ public function apply( string $run_id, array $opts = array() ): array|\WP_Error
$this->record_apply_result( $worktree_rows, $results['worktree_removal'], 'removed' );
}

$this->repository->update_run( $run_id, array( 'status' => 'completed', 'completed_at' => gmdate( 'Y-m-d H:i:s' ), 'summary' => $this->status( $run_id )['summary'] ?? array() ) );
$this->repository->update_run( $run_id, array(
'status' => 'completed',
'completed_at' => gmdate( 'Y-m-d H:i:s' ),
'summary' => $this->status( $run_id )['summary'] ?? array(),
) );

return array(
'success' => true,
Expand Down Expand Up @@ -138,8 +145,8 @@ public function status( string $run_id ): array|\WP_Error {
'pending_or_failed' => 0,
);
foreach ( $items as $item ) {
$status = (string) ( $item['status'] ?? 'unknown' );
$type = (string) ( $item['item_type'] ?? 'unknown' );
$status = (string) ( $item['status'] ?? 'unknown' );
$type = (string) ( $item['item_type'] ?? 'unknown' );
$summary['items_by_status'][ $status ] = ( $summary['items_by_status'][ $status ] ?? 0 ) + 1;
$summary['items_by_type'][ $type ] = ( $summary['items_by_type'][ $type ] ?? 0 ) + 1;
$summary['bytes_reclaimed'] += max( 0, (int) ( $item['bytes_reclaimed'] ?? 0 ) );
Expand Down Expand Up @@ -190,7 +197,10 @@ public function cancel( string $run_id ): array|\WP_Error {
$this->repository->update_item( (int) $item['id'], array( 'status' => 'cancelled' ) );
}
}
$this->repository->update_run( $run_id, array( 'status' => 'cancelled', 'completed_at' => gmdate( 'Y-m-d H:i:s' ) ) );
$this->repository->update_run( $run_id, array(
'status' => 'cancelled',
'completed_at' => gmdate( 'Y-m-d H:i:s' ),
) );
return $this->status( $run_id );
}

Expand Down Expand Up @@ -227,13 +237,17 @@ private function plan_items( array $plan ): array {
}

private function pending_rows_of_type( array $items, string $type ): array {
return array_values( array_filter( $items, fn( $item ) => $type === (string) ( $item['item_type'] ?? '' ) && in_array( (string) ( $item['status'] ?? '' ), array( 'pending', 'failed' ), true ) ) );
return array_values( array_filter( $items, fn( $item ) => (string) ( $item['item_type'] ?? '' ) === $type && in_array( (string) ( $item['status'] ?? '' ), array( 'pending', 'failed' ), true ) ) );
}

private function record_apply_result( array $items, mixed $result, string $applied_key ): void {
if ( $result instanceof \WP_Error ) {
foreach ( $items as $item ) {
$this->repository->update_item( (int) $item['id'], array( 'status' => 'failed', 'reason_code' => $result->get_error_code(), 'reason' => $result->get_error_message() ) );
$this->repository->update_item( (int) $item['id'], array(
'status' => 'failed',
'reason_code' => $result->get_error_code(),
'reason' => $result->get_error_message(),
) );
}
return;
}
Expand Down
7 changes: 3 additions & 4 deletions inc/Workspace/WorkspaceArtifactCleanup.php
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ public function worktree_cleanup_artifacts( array $opts = array() ): array|\WP_E
'reason' => sprintf( 'failed to remove artifact %s: %s', (string) ( $artifact['path'] ?? '' ), $remove->get_error_message() ),
'artifacts' => array( $artifact ),
);
$failed = true;
$failed = true;
break;
}

Expand Down Expand Up @@ -417,7 +417,7 @@ private function build_worktree_artifact_cleanup_summary( array $candidates, arr
foreach ( $candidates as $row ) {
$repo = (string) ( $row['repo'] ?? 'unknown' );
foreach ( (array) ( $row['artifacts'] ?? array() ) as $artifact ) {
$bytes = (int) ( is_array( $artifact ) ? ( $artifact['size_bytes'] ?? 0 ) : 0 );
$bytes = (int) ( is_array( $artifact ) ? ( $artifact['size_bytes'] ?? 0 ) : 0 );
$would_bytes += max( 0, $bytes );
++$would_count;
$artifact_by_repo[ $repo ] = ( $artifact_by_repo[ $repo ] ?? 0 ) + max( 0, $bytes );
Expand Down Expand Up @@ -522,7 +522,7 @@ private function scope_worktree_artifact_cleanup_to_plan( array $planned_candida
}
}

$skip = $complete ? array(
$skip = $complete ? array(
'handle' => $handle,
'repo' => (string) ( $plan_row['repo'] ?? '' ),
'branch' => (string) ( $plan_row['branch'] ?? '' ),
Expand Down Expand Up @@ -674,5 +674,4 @@ private function is_active_studio_symlink_target( string $worktree_path ): bool

return false;
}

}
Loading
Loading