Add mobile Tasks parity (#2452)

Co-authored-by: Orca <help@stably.ai>
This commit is contained in:
Jinwoo Hong
2026-05-21 20:26:07 -07:00
committed by GitHub
co-authored by Orca
parent 3efc669bd4
commit b1973657ea
123 changed files with 26492 additions and 1461 deletions
+47 -15
View File
@@ -2,6 +2,7 @@ import { execFile } from 'child_process'
import { promisify } from 'util'
import { gitExecFileAsync, glabExecFileAsync } from '../git/runner'
import type { ClassifiedError, GitLabProjectRef, IssueSourcePreference } from '../../shared/types'
import { getSshGitProvider } from '../providers/ssh-git-dispatch'
// Why: legacy generic execFile wrapper — only used by callers that don't need
// WSL-aware routing. Repo-scoped callers should use glabExecFileAsync from
@@ -167,22 +168,34 @@ export function parseGitLabProjectRef(
export async function getProjectRefForRemote(
repoPath: string,
remoteName: string,
knownHosts: readonly string[] = DEFAULT_GITLAB_HOSTS
knownHosts: readonly string[] = DEFAULT_GITLAB_HOSTS,
connectionId?: string | null
): Promise<ProjectRef | null> {
const cacheKey = `${repoPath}\0${remoteName}\0${knownHosts.join(',')}`
const cacheKey = `${connectionId ?? 'local'}\0${repoPath}\0${remoteName}\0${knownHosts.join(',')}`
if (projectRefCache.has(cacheKey)) {
return projectRefCache.get(cacheKey)!
}
try {
const { stdout } = await gitExecFileAsync(['remote', 'get-url', remoteName], {
cwd: repoPath
})
const sshGitProvider = connectionId ? getSshGitProvider(connectionId) : null
if (connectionId && !sshGitProvider) {
// Why: mobile can attempt GitLab loads before the SSH tunnel is ready.
// Caching that transient state would poison later loads after connect.
return null
}
const { stdout } = sshGitProvider
? await sshGitProvider.exec(['remote', 'get-url', remoteName], repoPath)
: await gitExecFileAsync(['remote', 'get-url', remoteName], { cwd: repoPath })
const result = parseGitLabProjectRef(stdout, knownHosts)
if (result) {
projectRefCache.set(cacheKey, result)
return result
}
} catch {
if (connectionId) {
// Why: remote SSH failures are often transient tunnel/process errors.
// Do not cache them as "not a GitLab repo" for the rest of the session.
return null
}
// ignore — non-GitLab remote or no remote configured
}
projectRefCache.set(cacheKey, null)
@@ -191,20 +204,22 @@ export async function getProjectRefForRemote(
export async function getProjectRef(
repoPath: string,
knownHosts?: readonly string[]
knownHosts?: readonly string[],
connectionId?: string | null
): Promise<ProjectRef | null> {
return getProjectRefForRemote(repoPath, 'origin', knownHosts)
return getProjectRefForRemote(repoPath, 'origin', knownHosts, connectionId)
}
export async function getIssueProjectRef(
repoPath: string,
knownHosts?: readonly string[]
knownHosts?: readonly string[],
connectionId?: string | null
): Promise<ProjectRef | null> {
const upstream = await getProjectRefForRemote(repoPath, 'upstream', knownHosts)
const upstream = await getProjectRefForRemote(repoPath, 'upstream', knownHosts, connectionId)
if (upstream) {
return upstream
}
return getProjectRefForRemote(repoPath, 'origin', knownHosts)
return getProjectRefForRemote(repoPath, 'origin', knownHosts, connectionId)
}
export type ResolvedIssueSource = {
@@ -222,23 +237,40 @@ export type ResolvedIssueSource = {
export async function resolveIssueSource(
repoPath: string,
preference: IssueSourcePreference | undefined,
knownHosts?: readonly string[]
knownHosts?: readonly string[],
connectionId?: string | null
): Promise<ResolvedIssueSource> {
if (preference === 'upstream') {
const upstream = await getProjectRefForRemote(repoPath, 'upstream', knownHosts)
const upstream = await getProjectRefForRemote(repoPath, 'upstream', knownHosts, connectionId)
if (upstream) {
return { source: upstream, fellBack: false }
}
const origin = await getProjectRefForRemote(repoPath, 'origin', knownHosts)
const origin = await getProjectRefForRemote(repoPath, 'origin', knownHosts, connectionId)
return { source: origin, fellBack: origin !== null }
}
if (preference === 'origin') {
return {
source: await getProjectRefForRemote(repoPath, 'origin', knownHosts),
source: await getProjectRefForRemote(repoPath, 'origin', knownHosts, connectionId),
fellBack: false
}
}
return { source: await getIssueProjectRef(repoPath, knownHosts), fellBack: false }
return { source: await getIssueProjectRef(repoPath, knownHosts, connectionId), fellBack: false }
}
export function glabRepoExecOptions(
repoPath: string,
connectionId?: string | null
): { cwd?: string } {
return connectionId ? {} : { cwd: repoPath }
}
export function glabHostnameArgs(
projectRef: Pick<ProjectRef, 'host'> | null | undefined,
connectionId?: string | null
): string[] {
// Why: local glab commands can infer host from cwd; SSH-backed calls have
// no local cwd, so self-hosted instances need an explicit hostname.
return connectionId && projectRef?.host ? ['--hostname', projectRef.host] : []
}
// ── Known-hosts discovery via `glab auth status` ────────────────────