Skip to content

Commit 09eca91

Browse files
committed
fix: add read-only context repositories
1 parent c474155 commit 09eca91

10 files changed

Lines changed: 732 additions & 22 deletions

inc/Abilities/WorkspaceAbilities.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ private function registerAbilities(): void {
113113
),
114114
'type' => array(
115115
'type' => 'string',
116-
'enum' => array( 'primary', 'worktree' ),
117-
'description' => 'Optional checkout type filter. Use "primary" for base checkouts or "worktree" for branch worktrees.',
116+
'enum' => array( 'primary', 'worktree', 'context' ),
117+
'description' => 'Optional checkout type filter. Use "primary" for base checkouts, "worktree" for branch worktrees, or "context" for read-only context repositories.',
118118
),
119119
),
120120
),

inc/Tools/WorkspaceTools.php

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1035,8 +1035,8 @@ public function getListDefinition(): array
10351035
),
10361036
'type' => array(
10371037
'type' => 'string',
1038-
'enum' => array( 'primary', 'worktree' ),
1039-
'description' => 'Optional checkout type filter. Use "primary" for base checkouts or "worktree" for branch worktrees.',
1038+
'enum' => array( 'primary', 'worktree', 'context' ),
1039+
'description' => 'Optional checkout type filter. Use "primary" for base checkouts, "worktree" for branch worktrees, or "context" for read-only context repositories.',
10401040
),
10411041
),
10421042
'required' => array(),

inc/Workspace/RemoteWorkspaceBackend.php

