From b6906bbcaaa50fc8a8c60beb6d2d38bcb7dda758 Mon Sep 17 00:00:00 2001 From: Sam Atkins Date: Fri, 21 Jun 2024 16:59:32 +0100 Subject: [PATCH] feat(git): Resolve more forms of commit reference Specifically, we previously didn't resolve short commit hashes into the full ones. These functions provide a single place to later add more ways of selection a commit, such as `HEAD~1`. --- packages/git/src/git-helpers.js | 41 ++++++++++++++++++++++++ packages/git/src/subcommands/checkout.js | 18 +++++------ packages/git/src/subcommands/diff.js | 19 +++++------ packages/git/src/subcommands/push.js | 4 +-- packages/git/src/subcommands/show.js | 9 ++---- 5 files changed, 63 insertions(+), 28 deletions(-) diff --git a/packages/git/src/git-helpers.js b/packages/git/src/git-helpers.js index fa8c64e0..28ac74a4 100644 --- a/packages/git/src/git-helpers.js +++ b/packages/git/src/git-helpers.js @@ -17,6 +17,7 @@ * along with this program. If not, see . */ import path from 'path-browserify'; +import git from 'isomorphic-git'; /** * Attempt to locate the git repository directory. @@ -137,3 +138,43 @@ export const group_positional_arguments = (arg_tokens) => { return result; } + +/** + * Take some kind of reference, and resolve it to a full oid if possible. + * @param git_context Object of common parameters to isomorphic-git methods + * @param ref Reference to resolve + * @returns {Promise} Full oid, or a thrown Error + */ +export const resolve_to_oid = async (git_context, ref) => { + const [ resolved_oid, expanded_oid ] = await Promise.allSettled([ + git.resolveRef({ ...git_context, ref }), + git.expandOid({ ...git_context, oid: ref }), + ]); + let oid; + if (resolved_oid.status === 'fulfilled') { + oid = resolved_oid.value; + } else if (expanded_oid.status === 'fulfilled') { + oid = expanded_oid.value; + } else { + throw new Error(`Unable to resolve reference '${ref}'`); + } + // TODO: Advanced revision selection, see https://git-scm.com/book/en/v2/Git-Tools-Revision-Selection + // and https://git-scm.com/docs/gitrevisions + return oid; +} + +/** + * Similar to resolve_to_oid, but makes sure the oid refers to a commit. + * Returns the commit object because we had to retrieve it anyway. + * @param git_context Object of common parameters to isomorphic-git methods + * @param ref Reference to resolve + * @returns {Promise} ReadCommitResult object as returned by git.readCommit(), or a thrown Error + */ +export const resolve_to_commit = async (git_context, ref) => { + const resolved_oid = await resolve_to_oid(git_context, ref); + try { + return await git.readCommit({ ...git_context, oid: resolved_oid }); + } catch (e) { + throw new Error(`bad revision '${ref}'`); + } +} diff --git a/packages/git/src/subcommands/checkout.js b/packages/git/src/subcommands/checkout.js index 28b2ad29..f8e8853e 100644 --- a/packages/git/src/subcommands/checkout.js +++ b/packages/git/src/subcommands/checkout.js @@ -17,7 +17,7 @@ * along with this program. If not, see . */ import git from 'isomorphic-git'; -import { find_repo_root, shorten_hash } from '../git-helpers.js'; +import { find_repo_root, resolve_to_commit, shorten_hash } from '../git-helpers.js'; import { SHOW_USAGE } from '../help.js'; const CHECKOUT = { @@ -137,10 +137,11 @@ const CHECKOUT = { return; } - const old_oid = await git.resolveRef({ fs, dir, gitdir, ref: 'HEAD' }); - - const oid = await git.resolveRef({ fs, dir, gitdir, ref: reference }); - if (!oid) + const [ old_commit, commit ] = await Promise.all([ + resolve_to_commit({ fs, dir, gitdir, cache }, 'HEAD'), + resolve_to_commit({ fs, dir, gitdir, cache }, reference ), + ]); + if (!commit) throw new Error(`Reference '${reference}' not found.`); await git.checkout({ @@ -154,14 +155,11 @@ const CHECKOUT = { if (branches.includes(reference)) { stdout(`Switched to branch '${reference}'`); } else { - const commit = await git.readCommit({ fs, dir, gitdir, oid }); const commit_title = commit.commit.message.split('\n', 2)[0]; - - const old_commit = await git.readCommit({ fs, dir, gitdir, oid: old_oid }); const old_commit_title = old_commit.commit.message.split('\n', 2)[0]; - stdout(`Previous HEAD position was ${shorten_hash(old_oid)} ${old_commit_title}`); - stdout(`HEAD is now at ${shorten_hash(oid)} ${commit_title}`); + stdout(`Previous HEAD position was ${shorten_hash(old_commit.oid)} ${old_commit_title}`); + stdout(`HEAD is now at ${shorten_hash(commit.oid)} ${commit_title}`); } } } diff --git a/packages/git/src/subcommands/diff.js b/packages/git/src/subcommands/diff.js index 0775de20..d4a9d291 100644 --- a/packages/git/src/subcommands/diff.js +++ b/packages/git/src/subcommands/diff.js @@ -17,7 +17,7 @@ * along with this program. If not, see . */ import git, { STAGE, TREE, WORKDIR } from 'isomorphic-git'; -import { find_repo_root, group_positional_arguments } from '../git-helpers.js'; +import { find_repo_root, group_positional_arguments, resolve_to_commit, resolve_to_oid } from '../git-helpers.js'; import { SHOW_USAGE } from '../help.js'; import * as Diff from 'diff'; import path from 'path-browserify'; @@ -123,13 +123,14 @@ export default { const { before: commit_args, after: path_args } = group_positional_arguments(tokens); // Ensure all commit_args are commit references - const resolved_commit_refs = await Promise.allSettled(commit_args.map(commit_arg => { - return git.resolveRef({ fs, dir, gitdir, ref: commit_arg }); + const resolved_commits = await Promise.allSettled(commit_args.map(commit_arg => { + return resolve_to_commit({ fs, dir, gitdir, cache }, commit_arg); })); - for (const [i, ref] of resolved_commit_refs.entries()) { - if (ref.status === 'rejected') + for (const [i, commit] of resolved_commits.entries()) { + if (commit.status === 'rejected') throw new Error(`bad revision '${commit_args[i]}'`); } + const [ from_oid, to_oid ] = resolved_commits.map(it => it.value.oid); const path_filters = path_args.map(it => path.resolve(env.PWD, it)); const diff_ctx = { @@ -153,7 +154,7 @@ export default { // Show staged changes diffs = await diff_git_trees({ ...diff_ctx, - a_tree: TREE({ ref: commit_args[0] ?? 'HEAD' }), + a_tree: TREE({ ref: from_oid ?? 'HEAD' }), b_tree: STAGE(), read_a: read_tree, read_b: read_staged, @@ -171,7 +172,7 @@ export default { // Changes from commit to workdir diffs = await diff_git_trees({ ...diff_ctx, - a_tree: TREE({ ref: commit_args[0] }), + a_tree: TREE({ ref: from_oid }), b_tree: WORKDIR(), read_a: read_tree, read_b: read_tree, @@ -180,8 +181,8 @@ export default { // Changes from one commit to another diffs = await diff_git_trees({ ...diff_ctx, - a_tree: TREE({ ref: commit_args[0] }), - b_tree: TREE({ ref: commit_args[1] }), + a_tree: TREE({ ref: from_oid }), + b_tree: TREE({ ref: to_oid }), read_a: read_tree, read_b: read_tree, }); diff --git a/packages/git/src/subcommands/push.js b/packages/git/src/subcommands/push.js index fe0f30b1..76a052fb 100644 --- a/packages/git/src/subcommands/push.js +++ b/packages/git/src/subcommands/push.js @@ -18,7 +18,7 @@ */ import git from 'isomorphic-git'; import http from 'isomorphic-git/http/web'; -import { determine_fetch_remote, find_repo_root, shorten_hash } from '../git-helpers.js'; +import { determine_fetch_remote, find_repo_root, resolve_to_oid, shorten_hash } from '../git-helpers.js'; import { SHOW_USAGE } from '../help.js'; import { authentication_options, Authenticator } from '../auth.js'; import { color_options, process_color_options } from '../color.js'; @@ -209,7 +209,7 @@ export default { // TODO: Canonical git only pushes "new" branches to the remote when configured to do so, or with --set-upstream. // So, we should show some kind of warning and stop, if that's not the case. - const source_oid = await git.resolveRef({ fs, dir, gitdir, ref: source }); + const source_oid = await resolve_to_oid({ fs, dir, gitdir, cache }, source); const old_dest_oid = remote_ref?.oid; const is_up_to_date = source_oid === old_dest_oid; diff --git a/packages/git/src/subcommands/show.js b/packages/git/src/subcommands/show.js index 8799a801..10d860a5 100644 --- a/packages/git/src/subcommands/show.js +++ b/packages/git/src/subcommands/show.js @@ -17,7 +17,7 @@ * along with this program. If not, see . */ import git, { TREE } from 'isomorphic-git'; -import { find_repo_root } from '../git-helpers.js'; +import { find_repo_root, resolve_to_oid } from '../git-helpers.js'; import { commit_formatting_options, diff_formatting_options, @@ -108,12 +108,7 @@ export default { for (const ref of objects) { // Could be any ref, so first get the oid that's referred to. - const oid = await git.resolveRef({ - fs, - dir, - gitdir, - ref, - }); + const oid = await resolve_to_oid({ fs, dir, gitdir, cache }, ref); // Then obtain the object and parse it. const parsed_object = await git.readObject({