test(docs): pin the repository-reference prefix search

uniquePrefixMatch is exported and covered for unique, shared, last,
exact, absent, and empty cases, and a fixture repository with two commits
whose names share a seven-digit prefix shows that the shared prefix is
accepted while a longer unique prefix is rejected.

Refs #5852
This commit is contained in:
imccyu committed 2026-10-12 11:06:56 +08:00
1 parent 5222ef2751
commit a99cf28864
2 files changed
+43 -2

No files matched your search

+42 -1
View File
@@ -1,9 +1,10 @@
import { execFileSync } from 'node:child_process'
import { createHash } from 'node:crypto'
import { mkdirSync, mkdtempSync, rmSync, symlinkSync, unlinkSync, writeFileSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { dirname, join } from 'node:path'
import { describe, expect, it, type TestContext } from 'vitest'
import { findRepositoryReferences, scanRepositoryReferences } from './verify-repository-references.ts'
import { findRepositoryReferences, scanRepositoryReferences, uniquePrefixMatch } from './verify-repository-references.ts'
const organizationUrl = `https://${['github.com', ['deepseek', 'harness'].join('-')].join('/')}`
@@ -43,6 +44,33 @@ function repository(test: TestContext) {
return { root, git, write, commit, tree }
}
/** Two commit object bodies whose object names share at least `length` leading hexadecimal digits. */
function collidingCommitBodies(tree: string, length: number): [string, string] {
const algorithm = tree.length === 64 ? 'sha256' : 'sha1'
const seen = new Map<string, string>()
for (let index = 0; ; index += 1) {
const body = `tree ${tree}\nauthor T <t@example.invalid> 0 +0000\ncommitter T <t@example.invalid> 0 +0000\n\ncollision ${String(index)}\n`
const prefix = createHash(algorithm).update(`commit ${String(Buffer.byteLength(body))}\0${body}`).digest('hex').slice(0, length)
const previous = seen.get(prefix)
if (previous !== undefined) return [previous, body]
seen.set(prefix, body)
}
}
describe('unique object-name prefix search', () => {
it.each([
[['aaa1', 'bbb2', 'ccc3'], 'bbb', 'bbb2'],
[['abc1', 'abc2'], 'abc', undefined],
[['a1', 'b2'], 'b', 'b2'],
[['abc', 'abd'], 'abc', 'abc'],
[['abc'], 'abd', undefined],
[['abc'], 'abcd', undefined],
[[], 'abc', undefined],
] as const)('finds in %j the one name starting with %s', (objects, prefix, expected) => {
expect(uniquePrefixMatch(objects, prefix)).toBe(expected)
})
})
describe('maintained repository reference policy', () => {
it('permits only the independent kit repository and its source URLs', () => {
for (const suffix of ['', '.git', '/tree/main/packages/entry']) {
@@ -112,6 +140,19 @@ describe('maintained repository reference policy', () => {
expect(scanRepositoryReferences(fixture.root)).toEqual([])
})
it('accepts a prefix that two available commits share and rejects a longer unique prefix', (test) => {
const fixture = repository(test)
const [first, second] = collidingCommitBodies(fixture.tree, 7).map(body =>
fixture.git(['hash-object', '-t', 'commit', '-w', '--stdin'], body))
if (first === undefined || second === undefined) throw new Error('fixture must write two commits')
let shared = 0
while (first[shared] === second[shared]) shared += 1
expect(shared).toBeGreaterThanOrEqual(7)
fixture.write('ambiguous.md', first.slice(0, 7))
fixture.write('unique.md', first.slice(0, shared + 1))
expect(scanRepositoryReferences(fixture.root)).toEqual([{ file: 'unique.md', line: 1, kind: 'commit-hash' }])
})
it('accepts hexadecimal branch names that do not match the referenced commit identifier', (test) => {
const fixture = repository(test)
const branch = 'b'.repeat(12)
+1 -1
View File
@@ -90,7 +90,7 @@ function gitLines(repoRoot: string, args: readonly string[], maxBuffer: number,
* @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 {
export function uniquePrefixMatch(objects: readonly string[], prefix: string): string | undefined {
let low = 0
let high = objects.length
while (low < high) {