tiny-commit-walker
Version:
tiny commit walker
183 lines • 6.84 kB
JavaScript
"use strict";
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
Object.defineProperty(exports, "__esModule", { value: true });
const fs = require("fs");
const promisify = require("util.promisify");
const zlib = require("zlib");
const path = require("path");
const readFileAsync = promisify(fs.readFile);
const inflateAsync = promisify(zlib.inflate);
class Commit {
constructor(gitDir, hash, data, _packs) {
this.gitDir = gitDir;
this.hash = hash;
this.data = data;
this._packs = _packs;
const [head, ...rest] = data.split('\u0000');
const [type, size] = head.split(/\s/);
this.size = +size;
this.body = rest.join('\u0000');
// parse parent hashes
const m = this.body.match(/^parent\s[a-f0-9]{40}/gm);
this.parentHashes = m ? m.map(s => s.split(/\s/).pop()) : [];
this.message = this.body.split('\n\n').slice(1).join('\n\n').trim();
}
_parseAuthorOrCommitter(type) {
const r = new RegExp(`^${type} ([^<>]+) <(\\S+)> (\\d+) ([+-]?\\d{2})(\\d{2})$`, 'm');
const [, name, email, dateStr, tzHourStr, tzMinuteStr] = this.body.match(r);
const time = +dateStr * 1000;
const date = new Date(time);
const timezoneOffset = (+tzHourStr * 60 + +tzMinuteStr) * 60 * 1000;
return { name, email, date, timezoneOffset };
}
get author() {
if (this._author) {
return this._author;
}
return this._author = this._parseAuthorOrCommitter('author');
}
get committer() {
if (this._committer) {
return this._committer;
}
return this._committer = this._parseAuthorOrCommitter('committer');
}
get hasParents() {
return !!this.parentHashes.length;
}
get isMergeCommit() {
return this.parentHashes.length >= 2;
}
get baseParentHash() {
return this.parentHashes[0];
}
get mergedParentHashes() {
return this.parentHashes.slice(1);
}
/**
* Get parent commit by a parent hash.
*
* ```ts
* // default is the baseParentHash.
* let parentCommit = await commit.walk();
*
* // or set a parent hash manually.
* parentCommit = await commit.walk(commit.parentHash[1]);
*
* // same mean
* parentCommit = await commit.walk(1);
* ```
*/
walk(parentHash = 0) {
return __awaiter(this, void 0, void 0, function* () {
if (typeof parentHash === 'number')
parentHash = this.parentHashes[parentHash];
return yield Commit.readCommit(this.gitDir, parentHash, this._packs);
});
}
/**
* Get parent commit by a parent hash sync.
*
* ```ts
* let parentCommit = commit.walkSync();
* parentCommit = commit.walkSync(commit.parentHash[1]);
* ```
*/
walkSync(parentHash = 0) {
if (typeof parentHash === 'number')
parentHash = this.parentHashes[parentHash];
return Commit.readCommitSync(this.gitDir, parentHash, this._packs);
}
static readCommit(gitDir, hash, packs) {
return __awaiter(this, void 0, void 0, function* () {
if (packs && packs.hasPackFiles) {
try {
const body = yield packs.unpackGitObject(hash);
const s = body.toString('utf8');
if (s.startsWith('object')) {
return yield Commit.readCommit(gitDir, getCommitHashFromAnnotatedTag(s), packs);
}
return new Commit(gitDir, hash, createCommitData(body), packs);
}
catch (e) {
if (e instanceof InvalidAnnotatedTagError)
throw e;
}
}
const deflatedData = yield readFileAsync(getObjectPath(gitDir, hash));
const data = (yield inflateAsync(deflatedData)).toString('utf8');
if (data.startsWith('tag')) {
return yield Commit.readCommit(gitDir, getCommitHashFromAnnotatedTag(data), packs);
}
return new Commit(gitDir, hash, data, packs);
});
}
static readCommitSync(gitDir, hash, packs) {
if (packs && packs.hasPackFiles) {
try {
const body = packs.unpackGitObjectSync(hash);
const s = body.toString('utf8');
if (s.startsWith('object')) {
return Commit.readCommitSync(gitDir, getCommitHashFromAnnotatedTag(s), packs);
}
return new Commit(gitDir, hash, createCommitData(body), packs);
}
catch (e) {
if (e instanceof InvalidAnnotatedTagError)
throw e;
}
}
const deflatedData = fs.readFileSync(getObjectPath(gitDir, hash));
const data = zlib.inflateSync(deflatedData).toString('utf8');
if (data.startsWith('tag')) {
return Commit.readCommitSync(gitDir, getCommitHashFromAnnotatedTag(data), packs);
}
return new Commit(gitDir, hash, data, packs);
}
}
exports.Commit = Commit;
function createCommitData(body) {
return `commit ${body.length}\u0000${body.toString('utf8')}`;
}
function getObjectPath(gitDir, hash) {
return path.join(gitDir, 'objects', hash.replace(/^(.{2})(.{38})$/, `$1${path.sep}$2`));
}
function getGitObjectBody(s) {
let i = 0;
while (s[i++] !== '\u0000')
;
return s.slice(i);
}
class InvalidAnnotatedTagError extends Error {
constructor(message) {
super(message);
this.message = `Invalid git object type: ${message}`;
}
}
function getCommitHashFromAnnotatedTag(tagBody) {
if (tagBody.startsWith('tag'))
tagBody = getGitObjectBody(tagBody);
const lines = tagBody.split('\n');
const tag = {};
lines.some(line => {
if (!line.length)
return true;
const [k, v] = line.split(/\s/);
switch (k) {
case 'object':
case 'type': tag[k] = v;
}
return false;
});
if (tag.type !== 'commit')
throw new InvalidAnnotatedTagError(tag.type);
return tag.object;
}
//# sourceMappingURL=commit.js.map