perf(docs): resolve repository-reference candidates with one object listing

verify-repository-references asked git cat-file for each of about 26,000
abbreviated hexadecimal candidates, which took 135 s on a network
filesystem. One --batch-all-objects listing (about 1.2 million objects,
0.6 s) now feeds an in-memory prefix search; a candidate still counts
only when its prefix names exactly one available object and that object
is a commit. The gate drops from 166.8 s to 15.7 s, and both
implementations classify 27,050 real and synthetic candidates
identically.

Refs #5852
This commit is contained in:
imccyu committed 2026-10-12 11:06:50 +08:00
1 parent ed7bf8cc42
commit a8fd8da98a
1 file changed
+54 -13
+54 -13
View File
@@ -14,6 +14,8 @@ const kitRepositoryUrl = new RegExp(`\\bgithub\\.com/${organization}/libreoffice
const commitCandidate = /(?<![a-z0-9])[\da-f]{7,40}(?![a-z0-9])/gi
const excludedPrefixes = ['vendor/', '.agents/notes/archived/']
const gitOutputLimit = 64 * 1024 * 1024
// One 41-byte line per object; 1.2 million objects produce about 49 MiB.
const objectListingLimit = 1024 * 1024 * 1024
/** One prohibited reference in a maintained source file. */
export interface RepositoryReference {
@@ -70,24 +72,63 @@ function readMaintainedFiles(repoRoot: string): Map<string, string> {
return sources
}
function repositoryCommits(repoRoot: string, sources: Iterable<string>): Set<string> {
const candidates = [...new Set([...sources].flatMap(source =>
[...source.matchAll(commitCandidate)].map(match => match[0].toLowerCase())))]
if (candidates.length === 0) return new Set()
const results = execFileSync('git', ['cat-file', '--batch-check=%(objectname) %(objecttype)'], {
function gitLines(repoRoot: string, args: readonly string[], maxBuffer: number, input?: string): string[] {
const output = execFileSync('git', args, {
cwd: repoRoot,
env: { ...process.env, GIT_NO_LAZY_FETCH: '1' },
encoding: 'utf8',
input: `${candidates.join('\n')}\n`,
maxBuffer: gitOutputLimit,
input,
maxBuffer,
stdio: ['pipe', 'pipe', 'pipe'],
}).trimEnd().split('\n')
// Git resolves prefixes across all available objects, including unreachable ones.
}).trimEnd()
return output === '' ? [] : output.split('\n')
}
/**
* Find the one object whose name starts with a prefix.
* @param objects - Every object name in the local object database, sorted.
* @param prefix - Lowercase hexadecimal prefix.
* @returns The matching object name, or `undefined` when none or several match.
*/
function uniquePrefixMatch(objects: readonly string[], prefix: string): string | undefined {
let low = 0
let high = objects.length
while (low < high) {
const middle = (low + high) >>> 1
if ((objects[middle] ?? prefix) < prefix) low = middle + 1
else high = middle
}
const match = objects[low]
if (match?.startsWith(prefix) !== true || objects[low + 1]?.startsWith(prefix) === true) return undefined
return match
}
/**
* Resolve commit-identifier candidates in maintained sources against the local object database.
* @param repoRoot - Working tree whose Git objects are inspected.
* @param sources - File texts or symlink targets to extract candidates from.
* @returns Lowercase candidates that name exactly one available object, which is a commit.
*/
export function repositoryCommits(repoRoot: string, sources: Iterable<string>): Set<string> {
const candidates = [...new Set([...sources].flatMap(source =>
[...source.matchAll(commitCandidate)].map(match => match[0].toLowerCase())))]
if (candidates.length === 0) return new Set()
// Git resolves prefixes across all available objects, including unreachable and alternate ones;
// one listing of those objects replaces one Git prefix lookup per candidate.
// Ambiguous prefixes do not identify one object and cannot establish a commit reference.
return new Set(candidates.filter((candidate, index) => {
const [object, type] = results[index]?.split(' ') ?? []
return type === 'commit' && object?.startsWith(candidate) === true
}))
const objects = gitLines(repoRoot, ['cat-file', '--batch-all-objects', '--batch-check=%(objectname)'], objectListingLimit).sort()
const matches = new Map<string, string>()
for (const candidate of candidates) {
const object = uniquePrefixMatch(objects, candidate)
if (object !== undefined) matches.set(candidate, object)
}
if (matches.size === 0) return new Set()
const matched = [...new Set(matches.values())]
const commits = new Set(gitLines(repoRoot, ['cat-file', '--batch-check=%(objectname) %(objecttype)'], gitOutputLimit, `${matched.join('\n')}\n`)
.map(line => line.split(' '))
.filter(([, type]) => type === 'commit')
.map(([object]) => object))
return new Set([...matches].filter(([, object]) => commits.has(object)).map(([candidate]) => candidate))
}
/**