git-cat-file
Version:
Pure JavaScript `git cat-file -p` for node.js
158 lines (157 loc) • 5.26 kB
JavaScript
;
/**
* https://github.com/kawanet/git-cat-file
*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.Ref = void 0;
const fs_1 = require("fs");
const cache_1 = require("./cache");
const commit_1 = require("./commit");
const tag_1 = require("./tag");
const isObjectId = (oid) => (oid && /^[0-9a-f]{40}$/i.test(oid));
class Ref {
constructor(root) {
this.root = root;
this.readPackedRefIndex = (0, cache_1.shortCache)(async () => {
const index = {};
const list = await this.readPackedRefList();
for (const ref of list) {
index[ref.ref] = ref.commit;
}
return index;
});
this.readTextFile = (0, cache_1.shortCache)((name) => {
const path = `${this.root}/${name}`;
// console.warn(`readFile: ${path}`);
return fs_1.promises.readFile(path, "utf-8");
});
//
}
async findCommitId(revision, store) {
if (!revision || !/[^.\/]/.test(revision)) {
throw new Error(`Invalid revision: ${revision}`);
}
let ancestry;
revision = revision.replace(/([~^]\d*)+$/, match => {
ancestry = match.split(/([~^]\d*)/).filter(v => v);
return "";
});
if (revision === "@") {
revision = "HEAD";
}
const id = await this.findId(revision);
if (id && !ancestry)
return id;
const obj = await this.getRawCommit(id || revision, store);
if (!obj)
return;
if (obj && !ancestry)
return obj.oid;
// https://git-scm.com/book/en/v2/Git-Tools-Revision-Selection#_ancestry_references
// HEAD^
// HEAD~2
let commit = new commit_1.Commit(obj, store);
for (const gen of ancestry) {
const mark = gen[0];
const num = gen.substring(1);
const count = (num === "0") ? 0 : (+num || 1);
const tilde = (mark === "~") ? count : 1;
const caret = (mark === "^") ? count : 1;
for (let i = 0; i < tilde; i++) {
const parents = await commit.getParents();
if (!parents)
return;
if (caret)
commit = parents[caret - 1];
if (!commit)
return;
}
}
return commit.getId();
}
async findId(revision) {
while (revision) {
revision = await this.searchRef(revision);
if (!revision)
break;
const ref = revision.replace(/^ref:\s*/, "");
if (/^refs\//.test(ref)) {
revision = await this.findRef(ref);
}
if (isObjectId(revision)) {
return revision; // commit
}
}
}
async getRawCommit(revision, store) {
const object_id = await store.findObjectId(revision);
if (!object_id)
return; // not found
let obj = await store.getObject(object_id);
if ((obj === null || obj === void 0 ? void 0 : obj.type) === "tag") {
const tag = new tag_1.Tag(obj, store);
const object_id = tag.getMeta("object");
obj = await store.getObject(object_id);
}
if (obj.type === "commit")
return obj;
}
async readPackedRefList() {
const list = [];
const text = await this.readTextFile(`packed-refs`).catch(_ => null);
if (!text)
return list;
const lines = text.split(/\r?\n/).filter(s => /^\w/.test(s));
for (const line of lines) {
const [commit, ref] = splitBySpace(line);
list.push({ commit, ref });
}
return list;
}
async searchRef(ref) {
// .git/HEAD
if (/HEAD$/.test(ref)) {
const commit = await this.readFirstLine(ref).catch(_ => null);
if (commit)
return commit;
}
// .git/refs/heads/main
{
const commit = await this.findRef(`refs/heads/${ref}`).catch(_ => null);
if (commit)
return commit;
}
// .git/refs/tags/xxxx
{
const commit = await this.findRef(`refs/tags/${ref}`).catch(_ => null);
if (commit)
return commit;
}
// .git/refs/remotes/xxxx
{
const commit = await this.findRef(`refs/remotes/${ref}`).catch(_ => null);
if (commit)
return commit;
}
}
async findRef(name) {
// loose ref
const ref = await this.readFirstLine(name);
if (ref)
return ref;
// packed ref
const index = await this.readPackedRefIndex();
if (index[name])
return index[name];
}
async readFirstLine(name) {
const text = await this.readTextFile(name).catch(_ => null);
if (text)
return text.split(/\r?\n/).filter(s => /^[^#\s]/.test(s)).shift();
}
}
exports.Ref = Ref;
function splitBySpace(line) {
const sp = line.indexOf(" ");
return [line.slice(0, sp), line.slice(sp + 1)];
}