UNPKG

tiny-commit-walker

Version:
468 lines 16.3 kB
"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 path = require("path"); const commit_1 = require("./commit"); const pack_1 = require("./pack"); const statAsync = promisify(fs.stat); const readFileAsync = promisify(fs.readFile); const readDirAsync = promisify(fs.readdir); class Ref { constructor(name, _gitDir, _hash, _packs) { this.name = name; this._gitDir = _gitDir; this._hash = _hash; this._packs = _packs; } get commit() { if (this._commit) { return this._commit; } return this._commit = commit_1.Commit.readCommitSync(this._gitDir, this._hash, this._packs); } } exports.Ref = Ref; const processings = {}; class Repository { constructor(gitDir) { this.gitDir = gitDir; this._refMaps = {}; this._refsDir = path.join(gitDir, 'refs'); } /** * Find a git directory. This function find one from current or parents directories. */ static findGitDir(repositoryPath = process.cwd()) { return __awaiter(this, void 0, void 0, function* () { try { const gitDir = path.resolve(repositoryPath, '.git'); const stat = yield statAsync(gitDir); if (stat.isDirectory()) { return gitDir; } else if (stat.isFile()) { const gitDirRef = yield readFileAsync(gitDir, 'utf8').trim().split(/\s/).pop(); return path.resolve(repositoryPath, gitDirRef); } } catch (err) { const parent = path.resolve(repositoryPath, '..'); if (repositoryPath === parent) return; return yield Repository.findGitDir(parent); } }); } /** * Find a git directory sync. This function find one from current or parents directories. */ static findGitDirSync(repositoryPath = process.cwd()) { try { const gitDir = path.resolve(repositoryPath, '.git'); const stat = fs.statSync(gitDir); if (stat.isDirectory()) { return gitDir; } else if (stat.isFile()) { const gitDirRef = fs.readFileSync(gitDir, 'utf8').trim().split(/\s/).pop(); return path.resolve(repositoryPath, gitDirRef); } } catch (err) { const parent = path.resolve(repositoryPath, '..'); if (repositoryPath === parent) return; return Repository.findGitDirSync(parent); } } _initRefs() { return __awaiter(this, void 0, void 0, function* () { if (this._refs) { return; } if (processings[this.gitDir]) { return yield processings[this.gitDir]; } if (!this._packs) { this._packs = yield pack_1.Packs.initialize(this.gitDir); } const createMap = (dir, prefix = '') => __awaiter(this, void 0, void 0, function* () { const map = new Map(); try { const pathes = yield readFilePathes(path.join(this._refsDir, dir)); for (let i = 0; i < pathes.length; i++) { const p = pathes[i]; if (p === 'HEAD') continue; const hash = (yield readFileAsync(path.join(this._refsDir, dir, p), 'utf8')).trim(); map.set(prefix + p, hash); } } catch (e) { } return map; }); const readCommits = (map) => __awaiter(this, void 0, void 0, function* () { const refs = []; for (const [name, hash] of map.entries()) { refs[refs.length] = new Ref(name, this.gitDir, hash, this._packs); } return refs; }); const branchMap = yield createMap('heads'); const tagMap = yield createMap('tags'); const remoteBranchMap = new Map(); try { const dirs = yield readDirAsync(path.join(this._refsDir, 'remotes')); for (let i = 0; i < dirs.length; i++) { const dir = dirs[i]; const map = yield createMap(path.join('remotes', dir), `${dir}/`); for (const [key, hash] of map.entries()) { remoteBranchMap.set(key, hash); } } } catch (e) { } try { const s = yield readFileAsync(path.join(this.gitDir, 'packed-refs'), 'utf8'); addPackedRefs(s, branchMap, tagMap, remoteBranchMap); } catch (e) { } const promise = Promise.all([ readCommits(branchMap), readCommits(tagMap), readCommits(remoteBranchMap), ]).then(([heads, tags, remotes]) => { this._refs = { heads, tags, remotes }; this._initRefMaps(); delete processings[this.gitDir]; }); processings[this.gitDir] = promise; return yield promise; }); } _initRefsSync() { if (this._refs) { return; } if (!this._packs) { this._packs = pack_1.Packs.initializeSync(this.gitDir); } const createMap = (dir, prefix = '') => { const map = new Map(); try { const pathes = readFilePathesSync(path.join(this._refsDir, dir)); for (let i = 0; i < pathes.length; i++) { const p = pathes[i]; if (p === 'HEAD') continue; const hash = fs.readFileSync(path.join(this._refsDir, dir, p), 'utf8').trim(); map.set(prefix + p, hash); } } catch (e) { } return map; }; const readCommits = (map) => { const refs = []; for (const [name, hash] of map.entries()) { refs[refs.length] = new Ref(name, this.gitDir, hash, this._packs); } return refs; }; const branchMap = createMap('heads'); const tagMap = createMap('tags'); const remoteBranchMap = new Map(); try { const dirs = fs.readdirSync(path.join(this._refsDir, 'remotes')); for (let i = 0; i < dirs.length; i++) { const dir = dirs[i]; const map = createMap(path.join('remotes', dir), `${dir}/`); for (const [key, hash] of map.entries()) { remoteBranchMap.set(key, hash); } } } catch (e) { } try { const s = fs.readFileSync(path.join(this.gitDir, 'packed-refs'), 'utf8'); addPackedRefs(s, branchMap, tagMap, remoteBranchMap); } catch (e) { } this._refs = { heads: readCommits(branchMap), tags: readCommits(tagMap), remotes: readCommits(remoteBranchMap), }; this._initRefMaps(); } _initRefMaps() { Object.keys(this._refs).forEach((k) => { const m = new Map(); this._refs[k].forEach(ref => m.set(ref.name, ref)); this._refMaps[k] = m; }); } /** * Read a infomation of `.git/HEAD`. * * ```ts * const head = repo.readHead(); * // if type is 'branch' * console.log(head.type === 'branch'); * console.log(head.branch); * // if type is 'commit' * console.log(head.type === 'commit); * console.log(head.branch); * ``` */ readHead() { return __awaiter(this, void 0, void 0, function* () { if (!this._packs) { this._packs = yield pack_1.Packs.initialize(this.gitDir); } const s = (yield readFileAsync(path.join(this.gitDir, 'HEAD'), 'utf8')).trim(); if (s.startsWith('ref')) { const name = s.match(/^ref: refs\/heads\/(.+)$/)[1]; return { type: 'branch', branch: yield this._readRef('heads', name), }; } else { return { type: 'commit', commit: yield commit_1.Commit.readCommit(this.gitDir, s, this._packs), }; } }); } /** * Read a infomation of `.git/HEAD` sync. */ readHeadSync() { if (!this._packs) { this._packs = pack_1.Packs.initializeSync(this.gitDir); } const s = fs.readFileSync(path.join(this.gitDir, 'HEAD'), 'utf8').trim(); if (s.startsWith('ref')) { const name = s.match(/^ref: refs\/heads\/(.+)$/)[1]; return { type: 'branch', branch: this._readRefSync('heads', name), }; } else { return { type: 'commit', commit: commit_1.Commit.readCommitSync(this.gitDir, s, this._packs), }; } } _readRefs(dir) { return __awaiter(this, void 0, void 0, function* () { if (!this._refs) yield this._initRefs(); return this._refs[dir]; }); } _readRefsSync(dir) { if (!this._refs) this._initRefsSync(); return this._refs[dir]; } _findRef(dir, name) { const ref = this._refMaps[dir].get(name); if (!ref) throw new Error(`refs/${dir}/${name} is not found.`); return ref; } _readRef(dir, name) { return __awaiter(this, void 0, void 0, function* () { if (!this._refs) yield this._initRefs(); return this._findRef(dir, name); }); } _readRefSync(dir, name) { if (!this._refs) this._initRefsSync(); return this._findRef(dir, name); } /** * Read branches. * * ```ts * const heads = await repo.readBranches(); // or await repo.readBranches('heads'); * const remotes = await repo.readBranches('remotes'); * const allBranches = await repo.readBranches(['heads', 'remotes']); * console.log(heads[0].name, heads[0].commit); * ``` */ readBranches(dirs = ['heads']) { return __awaiter(this, void 0, void 0, function* () { if (!Array.isArray(dirs)) { dirs = [dirs]; } return yield Promise .all(dirs.map((dir) => __awaiter(this, void 0, void 0, function* () { return yield this._readRefs(dir); }))) .then(results => Array.prototype.concat.apply([], results)); }); } /** * Read branches sync. */ readBranchesSync(dirs = ['heads']) { if (!Array.isArray(dirs)) { dirs = [dirs]; } return Array.prototype.concat.apply([], dirs.map(dir => this._readRefsSync(dir))); } /** * Read a commit by branch name. * * ```ts * const masterCommit = await repo.readCommitByBranch('master'); * const originMasterCommit = await repo.readCommitByBranch('origin/master'); * ``` */ readCommitByBranch(branchName, scope = ['heads', 'remotes']) { return __awaiter(this, void 0, void 0, function* () { if (!Array.isArray(scope)) scope = [scope]; let err = null; for (let i = 0; i < scope.length; i++) { try { return (yield this._readRef(scope[i], branchName)).commit; } catch (e) { err = e; } } throw err; }); } /** * Read a commit by branch name sync. */ readCommitByBranchSync(branchName, scope = ['heads', 'remotes']) { if (!Array.isArray(scope)) scope = [scope]; let err = null; for (let i = 0; i < scope.length; i++) { try { return this._readRefSync(scope[i], branchName).commit; } catch (e) { err = e; } } throw err; } /** * Read tags. * * ```ts * const tags = await repo.readTags(); * ``` */ readTags() { return __awaiter(this, void 0, void 0, function* () { return yield this._readRefs('tags'); }); } /** * Read tags sync. */ readTagsSync() { return this._readRefsSync('tags'); } /** * Read a commit by tag name. * * ```ts * const commit = repo.readCommitByTag('v1.0'); * ``` */ readCommitByTag(tagName) { return __awaiter(this, void 0, void 0, function* () { return (yield this._readRef('tags', tagName)).commit; }); } /** * Read a commit by tag name sync. */ readCommitByTagSync(tagName) { return this._readRefSync('tags', tagName).commit; } } exports.Repository = Repository; function addPackedRefs(s, branchMap, tagMap, remoteBranchMap) { const _tagMap = new Map(); const lines = s.trim().split('\n').forEach((line) => { const m = line.match(/([a-f\d]{40}) refs\/(heads|remotes|tags)\/(.+)/); if (!m) return; const [, hash, type, name] = m; switch (type) { case 'heads': !branchMap.has(name) && branchMap.set(name, hash); break; case 'remotes': !remoteBranchMap.has(name) && remoteBranchMap.set(name, hash); break; case 'tags': !tagMap.has(name) && tagMap.set(name, hash); break; } }); } function normalizePath(dir, pathes) { pathes = pathes.map(p => p.slice(dir.length + 1)); return path.sep === '/' ? pathes : pathes.map(p => p.replace(/\\/g, '/')); } function readFilePathes(dir) { return __awaiter(this, void 0, void 0, function* () { const pathes = []; function _getFilePathes(dir) { return __awaiter(this, void 0, void 0, function* () { let names = yield readDirAsync(dir); for (let i = 0; i < names.length; i++) { const p = path.join(dir, names[i]); const stats = yield statAsync(p); if (stats.isFile()) pathes.push(p); if (stats.isDirectory()) yield _getFilePathes(p); } }); } yield _getFilePathes(dir); return normalizePath(dir, pathes); }); } function readFilePathesSync(dir) { const pathes = []; function _getFilePathes(dir) { let names = fs.readdirSync(dir); for (let i = 0; i < names.length; i++) { const p = path.join(dir, names[i]); const stats = fs.statSync(p); if (stats.isFile()) pathes.push(p); if (stats.isDirectory()) _getFilePathes(p); } } _getFilePathes(dir); return normalizePath(dir, pathes); } //# sourceMappingURL=repo.js.map