Skip to content

feat: add Gitea provider support#502

Open
ironyh wants to merge 2 commits intolaurentenhoor:mainfrom
ironyh:feature/gitea-provider
Open

feat: add Gitea provider support#502
ironyh wants to merge 2 commits intolaurentenhoor:mainfrom
ironyh:feature/gitea-provider

Conversation

@ironyh
Copy link

@ironyh ironyh commented Mar 8, 2026

Summary

Adds support for Gitea repositories via the tea CLI.

Problem

Gitea repos were incorrectly detected as GitLab, causing spawn glab ENOENT errors.

Solution

  • New GiteaProvider class using tea CLI
  • Updated detectProvider() to detect Gitea (URL contains "gitea" or tea repo info works)
  • Core features: create/list/close issues, PR status, merge

Prerequisites

Users need tea CLI installed: https://gitea.com/gitea/tea/releases

Copilot AI review requested due to automatic review settings March 8, 2026 00:46
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new issue/PR provider implementation for Gitea repositories using the tea CLI, and updates provider auto-detection to avoid misclassifying Gitea repos as GitLab (which previously led to spawn glab ENOENT failures).

Changes:

  • Extend provider factory types + selection logic to include a new "gitea" provider.
  • Update detectProvider() to identify Gitea via remote URL heuristics and a tea repo info probe.
  • Introduce GiteaProvider implementing the IssueProvider interface via tea commands.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 9 comments.

