Files
puter/packages/git/src/format.js
T
Sam Atkins 98c33fb3cc Implement git log
This is quite barebones for now.

Commit formatting is done in a separate file, as this is used by other
git commands, such as `show`.
2024-05-22 16:00:42 +01:00

178 lines
6.1 KiB
JavaScript

/*
* 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/>.
*/
import { shorten_hash } from './git-helpers.js';
export const commit_formatting_options = {
'abbrev-commit': {
description: 'Display commit hashes in abbreviated form.',
type: 'boolean',
},
'no-abbrev-commit': {
description: 'Always show full commit hashes.',
type: 'boolean',
},
'format': {
description: 'Format to use for commits.',
type: 'string',
},
'oneline': {
description: 'Shorthand for "--format=oneline --abbrev-commit".',
type: 'boolean',
},
};
/**
* Process command-line options related to commit formatting, and modify them in place.
* May throw if the options are in some way invalid.
* @param options Parsed command-line options, which will be modified in place.
*/
export const process_commit_formatting_options = (options) => {
if (options.oneline) {
options.format = 'oneline';
options['abbrev-commit'] = true;
}
options.short_hashes = (options['abbrev-commit'] === true) && (options['no-abbrev-commit'] !== true);
delete options['abbrev-commit'];
delete options['no-abbrev-commit'];
if (!options.format) {
options.format = 'medium';
}
if (!['oneline', 'short', 'medium', 'full', 'fuller', 'raw'].includes(options.format)) {
throw new Error(`Invalid --format format: ${options.format}`);
}
}
/**
* Format the given oid hash, followed by any refs that point to it
* @param oid
* @param short_hashes Whwther to shorten the hash
* @returns {String}
*/
export const format_oid = (oid, { short_hashes = false } = {}) => {
// TODO: List refs at this commit, after the hash
return short_hashes ? shorten_hash(oid) : oid;
}
/**
* Format the person's name and email as `${name} <${email}>`
* @param person
* @returns {`${string} <${string}>`}
*/
export const format_person = (person) => {
return `${person.name} <${person.email}>`;
}
/**
* Format a date
* @param date
* @param options
* @returns {string}
*/
export const format_date = (date, options = {}) => {
// TODO: This needs to obey date-format options, and should show the correct timezone not UTC
return new Date(date.timestamp * 1000).toUTCString();
}
/**
* Format the date, according to the "raw" display format.
* @param owner
* @returns {`${string} ${string}${string}${string}`}
*/
export const format_timestamp_and_offset = (owner) => {
// FIXME: The timezone offset is inverted.
// Either this is correct here, or we should be inverting it when creating the commit -
// Isomorphic git uses (new Date()).timezoneOffset() there, which returns -60 for BST, which is UTC+0100
const offset = -owner.timezoneOffset;
const offset_hours = Math.floor(offset / 60);
const offset_minutes = offset % 60;
const pad = (number) => `${Math.abs(number) < 10 ? '0' : ''}${Math.abs(number)}`;
return `${owner.timestamp} ${offset < 0 ? '-' : '+'}${pad(offset_hours)}${pad(offset_minutes)}`;
}
/**
* Produce a string representation of a commit.
* @param commit A CommitObject
* @param oid Commit hash
* @param options Options returned by parsing the command arguments in `commit_formatting_options`
* @returns {string}
*/
export const format_commit = (commit, oid, options = {}) => {
const title_line = () => commit.message.split('\n')[0];
switch (options.format || 'medium') {
// TODO: Other formats
case 'oneline':
return `${format_oid(oid, options)} ${title_line()}`;
case 'short': {
let s = '';
s += `commit ${format_oid(oid, options)}\n`;
s += `Author: ${format_person(commit.author)}\n`;
s += '\n';
s += title_line();
return s;
}
case 'medium': {
let s = '';
s += `commit ${format_oid(oid, options)}\n`;
s += `Author: ${format_person(commit.author)}\n`;
s += `Date: ${format_date(commit.author)}\n`;
s += '\n';
s += commit.message;
return s;
}
case 'full': {
let s = '';
s += `commit ${format_oid(oid, options)}\n`;
s += `Author: ${format_person(commit.author)}\n`;
s += `Commit: ${format_person(commit.committer)}\n`;
s += '\n';
s += commit.message;
return s;
}
case 'fuller': {
let s = '';
s += `commit ${format_oid(oid, options)}\n`;
s += `Author: ${format_person(commit.author)}\n`;
s += `AuthorDate: ${format_date(commit.author)}\n`;
s += `Commit: ${format_person(commit.committer)}\n`;
s += `CommitDate: ${format_date(commit.committer)}\n`;
s += '\n';
s += commit.message;
return s;
}
case 'raw': {
let s = '';
s += `commit ${oid}\n`;
s += `tree ${commit.tree}\n`;
if (commit.parent[0])
s += `parent ${commit.parent[0]}\n`;
s += `author ${format_person(commit.author)} ${format_timestamp_and_offset(commit.author)}\n`;
s += `committer ${format_person(commit.committer)} ${format_timestamp_and_offset(commit.committer)}\n`;
s += '\n';
s += commit.message;
return s;
}
default: {
throw new Error(`Invalid --format format: ${options.format}`);
}
}
}