UNPKG

git-secure-tag

Version:

Git tags with higher level of security

144 lines (115 loc) 3.95 kB
'use strict'; const assert = require('assert'); const path = require('path'); const crypto = require('crypto'); const Buffer = require('buffer').Buffer; const async = require('async'); const gst = require('../git-secure-tag'); function Hash(options) { this.options = options || {}; this.cwd = null; } module.exports = Hash; function compareEntries(a, b) { return a.name > b.name ? 1 : a.name < b.name ? -1 : 0; } Hash.prototype.hashBlob = function hashBlob(batch, blob, callback) { const hash = crypto.createHash('sha512'); const stream = batch.query(blob); stream.pipe(hash); stream.on('error', err => callback(err)); let content = []; hash.on('data', chunk => content.push(chunk)); hash.on('end', () => { content = Buffer.concat(content); callback(null, content.toString('hex')); }); }; Hash.prototype.hashSubmodule = function hashSubmodule(dir, ref, callback) { const hash = new Hash(this.options); hash._calculate(ref, dir, path.join(this.cwd, dir), callback); }; Hash.prototype.flattenTree = function flattenTree(batch, dir, tree, callback) { const stream = batch.query(tree); let content = []; stream.on('data', chunk => content.push(chunk)); stream.on('error', err => callback(err)); const recurse = (entry, callback) => { const fullPath = dir === '' ? entry.name : dir + '/' + entry.name; function next(err, content) { entry.content = content; callback(err, entry); } if (entry.mode === '40000') { return this.flattenTree(batch, fullPath, entry.hash, next); } else if (entry.mode === '160000') { return this.hashSubmodule(fullPath, entry.hash, next); } return this.hashBlob(batch, entry.hash, next); }; stream.on('end', () => { content = Buffer.concat(content); content = gst.common.parseTree(content); content.sort(compareEntries); async.map(content, recurse, callback); }); }; Hash.prototype.digest = function digest(hash, line) { hash.update(line + '\n'); if (this.options.verbose) process.stdout.write(line + '\n'); }; Hash.prototype.hashTree = function hashTree(tree, digest) { tree.forEach((entry) => { const line = entry.mode + ' ' + entry.name + ' ' + entry.hash; if (Array.isArray(entry.content)) { this.digest(digest, line + ' ' + entry.content.length); this.hashTree(entry.content, digest); } else { this.digest(digest, line + ' ' + entry.content); } }); }; Hash.prototype.getTree = function getTree(batch, ref, callback) { const stream = batch.query(ref); stream.on('header', (type) => { let content = ''; stream.on('data', chunk => content += chunk); stream.once('end', () => { if (type === 'tag') { const match = content.match(/(?:^|[\r\n])object ([a-z0-9]{40})/); assert(match !== null, 'Tag without `object`'); return this.getTree(batch, match[1], callback); } assert.equal(type, 'commit'); const match = content.match(/(?:^|[\r\n])tree ([a-z0-9]{40})/); assert(match !== null, 'Commit without `tree`'); return callback(null, match[1]); }); }); }; Hash.prototype._calculate = function _calculate(hash, relative, dir, callback) { const digest = crypto.createHash('sha512'); const batch = new gst.Batch(dir); this.cwd = dir; // `update-index`? async.waterfall([ (callback) => { this.getTree(batch, hash || 'HEAD', callback); }, (hash, callback) => { this.flattenTree(batch, relative, hash, (err, tree) => callback(err, tree, hash)); } ], (err, tree, hash) => { if (err) return callback(err); batch.destroy(); this.digest(digest, 'write-tree ' + hash + ' ' + tree.length); this.hashTree(tree, digest); callback(null, digest.digest('hex')); }); }; Hash.prototype.calculate = function calculate(dir, hash, callback) { this._calculate(hash, '', dir, callback); };