File Description
lib/providers/index.ts Adds "gitea" to provider options/types and extends auto-detection + factory instantiation.
lib/providers/gitea.ts New IssueProvider implementation backed by the tea CLI (issues + PR status/merge primitives).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +147 to +154
// Search PRs mentioning the issue
try {
const raw = await this.tea(["pull", "list", "--output", "json"]);
const prs = JSON.parse(raw);
// Find PRs that mention this issue
const mentioningPr = prs.find((pr: any) =>
pr.body?.includes(`#${issueId}`) ||
pr.title?.includes(`#${issueId}`)
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getMergedMRUrl() is supposed to return a URL for a merged PR/MR, but the current implementation returns the first PR mentioning the issue without checking merge state. That can cause callers to treat an open/closed (unmerged) PR as merged. Filter the candidate PRs to merged state (or fetch the specific PR and verify merged) before returning a URL.

Suggested change
// Search PRs mentioning the issue
try {
const raw = await this.tea(["pull", "list", "--output", "json"]);
const prs = JSON.parse(raw);
// Find PRs that mention this issue
const mentioningPr = prs.find((pr: any) =>
pr.body?.includes(`#${issueId}`) ||
pr.title?.includes(`#${issueId}`)
// Search PRs mentioning the issue that are merged
try {
const raw = await this.tea(["pull", "list", "--output", "json"]);
const prs = JSON.parse(raw);
// Find merged PRs that mention this issue
const mentioningPr = prs.find((pr: any) =>
pr.state === "merged" && (
pr.body?.includes(`#${issueId}`) ||
pr.title?.includes(`#${issueId}`)
)

Copilot uses AI. Check for mistakes.
Comment on lines +37 to +40
// Check for Gitea: URL contains "gitea" or tea CLI recognizes this repo
if (url.includes("gitea") || (await isGiteaRepo(repoPath, runCommand))) {
return "gitea";
}
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

detectProvider() will spawn tea repo info for any origin URL that isn’t github.com or gitlab.com (including self-hosted GitLab/GitHub), which can add up to 5s latency to provider creation on those repos (and will happen on every invocation when opts.provider isn’t persisted). Consider narrowing the tea probe (e.g. only after checking for obvious GitLab/GitHub patterns, or first verifying tea --version, or caching the detection result per repoPath) to avoid repeated slow/failed subprocess calls.

Copilot uses AI. Check for mistakes.
Comment on lines +30 to +33
export class GiteaProvider implements IssueProvider {
private repoPath: string;
private workflow: WorkflowConfig;
private runCommand: GiteaProviderOpts["runCommand"];
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR introduces a new provider implementation and new detection logic, but there are no accompanying tests. The repo already has provider-focused tests (e.g. provider-pr-status.test.ts) that validate cross-provider invariants. Adding GiteaProvider tests for key behaviors (provider detection, getPrStatus no-PR/merged/closed semantics, reopen/comment flows) would help prevent regressions and ensure it matches the IssueProvider contract.

Copilot uses AI. Check for mistakes.
Comment on lines +16 to +19
import type { WorkflowConfig } from "../workflow.js";
import { getStateLabels, getLabelColors } from "../labels.js";
import { DEFAULT_WORKFLOW } from "../workflow.js";

Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The imports for workflow + label helpers look incorrect for this repo structure: there is no lib/workflow.ts or lib/labels.ts (other providers import from ../workflow/index.js). As written, this file won’t compile. Update these imports to the actual workflow module exports used elsewhere (e.g. DEFAULT_WORKFLOW, getStateLabels, getLabelColors, WorkflowConfig).

Suggested change
import type { WorkflowConfig } from "../workflow.js";
import { getStateLabels, getLabelColors } from "../labels.js";
import { DEFAULT_WORKFLOW } from "../workflow.js";
import type { WorkflowConfig } from "../workflow/index.js";
import { DEFAULT_WORKFLOW, getStateLabels, getLabelColors } from "../workflow/index.js";

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +51
// tea doesn't have native label management via CLI
// Labels can be created via Gitea web UI or API
// This is a no-op for now - labels should be created manually
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ensureLabel() is currently a silent no-op, but the admin flows (project_register, sync_labels) rely on provider.ensureAllStateLabels()/ensureLabel() to actually create/update workflow labels. With this implementation, label sync will report success while labels remain missing, and later state transitions/label edits can fail. Consider implementing label creation via tea api (or whatever tea subcommand supports labels), or at least throw an actionable error so registration/sync fails loudly on Gitea instead of silently doing nothing.

Suggested change
// tea doesn't have native label management via CLI
// Labels can be created via Gitea web UI or API
// This is a no-op for now - labels should be created manually
// tea doesn't have native label management via CLI.
// We fail loudly here so that admin flows (project_register, sync_labels)
// don't silently report success while required workflow labels are missing.
throw new Error(
`GiteaProvider cannot automatically ensure label "${name}" (${color}) in repo at "${this.repoPath}". ` +
`Please create or update this label manually in Gitea (via web UI or API), then re-run the operation.`
);

Copilot uses AI. Check for mistakes.
try {
const args = ["issue", "list", "--output", "json"];
if (opts?.label) args.push("--labels", opts.label);
if (opts?.state === "closed") args.push("--closed");
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

listIssues() ignores opts.state === "all", so callers requesting all issues will likely only get the CLI default (typically open issues). The IssueProvider contract supports open | closed | all and other providers implement all. Add explicit handling for all (and for open if the CLI needs an explicit flag) so results match the requested filter.

Suggested change
if (opts?.state === "closed") args.push("--closed");
if (opts?.state === "closed") {
args.push("--closed");
} else if (opts?.state === "open") {
args.push("--open");
} else if (opts?.state === "all") {
args.push("--all");
}

Copilot uses AI. Check for mistakes.
Comment on lines +172 to +174
if (!pr) {
return { state: PrState.OPEN, url: null };
}
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When no PR is found, this returns { state: PrState.OPEN, url: null }. Other services rely on the invariant "no PR" → { state: PrState.CLOSED, url: null } (e.g. heartbeat review’s git-history fallback only runs when state === CLOSED and url === null). Returning OPEN here will skip that fallback and can change workflow behavior. Align with the other providers: return PrState.CLOSED when url is null (and use OPEN/APPROVED/etc only when a PR URL exists).

Copilot uses AI. Check for mistakes.
Comment on lines +141 to +142
// tea doesn't support reopen via CLI
throw new Error("Reopen not supported via tea CLI");
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reopenIssue() throws unconditionally, but the default workflow includes Action.REOPEN_ISSUE (e.g. on test FAIL), and the pipeline executes provider.reopenIssue() without a protective try/catch. This will break the standard failure loop on Gitea. Implement reopen via tea/API if possible, or make this best-effort (no-op) while ensuring the workflow doesn’t require reopen for Gitea projects.

Suggested change
// tea doesn't support reopen via CLI
throw new Error("Reopen not supported via tea CLI");
// Best-effort reopen: try via tea CLI, but don't fail the workflow if unsupported.
try {
await this.tea(["issue", "reopen", String(issueId)]);
} catch {
// Reopen not supported or failed; ignore to keep failure loop functional.
}

Copilot uses AI. Check for mistakes.
Comment on lines +285 to +286
// tea doesn't support adding comments via CLI
throw new Error("addComment not supported via tea CLI");
Copy link

Copilot AI Mar 8, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

addComment() throws "not supported", but core tools call provider.addComment() without catching (e.g. task_comment, task_attach). On Gitea this will cause those tools to fail hard. Implement issue commenting via tea (or tea api), or adjust the provider/tooling contract so commenting is explicitly unsupported and handled gracefully by callers.

Suggested change
// tea doesn't support adding comments via CLI
throw new Error("addComment not supported via tea CLI");
// Use tea CLI to add a comment to an issue.
// Note: comment ID is not parsed from output here; callers that rely on
// the side-effect (the comment being created) can proceed without it.
await this.tea(["issue", "comment", "create", String(issueId), "--body", body]);
return 0;

Copilot uses AI. Check for mistakes.
Fixes based on GitHub Copilot review comments on PR laurentenhoor#502:

1. getMergedMRUrl(): Now filters on pr.state === 'merged' before returning URL
2. ensureLabel(): Throws actionable error instead of silent no-op
3. listIssues(): Handles opts.state === 'all' explicitly
4. reopenIssue(): Best-effort implementation (try/catch) to prevent workflow breakage
5. addComment(): Implemented via 'tea issue comment create' CLI command

These changes improve robustness and ensure the provider follows the
IssueProvider contract correctly.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants