feat: remote tab in task-create dialog — multi-row GitHub repo picker (#1116)

This commit is contained in:
Carlos Florêncio
2026-05-30 15:47:02 +01:00
committed by GitHub
parent d288bd6a6d
commit d66d9e6f67
81 changed files with 8990 additions and 1441 deletions
@@ -289,9 +289,13 @@ func (s *Service) createTaskRepositories(ctx context.Context, taskID, workspaceI
// Multi-repo validation: each repository may appear at most once per task.
// Without this guard the unique (task_id, repository_id) constraint on
// task_repositories would surface as an opaque DB error mid-loop, leaving
// some rows already inserted.
// some rows already inserted. Name the repository (owner/name) instead of
// the raw UUID so the create-task dialog's error toast is human-readable —
// two PR URLs of the same repo (e.g. /pull/1116 and /pull/1117) collapse
// to the same repositoryID and hit this path.
if seen[repositoryID] {
return fmt.Errorf("repository %q listed more than once for the task", repositoryID)
label := s.repoDisplayLabel(ctx, repoInput, repositoryID)
return fmt.Errorf("repository %q is listed more than once for this task", label)
}
seen[repositoryID] = true
metadata := make(map[string]interface{})
@@ -314,6 +318,28 @@ func (s *Service) createTaskRepositories(ctx context.Context, taskID, workspaceI
return nil
}
// repoDisplayLabel returns a human-readable label for a repository to surface
// in the duplicate-repository error. It prefers owner/name parsed from the
// input's GitHub URL, then the resolved repo entity's owner/name (or bare
// name), and finally falls back to the repositoryID so the message is never
// empty. Best-effort: lookup failures degrade to the next fallback.
func (s *Service) repoDisplayLabel(ctx context.Context, repoInput TaskRepositoryInput, repositoryID string) string {
if repoInput.GitHubURL != "" {
if owner, name, err := parseGitHubRepoURL(repoInput.GitHubURL); err == nil {
return owner + "/" + name
}
}
if repo, err := s.repoEntities.GetRepository(ctx, repositoryID); err == nil && repo != nil {
if repo.ProviderOwner != "" && repo.ProviderName != "" {
return repo.ProviderOwner + "/" + repo.ProviderName
}
if repo.Name != "" {
return repo.Name
}
}
return repositoryID
}
// resolveRepoInput resolves a RepositoryInput to a repositoryID and baseBranch,
// creating the repository if it doesn't exist yet.
func (s *Service) resolveRepoInput(ctx context.Context, workspaceID string, repoInput TaskRepositoryInput, repoByPath map[string]*models.Repository) (repositoryID, baseBranch string, err error) {