diff --git a/lib/util/git/index.spec.ts b/lib/util/git/index.spec.ts index 7387019ab7ac911b9a29e1a12fb11822a1dfe649..e3f76563782a3121223a0aa5f88446183011180c 100644 --- a/lib/util/git/index.spec.ts +++ b/lib/util/git/index.spec.ts @@ -753,4 +753,29 @@ describe('util/git/index', () => { }); }); }); + + describe('pushCommitAsRef', () => { + it('creates non-branch ref', async () => { + const commit = git.getBranchCommit('develop'); + await git.pushCommitAsRef(commit, 'refs/foo/bar'); + const repo = Git(tmpDir.path); + const res = (await repo.raw(['ls-remote'])).split(/\s+/); + expect(res).toContain('refs/foo/bar'); + }); + }); + + describe('listCommitTree', () => { + it('creates non-branch ref', async () => { + const commit = git.getBranchCommit('develop'); + const res = await git.listCommitTree(commit); + expect(res).toEqual([ + { + mode: '100644', + path: 'past_file', + sha: '913705ab2ca79368053a476efa48aa6912d052c5', + type: 'blob', + }, + ]); + }); + }); }); diff --git a/lib/util/git/index.ts b/lib/util/git/index.ts index 056aa958914e3a98ca4f95d80b977f10b32d61e2..18e7c52c733dd11ae844d3f5050aeb75ab9bbd93 100644 --- a/lib/util/git/index.ts +++ b/lib/util/git/index.ts @@ -40,6 +40,7 @@ import type { LocalConfig, StatusResult, StorageConfig, + TreeItem, } from './types'; export { setNoVerify } from './config'; @@ -704,11 +705,12 @@ export async function prepareCommit({ }: CommitFilesConfig): Promise<CommitResult | null> { const { localDir } = GlobalConfig.get(); await syncGit(); - logger.debug(`Preparing files for commiting to branch ${branchName}`); + logger.debug(`Preparing files for committing to branch ${branchName}`); await handleCommitAuth(localDir); try { await git.reset(ResetMode.HARD); await git.raw(['clean', '-fd']); + const parentCommitSha = config.currentBranchSha; await git.checkout(['-B', branchName, 'origin/' + config.currentBranch]); const deletedFiles: string[] = []; const addedModifiedFiles: string[] = []; @@ -787,7 +789,7 @@ export async function prepareCommit({ { deletedFiles, ignoredFiles, result: commitRes }, `git commit` ); - const commit = commitRes?.commit || 'unknown'; + const commitSha = commitRes?.commit || 'unknown'; if (!force && !(await hasDiff(`origin/${branchName}`))) { logger.debug( { branchName, deletedFiles, addedModifiedFiles, ignoredFiles }, @@ -797,7 +799,8 @@ export async function prepareCommit({ } const result: CommitResult = { - sha: commit, + parentCommitSha, + commitSha, files: files.filter((fileChange) => { if (fileChange.type === 'deletion') { return deletedFiles.includes(fileChange.path); @@ -898,3 +901,48 @@ export function getUrl({ pathname: repository + '.git', }); } + +export async function pushCommitAsRef( + commitSha: string, + refName: string +): Promise<void> { + await git.raw(['update-ref', refName, commitSha]); + await git.raw(['push', '--force', 'origin', refName]); +} + +const treeItemRegex = regEx( + /^(?<mode>\d{6})\s+(?<type>blob|tree)\s+(?<sha>[0-9a-f]{40})\s+(?<path>.*)$/ +); + +const treeShaRegex = regEx(/tree\s+(?<treeSha>[0-9a-f]{40})\s*/); + +/** + * + * $ git cat-file -p <commit-sha> + * + * > tree <tree-sha> + * > parent 59b8b0e79319b7dc38f7a29d618628f3b44c2fd7 + * > ... + * + * $ git cat-file -p <tree-sha> + * + * > 040000 tree 389400684d1f004960addc752be13097fe85d776 .devcontainer + * > 100644 blob 7d2edde437ad4e7bceb70dbfe70e93350d99c98b .editorconfig + * > ... + * + */ +export async function listCommitTree(commitSha: string): Promise<TreeItem[]> { + const commitOutput = await git.raw(['cat-file', '-p', commitSha]); + const { treeSha } = treeShaRegex.exec(commitOutput)?.groups ?? {}; + const contents = await git.raw(['cat-file', '-p', treeSha]); + const lines = contents.split(newlineRegex); + const result: TreeItem[] = []; + for (const line of lines) { + const matchGroups = treeItemRegex.exec(line)?.groups; + if (matchGroups) { + const { path, mode, type, sha } = matchGroups; + result.push({ path, mode, type, sha }); + } + } + return result; +} diff --git a/lib/util/git/types.ts b/lib/util/git/types.ts index a84f56e0e9c2ee2121a4ceb833350ce35b1abfeb..c09dce923ef3939dda34b7b9225f39a7cb1678f6 100644 --- a/lib/util/git/types.ts +++ b/lib/util/git/types.ts @@ -93,10 +93,18 @@ export interface SourceBranchConflict { } export interface CommitResult { - sha: string; + parentCommitSha: string; + commitSha: string; files: FileChange[]; } +export interface TreeItem { + path: string; + mode: string; + type: string; + sha: string; +} + /** * Represents a git authentication rule in the form of e.g.: * git config --global url."https://api@github.com/".insteadOf "https://github.com/"