From 51a9b5e3b88019b8d914e8defd1e6adcf95dc0d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C5=81ukasz=20Magiera?= Date: Sat, 2 Sep 2017 22:47:06 +0200 Subject: [PATCH] feat: v0.1.0 --- package.json | 11 +- src/index.js | 1 - src/resolver.js | 144 +++++++- src/util.js | 75 ++++- src/util/commit.js | 89 +++++ src/util/tag.js | 65 ++++ src/util/tree.js | 56 ++++ src/util/util.js | 65 ++++ .../0a1690e0640a212aafed1824eb208ffddab1789b | Bin 0 -> 47 bytes .../19f0805d8de36b6442e8c573074112ba72ad6780 | Bin 0 -> 22 bytes .../1e2cb60e9e29de3320459f02cb156b66a86925aa | Bin 0 -> 246 bytes .../4bbc624c5e1d7e13fac32698c20cf47bc7df3ffa | Bin 0 -> 135 bytes .../5af4dc18899e8ac95904eaf2b4abf1a2ca5e1507 | Bin 0 -> 47 bytes .../672ef117424f54b71e5e058d1184de6a07450d0e | Bin 0 -> 131 bytes .../6860d53002bcd8f9a2e371462743340abc1c47a8 | Bin 0 -> 157 bytes .../70ce33c808791776ea89c98bb28bdab6352fc6e3 | 2 + .../788506cb0585ff19bfe9a5add28ab03d1b6db165 | Bin 0 -> 103 bytes .../7ffd1401f599c70364eda431d29363e037b2c92c | Bin 0 -> 105 bytes .../802992c4220de19a90767f3000a79a31b98d0df7 | Bin 0 -> 28 bytes .../832b4a8497de78248f70c06e0f06e785a74fea4c | Bin 0 -> 79 bytes .../933b7583b7767b07ea4cf242c1be29162eb8bb85 | Bin 0 -> 21 bytes .../9f358a4addefcab294b83e4282bfef1f9625a249 | Bin 0 -> 22 bytes test/fixtures/README.md | 1 + .../a0f06ca3cdb1e6e7f603794ef1cd9f9867f85551 | Bin 0 -> 22 bytes .../a847fad8424bf2fed664658d215921a9cf0275a5 | Bin 0 -> 158 bytes .../aa9080345b7881124ee5a605b72d1d7b6892d1dc | 1 + .../adf239853d9b280be483592b40c4bd8b5d9fb524 | Bin 0 -> 104 bytes .../af99300d66a5078022b14c388d214046cb2647ca | Bin 0 -> 104 bytes .../bcd6c57873ddd85c30e7758a5c34d5ffa37496f8 | 2 + .../bdc9b0f98b578af9015359103503d5de3218efdd | 2 + .../ce717819ba52636f465c805794cef0c3103770c4 | 2 + .../d31ec9422b3048f104b4c090cf1c0f5be0963cd9 | Bin 0 -> 20 bytes .../d94c1dbabf13cff8bed80d8818c41b20bca4ee39 | 3 + .../dee159b7de821f3dcfc75b9e4f3857689759aa37 | Bin 0 -> 176 bytes .../e247bdd4cdbe6a3b447cb2dcf1fa03fba2f3d64a | Bin 0 -> 129 bytes .../f5227cbd32973ec90f48f2547e6fe16c80b92bd5 | Bin 0 -> 103 bytes .../fa59b93c6ce9db82ce57de9046eb738e9f3c0952 | Bin 0 -> 49 bytes .../fc80daf21bb484cfd42ad4ed4d17da48638199ea | Bin 0 -> 22 bytes .../ffef5350b6f8762cc6272b0255e968f50b6577ed | Bin 0 -> 104 bytes test/fixtures/objects.json | 32 ++ test/fixtures/update.sh | 12 + test/parse.spec.js | 107 ++++++ test/resolver.spec.js | 308 ++++++++++++++++++ 43 files changed, 961 insertions(+), 17 deletions(-) create mode 100644 src/util/commit.js create mode 100644 src/util/tag.js create mode 100644 src/util/tree.js create mode 100644 src/util/util.js create mode 100644 test/fixtures/0a1690e0640a212aafed1824eb208ffddab1789b create mode 100644 test/fixtures/19f0805d8de36b6442e8c573074112ba72ad6780 create mode 100644 test/fixtures/1e2cb60e9e29de3320459f02cb156b66a86925aa create mode 100644 test/fixtures/4bbc624c5e1d7e13fac32698c20cf47bc7df3ffa create mode 100644 test/fixtures/5af4dc18899e8ac95904eaf2b4abf1a2ca5e1507 create mode 100644 test/fixtures/672ef117424f54b71e5e058d1184de6a07450d0e create mode 100644 test/fixtures/6860d53002bcd8f9a2e371462743340abc1c47a8 create mode 100644 test/fixtures/70ce33c808791776ea89c98bb28bdab6352fc6e3 create mode 100644 test/fixtures/788506cb0585ff19bfe9a5add28ab03d1b6db165 create mode 100644 test/fixtures/7ffd1401f599c70364eda431d29363e037b2c92c create mode 100644 test/fixtures/802992c4220de19a90767f3000a79a31b98d0df7 create mode 100644 test/fixtures/832b4a8497de78248f70c06e0f06e785a74fea4c create mode 100644 test/fixtures/933b7583b7767b07ea4cf242c1be29162eb8bb85 create mode 100644 test/fixtures/9f358a4addefcab294b83e4282bfef1f9625a249 create mode 100644 test/fixtures/README.md create mode 100644 test/fixtures/a0f06ca3cdb1e6e7f603794ef1cd9f9867f85551 create mode 100644 test/fixtures/a847fad8424bf2fed664658d215921a9cf0275a5 create mode 100644 test/fixtures/aa9080345b7881124ee5a605b72d1d7b6892d1dc create mode 100644 test/fixtures/adf239853d9b280be483592b40c4bd8b5d9fb524 create mode 100644 test/fixtures/af99300d66a5078022b14c388d214046cb2647ca create mode 100644 test/fixtures/bcd6c57873ddd85c30e7758a5c34d5ffa37496f8 create mode 100644 test/fixtures/bdc9b0f98b578af9015359103503d5de3218efdd create mode 100644 test/fixtures/ce717819ba52636f465c805794cef0c3103770c4 create mode 100644 test/fixtures/d31ec9422b3048f104b4c090cf1c0f5be0963cd9 create mode 100644 test/fixtures/d94c1dbabf13cff8bed80d8818c41b20bca4ee39 create mode 100644 test/fixtures/dee159b7de821f3dcfc75b9e4f3857689759aa37 create mode 100644 test/fixtures/e247bdd4cdbe6a3b447cb2dcf1fa03fba2f3d64a create mode 100644 test/fixtures/f5227cbd32973ec90f48f2547e6fe16c80b92bd5 create mode 100644 test/fixtures/fa59b93c6ce9db82ce57de9046eb738e9f3c0952 create mode 100644 test/fixtures/fc80daf21bb484cfd42ad4ed4d17da48638199ea create mode 100644 test/fixtures/ffef5350b6f8762cc6272b0255e968f50b6577ed create mode 100644 test/fixtures/objects.json create mode 100755 test/fixtures/update.sh create mode 100644 test/parse.spec.js create mode 100644 test/resolver.spec.js diff --git a/package.json b/package.json index e6d23fe..6d0a2db 100644 --- a/package.json +++ b/package.json @@ -37,10 +37,13 @@ }, "homepage": "https://github.com/ipld/js-ipld-git", "dependencies": { - "async": "^2.3.0", - "cids": "~0.5.0", - "multihashes": "~0.4.5", - "multihashing-async": "~0.4.5", + "async": "^2.5.0", + "cids": "~0.5.1", + "multicodec": "~0.1.9", + "multihashes": "~0.4.8", + "multihashing-async": "~0.4.6", + "smart-buffer": "^3.0.3", + "traverse": "^~0.6.6" }, "devDependencies": { "aegir": "^11.0.1", diff --git a/src/index.js b/src/index.js index 7dc585b..adc8a1b 100644 --- a/src/index.js +++ b/src/index.js @@ -2,4 +2,3 @@ exports.util = require('./util.js') exports.resolver = require('./resolver.js') - diff --git a/src/resolver.js b/src/resolver.js index 84e28d6..27e8de8 100644 --- a/src/resolver.js +++ b/src/resolver.js @@ -1,16 +1,152 @@ 'use strict' const util = require('./util') -const setImmediate = require('async/setImmediate') +const traverse = require('traverse') exports = module.exports -exports.multicodec = 'git' +exports.multicodec = 'git-raw' + +const personInfoPaths = [ + 'original', + 'name', + 'email', + 'date' +] exports.resolve = (block, path, callback) => { - setImmediate(() => callback(new Error("not implemented"), null)) + if (typeof path === 'function') { + callback = path + path = undefined + } + + util.deserialize(block.data, (err, node) => { + if (err) { + return callback(err) + } + + if (!path || path === '/') { + return callback(null, { + value: node, + remainderPath: '' + }) + } + + if (Buffer.isBuffer(node)) { // git blob + return callback(null, { + value: node, + remainderPath: path + }) + } + + const parts = path.split('/') + const val = traverse(node).get(parts) + + if (val) { + return callback(null, { + value: val, + remainderPath: '' + }) + } + + let value + let len = parts.length + + for (let i = 0; i < len; i++) { + const partialPath = parts.shift() + + if (Array.isArray(node)) { + value = node[Number(partialPath)] + } if (node[partialPath]) { + value = node[partialPath] + } else { + // can't traverse more + if (!value) { + return callback(new Error('path not available at root')) + } else { + parts.unshift(partialPath) + return callback(null, { + value: value, + remainderPath: parts.join('/') + }) + } + } + node = value + } + }) } exports.tree = (block, options, callback) => { - setImmediate(() => callback(new Error("not implemented"), null)) + if (typeof options === 'function') { + callback = options + options = undefined + } + + options = options || {} + + util.deserialize(block.data, (err, node) => { + if (err) { + return callback(err) + } + + if (Buffer.isBuffer(node)) { // git blob + return callback(null, []) + } + + let paths = [] + switch (node.gitType) { + case 'commit': + paths = [ + 'message', + 'tree' + ] + + paths = paths.concat(personInfoPaths.map(e => 'author/' + e)) + paths = paths.concat(personInfoPaths.map(e => 'committer/' + e)) + paths = paths.concat([...node.parents.keys()].map(e => 'parents/' + e)) + + if (node.encoding) { + paths.push('encoding') + } + break + case 'tag': + paths = [ + 'object', + 'type', + 'tag', + 'message' + ] + + if (node.tagger) { + paths = paths.concat(personInfoPaths.map((e) => 'tagger/' + e)) + } + + break + default: // tree + Object.keys(node).forEach(dir => { + paths.push(dir) + paths.push(dir + '/hash') + paths.push(dir + '/mode') + }) + } + callback(null, paths) + }) +} + +exports.isLink = (block, path, callback) => { + exports.resolve(block, path, (err, result) => { + if (err) { + return callback(err) + } + + if (result.remainderPath.length > 0) { + return callback(new Error('path out of scope')) + } + + if (typeof result.value === 'object' && result.value['/']) { + callback(null, result.value) + } else { + callback(null, false) + } + }) } diff --git a/src/util.js b/src/util.js index e065879..3b12aeb 100644 --- a/src/util.js +++ b/src/util.js @@ -1,19 +1,78 @@ 'use strict' const setImmediate = require('async/setImmediate') +const waterfall = require('async/waterfall') +const multihashing = require('multihashing-async') +const CID = require('cids') -const CID_GIT_TAG = 0x78 +const resolver = require('./resolver') +const gitUtil = require('./util/util') + +const commit = require('./util/commit') +const tag = require('./util/tag') +const tree = require('./util/tree') exports = module.exports exports.serialize = (dagNode, callback) => { - setImmediate(() => callback(new Error("not implemented"), null)) -}) + if (dagNode === null) { + setImmediate(() => callback(new Error('dagNode passed to serialize was null'), null)) + return + } + + if (Buffer.isBuffer(dagNode)) { + if (dagNode.slice(0, 4).toString() === 'blob') { + setImmediate(() => callback(null, dagNode)) + } else { + setImmediate(() => callback(new Error('unexpected dagNode passed to serialize'), null)) + } + return + } + + switch (dagNode.gitType) { + case 'commit': + commit.serialize(dagNode, callback) + break + case 'tag': + tag.serialize(dagNode, callback) + break + default: + // assume tree as a file named 'type' is legal + tree.serialize(dagNode, callback) + } +} + +exports.deserialize = (data, callback) => { + let headLen = gitUtil.find(data, 0) + let head = data.slice(0, headLen).toString() + let typeLen = head.match(/([^ ]+) (\d+)/) + if (!typeLen) { + setImmediate(() => callback(new Error('invalid object header'), null)) + return + } -exports.deserialize = (dagNode, callback) => { - setImmediate(() => callback(new Error("not implemented"), null)) -}) + switch (typeLen[1]) { + case 'blob': + callback(null, data) + break + case 'commit': + commit.deserialize(data.slice(headLen + 1), callback) + break + case 'tag': + tag.deserialize(data.slice(headLen + 1), callback) + break + case 'tree': + tree.deserialize(data.slice(headLen + 1), callback) + break + default: + setImmediate(() => callback(new Error('unknown object type ' + typeLen[1]), null)) + } +} exports.cid = (dagNode, callback) => { - setImmediate(() => callback(new Error("not implemented"), null)) -}) + waterfall([ + (cb) => exports.serialize(dagNode, cb), + (serialized, cb) => multihashing(serialized, 'sha1', cb), + (mh, cb) => cb(null, new CID(1, resolver.multicodec, mh)) + ], callback) +} diff --git a/src/util/commit.js b/src/util/commit.js new file mode 100644 index 0000000..0f93a42 --- /dev/null +++ b/src/util/commit.js @@ -0,0 +1,89 @@ +'use strict' + +const setImmediate = require('async/setImmediate') +const SmartBuffer = require('smart-buffer').SmartBuffer +const gitUtil = require('./util') + +exports = module.exports + +exports.serialize = (dagNode, callback) => { + let lines = [] + lines.push('tree ' + gitUtil.cidToSha(dagNode.tree['/']).toString('hex')) + dagNode.parents.forEach((parent) => { + lines.push('parent ' + gitUtil.cidToSha(parent['/']).toString('hex')) + }) + lines.push('author ' + gitUtil.serializePersonLine(dagNode.author)) + lines.push('committer ' + gitUtil.serializePersonLine(dagNode.committer)) + if (dagNode.encoding) { + lines.push('encoding ' + dagNode.encoding) + } + if (dagNode.signature) { + lines.push('gpgsig -----BEGIN PGP SIGNATURE-----') + lines.push(dagNode.signature.text) + lines.push(' -----END PGP SIGNATURE-----') + } + lines.push('') + lines.push(dagNode.message) + + let data = lines.join('\n') + + let outBuf = new SmartBuffer() + outBuf.writeString('commit ') + outBuf.writeString(data.length.toString()) + outBuf.writeUInt8(0) + outBuf.writeString(data) + setImmediate(() => callback(null, outBuf.toBuffer())) +} + +exports.deserialize = (data, callback) => { + let lines = data.toString().split('\n') + let res = {gitType: 'commit', parents: []} + + for (let line = 0; line < lines.length; line++) { + let m = lines[line].match(/^([^ ]+) (.+)$/) + if (!m) { + if (lines[line] !== '') { + setImmediate(() => callback(new Error('Invalid commit line ' + line))) + } + res.message = lines.slice(line + 1).join('\n') + break + } + + let key = m[1] + let value = m[2] + switch (key) { + case 'tree': + res.tree = {'/': gitUtil.shaToCid(new Buffer(value, 'hex'))} + break + case 'committer': + res.committer = gitUtil.parsePersonLine(value) + break + case 'author': + res.author = gitUtil.parsePersonLine(value) + break + case 'parent': + res.parents.push({'/': gitUtil.shaToCid(new Buffer(value, 'hex'))}) + break + case 'gpgsig': { + if (value !== '-----BEGIN PGP SIGNATURE-----') { + setImmediate(() => callback(new Error('Invalid commit line ' + line))) + } + res.signature = {} + + let startLine = line + for (; line < lines.length - 1; line++) { + if (lines[line + 1] === ' -----END PGP SIGNATURE-----') { + res.signature.text = lines.slice(startLine + 1, line + 1).join('\n') + break + } + } + line++ + break + } + default: + res[key] = value + } + } + + setImmediate(() => callback(null, res)) +} diff --git a/src/util/tag.js b/src/util/tag.js new file mode 100644 index 0000000..1fbb159 --- /dev/null +++ b/src/util/tag.js @@ -0,0 +1,65 @@ +'use strict' + +const setImmediate = require('async/setImmediate') +const SmartBuffer = require('smart-buffer').SmartBuffer +const gitUtil = require('./util') + +exports = module.exports + +exports.serialize = (dagNode, callback) => { + let lines = [] + lines.push('object ' + gitUtil.cidToSha(dagNode.object['/']).toString('hex')) + lines.push('type ' + dagNode.type) + lines.push('tag ' + dagNode.tag) + if (dagNode.tagger !== null) { + lines.push('tagger ' + gitUtil.serializePersonLine(dagNode.tagger)) + } + lines.push('') + lines.push(dagNode.message) + + let data = lines.join('\n') + + let outBuf = new SmartBuffer() + outBuf.writeString('tag ') + outBuf.writeString(data.length.toString()) + outBuf.writeUInt8(0) + outBuf.writeString(data) + setImmediate(() => callback(null, outBuf.toBuffer())) +} + +exports.deserialize = (data, callback) => { + let lines = data.toString().split('\n') + let res = {gitType: 'tag'} + + for (let line = 0; line < lines.length; line++) { + let m = lines[line].match(/^([^ ]+) (.+)$/) + if (m === null) { + if (lines[line] !== '') { + setImmediate(() => callback(new Error('Invalid tag line ' + line))) + } + res.message = lines.slice(line + 1).join('\n') + break + } + + let key = m[1] + let value = m[2] + switch (key) { + case 'object': + res.object = {'/': gitUtil.shaToCid(new Buffer(value, 'hex'))} + break + case 'tagger': + res.tagger = gitUtil.parsePersonLine(value) + break + case 'tag': + res.tag = value + break + case 'type': + res.type = value + break + default: + res[key] = value + } + } + + setImmediate(() => callback(null, res)) +} diff --git a/src/util/tree.js b/src/util/tree.js new file mode 100644 index 0000000..41520c2 --- /dev/null +++ b/src/util/tree.js @@ -0,0 +1,56 @@ +'use strict' + +const setImmediate = require('async/setImmediate') +const SmartBuffer = require('smart-buffer').SmartBuffer +const gitUtil = require('./util') + +exports = module.exports + +exports.serialize = (dagNode, callback) => { + let entries = [] + Object.keys(dagNode).forEach((name) => { + entries.push([name, dagNode[name]]) + }) + entries.sort((a, b) => a[0] > b[0] ? 1 : -1) + let buf = new SmartBuffer() + entries.forEach((entry) => { + buf.writeStringNT(entry[1].mode + ' ' + entry[0]) + buf.writeBuffer(gitUtil.cidToSha(entry[1].hash['/'])) + }) + + let outBuf = new SmartBuffer() + outBuf.writeString('tree ') + outBuf.writeString(buf.length.toString()) + outBuf.writeUInt8(0) + outBuf.writeBuffer(buf.toBuffer()) + setImmediate(() => callback(null, outBuf.toBuffer())) +} + +exports.deserialize = (data, callback) => { + let res = {} + let buf = SmartBuffer.fromBuffer(data, 'utf8') + + for (;;) { + let modeName = buf.readStringNT() + if (modeName === '') { + break + } + + let hash = buf.readBuffer(gitUtil.SHA1_LENGTH) + let modNameMatched = modeName.match(/^(\d+) (.+)$/) + if (!modNameMatched) { + setImmediate(() => callback(new Error('invalid file mode/name'))) + } + + if (res[modNameMatched[2]]) { + setImmediate(() => callback(new Error('duplicate file in tree'))) + } + + res[modNameMatched[2]] = { + mode: modNameMatched[1], + hash: {'/': gitUtil.shaToCid(hash)} + } + } + + setImmediate(() => callback(null, res)) +} diff --git a/src/util/util.js b/src/util/util.js new file mode 100644 index 0000000..a4b4a71 --- /dev/null +++ b/src/util/util.js @@ -0,0 +1,65 @@ +'use strict' + +const SmartBuffer = require('smart-buffer').SmartBuffer +const multihashes = require('multihashes/src/constants') +const multicodecs = require('multicodec/src/base-table') +const multihash = require('multihashes') +const CID = require('cids') + +exports = module.exports + +exports.SHA1_LENGTH = multihashes.defaultLengths[multihashes.names.sha1] + +exports.find = (buf, byte) => { + for (let i = 0; i < buf.length; i++) { + if (buf[i] === byte) { + return i + } + } + return -1 +} + +exports.parsePersonLine = (line) => { + let matched = line.match(/^(([^<]+)\s)?\s?<([^>]+)>\s?(\d+\s[+\-\d]+)?$/) + if (matched === null) { + return null + } + + return { + name: matched[2], + email: matched[3], + date: matched[4] + } +} + +exports.serializePersonLine = (node) => { + let parts = [] + if (node.name) { + parts.push(node.name) + } + parts.push('<' + node.email + '>') + if (node.date) { + parts.push(node.date) + } + + return parts.join(' ') +} + +exports.shaToCid = (buf) => { + let mhashBuf = new SmartBuffer() + mhashBuf.writeUInt8(1) + mhashBuf.writeBuffer(multicodecs['git-raw']) + mhashBuf.writeUInt8(multihashes.names.sha1) + mhashBuf.writeUInt8(exports.SHA1_LENGTH) + mhashBuf.writeBuffer(buf) + return mhashBuf.toBuffer() +} + +exports.cidToSha = (cidBuf) => { + let mh = multihash.decode(new CID(cidBuf).multihash) + if (mh.name !== 'sha1') { + return null + } + + return mh.digest +} diff --git a/test/fixtures/0a1690e0640a212aafed1824eb208ffddab1789b b/test/fixtures/0a1690e0640a212aafed1824eb208ffddab1789b new file mode 100644 index 0000000000000000000000000000000000000000..31f6b188c9a93d5af2e76f3f1041a860ad6b20d5 GIT binary patch literal 47 zcmbYF} DjAIiO literal 0 HcmV?d00001 diff --git a/test/fixtures/19f0805d8de36b6442e8c573074112ba72ad6780 b/test/fixtures/19f0805d8de36b6442e8c573074112ba72ad6780 new file mode 100644 index 0000000000000000000000000000000000000000..8a375168cae1b4d841120cf6f8a6711619b1fba8 GIT binary patch literal 22 dcmbOJP)1IVOxknv4uLUmG@XWzjZzPwU^D|FE=Zl@kq$N0eO0A2&BJxlmGw# literal 0 HcmV?d00001 diff --git a/test/fixtures/4bbc624c5e1d7e13fac32698c20cf47bc7df3ffa b/test/fixtures/4bbc624c5e1d7e13fac32698c20cf47bc7df3ffa new file mode 100644 index 0000000000000000000000000000000000000000..d32213e85a78c62d951ec063ec7783c6d6e64986 GIT binary patch literal 135 zcmV;20C@j+0Tqlv4#FT106p`Hy_*sEkJye9RI%6O5?el^}8{V->YMcJ;?V8$9^6z?}&$ pQ?tZ$qfaR7OsAG#K^dvhdEI$9N|aKFCl`X9w)e=3_yIuJD|ZfzJOTg! literal 0 HcmV?d00001 diff --git a/test/fixtures/5af4dc18899e8ac95904eaf2b4abf1a2ca5e1507 b/test/fixtures/5af4dc18899e8ac95904eaf2b4abf1a2ca5e1507 new file mode 100644 index 0000000000000000000000000000000000000000..34be59774ec058c186bb3b6b01cf6b7b5ffacba4 GIT binary patch literal 47 zcmV+~0MP$<0V^p=O;s>9U@$Z=Ff%bxNHb>m({SsP^p=+MSG2CY^%cM6k=!`*6#xZg F5Hhl-6e0is literal 0 HcmV?d00001 diff --git a/test/fixtures/672ef117424f54b71e5e058d1184de6a07450d0e b/test/fixtures/672ef117424f54b71e5e058d1184de6a07450d0e new file mode 100644 index 0000000000000000000000000000000000000000..1491802dd81c5b93bd608bacc9e3839f53486b08 GIT binary patch literal 131 zcmV-}0DS*=0V^p=O;s>7GG;I|FfcPQQAjgnko?dP+xs{>#p%V-Vs=NNT}5lt8(^{~ z3=2NwEIzyO+4FDAm3|-3&YzL~BQy{uYszq0?xd5pfyYOdEe9r?m*J0oFwN$si2)EO l6qhEYWEL@SiA{Kr!lkIS{;h<{YlZ&5w>DPH1^^6%G|;JdJRkr7 literal 0 HcmV?d00001 diff --git a/test/fixtures/6860d53002bcd8f9a2e371462743340abc1c47a8 b/test/fixtures/6860d53002bcd8f9a2e371462743340abc1c47a8 new file mode 100644 index 0000000000000000000000000000000000000000..d0b5b565273853e7e921f3bb7ee30968aeffaaf4 GIT binary patch literal 157 zcmV;O0Al}m0hNwR3c@fDKwak)*$Xn6{3amc!i9K?WICl4)07gS$G2B-_jvH&ajSLi z8jU~oO+@E0B?~ZwkUc~K#9|yk+WL!CQH{5Sgd6Nipj(nBvaxp?Ni(It4)i3G||`hK|_>!B8GImkK#`LA=~BRSbGIq?q#vN^76rDjToZ7 z+>z*<*L5BZ4vc1NLIMQhU=V6r9* z3qIs5KD+VR^KZmLglqW|KD31 LD`o=#o7E{qU#l+g literal 0 HcmV?d00001 diff --git a/test/fixtures/802992c4220de19a90767f3000a79a31b98d0df7 b/test/fixtures/802992c4220de19a90767f3000a79a31b98d0df7 new file mode 100644 index 0000000000000000000000000000000000000000..6a172c1cf873355d9613909ae7a260660203f11d GIT binary patch literal 28 kcmb4V4FlPAM9&g-A@e8|M$G?6V60JZuI3jhEB literal 0 HcmV?d00001 diff --git a/test/fixtures/832b4a8497de78248f70c06e0f06e785a74fea4c b/test/fixtures/832b4a8497de78248f70c06e0f06e785a74fea4c new file mode 100644 index 0000000000000000000000000000000000000000..ec2357529c593e1158567a7c263642896f563b85 GIT binary patch literal 79 zcmV-V0I>gf0V^p=O;s>6W-v4`Ff%bxNHb)R{Lm2F`#3wr>BZ4vc1NLIMQhU=ObmcP lp|~_DC9{ZuOKie}6fQ-r^=~CqUMuwfy|uAoHUMAZ8nn@KA&~$8 literal 0 HcmV?d00001 diff --git a/test/fixtures/933b7583b7767b07ea4cf242c1be29162eb8bb85 b/test/fixtures/933b7583b7767b07ea4cf242c1be29162eb8bb85 new file mode 100644 index 0000000000000000000000000000000000000000..d109799f50061e33fb74345b811581fec12c106e GIT binary patch literal 21 ccmbpUJYF(__hi2(p@stFYU literal 0 HcmV?d00001 diff --git a/test/fixtures/a847fad8424bf2fed664658d215921a9cf0275a5 b/test/fixtures/a847fad8424bf2fed664658d215921a9cf0275a5 new file mode 100644 index 0000000000000000000000000000000000000000..9e748a32d9e619ed65ba3e1ab32b766ac7e4a418 GIT binary patch literal 158 zcmV;P0Ac@l0hNwH3c@fDMP26E4Jtw`(gvrJz`2b#ldv|bE zAtq-mm+W9ve7CW#bgkn|mkK&Qwl!3o@rl#'$|M4i<=`_N0Ey=JY|Mz?., J?q~ta|]hp$/ܮ} ?)>X7suhB<3׾ĕ3} 㶰rǸ!P5įjD5@Le \ No newline at end of file diff --git a/test/fixtures/adf239853d9b280be483592b40c4bd8b5d9fb524 b/test/fixtures/adf239853d9b280be483592b40c4bd8b5d9fb524 new file mode 100644 index 0000000000000000000000000000000000000000..1b0a590e68d61d244abc5e3e7b2766d1e053f6ad GIT binary patch literal 104 zcmV-u0GI!G0V^p=O;xZoW-v4`Ff%bxNHb)R{Lm2F`#3wr>BZ4vc1NLIMQhU=V6vtR zm*q}6X&ZQaWZ80H!g(3~=m*nmZkiYXfkJU>!B8Ih}EL&}7Yj8hlj?{KIvbQ^S{#F&ZtPw-h zmpc-j^SVw(vb_4VW%b8Jr{YA}4Gj#;OiUEgGILTH8Z;*zQR00#YeHGQ0mJfHhC6$C KzXJe$j3>SBVk}<( literal 0 HcmV?d00001 diff --git a/test/fixtures/bcd6c57873ddd85c30e7758a5c34d5ffa37496f8 b/test/fixtures/bcd6c57873ddd85c30e7758a5c34d5ffa37496f8 new file mode 100644 index 0000000..c811391 --- /dev/null +++ b/test/fixtures/bcd6c57873ddd85c30e7758a5c34d5ffa37496f8 @@ -0,0 +1,2 @@ +xAJD1]I'q1+CtΌ x|{U},TnY1,&1Zo1*1)AzJ1t\ki) +iy>~t}S xHRb.zYҽ~6Xzo~dR \ No newline at end of file diff --git a/test/fixtures/bdc9b0f98b578af9015359103503d5de3218efdd b/test/fixtures/bdc9b0f98b578af9015359103503d5de3218efdd new file mode 100644 index 0000000..09ff978 --- /dev/null +++ b/test/fixtures/bdc9b0f98b578af9015359103503d5de3218efdd @@ -0,0 +1,2 @@ +x= +1@abzQI@d,Dooy|kie LA4 =~(=%GmuVH:ADXE뇗ڍB?O 83a1ZhA9iuGJ-W** \ No newline at end of file diff --git a/test/fixtures/ce717819ba52636f465c805794cef0c3103770c4 b/test/fixtures/ce717819ba52636f465c805794cef0c3103770c4 new file mode 100644 index 0000000..cc22d08 --- /dev/null +++ b/test/fixtures/ce717819ba52636f465c805794cef0c3103770c4 @@ -0,0 +1,2 @@ +x5̱0`>FsWc\}?b=Bo/nv$9T&1D: CDjMZ81\̠`:dv@iU2n|" n46eo +S* \ No newline at end of file diff --git a/test/fixtures/d31ec9422b3048f104b4c090cf1c0f5be0963cd9 b/test/fixtures/d31ec9422b3048f104b4c090cf1c0f5be0963cd9 new file mode 100644 index 0000000000000000000000000000000000000000..1efa5e5d4342b53b9d7e72d4189ce79484fb4bca GIT binary patch literal 20 ccmbVFhlI7*";`%vS&SBgR#{9mdig^O}c}ntG{#2#_ literal 0 HcmV?d00001 diff --git a/test/fixtures/e247bdd4cdbe6a3b447cb2dcf1fa03fba2f3d64a b/test/fixtures/e247bdd4cdbe6a3b447cb2dcf1fa03fba2f3d64a new file mode 100644 index 0000000000000000000000000000000000000000..34800fbc6cadd6f4bfed5586945ee2f214baacc3 GIT binary patch literal 129 zcmV-{0Dk{?0V^p=O;s>7GG;I_00M=S%p!(#y^rEf{vq4t;#hkHTkd7CyYlkEWsMl3 zzTA=MoY!?SlI7K>Evr8+Iu$3%ZfIa&W@4g{X2vkty0m$FSvC7BpHEH)_i2jh?bzK4 jlg-RYWoXcxbVP~w;j9T|^#%;fXBqD7<^2u-xUDd(u46$O literal 0 HcmV?d00001 diff --git a/test/fixtures/f5227cbd32973ec90f48f2547e6fe16c80b92bd5 b/test/fixtures/f5227cbd32973ec90f48f2547e6fe16c80b92bd5 new file mode 100644 index 0000000000000000000000000000000000000000..e33b610fe781c6b56c2a82a5497ab323f6f42f8a GIT binary patch literal 103 zcmV-t0GR)H0V^p=O;xZoW-u`T0)>>!B8Fydua@ceDpdLl4&?E(J#Ss^|H=n0Ys3)s z<&H$>ysndxEU!LoS^aU*sW?$~Ljwad6BC8B%$!t)2F*!Flz1P`now46z_5Il;m%&( J?*MfnC$MGOFcAO% literal 0 HcmV?d00001 diff --git a/test/fixtures/fa59b93c6ce9db82ce57de9046eb738e9f3c0952 b/test/fixtures/fa59b93c6ce9db82ce57de9046eb738e9f3c0952 new file mode 100644 index 0000000000000000000000000000000000000000..1cbecd683796f279b5e1f420ec2c04cec26efe73 GIT binary patch literal 49 zcmb;b#t6eIuu literal 0 HcmV?d00001 diff --git a/test/fixtures/fc80daf21bb484cfd42ad4ed4d17da48638199ea b/test/fixtures/fc80daf21bb484cfd42ad4ed4d17da48638199ea new file mode 100644 index 0000000000000000000000000000000000000000..d94fbe09c4bda1d84234772c405ac86738361e4c GIT binary patch literal 22 ecmb>!B8K|EB8*>W9%oK@yTtI)Evr8+Iu$3%ZfIa&W@4g{mYI{v(4aZ#h!XF^Srf|Y4H%ZsGThnA K`yBwzdnn!xk2C}T literal 0 HcmV?d00001 diff --git a/test/fixtures/objects.json b/test/fixtures/objects.json new file mode 100644 index 0000000..92bde9a --- /dev/null +++ b/test/fixtures/objects.json @@ -0,0 +1,32 @@ +[ + "1e2cb60e9e29de3320459f02cb156b66a86925aa", + "aa9080345b7881124ee5a605b72d1d7b6892d1dc", + "ce717819ba52636f465c805794cef0c3103770c4", + "bdc9b0f98b578af9015359103503d5de3218efdd", + "dee159b7de821f3dcfc75b9e4f3857689759aa37", + "e247bdd4cdbe6a3b447cb2dcf1fa03fba2f3d64a", + "933b7583b7767b07ea4cf242c1be29162eb8bb85", + "bcd6c57873ddd85c30e7758a5c34d5ffa37496f8", + "788506cb0585ff19bfe9a5add28ab03d1b6db165", + "672ef117424f54b71e5e058d1184de6a07450d0e", + "6860d53002bcd8f9a2e371462743340abc1c47a8", + "af99300d66a5078022b14c388d214046cb2647ca", + "adf239853d9b280be483592b40c4bd8b5d9fb524", + "d31ec9422b3048f104b4c090cf1c0f5be0963cd9", + "4bbc624c5e1d7e13fac32698c20cf47bc7df3ffa", + "a847fad8424bf2fed664658d215921a9cf0275a5", + "ffef5350b6f8762cc6272b0255e968f50b6577ed", + "7ffd1401f599c70364eda431d29363e037b2c92c", + "a0f06ca3cdb1e6e7f603794ef1cd9f9867f85551", + "70ce33c808791776ea89c98bb28bdab6352fc6e3", + "f5227cbd32973ec90f48f2547e6fe16c80b92bd5", + "5af4dc18899e8ac95904eaf2b4abf1a2ca5e1507", + "832b4a8497de78248f70c06e0f06e785a74fea4c", + "0a1690e0640a212aafed1824eb208ffddab1789b", + "fc80daf21bb484cfd42ad4ed4d17da48638199ea", + "9f358a4addefcab294b83e4282bfef1f9625a249", + "19f0805d8de36b6442e8c573074112ba72ad6780", + "d94c1dbabf13cff8bed80d8818c41b20bca4ee39", + "fa59b93c6ce9db82ce57de9046eb738e9f3c0952", + "802992c4220de19a90767f3000a79a31b98d0df7" +] diff --git a/test/fixtures/update.sh b/test/fixtures/update.sh new file mode 100755 index 0000000..a706f26 --- /dev/null +++ b/test/fixtures/update.sh @@ -0,0 +1,12 @@ +#!/bin/bash + +ls . | awk -F '' 'NF == 40' | xargs rm +rm testdata.tar.gz -f +wget https://github.com/ipfs/go-ipld-git/raw/master/testdata.tar.gz +tar xzf testdata.tar.gz +rm testdata.tar.gz +mv .git git + +find git/objects -type f | cut -d'/' -f3- | sed 's/\///g' | jq --raw-input . | jq --slurp . > objects.json +paste <(find git/objects -type f) <(find git/objects -type f | cut -d'/' -f3- | sed 's/\///g') | xargs -L 1 mv -v +rm -rf git diff --git a/test/parse.spec.js b/test/parse.spec.js new file mode 100644 index 0000000..4d8792e --- /dev/null +++ b/test/parse.spec.js @@ -0,0 +1,107 @@ +/* eslint-env mocha */ +/* eslint max-nested-callbacks: ["error", 8] */ + +'use strict' + +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const expect = chai.expect +chai.use(dirtyChai) +const loadFixture = require('aegir/fixtures') +const zlib = require('zlib') +const ipldGit = require('../src') +const util = require('../src/util/util') +const waterfall = require('async/waterfall') + +const testObjectsJSON = require('./fixtures/objects.json') + +describe('utils', () => { + describe('person line parsing', () => { + it('parses generic line', (done) => { + let info = util.parsePersonLine('Someone 123456 +0123') + expect(info).to.exist() + expect(info.name).to.equal('Someone') + expect(info.email).to.equal('some@one.somewhere') + expect(info.date).to.equal('123456 +0123') + done() + }) + + it('parses 3 segment name', (done) => { + let info = util.parsePersonLine('So Me One 123456 +0123') + expect(info).to.exist() + expect(info.name).to.equal('So Me One') + expect(info.email).to.equal('some@one.somewhere') + expect(info.date).to.equal('123456 +0123') + done() + }) + + it('parses no name line', (done) => { + let info = util.parsePersonLine(' 123456 +0123') + expect(info).to.exist() + expect(info.name).to.not.exist() + expect(info.email).to.equal('some@one.somewhere') + expect(info.date).to.equal('123456 +0123') + done() + }) + + it('parses no name line with space in front', (done) => { + let info = util.parsePersonLine(' 123456 +0123') + expect(info).to.exist() + expect(info.name).to.not.exist() + expect(info.email).to.equal('some@one.somewhere') + expect(info.date).to.equal('123456 +0123') + done() + }) + + it('parses line with nonstandard info', (done) => { + let info = util.parsePersonLine('Some One & Other One 987654 +4321') + expect(info).to.exist() + expect(info.name).to.equal('Some One & Other One') + expect(info.email).to.equal('some@one.somewhere, other@one.elsewhere') + expect(info.date).to.equal('987654 +4321') + done() + }) + + it('parses line without date info', (done) => { + let info = util.parsePersonLine('Someone ') + expect(info).to.exist() + expect(info.name).to.equal('Someone') + expect(info.email).to.equal('some.one@some.where') + expect(info.date).to.not.exist() + done() + }) + }) +}) + +describe('git object parsing', () => { + let objects + + before((done) => { + objects = testObjectsJSON.map(o => [o, zlib.inflateSync(loadFixture(__dirname, '/fixtures/' + o))]) + done() + }) + + it('is parsing and serializing properly', (done) => { + waterfall(objects.map((object) => { + return (cb) => { + ipldGit.util.deserialize(object[1], (err, node) => { + expect(err).to.not.exist() + expect(node).to.exist() + + let expCid = util.shaToCid(new Buffer(object[0], 'hex')) + + ipldGit.util.cid(node, (err, cid) => { + expect(err).to.not.exist() + expect(cid).to.exist() + + expect(cid.buffer.toString('hex')).to.equal(expCid.toString('hex'), 'expected ' + + object[0] + ', got ' + cid.toBaseEncodedString('base16') + ', objtype ' + + node._objtype + ', blob:' + Buffer.isBuffer(node)) + + cb(null) + }) + }) + } + }), done) + }) +}) diff --git a/test/resolver.spec.js b/test/resolver.spec.js new file mode 100644 index 0000000..140134f --- /dev/null +++ b/test/resolver.spec.js @@ -0,0 +1,308 @@ +/* eslint max-nested-callbacks: ["error", 8] */ +/* eslint-env mocha */ +'use strict' + +const chai = require('chai') +const dirtyChai = require('dirty-chai') +const expect = chai.expect +chai.use(dirtyChai) + +const Block = require('ipfs-block') +const map = require('async/map') +const waterfall = require('async/waterfall') +const parallel = require('async/parallel') +const CID = require('cids') +const multihashing = require('multihashing-async') + +const ipldGit = require('../src') +const resolver = ipldGit.resolver + +describe('IPLD format resolver (local)', () => { + let commitBlock + let tagBlock + let treeBlock + let blobBlock + + before((done) => { + const commitNode = { + gitType: 'commit', + tree: {'/': new CID('z8mWaJ1dZ9fH5EetPuRsj8jj26pXsgpsr').buffer}, + parents: [ + {'/': new CID('z8mWaFY1zpiZSXTBrz8i6A3o9vNvAs2CH').buffer} + ], + author: { + name: 'John Doe', + email: 'johndoe@example.com', + date: '1497302532 +0200' + }, + committer: { + name: 'John Doe', + email: 'johndoe@example.com', + date: '1497302532 +0200' + }, + encoding: 'ISO-8859-1', + message: 'Encoded\n' + } + + const tagNode = { + gitType: 'tag', + object: {'/': new CID('z8mWaHQaEAKd5KMRNU3npB3saSZmhFh3e').buffer}, + type: 'commit', + tag: 'v0.0.0', + tagger: { + name: 'John Doe', + email: 'johndoe@example.com', + date: '1497302532 +0200' + }, + message: 'A message\n' + } + + const treeNode = { + somefile: { + hash: {'/': new CID('z8mWaJNVTadD7oum3m7f1dmarHvYhFV5b').buffer}, + mode: '100644' + }, + somedir: { + hash: {'/': new CID('z8mWaFY1zpiZSXTBrz8i6A3o9vNvAs2CH').buffer}, + mode: '40000' + } + } + + const blobNode = new Buffer('626c6f62203800736f6d6564617461', 'hex') // blob 8\0somedata + + waterfall([ + (cb) => parallel([ + (cb) => ipldGit.util.serialize(commitNode, cb), + (cb) => ipldGit.util.serialize(tagNode, cb), + (cb) => ipldGit.util.serialize(treeNode, cb), + (cb) => ipldGit.util.serialize(blobNode, cb) + ], cb), + (res, cb) => map(res, (s, cb) => { + multihashing(s, 'sha1', (err, multihash) => { + expect(err).to.not.exist() + cb(null, new Block(s, new CID(multihash))) + }) + }, cb), + (blocks, cb) => { + commitBlock = blocks[0] + tagBlock = blocks[1] + treeBlock = blocks[2] + blobBlock = blocks[3] + cb() + } + ], done) + }) + + describe('commit', () => { + it('resolver.tree', (done) => { + resolver.tree(commitBlock, (err, paths) => { + expect(err).to.not.exist() + + expect(paths).to.eql([ + 'message', + 'tree', + 'author/original', + 'author/name', + 'author/email', + 'author/date', + 'committer/original', + 'committer/name', + 'committer/email', + 'committer/date', + 'parents/0', + 'encoding' + ]) + + done() + }) + }) + + it('resolver.isLink with valid Link', (done) => { + resolver.isLink(commitBlock, 'tree', (err, link) => { + expect(err).to.not.exist() + const linkCID = new CID(link['/']) + expect(CID.isCID(linkCID)).to.equal(true) + done() + }) + }) + + it('resolver.isLink with invalid Link', (done) => { + resolver.isLink(commitBlock, '', (err, link) => { + expect(err).to.not.exist() + expect(link).to.equal(false) + done() + }) + }) + + describe('resolver.resolve', () => { + it('path within scope', (done) => { + resolver.resolve(commitBlock, 'message', (err, result) => { + expect(err).to.not.exist() + expect(result.value).to.equal('Encoded\n') + done() + }) + }) + + it('path within scope, but nested', (done) => { + resolver.resolve(commitBlock, 'author/name', (err, result) => { + expect(err).to.not.exist() + expect(result.value).to.equal('John Doe') + done() + }) + }) + + it('path out of scope', (done) => { + resolver.resolve(commitBlock, 'tree/foo/hash/bar/mode', (err, result) => { + expect(err).to.not.exist() + expect(result.value).to.eql({ + '/': new CID('z8mWaJ1dZ9fH5EetPuRsj8jj26pXsgpsr').buffer + }) + expect(result.remainderPath).to.equal('foo/hash/bar/mode') + done() + }) + }) + }) + }) + + describe('tag', () => { + it('resolver.tree', (done) => { + resolver.tree(tagBlock, (err, paths) => { + expect(err).to.not.exist() + + expect(paths).to.eql([ + 'object', + 'type', + 'tag', + 'message', + 'tagger/original', + 'tagger/name', + 'tagger/email', + 'tagger/date' + ]) + + done() + }) + }) + + it('resolver.isLink with valid Link', (done) => { + resolver.isLink(tagBlock, 'object', (err, link) => { + expect(err).to.not.exist() + const linkCID = new CID(link['/']) + expect(CID.isCID(linkCID)).to.equal(true) + done() + }) + }) + + it('resolver.isLink with invalid Link', (done) => { + resolver.isLink(tagBlock, '', (err, link) => { + expect(err).to.not.exist() + expect(link).to.equal(false) + done() + }) + }) + + describe('resolver.resolve', () => { + it('path within scope', (done) => { + resolver.resolve(tagBlock, 'message', (err, result) => { + expect(err).to.not.exist() + expect(result.value).to.equal('A message\n') + done() + }) + }) + + it('path within scope, but nested', (done) => { + resolver.resolve(tagBlock, 'tagger/name', (err, result) => { + expect(err).to.not.exist() + expect(result.value).to.equal('John Doe') + done() + }) + }) + + it('path out of scope', (done) => { + resolver.resolve(tagBlock, 'object/tree/foo/mode', (err, result) => { + expect(err).to.not.exist() + expect(result.value).to.eql({ + '/': new CID('z8mWaHQaEAKd5KMRNU3npB3saSZmhFh3e').buffer + }) + expect(result.remainderPath).to.equal('tree/foo/mode') + done() + }) + }) + }) + }) + + describe('tree', () => { + it('resolver.tree', (done) => { + resolver.tree(treeBlock, (err, paths) => { + expect(err).to.not.exist() + + expect(paths).to.eql([ + 'somedir', + 'somedir/hash', + 'somedir/mode', + 'somefile', + 'somefile/hash', + 'somefile/mode' + ]) + + done() + }) + }) + + it('resolver.isLink with valid Link', (done) => { + resolver.isLink(treeBlock, 'somefile/hash', (err, link) => { + expect(err).to.not.exist() + const linkCID = new CID(link['/']) + expect(CID.isCID(linkCID)).to.equal(true) + done() + }) + }) + + it('resolver.isLink with invalid Link', (done) => { + resolver.isLink(treeBlock, '', (err, link) => { + expect(err).to.not.exist() + expect(link).to.equal(false) + done() + }) + }) + + describe('resolver.resolve', () => { + it('path within scope, nested', (done) => { + resolver.resolve(treeBlock, 'somedir/mode', (err, result) => { + expect(err).to.not.exist() + expect(result.value).to.equal('40000') + done() + }) + }) + + it('path out of scope', (done) => { + resolver.resolve(treeBlock, 'somedir/hash/subfile/mode', (err, result) => { + expect(err).to.not.exist() + expect(result.value).to.eql({ + '/': new CID('z8mWaFY1zpiZSXTBrz8i6A3o9vNvAs2CH').buffer + }) + expect(result.remainderPath).to.equal('subfile/mode') + done() + }) + }) + }) + }) + + describe('blob', () => { + it('resolver.tree', (done) => { + resolver.tree(blobBlock, (err, paths) => { + expect(err).to.not.exist() + expect(paths).to.eql([]) + done() + }) + }) + + it('resolver.isLink with invalid Link', (done) => { + resolver.isLink(treeBlock, '', (err, link) => { + expect(err).to.not.exist() + expect(link).to.equal(false) + done() + }) + }) + }) +})