feat(git): Display ref names in git log and git show

This commit is contained in:
Sam Atkins 2024-06-26 17:09:15 +01:00 committed by Eric Dubé
parent 351d3f50ba
commit 45cdfcb5bf
2 changed files with 129 additions and 9 deletions

View File

@ -18,6 +18,7 @@
*/
import { shorten_hash } from './git-helpers.js';
import chalk from 'chalk';
import { get_matching_refs } from './refs.js';
export const commit_formatting_options = {
'abbrev-commit': {
@ -68,9 +69,35 @@ export const process_commit_formatting_options = (options) => {
* @param short_hashes Whwther to shorten the hash
* @returns {String}
*/
export const format_oid = async (git_context, oid, { short_hashes = false } = {}) => {
// TODO: List refs at this commit, after the hash
return short_hashes ? shorten_hash(git_context, oid) : oid;
export const format_commit_oid = async (git_context, oid, { short_hashes = false } = {}) => {
const hash = short_hashes ? await shorten_hash(git_context, oid) : oid;
const refs = await get_matching_refs(git_context, oid);
if (refs.length === 0)
return hash;
let s = `${hash} (`;
s += refs.map(ref => {
// Different kinds of ref are styled differently, but all are in bold:
// HEAD and local branches are cyan
if (ref === 'HEAD') {
// TODO: If HEAD points to another ref, that should be shown here as `HEAD -> other`
return chalk.bold.cyan(ref);
}
if (ref.startsWith('refs/heads/'))
return chalk.bold.cyanBright(ref.slice('refs/heads/'.length));
// Tags are `tag: foo` in yellow
if (ref.startsWith('refs/tags/'))
return chalk.bold.yellowBright(`tag: ${ref.slice('refs/tags/'.length)}`);
// Remote branches are red
if (ref.startsWith('refs/remotes/'))
return chalk.bold.red(ref.slice('refs/remotes/'.length));
// Assuming there's anything else, we'll just bold it.
return chalk.bold(ref);
}).join(', ');
s += ')';
return s;
}
/**
@ -124,10 +151,10 @@ export const format_commit = async (git_context, commit, oid, options = {}) => {
switch (options.format || 'medium') {
// TODO: Other formats
case 'oneline':
return `${chalk.yellow(await format_oid(git_context, oid, options))} ${title_line()}`;
return `${chalk.yellow(await format_commit_oid(git_context, oid, options))} ${title_line()}`;
case 'short': {
let s = '';
s += chalk.yellow(`commit ${await format_oid(git_context, oid, options)}\n`);
s += chalk.yellow(`commit ${await format_commit_oid(git_context, oid, options)}\n`);
s += `Author: ${format_person(commit.author)}\n`;
s += '\n';
s += indent(title_line());
@ -135,7 +162,7 @@ export const format_commit = async (git_context, commit, oid, options = {}) => {
}
case 'medium': {
let s = '';
s += chalk.yellow(`commit ${await format_oid(git_context, oid, options)}\n`);
s += chalk.yellow(`commit ${await format_commit_oid(git_context, oid, options)}\n`);
s += `Author: ${format_person(commit.author)}\n`;
s += `Date: ${format_date(commit.author)}\n`;
s += '\n';
@ -144,7 +171,7 @@ export const format_commit = async (git_context, commit, oid, options = {}) => {
}
case 'full': {
let s = '';
s += chalk.yellow(`commit ${await format_oid(git_context, oid, options)}\n`);
s += chalk.yellow(`commit ${await format_commit_oid(git_context, oid, options)}\n`);
s += `Author: ${format_person(commit.author)}\n`;
s += `Commit: ${format_person(commit.committer)}\n`;
s += '\n';
@ -153,7 +180,7 @@ export const format_commit = async (git_context, commit, oid, options = {}) => {
}
case 'fuller': {
let s = '';
s += chalk.yellow(`commit ${await format_oid(git_context, oid, options)}\n`);
s += chalk.yellow(`commit ${await format_commit_oid(git_context, oid, options)}\n`);
s += `Author: ${format_person(commit.author)}\n`;
s += `AuthorDate: ${format_date(commit.author)}\n`;
s += `Commit: ${format_person(commit.committer)}\n`;
@ -164,7 +191,7 @@ export const format_commit = async (git_context, commit, oid, options = {}) => {
}
case 'raw': {
let s = '';
s += chalk.yellow(`commit ${oid}\n`);
s += chalk.yellow(`commit ${await format_commit_oid(git_context, oid, options)}\n`);
s += `tree ${commit.tree}\n`;
if (commit.parent[0])
s += `parent ${commit.parent[0]}\n`;

93
packages/git/src/refs.js Normal file
View File

@ -0,0 +1,93 @@
/*
* Copyright (C) 2024 Puter Technologies Inc.
*
* This file is part of Puter's Git client.
*
* Puter's Git client is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
// Map of hash -> array of full reference names
import git from 'isomorphic-git';
const hash_to_refs = new Map();
const add_hash = (hash, ref) => {
const existing_array = hash_to_refs.get(hash);
if (existing_array) {
existing_array.push(ref);
} else {
hash_to_refs.set(hash, [ref]);
}
}
// Avoid loading everything multiple times
let mark_cache_loaded;
let started_loading = false;
const cache_loaded = new Promise(resolve => { mark_cache_loaded = resolve });
/**
* Reverse search from a commit hash to the refs that point to it.
* The first time this is called, we retrieve all the references and cache them, meaning that
* later calls are much faster, but won't reflect changes.
* @param git_context {{ fs, dir, gitdir, cache }} as taken by most isomorphic-git methods.
* @param commit_oid
* @returns {Promise<[string]>} An array of full references, eg `HEAD`, `refs/heads/main`, `refs/tags/foo`, or `refs/remotes/origin/main`
*/
export const get_matching_refs = async (git_context, commit_oid) => {
if (started_loading) {
// If someone else started loading the cache, just wait for it to be ready
await cache_loaded;
} else {
// Otherwise, we have to load it!
started_loading = true;
// HEAD
add_hash(await git.resolveRef({ ...git_context, ref: 'HEAD' }), 'HEAD');
// Branches
const branch_names = await git.listBranches(git_context);
for (const branch of branch_names) {
const ref = `refs/heads/${branch}`;
add_hash(await git.resolveRef({ ...git_context, ref}), ref);
}
// Tags
const tags = await git.listTags(git_context);
for (const tag of tags)
add_hash(await git.resolveRef({ ...git_context, ref: tag }), `refs/tags/${tag}`);
// Remote branches
const remotes = await git.listRemotes(git_context);
for (const { remote } of remotes) {
const remote_branches = await git.listBranches({ ...git_context, remote });
for (const branch of remote_branches) {
const ref = `refs/remotes/${remote}/${branch}`;
add_hash(await git.resolveRef({ ...git_context, ref }), ref);
}
}
if (window.DEBUG) {
console.groupCollapsed('Collected refs');
for (const [ hash, ref_list ] of hash_to_refs) {
console.groupCollapsed(hash);
for (const ref of ref_list)
console.log(ref);
console.groupEnd();
}
console.groupEnd();
}
mark_cache_loaded();
}
return hash_to_refs.get(commit_oid) ?? [];
}