Skip to content

Commit 30febf7

Browse files
authored
Fix remote worktree remove for reused handles (#658)
1 parent 038870b commit 30febf7

2 files changed

Lines changed: 58 additions & 8 deletions

File tree

inc/Workspace/RemoteWorkspaceBackend.php

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,11 @@ public function worktree_remove( string $repo_name, string $branch ): array|\WP_
149149
$handle = $repo_name . '@' . $this->branch_slug($branch);
150150
$state = $this->state();
151151
if ( ! isset($state['worktrees'][ $handle ]) ) {
152-
return new \WP_Error('remote_workspace_worktree_not_found', sprintf('Remote workspace worktree "%s" is not registered.', $handle), array( 'status' => 404 ));
152+
$stored_handle = $this->find_worktree_handle_by_repo_branch($state, $repo_name, $branch);
153+
if ( null === $stored_handle ) {
154+
return new \WP_Error('remote_workspace_worktree_not_found', sprintf('Remote workspace worktree "%s" is not registered.', $handle), array( 'status' => 404 ));
155+
}
156+
$handle = $stored_handle;
153157
}
154158

155159
unset($state['worktrees'][ $handle ]);
@@ -163,6 +167,37 @@ public function worktree_remove( string $repo_name, string $branch ): array|\WP_
163167
);
164168
}
165169

170+
/**
171+
* Find the stored worktree handle for a repo/branch pair.
172+
*
173+
* Remote worktree handles can outlive branch changes when an existing
174+
* worktree is reused for a fresh branch. Remove by the current branch should
175+
* still clear that registered row instead of requiring operators to know the
176+
* stale handle slug.
177+
*
178+
* @param array<string,mixed> $state Remote workspace state.
179+
* @param string $repo_name Workspace repo name.
180+
* @param string $branch Current branch name.
181+
* @return string|null Stored handle, if exactly matched.
182+
*/
183+
private function find_worktree_handle_by_repo_branch( array $state, string $repo_name, string $branch ): ?string {
184+
foreach ( (array) ( $state['worktrees'] ?? array() ) as $stored_handle => $worktree ) {
185+
if ( ! is_array($worktree) ) {
186+
continue;
187+
}
188+
if ( $repo_name !== (string) ( $worktree['repo_name'] ?? '' ) ) {
189+
continue;
190+
}
191+
if ( $branch !== (string) ( $worktree['branch'] ?? '' ) ) {
192+
continue;
193+
}
194+
195+
return (string) $stored_handle;
196+
}
197+
198+
return null;
199+
}
200+
166201
/**
167202
* Prune remote worktree state whose primary repo registration disappeared.
168203
*

tests/smoke-remote-workspace-backend.php

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -262,13 +262,28 @@ function update_option( string $key, mixed $value, bool $autoload = true ): bool
262262
$second_worktree = $backend->worktree_add('example', 'fix/remove-me');
263263
$assert('second worktree add succeeds', ! is_wp_error($second_worktree) && 'example@fix-remove-me' === $second_worktree['handle']);
264264

265-
$remove = $backend->worktree_remove('example', 'fix/remove-me');
266-
$assert('worktree remove clears remote runtime state', ! is_wp_error($remove) && 'example@fix-remove-me' === $remove['handle']);
267-
$removed_status = $backend->git_status('example@fix-remove-me');
268-
$assert('removed worktree no longer resolves', is_wp_error($removed_status) && 'remote_workspace_repo_not_found' === $removed_status->get_error_code());
269-
270-
$state = $GLOBALS['dmc_remote_workspace_options']['datamachine_code_remote_workspace_state'];
271-
$state['worktrees']['missing@stale'] = array(
265+
$remove = $backend->worktree_remove('example', 'fix/remove-me');
266+
$assert('worktree remove clears remote runtime state', ! is_wp_error($remove) && 'example@fix-remove-me' === $remove['handle']);
267+
$removed_status = $backend->git_status('example@fix-remove-me');
268+
$assert('removed worktree no longer resolves', is_wp_error($removed_status) && 'remote_workspace_repo_not_found' === $removed_status->get_error_code());
269+
270+
$state = $GLOBALS['dmc_remote_workspace_options']['datamachine_code_remote_workspace_state'];
271+
$state['worktrees']['example@old-reused-handle'] = array(
272+
'repo_name' => 'example',
273+
'repo' => 'chubes4/example',
274+
'branch' => 'fix/current-branch',
275+
'pending_files' => array(),
276+
'changed_files' => array(),
277+
'last_commit_sha' => '',
278+
);
279+
$GLOBALS['dmc_remote_workspace_options']['datamachine_code_remote_workspace_state'] = $state;
280+
$reused_remove = $backend->worktree_remove('example', 'fix/current-branch');
281+
$assert('worktree remove finds reused handle by stored branch', ! is_wp_error($reused_remove) && 'example@old-reused-handle' === $reused_remove['handle']);
282+
$state = $GLOBALS['dmc_remote_workspace_options']['datamachine_code_remote_workspace_state'];
283+
$assert('worktree remove clears reused handle row', ! isset($state['worktrees']['example@old-reused-handle']));
284+
285+
$state = $GLOBALS['dmc_remote_workspace_options']['datamachine_code_remote_workspace_state'];
286+
$state['worktrees']['missing@stale'] = array(
272287
'repo_name' => 'missing',
273288
'repo' => 'chubes4/missing',
274289
'branch' => 'stale',

0 commit comments

Comments
 (0)