mirror of
https://github.com/stablyai/orca.git
synced 2026-10-09 08:02:35 +00:00
Co-authored-by: makoto-developer <72484465+makoto-developer@users.noreply.github.com>
143 lines
4.0 KiB
TypeScript
143 lines
4.0 KiB
TypeScript
import type { GitLabIssueUpdate } from '../../shared/gitlab-types'
|
|
import type { IssueSourcePreference } from '../../shared/repo-types'
|
|
import {
|
|
acquire,
|
|
classifyGlabError,
|
|
getGlabKnownHosts,
|
|
glabExecFileAsync,
|
|
glabHostEnvOptions,
|
|
glabHostnameArgs,
|
|
glabRepoExecOptions,
|
|
release,
|
|
resolveIssueSource,
|
|
type LocalGitExecOptions,
|
|
type ProjectRef
|
|
} from './gl-utils'
|
|
import { encodedProject } from './project-path-encoding'
|
|
|
|
/**
|
|
* Update an existing GitLab issue.
|
|
*
|
|
* Why: callers that list through a per-repo issue source preference must
|
|
* mutate the same GitLab project, or identical IIDs on origin/upstream can
|
|
* silently edit the wrong issue.
|
|
*/
|
|
export async function updateIssue(
|
|
repoPath: string,
|
|
issueNumber: number,
|
|
updates: GitLabIssueUpdate,
|
|
preference?: IssueSourcePreference,
|
|
connectionId?: string | null,
|
|
projectRefOverride?: ProjectRef | null,
|
|
localGitOptions: LocalGitExecOptions = {}
|
|
): Promise<{ ok: true } | { ok: false; error: string }> {
|
|
const projectRef =
|
|
projectRefOverride ??
|
|
(
|
|
await resolveIssueSource(
|
|
repoPath,
|
|
preference,
|
|
await getGlabKnownHosts(connectionId, localGitOptions),
|
|
connectionId,
|
|
localGitOptions
|
|
)
|
|
).source
|
|
if (!projectRef) {
|
|
return {
|
|
ok: false,
|
|
error: 'Could not resolve GitLab project for this repository'
|
|
}
|
|
}
|
|
|
|
const repoFlag = projectRef.path
|
|
const errors: string[] = []
|
|
|
|
// State change requires a separate command (parallel to github's split).
|
|
if (updates.state) {
|
|
await acquire()
|
|
try {
|
|
const cmd = updates.state === 'closed' ? 'close' : 'reopen'
|
|
await glabExecFileAsync(['issue', cmd, String(issueNumber), '-R', repoFlag], {
|
|
...glabRepoExecOptions(repoPath, connectionId, localGitOptions),
|
|
...glabHostEnvOptions(projectRef, connectionId)
|
|
})
|
|
} catch (err) {
|
|
const stderr = err instanceof Error ? err.message : String(err)
|
|
// Treat "already closed/reopened" as a no-op (matches gh path).
|
|
if (!stderr.toLowerCase().includes('already')) {
|
|
errors.push(classifyGlabError(stderr).message)
|
|
}
|
|
} finally {
|
|
release()
|
|
}
|
|
}
|
|
|
|
if (updates.body !== undefined) {
|
|
await acquire()
|
|
try {
|
|
await glabExecFileAsync(
|
|
[
|
|
'api',
|
|
...glabHostnameArgs(projectRef, connectionId),
|
|
'-X',
|
|
'PUT',
|
|
`projects/${encodedProject(repoFlag)}/issues/${issueNumber}`,
|
|
'-f',
|
|
`description=${updates.body}`
|
|
],
|
|
glabRepoExecOptions(repoPath, connectionId, localGitOptions)
|
|
)
|
|
} catch (err) {
|
|
const stderr = err instanceof Error ? err.message : String(err)
|
|
errors.push(classifyGlabError(stderr).message)
|
|
} finally {
|
|
release()
|
|
}
|
|
}
|
|
|
|
// Field edits via `glab issue update`.
|
|
const editArgs: string[] = ['issue', 'update', String(issueNumber), '-R', repoFlag]
|
|
let hasEditArgs = false
|
|
|
|
if (updates.title) {
|
|
editArgs.push('--title', updates.title)
|
|
hasEditArgs = true
|
|
}
|
|
for (const label of updates.addLabels ?? []) {
|
|
editArgs.push('--label', label)
|
|
hasEditArgs = true
|
|
}
|
|
for (const label of updates.removeLabels ?? []) {
|
|
editArgs.push('--unlabel', label)
|
|
hasEditArgs = true
|
|
}
|
|
for (const assignee of updates.addAssignees ?? []) {
|
|
editArgs.push('--assignee', assignee)
|
|
hasEditArgs = true
|
|
}
|
|
for (const assignee of updates.removeAssignees ?? []) {
|
|
editArgs.push('--unassignee', assignee)
|
|
hasEditArgs = true
|
|
}
|
|
|
|
if (hasEditArgs) {
|
|
await acquire()
|
|
try {
|
|
await glabExecFileAsync(editArgs, {
|
|
...glabRepoExecOptions(repoPath, connectionId, localGitOptions),
|
|
...glabHostEnvOptions(projectRef, connectionId)
|
|
})
|
|
} catch (err) {
|
|
const stderr = err instanceof Error ? err.message : String(err)
|
|
errors.push(classifyGlabError(stderr).message)
|
|
} finally {
|
|
release()
|
|
}
|
|
}
|
|
|
|
if (errors.length > 0) {
|
|
return { ok: false, error: errors.join('; ') }
|
|
}
|
|
return { ok: true }
|
|
}
|