Files
deepseek-harness/packages/experimental/forge-github/src/urls.ts
T
Yichen Jiang 7dc864a931 fix(forge-github): read pinned diffs through the compare API
The GitHub provider now reads headRefOid and baseRefOid in one GraphQL query,
returns `stale` when the head moved, and reads the patch from
`compare/<baseRefOid>...<headSha>` instead of bracketing `gh pr diff` with two
head reads. Both ends are fixed commits. The compare read lives in the internal
`readComparison` helper so commit-range diffs can reuse it.

For every open and merged pull request measured, the compare patch is
byte-identical to `gh pr diff`. Unlike the pull request diff, it returns diffs
over 300 files, so such a pull request now shows its complete diff; only output
over `maxOutputBytes` or GitHub's HTTP 406 / `too_large` refusal is
`too-large`. The read passes `--allow-escape-sequences`, which gh 2.97 and
newer require to print patches whose files contain terminal escape sequences.
2026-11-06 17:05:05 +08:00

90 lines
3.8 KiB
TypeScript

/** GitHub remote URL and pull request URL parsing, and validation of the parts inlined into requests. */
import type { ChangeRequestRef, ForgeRepository } from '@deepseek-ai/dsh-experimental-forge'
/** Characters GitHub permits in owner and repository names. */
const SEGMENT = /^[A-Za-z0-9_.-]+$/
/** A full SHA-1 or SHA-256 object name. */
const COMMIT = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/i
/** Host, owner, and repository name addressed by a URL. */
export interface GitHubLocation {
/** Lowercase host name. */
readonly host: string
/** Owner as the URL spells it. */
readonly owner: string
/** Repository name without a `.git` suffix. */
readonly name: string
}
/**
* Check one owner or repository name segment.
* @param value - candidate segment.
* @returns whether GitHub permits the segment and it is not a dot path.
*/
export function isSegment(value: string): boolean {
return SEGMENT.test(value) && value !== '.' && value !== '..'
}
/**
* Validate a pull request before its owner, name, and number are inlined into
* a GraphQL document, a `gh` argument, or an API path.
* @param ref - pull request on a GitHub host.
* @returns its repository.
* @throws when the owner or name is not a permitted segment or the number is not a positive integer.
*/
export function checkedRepository(ref: ChangeRequestRef): ForgeRepository {
const { owner, name } = ref.repository
if (!isSegment(owner) || !isSegment(name)) throw new Error(`forge-github: invalid repository ${JSON.stringify(`${owner}/${name}`)}`)
if (!Number.isSafeInteger(ref.number) || ref.number <= 0) throw new Error(`forge-github: invalid pull request number ${ref.number}`)
return ref.repository
}
/**
* Whether a value is a full commit object name that can be inlined into a request.
* @param value - candidate commit.
* @returns whether it is 40 or 64 hexadecimal digits.
*/
export function isCommit(value: string): boolean {
return COMMIT.test(value)
}
/**
* Parse a Git remote URL. Accepts `http(s)://[user@]host[:port]/owner/name`,
* `ssh://[user@]host[:port]/owner/name`, `git://host/owner/name`, and the
* scp-like `[user@]host:owner/name`, each with an optional `.git` suffix or
* trailing slash. HTTP(S) ports belong to the host identity; SSH and Git
* transport ports do not.
* @param url - fetch or push URL as Git reports it.
* @returns the location, or undefined for other URL shapes.
*/
export function parseRemoteUrl(url: string): GitHubLocation | undefined {
const trimmed = url.trim()
const scheme = /^https?:\/\/(?:[^@/]+@)?([^/:]+(?::\d+)?)\/(.+)$/i.exec(trimmed)
?? /^(?:ssh|git):\/\/(?:[^@/]+@)?([^/:]+)(?::\d+)?\/(.+)$/i.exec(trimmed)
const scp = scheme === null ? /^(?:[^@/:]+@)?([^/:]+):(?!\/\/)(.+)$/.exec(trimmed) : null
const match = scheme ?? scp
if (match === null) return undefined
const [, host = '', path = ''] = match
const parts = path.replace(/\/+$/, '').replace(/\.git$/i, '').split('/')
if (parts.length !== 2) return undefined
const [owner = '', name = ''] = parts
if (!isSegment(owner) || !isSegment(name)) return undefined
return { host: host.toLowerCase(), owner, name }
}
/**
* Parse a pull request web URL such as `https://github.com/owner/name/pull/12/files`.
* @param url - web URL of a pull request.
* @returns the location and number, or undefined for other URLs.
*/
export function parsePullRequestUrl(url: string): (GitHubLocation & { readonly number: number }) | undefined {
const match = /^https?:\/\/([^/]+)\/([^/]+)\/([^/]+)\/pull\/(\d+)(?:[/?#].*)?$/i.exec(url.trim())
if (match === null) return undefined
const [, host = '', owner = '', name = '', digits = ''] = match
const number = Number(digits)
if (!isSegment(owner) || !isSegment(name) || !Number.isSafeInteger(number) || number <= 0) return undefined
return { host: host.toLowerCase(), owner, name, number }
}