Skip to content
This repository was archived by the owner on Aug 24, 2021. It is now read-only.

feat: new IPLD Format API #51

Merged
merged 1 commit into from
May 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 2 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,8 @@ const zlib = require('zlib')

// `gitObject` is a Buffer containing a git object
inflatedObject = zlib.inflateSync(gitObject)
IpldGit.util.deserialize(inflatedObject, (err, dagNode) => {
if (err) throw err
console.log(dagNode)
})

const dagNode = IpldGit.util.deserialize(inflatedObject)
console.log(dagNode)
```

## Contribute
Expand Down
9 changes: 3 additions & 6 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,21 +36,18 @@
},
"homepage": "https://github.com/ipld/js-ipld-git",
"dependencies": {
"async": "^2.6.2",
"cids": "~0.6.0",
"multicodec": "~0.5.0",
"multihashes": "~0.4.14",
"multihashing-async": "~0.6.0",
"multihashing-async": "~0.7.0",
"smart-buffer": "^4.0.2",
"traverse": "~0.6.6",
"strftime": "~0.10.0"
},
"devDependencies": {
"aegir": "^18.2.1",
"chai": "^4.2.0",
"deep-freeze": "0.0.1",
"dirty-chai": "^2.0.1",
"garbage": "0.0.0"
"chai-as-promised": "^7.1.1",
"dirty-chai": "^2.0.1"
},
"contributors": [
"Alan Shaw <[email protected]>",
Expand Down
2 changes: 2 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,5 @@

exports.util = require('./util.js')
exports.resolver = require('./resolver.js')
exports.codec = exports.util.codec
exports.defaultHashAlg = exports.util.defaultHashAlg
193 changes: 55 additions & 138 deletions src/resolver.js
Original file line number Diff line number Diff line change
@@ -1,153 +1,70 @@
'use strict'

const util = require('./util')
const traverse = require('traverse')

exports = module.exports

exports.multicodec = 'git-raw'
exports.defaultHashAlg = 'sha1'
const CID = require('cids')

const personInfoPaths = [
'original',
'name',
'email',
'date'
]

exports.resolve = (binaryBlob, path, callback) => {
if (typeof path === 'function') {
callback = path
path = undefined
}

util.deserialize(binaryBlob, (err, node) => {
if (err) {
return callback(err)
}
const util = require('./util')

if (!path || path === '/') {
return callback(null, {
value: node,
remainderPath: ''
})
/**
* Resolves a path within a Git block.
*
* Returns the value or a link and the partial mising path. This way the
* IPLD Resolver can fetch the link and continue to resolve.
*
* @param {Buffer} binaryBlob - Binary representation of a Git block
* @param {string} [path='/'] - Path that should be resolved
* @returns {Object} result - Result of the path it it was resolved successfully
* @returns {*} result.value - Value the path resolves to
* @returns {string} result.remainderPath - If the path resolves half-way to a
* link, then the `remainderPath` is the part after the link that can be used
* for further resolving
*/
exports.resolve = (binaryBlob, path) => {
let node = util.deserialize(binaryBlob)

const parts = path.split('/').filter(Boolean)
while (parts.length) {
const key = parts.shift()
if (node[key] === undefined) {
throw new Error(`Object has no property '${key}'`)
}

if (Buffer.isBuffer(node)) { // git blob
return callback(null, {
node = node[key]
if (CID.isCID(node)) {
return {
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('/')
})
}
remainderPath: parts.join('/')
}
node = value
}
})
}

exports.tree = (binaryBlob, options, callback) => {
if (typeof options === 'function') {
callback = options
options = undefined
}

options = options || {}

util.deserialize(binaryBlob, (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.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)
})
return {
value: node,
remainderPath: ''
}
}

exports.isLink = (binaryBlob, path, callback) => {
exports.resolve(binaryBlob, path, (err, result) => {
if (err) {
return callback(err)
}

if (result.remainderPath.length > 0) {
return callback(new Error('path out of scope'))
}
const traverse = function * (node, path) {
// Traverse only objects and arrays
if (Buffer.isBuffer(node) || CID.isCID(node) || typeof node === 'string' ||
node === null) {
return
}
for (const item of Object.keys(node)) {
const nextpath = path === undefined ? item : path + '/' + item
yield nextpath
yield * traverse(node[item], nextpath)
}
}

if (typeof result.value === 'object' && result.value['/']) {
callback(null, result.value)
} else {
callback(null, false)
}
})
/**
* Return all available paths of a block.
*
* @generator
* @param {Buffer} binaryBlob - Binary representation of a Bitcoin block
* @yields {string} - A single path
*/
exports.tree = function * (binaryBlob) {
const node = util.deserialize(binaryBlob)

yield * traverse(node)
}
96 changes: 45 additions & 51 deletions src/util.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
'use strict'

const setImmediate = require('async/setImmediate')
const waterfall = require('async/waterfall')
const multihashing = require('multihashing-async')
const CID = require('cids')
const multicodec = require('multicodec')

const resolver = require('./resolver')
const gitUtil = require('./util/util')

const commit = require('./util/commit')
Expand All @@ -14,87 +12,83 @@ const tree = require('./util/tree')

exports = module.exports

exports.serialize = (dagNode, callback) => {
exports.codec = multicodec.GIT_RAW
exports.defaultHashAlg = multicodec.SHA1

/**
* Serialize internal representation into a binary Git block.
*
* @param {GitBlock} dagNode - Internal representation of a Git block
* @returns {Buffer}
*/
exports.serialize = (dagNode) => {
if (dagNode === null) {
setImmediate(() => callback(new Error('dagNode passed to serialize was null'), null))
return
throw new Error('dagNode passed to serialize was null')
}

if (Buffer.isBuffer(dagNode)) {
if (dagNode.slice(0, 4).toString() === 'blob') {
setImmediate(() => callback(null, dagNode))
return dagNode
} else {
setImmediate(() => callback(new Error('unexpected dagNode passed to serialize'), null))
throw new Error('unexpected dagNode passed to serialize')
}
return
}

switch (dagNode.gitType) {
case 'commit':
commit.serialize(dagNode, callback)
break
return commit.serialize(dagNode)
case 'tag':
tag.serialize(dagNode, callback)
break
return tag.serialize(dagNode)
default:
// assume tree as a file named 'type' is legal
tree.serialize(dagNode, callback)
return tree.serialize(dagNode)
}
}

exports.deserialize = (data, callback) => {
/**
* Deserialize Git block into the internal representation.
*
* @param {Buffer} data - Binary representation of a Git block.
* @returns {BitcoinBlock}
*/
exports.deserialize = (data) => {
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
throw new Error('invalid object header')
}

switch (typeLen[1]) {
case 'blob':
callback(null, data)
break
return data
case 'commit':
commit.deserialize(data.slice(headLen + 1), callback)
break
return commit.deserialize(data.slice(headLen + 1))
case 'tag':
tag.deserialize(data.slice(headLen + 1), callback)
break
return tag.deserialize(data.slice(headLen + 1))
case 'tree':
tree.deserialize(data.slice(headLen + 1), callback)
break
return tree.deserialize(data.slice(headLen + 1))
default:
setImmediate(() => callback(new Error('unknown object type ' + typeLen[1]), null))
throw new Error('unknown object type ' + typeLen[1])
}
}

/**
* @callback CidCallback
* @param {?Error} error - Error if getting the CID failed
* @param {?CID} cid - CID if call was successful
*/
/**
* Get the CID of the DAG-Node.
* Calculate the CID of the binary blob.
*
* @param {Object} dagNode - Internal representation
* @param {Object} [options] - Options to create the CID
* @param {number} [options.version=1] - CID version number
* @param {string} [options.hashAlg='sha1'] - Hashing algorithm
* @param {CidCallback} callback - Callback that handles the return value
* @returns {void}
* @param {Object} binaryBlob - Encoded IPLD Node
* @param {Object} [userOptions] - Options to create the CID
* @param {number} [userOptions.cidVersion=1] - CID version number
* @param {string} [UserOptions.hashAlg] - Defaults to the defaultHashAlg of the format
* @returns {Promise.<CID>}
*/
exports.cid = (dagNode, options, callback) => {
if (typeof options === 'function') {
callback = options
options = {}
}
options = options || {}
const hashAlg = options.hashAlg || resolver.defaultHashAlg
const version = typeof options.version === 'undefined' ? 1 : options.version
waterfall([
(cb) => exports.serialize(dagNode, cb),
(serialized, cb) => multihashing(serialized, hashAlg, cb),
(mh, cb) => cb(null, new CID(version, resolver.multicodec, mh))
], callback)
exports.cid = async (binaryBlob, userOptions) => {
const defaultOptions = { cidVersion: 1, hashAlg: exports.defaultHashAlg }
const options = Object.assign(defaultOptions, userOptions)

const multihash = await multihashing(binaryBlob, options.hashAlg)
const codecName = multicodec.print[exports.codec]
const cid = new CID(options.cidVersion, codecName, multihash)

return cid
}
Loading