Lines changed: 105 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,11 @@ public function worktree_prune(): array {
198198
* @return array<string,mixed>|\WP_Error
199199
*/
200200
public function read_file( string $handle, string $path, int $max_size, ?int $offset = null, ?int $limit = null ): array|\WP_Error {
201+
$policy_error = WorkspaceAliasResolver::read_error_if_disallowed($handle, $path);
202+
if ( null !== $policy_error ) {
203+
return $policy_error;
204+
}
205+
201206
$context = $this->resolve_handle($handle);
202207
if ( is_wp_error($context) ) {
203208
return $context;
@@ -234,13 +239,18 @@ public function read_file( string $handle, string $path, int $max_size, ?int $of
234239
$result_content = implode("\n", $lines);
235240
}
236241

237-
return array(
242+
$result = array(
238243
'success' => true,
239244
'backend' => 'github_api',
240245
'content' => $result_content,
241246
'path' => $path,
242247
'size' => $size,
243248
);
249+
if ( WorkspaceAliasResolver::is_context_repository($handle) ) {
250+
$result['workspace_policy'] = WorkspaceAliasResolver::policy_attestation($handle);
251+
}
252+
253+
return $result;
244254
}
245255

246256
/**
@@ -249,6 +259,11 @@ public function read_file( string $handle, string $path, int $max_size, ?int $of
249259
* @return array<string,mixed>|\WP_Error
250260
*/
251261
public function list_directory( string $handle, ?string $path = null ): array|\WP_Error {
262+
$policy_error = WorkspaceAliasResolver::read_error_if_disallowed($handle, $path ?? '');
263+
if ( null !== $policy_error ) {
264+
return $policy_error;
265+
}
266+
252267
$context = $this->resolve_handle($handle);
253268
if ( is_wp_error($context) ) {
254269
return $context;
@@ -287,13 +302,20 @@ public function list_directory( string $handle, ?string $path = null ): array|\W
287302
);
288303
}
289304

290-
return array(
305+
$entries = WorkspaceAliasResolver::filter_context_entries($handle, '' === $prefix ? '/' : $prefix, $entries);
306+
307+
$result = array(
291308
'success' => true,
292309
'backend' => 'github_api',
293310
'repo' => $handle,
294311
'path' => '' === $prefix ? '/' : $prefix,
295312
'entries' => $entries,
296313
);
314+
if ( WorkspaceAliasResolver::is_context_repository($handle) ) {
315+
$result['workspace_policy'] = WorkspaceAliasResolver::policy_attestation($handle);
316+
}
317+
318+
return $result;
297319
}
298320

299321
/**
@@ -302,6 +324,11 @@ public function list_directory( string $handle, ?string $path = null ): array|\W
302324
* @return array<string,mixed>|\WP_Error
303325
*/
304326
public function grep( string $handle, string $pattern, ?string $path = null, ?string $include_pattern = null, int $max_results = 100, int $context_lines = 0 ): array|\WP_Error {
327+
$policy_error = WorkspaceAliasResolver::read_error_if_disallowed($handle, $path ?? '');
328+
if ( null !== $policy_error ) {
329+
return $policy_error;
330+
}
331+
305332
$context = $this->resolve_handle($handle);
306333
if ( is_wp_error($context) ) {
307334
return $context;
@@ -354,6 +381,10 @@ public function grep( string $handle, string $pattern, ?string $path = null, ?st
354381

355382
foreach ( $files as $file ) {
356383
$file_path = (string) ( $file['path'] ?? '' );
384+
$context_policy = WorkspaceAliasResolver::context_policy_for($handle);
385+
if ( null !== $context_policy && ! WorkspaceAliasResolver::path_allowed_by_policy($file_path, $context_policy) ) {
386+
continue;
387+
}
357388
if ( '' === $file_path || isset($seen[ $file_path ]) || ! $this->path_matches_include($file_path, $include_pattern) ) {
358389
continue;
359390
}
@@ -380,7 +411,7 @@ public function grep( string $handle, string $pattern, ?string $path = null, ?st
380411
}
381412
}
382413

383-
return array(
414+
$result = array(
384415
'success' => true,
385416
'backend' => 'github_api',
386417
'repo' => $handle,
@@ -390,6 +421,11 @@ public function grep( string $handle, string $pattern, ?string $path = null, ?st
390421
'count' => count($matches),
391422
'truncated' => count($matches) >= $max_results,
392423
);
424+
if ( WorkspaceAliasResolver::is_context_repository($handle) ) {
425+
$result['workspace_policy'] = WorkspaceAliasResolver::policy_attestation($handle);
426+
}
427+
428+
return $result;
393429
}
394430

395431
/**
@@ -398,6 +434,10 @@ public function grep( string $handle, string $pattern, ?string $path = null, ?st
398434
* @return array<string,mixed>|\WP_Error
399435
*/
400436
public function write_file( string $handle, string $path, string $content ): array|\WP_Error {
437+
if ( WorkspaceAliasResolver::is_context_repository($handle) ) {
438+
return WorkspaceAliasResolver::mutation_error($handle, 'write');
439+
}
440+
401441
$context = $this->resolve_handle($handle);
402442
if ( is_wp_error($context) ) {
403443
return $context;
@@ -428,6 +468,10 @@ public function write_file( string $handle, string $path, string $content ): arr
428468
* @return array<string,mixed>|\WP_Error
429469
*/
430470
public function edit_file( string $handle, string $path, string $old_string, string $new_string, bool $replace_all = false ): array|\WP_Error {
471+
if ( WorkspaceAliasResolver::is_context_repository($handle) ) {
472+
return WorkspaceAliasResolver::mutation_error($handle, 'edit');
473+
}
474+
431475
$context = $this->resolve_handle($handle);
432476
if ( is_wp_error($context) ) {
433477
return $context;
@@ -490,12 +534,13 @@ public function show( string $handle ): array|\WP_Error {
490534

491535
$files = array_values(array_unique(array_values( (array) $context['changed_files'])));
492536

493-
return array(
537+
$result = array(
494538
'success' => true,
495539
'backend' => 'github_api',
496540
'name' => $handle,
497541
'repo' => $context['repo_name'],
498-
'is_worktree' => isset($context['branch']) && '' !== (string) $context['branch'],
542+
'is_worktree' => empty($context['read_only_context']) && isset($context['branch']) && '' !== (string) $context['branch'],
543+
'is_context' => ! empty($context['read_only_context']),
499544
'path' => 'github://' . $context['repo'] . ( '' !== (string) $context['branch']
500545
? '#' . $context['branch']
501546
: '' ),
@@ -505,6 +550,11 @@ public function show( string $handle ): array|\WP_Error {
505550
'dirty' => count($files),
506551
'files' => $files,
507552
);
553+
if ( WorkspaceAliasResolver::is_context_repository($handle) ) {
554+
$result['workspace_policy'] = WorkspaceAliasResolver::policy_attestation($handle);
555+
}
556+
557+
return $result;
508558
}
509559

510560
/**
@@ -514,6 +564,10 @@ public function show( string $handle ): array|\WP_Error {
514564
*/
515565
public function git_diff( string $handle, ?string $from = null, ?string $to = null, bool $staged = false, ?string $path = null ): array|\WP_Error {
516566
unset($staged);
567+
$policy_error = WorkspaceAliasResolver::read_error_if_disallowed($handle, $path ?? '');
568+
if ( null !== $policy_error ) {
569+
return $policy_error;
570+
}
517571

518572
$context = $this->resolve_handle($handle);
519573
if ( is_wp_error($context) ) {
@@ -549,13 +603,18 @@ public function git_diff( string $handle, ?string $from = null, ?string $to = nu
549603
$diff .= $this->build_unified_file_diff($changed_path, $old_content, (string) $new_content);
550604
}
551605

552-
return array(
606+
$result = array(
553607
'success' => true,
554608
'backend' => 'github_api',
555609
'name' => $handle,
556610
'repo' => $context['repo_name'],
557611
'diff' => $diff,
558612
);
613+
if ( WorkspaceAliasResolver::is_context_repository($handle) ) {
614+
$result['workspace_policy'] = WorkspaceAliasResolver::policy_attestation($handle);
615+
}
616+
617+
return $result;
559618
}
560619

561620
/**
@@ -570,19 +629,25 @@ public function git_status( string $handle ): array|\WP_Error {
570629
}
571630

572631
$files = array_values(array_unique(array_values( (array) $context['changed_files'])));
573-
return array(
632+
$result = array(
574633
'success' => true,
575634
'backend' => 'github_api',
576635
'name' => $handle,
577636
'repo' => $context['repo_name'],
578-
'is_worktree' => true,
637+
'is_worktree' => empty($context['read_only_context']),
638+
'is_context' => ! empty($context['read_only_context']),
579639
'path' => 'github://' . $context['repo'] . '#' . $context['branch'],
580640
'branch' => $context['branch'],
581641
'remote' => 'https://github.com/' . $context['repo'] . '.git',
582642
'commit' => '' !== $context['last_commit_sha'] ? $context['last_commit_sha'] : null,
583643
'dirty' => count($files),
584644
'files' => $files,
585645
);
646+
if ( WorkspaceAliasResolver::is_context_repository($handle) ) {
647+
$result['workspace_policy'] = WorkspaceAliasResolver::policy_attestation($handle);
648+
}
649+
650+
return $result;
586651
}
587652

588653
/**
@@ -591,6 +656,10 @@ public function git_status( string $handle ): array|\WP_Error {
591656
* @return array<string,mixed>|\WP_Error
592657
*/
593658
public function git_add( string $handle, array $paths ): array|\WP_Error {
659+
if ( WorkspaceAliasResolver::is_context_repository($handle) ) {
660+
return WorkspaceAliasResolver::mutation_error($handle, 'git add');
661+
}
662+
594663
$context = $this->resolve_handle($handle);
595664
if ( is_wp_error($context) ) {
596665
return $context;
@@ -611,6 +680,10 @@ public function git_add( string $handle, array $paths ): array|\WP_Error {
611680
* @return array<string,mixed>|\WP_Error
612681
*/
613682
public function git_commit( string $handle, string $message ): array|\WP_Error {
683+
if ( WorkspaceAliasResolver::is_context_repository($handle) ) {
684+
return WorkspaceAliasResolver::mutation_error($handle, 'git commit');
685+
}
686+
614687
$context = $this->resolve_handle($handle);
615688
if ( is_wp_error($context) ) {
616689
return $context;
@@ -1103,6 +1176,10 @@ private function group_diff_hunks( array $ops, int $context_lines ): array {
11031176
* @return array<string,mixed>|\WP_Error
11041177
*/
11051178
public function git_push( string $handle, string $remote = 'origin', ?string $branch = null ): array|\WP_Error {
1179+
if ( WorkspaceAliasResolver::is_context_repository($handle) ) {
1180+
return WorkspaceAliasResolver::mutation_error($handle, 'git push');
1181+
}
1182+
11061183
$context = $this->resolve_handle($handle);
11071184
if ( is_wp_error($context) ) {
11081185
return $context;
@@ -1131,6 +1208,26 @@ public function git_push( string $handle, string $remote = 'origin', ?string $br
11311208
* @return array<string,mixed>
11321209
*/
11331210
private function resolve_handle( string $handle ): array|\WP_Error {
1211+
$context_policy = WorkspaceAliasResolver::context_policy_for($handle);
1212+
if ( null !== $context_policy ) {
1213+
$repo = (string) ( $context_policy['repo'] ?? '' );
1214+
if ( '' === $repo ) {
1215+
$repo = (string) ( $context_policy['target'] ?? $handle );
1216+
}
1217+
1218+
return array(
1219+
'handle' => (string) $context_policy['alias'],
1220+
'repo_name' => (string) $context_policy['alias'],
1221+
'repo' => $repo,
1222+
'branch' => (string) ( $context_policy['ref'] ?? '' ),
1223+
'read_ref' => (string) ( $context_policy['ref'] ?? '' ),
1224+
'pending_files' => array(),
1225+
'changed_files' => array(),
1226+
'last_commit_sha' => '',
1227+
'read_only_context' => true,
1228+
);
1229+
}
1230+
11341231
$handle = $this->resolve_alias($handle);
11351232
$state = $this->state();
11361233
if ( isset($state['worktrees'][ $handle ]) ) {

0 commit comments

Comments
 (0)