Files
2026-09-06 15:19:25 +01:00

50 lines
1.5 KiB
Go

package service
import (
"errors"
"github.com/kandev/kandev/internal/worktree"
)
const TaskDeleteDirtyWorktreeErrorCode = "task_delete_dirty_worktree"
// TaskDeleteDirtyWorktreeError is returned before task deletion when one or
// more owned worktrees contain local changes and the caller did not grant
// discard consent.
type TaskDeleteDirtyWorktreeError struct {
DirtyWorktrees []worktree.DirtyWorktree
}
func (e *TaskDeleteDirtyWorktreeError) Error() string {
return "task worktree contains local changes"
}
func newTaskDeleteDirtyWorktreeError(dirty []worktree.DirtyWorktree) error {
if len(dirty) == 0 {
return nil
}
copyDirty := make([]worktree.DirtyWorktree, len(dirty))
copy(copyDirty, dirty)
for i := range copyDirty {
copyDirty[i].DirtyFiles = append([]string(nil), copyDirty[i].DirtyFiles...)
}
return &TaskDeleteDirtyWorktreeError{DirtyWorktrees: copyDirty}
}
func isDirtyWorktreeCleanupError(err error) bool {
// A dirty refusal is terminal only when it is the sole cleanup failure.
// Joined errors can also contain retryable environment or remote cleanup
// failures that must keep their retry schedule.
if err == nil {
return false
}
if joined, ok := err.(interface{ Unwrap() []error }); ok {
causes := joined.Unwrap()
return len(causes) == 1 && isDirtyWorktreeCleanupError(causes[0])
}
if wrapped, ok := err.(interface{ Unwrap() error }); ok {
return isDirtyWorktreeCleanupError(wrapped.Unwrap())
}
return errors.Is(err, worktree.ErrDirtyWorktreeCleanup)
}