mirror of
https://github.com/HeyPuter/puter
synced 2024-11-14 22:06:00 +00:00
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`.
This commit is contained in:
parent
b4e2ba4544
commit
98c33fb3cc
177
packages/git/src/format.js
Normal file
177
packages/git/src/format.js
Normal file
@ -0,0 +1,177 @@
|
||||
/*
|
||||
* 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}`);
|
||||
}
|
||||
}
|
||||
}
|
@ -21,6 +21,7 @@ import module_add from './add.js'
|
||||
import module_commit from './commit.js'
|
||||
import module_help from './help.js'
|
||||
import module_init from './init.js'
|
||||
import module_log from './log.js'
|
||||
import module_status from './status.js'
|
||||
import module_version from './version.js'
|
||||
|
||||
@ -29,6 +30,7 @@ export default {
|
||||
"commit": module_commit,
|
||||
"help": module_help,
|
||||
"init": module_init,
|
||||
"log": module_log,
|
||||
"status": module_status,
|
||||
"version": module_version,
|
||||
};
|
||||
|
64
packages/git/src/subcommands/log.js
Normal file
64
packages/git/src/subcommands/log.js
Normal file
@ -0,0 +1,64 @@
|
||||
/*
|
||||
* 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 git from 'isomorphic-git';
|
||||
import { find_repo_root } from '../git-helpers.js';
|
||||
import { commit_formatting_options, format_commit, process_commit_formatting_options } from '../format.js';
|
||||
|
||||
export default {
|
||||
name: 'log',
|
||||
usage: 'git log [<formatting-option>...] [--max-count <n>] <revision>',
|
||||
description: 'Show commit logs, starting at the given revision.',
|
||||
args: {
|
||||
allowPositionals: false,
|
||||
options: {
|
||||
...commit_formatting_options,
|
||||
'max-count': {
|
||||
description: 'Maximum number of commits to output.',
|
||||
type: 'string',
|
||||
short: 'n',
|
||||
},
|
||||
},
|
||||
},
|
||||
execute: async (ctx) => {
|
||||
const { io, fs, env, args } = ctx;
|
||||
const { stdout, stderr } = io;
|
||||
const { options, positionals } = args;
|
||||
|
||||
process_commit_formatting_options(options);
|
||||
|
||||
// TODO: Log of a specific file
|
||||
// TODO: Log of a specific branch
|
||||
// TODO: Log of a specific commit
|
||||
|
||||
const depth = Number(options['max-count']) || undefined;
|
||||
|
||||
const { repository_dir, git_dir } = await find_repo_root(fs, env.PWD);
|
||||
|
||||
const log = await git.log({
|
||||
fs,
|
||||
dir: repository_dir,
|
||||
gitdir: git_dir,
|
||||
depth,
|
||||
});
|
||||
|
||||
for (const commit of log) {
|
||||
stdout(format_commit(commit.commit, commit.oid, options));
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user