UNPKG

git-cat-file

Version:

Pure JavaScript `git cat-file -p` for node.js

158 lines (157 loc) 5.26 kB
"use strict"; /** * 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)]; }