[package-deps-hash] Expose `hashFilesAsync` API (#4934)

* [package-deps-hash] Expose `hashFilesAsync` API

* Update comments

* rush change

* Expose from index

---------

Co-authored-by: David Michon <dmichon-msft@users.noreply.github.com>
This commit is contained in:
David Michon 2024-09-20 16:22:34 -07:00 коммит произвёл GitHub
Родитель 23ed83a75b
Коммит d7b6ad074e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: B5690EEEBB952194
4 изменённых файлов: 77 добавлений и 20 удалений

Просмотреть файл

@ -0,0 +1,10 @@
{
"changes": [
{
"packageName": "@rushstack/package-deps-hash",
"comment": "Expose `hashFilesAsync` API. This serves a similar role as `getGitHashForFiles` but is asynchronous and allows for the file names to be provided as an async iterable.",
"type": "minor"
}
],
"packageName": "@rushstack/package-deps-hash"
}

Просмотреть файл

@ -22,6 +22,9 @@ export function getRepoRoot(currentWorkingDirectory: string, gitPath?: string):
// @beta
export function getRepoStateAsync(rootDirectory: string, additionalRelativePathsToHash?: string[], gitPath?: string): Promise<Map<string, string>>;
// @beta
export function hashFilesAsync(rootDirectory: string, filesToHash: Iterable<string> | AsyncIterable<string>, gitPath?: string): Promise<Iterable<[string, string]>>;
// @beta
export interface IFileDiffStatus {
// (undocumented)

Просмотреть файл

@ -298,6 +298,61 @@ async function spawnGitAsync(
return stdout;
}
function isIterable<T>(value: Iterable<T> | AsyncIterable<T>): value is Iterable<T> {
return Symbol.iterator in value;
}
/**
* Uses `git hash-object` to hash the provided files. Unlike `getGitHashForFiles`, this API is asynchronous, and also allows for
* the input file paths to be specified as an async iterable.
*
* @param rootDirectory - The root directory to which paths are specified relative. Must be the root of the Git repository.
* @param filesToHash - The file paths to hash using `git hash-object`
* @param gitPath - The path to the Git executable
* @returns An iterable of [filePath, hash] pairs
*
* @remarks
* The input file paths must be specified relative to the Git repository root, or else be absolute paths.
* @beta
*/
export async function hashFilesAsync(
rootDirectory: string,
filesToHash: Iterable<string> | AsyncIterable<string>,
gitPath?: string
): Promise<Iterable<[string, string]>> {
const hashPaths: string[] = [];
const input: Readable = Readable.from(
isIterable(filesToHash)
? (function* (): IterableIterator<string> {
for (const file of filesToHash) {
hashPaths.push(file);
yield `${file}\n`;
}
})()
: (async function* (): AsyncIterableIterator<string> {
for await (const file of filesToHash) {
hashPaths.push(file);
yield `${file}\n`;
}
})(),
{
encoding: 'utf-8',
objectMode: false,
autoDestroy: true
}
);
const hashObjectResult: string = await spawnGitAsync(
gitPath,
STANDARD_GIT_OPTIONS.concat(['hash-object', '--stdin-paths']),
rootDirectory,
input
);
return parseGitHashObject(hashObjectResult, hashPaths);
}
/**
* Gets the object hashes for all files in the Git repo, combining the current commit with working tree state.
* Uses async operations and runs all primary Git calls in parallel.
@ -346,12 +401,10 @@ export async function getRepoStateAsync(
rootDirectory
).then(parseGitStatus);
const hashPaths: string[] = [];
async function* getFilesToHash(): AsyncIterableIterator<string> {
if (additionalRelativePathsToHash) {
for (const file of additionalRelativePathsToHash) {
hashPaths.push(file);
yield `${file}\n`;
yield file;
}
}
@ -359,33 +412,23 @@ export async function getRepoStateAsync(
for (const [filePath, exists] of locallyModified) {
if (exists) {
hashPaths.push(filePath);
yield `${filePath}\n`;
yield filePath;
} else {
files.delete(filePath);
}
}
}
const hashObjectPromise: Promise<string> = spawnGitAsync(
gitPath,
STANDARD_GIT_OPTIONS.concat(['hash-object', '--stdin-paths']),
const hashObjectPromise: Promise<Iterable<[string, string]>> = hashFilesAsync(
rootDirectory,
Readable.from(getFilesToHash(), {
encoding: 'utf-8',
objectMode: false,
autoDestroy: true
})
getFilesToHash(),
gitPath
);
const [{ files, submodules }, hashObject] = await Promise.all([
statePromise,
hashObjectPromise,
locallyModifiedPromise
]);
const [{ files, submodules }] = await Promise.all([statePromise, locallyModifiedPromise]);
// The result of "git hash-object" will be a list of file hashes delimited by newlines
for (const [filePath, hash] of parseGitHashObject(hashObject, hashPaths)) {
for (const [filePath, hash] of await hashObjectPromise) {
files.set(filePath, hash);
}

Просмотреть файл

@ -19,5 +19,6 @@ export {
getRepoChanges,
getRepoRoot,
getRepoStateAsync,
ensureGitMinimumVersion
ensureGitMinimumVersion,
hashFilesAsync
} from './getRepoState';