Skip to content

Commit b6906bb

Browse files
AtkinsSJKernelDeimos
authored andcommitted
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`.
1 parent 60976b1 commit b6906bb

File tree

5 files changed

+63
-28
lines changed

5 files changed

+63
-28
lines changed

packages/git/src/git-helpers.js

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
* along with this program. If not, see <https://www.gnu.org/licenses/>.
1818
*/
1919
import path from 'path-browserify';
20+
import git from 'isomorphic-git';
2021

2122
/**
2223
* Attempt to locate the git repository directory.
@@ -137,3 +138,43 @@ export const group_positional_arguments = (arg_tokens) => {
137138

138139
return result;
139140
}
141+
142+
/**
143+
* Take some kind of reference, and resolve it to a full oid if possible.
144+
* @param git_context Object of common parameters to isomorphic-git methods
145+
* @param ref Reference to resolve
146+
* @returns {Promise<string>} Full oid, or a thrown Error
147+
*/
148+
export const resolve_to_oid = async (git_context, ref) => {
149+
const [ resolved_oid, expanded_oid ] = await Promise.allSettled([
150+
git.resolveRef({ ...git_context, ref }),
151+
git.expandOid({ ...git_context, oid: ref }),
152+
]);
153+
let oid;
154+
if (resolved_oid.status === 'fulfilled') {
155+
oid = resolved_oid.value;
156+
} else if (expanded_oid.status === 'fulfilled') {
157+
oid = expanded_oid.value;
158+
} else {
159+
throw new Error(`Unable to resolve reference '${ref}'`);
160+
}
161+
// TODO: Advanced revision selection, see https://git-scm.com/book/en/v2/Git-Tools-Revision-Selection
162+
// and https://git-scm.com/docs/gitrevisions
163+
return oid;
164+
}
165+
166+
/**
167+
* Similar to resolve_to_oid, but makes sure the oid refers to a commit.
168+
* Returns the commit object because we had to retrieve it anyway.
169+
* @param git_context Object of common parameters to isomorphic-git methods
170+
* @param ref Reference to resolve
171+
* @returns {Promise<ReadCommitResult>} ReadCommitResult object as returned by git.readCommit(), or a thrown Error
172+
*/
173+
export const resolve_to_commit = async (git_context, ref) => {
174+
const resolved_oid = await resolve_to_oid(git_context, ref);
175+
try {
176+
return await git.readCommit({ ...git_context, oid: resolved_oid });
177+
} catch (e) {
178+
throw new Error(`bad revision '${ref}'`);
179+
}
180+
}

packages/git/src/subcommands/checkout.js

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
* along with this program. If not, see <https://www.gnu.org/licenses/>.
1818
*/
1919
import git from 'isomorphic-git';
20-
import { find_repo_root, shorten_hash } from '../git-helpers.js';
20+
import { find_repo_root, resolve_to_commit, shorten_hash } from '../git-helpers.js';
2121
import { SHOW_USAGE } from '../help.js';
2222

2323
const CHECKOUT = {
@@ -137,10 +137,11 @@ const CHECKOUT = {
137137
return;
138138
}
139139

140-
const old_oid = await git.resolveRef({ fs, dir, gitdir, ref: 'HEAD' });
141-
142-
const oid = await git.resolveRef({ fs, dir, gitdir, ref: reference });
143-
if (!oid)
140+
const [ old_commit, commit ] = await Promise.all([
141+
resolve_to_commit({ fs, dir, gitdir, cache }, 'HEAD'),
142+
resolve_to_commit({ fs, dir, gitdir, cache }, reference ),
143+
]);
144+
if (!commit)
144145
throw new Error(`Reference '${reference}' not found.`);
145146

146147
await git.checkout({
@@ -154,14 +155,11 @@ const CHECKOUT = {
154155
if (branches.includes(reference)) {
155156
stdout(`Switched to branch '${reference}'`);
156157
} else {
157-
const commit = await git.readCommit({ fs, dir, gitdir, oid });
158158
const commit_title = commit.commit.message.split('\n', 2)[0];
159-
160-
const old_commit = await git.readCommit({ fs, dir, gitdir, oid: old_oid });
161159
const old_commit_title = old_commit.commit.message.split('\n', 2)[0];
162160

163-
stdout(`Previous HEAD position was ${shorten_hash(old_oid)} ${old_commit_title}`);
164-
stdout(`HEAD is now at ${shorten_hash(oid)} ${commit_title}`);
161+
stdout(`Previous HEAD position was ${shorten_hash(old_commit.oid)} ${old_commit_title}`);
162+
stdout(`HEAD is now at ${shorten_hash(commit.oid)} ${commit_title}`);
165163
}
166164
}
167165
}

packages/git/src/subcommands/diff.js

Lines changed: 10 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
* along with this program. If not, see <https://www.gnu.org/licenses/>.
1818
*/
1919
import git, { STAGE, TREE, WORKDIR } from 'isomorphic-git';
20-
import { find_repo_root, group_positional_arguments } from '../git-helpers.js';
20+
import { find_repo_root, group_positional_arguments, resolve_to_commit, resolve_to_oid } from '../git-helpers.js';
2121
import { SHOW_USAGE } from '../help.js';
2222
import * as Diff from 'diff';
2323
import path from 'path-browserify';
@@ -123,13 +123,14 @@ export default {
123123
const { before: commit_args, after: path_args } = group_positional_arguments(tokens);
124124

125125
// Ensure all commit_args are commit references
126-
const resolved_commit_refs = await Promise.allSettled(commit_args.map(commit_arg => {
127-
return git.resolveRef({ fs, dir, gitdir, ref: commit_arg });
126+
const resolved_commits = await Promise.allSettled(commit_args.map(commit_arg => {
127+
return resolve_to_commit({ fs, dir, gitdir, cache }, commit_arg);
128128
}));
129-
for (const [i, ref] of resolved_commit_refs.entries()) {
130-
if (ref.status === 'rejected')
129+
for (const [i, commit] of resolved_commits.entries()) {
130+
if (commit.status === 'rejected')
131131
throw new Error(`bad revision '${commit_args[i]}'`);
132132
}
133+
const [ from_oid, to_oid ] = resolved_commits.map(it => it.value.oid);
133134

134135
const path_filters = path_args.map(it => path.resolve(env.PWD, it));
135136
const diff_ctx = {
@@ -153,7 +154,7 @@ export default {
153154
// Show staged changes
154155
diffs = await diff_git_trees({
155156
...diff_ctx,
156-
a_tree: TREE({ ref: commit_args[0] ?? 'HEAD' }),
157+
a_tree: TREE({ ref: from_oid ?? 'HEAD' }),
157158
b_tree: STAGE(),
158159
read_a: read_tree,
159160
read_b: read_staged,
@@ -171,7 +172,7 @@ export default {
171172
// Changes from commit to workdir
172173
diffs = await diff_git_trees({
173174
...diff_ctx,
174-
a_tree: TREE({ ref: commit_args[0] }),
175+
a_tree: TREE({ ref: from_oid }),
175176
b_tree: WORKDIR(),
176177
read_a: read_tree,
177178
read_b: read_tree,
@@ -180,8 +181,8 @@ export default {
180181
// Changes from one commit to another
181182
diffs = await diff_git_trees({
182183
...diff_ctx,
183-
a_tree: TREE({ ref: commit_args[0] }),
184-
b_tree: TREE({ ref: commit_args[1] }),
184+
a_tree: TREE({ ref: from_oid }),
185+
b_tree: TREE({ ref: to_oid }),
185186
read_a: read_tree,
186187
read_b: read_tree,
187188
});

packages/git/src/subcommands/push.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
*/
1919
import git from 'isomorphic-git';
2020
import http from 'isomorphic-git/http/web';
21-
import { determine_fetch_remote, find_repo_root, shorten_hash } from '../git-helpers.js';
21+
import { determine_fetch_remote, find_repo_root, resolve_to_oid, shorten_hash } from '../git-helpers.js';
2222
import { SHOW_USAGE } from '../help.js';
2323
import { authentication_options, Authenticator } from '../auth.js';
2424
import { color_options, process_color_options } from '../color.js';
@@ -209,7 +209,7 @@ export default {
209209
// TODO: Canonical git only pushes "new" branches to the remote when configured to do so, or with --set-upstream.
210210
// So, we should show some kind of warning and stop, if that's not the case.
211211

212-
const source_oid = await git.resolveRef({ fs, dir, gitdir, ref: source });
212+
const source_oid = await resolve_to_oid({ fs, dir, gitdir, cache }, source);
213213
const old_dest_oid = remote_ref?.oid;
214214

215215
const is_up_to_date = source_oid === old_dest_oid;

packages/git/src/subcommands/show.js

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
* along with this program. If not, see <https://www.gnu.org/licenses/>.
1818
*/
1919
import git, { TREE } from 'isomorphic-git';
20-
import { find_repo_root } from '../git-helpers.js';
20+
import { find_repo_root, resolve_to_oid } from '../git-helpers.js';
2121
import {
2222
commit_formatting_options,
2323
diff_formatting_options,
@@ -108,12 +108,7 @@ export default {
108108

109109
for (const ref of objects) {
110110
// Could be any ref, so first get the oid that's referred to.
111-
const oid = await git.resolveRef({
112-
fs,
113-
dir,
114-
gitdir,
115-
ref,
116-
});
111+
const oid = await resolve_to_oid({ fs, dir, gitdir, cache }, ref);
117112

118113
// Then obtain the object and parse it.
119114
const parsed_object = await git.readObject({

0 commit comments

Comments
 (0)