diff --git a/.babelrc b/.babelrc new file mode 100644 index 0000000..2107c20 --- /dev/null +++ b/.babelrc @@ -0,0 +1,4 @@ +{ + "presets": ["es2015", "stage-0"], + "plugins": ["transform-flow-strip-types"] +} diff --git a/.eslintrc b/.eslintrc index f04828e..fb7ef13 100644 --- a/.eslintrc +++ b/.eslintrc @@ -1,23 +1,32 @@ { - "rules": { - "indent": [ 2, 4 ], - "quotes": [ 2, "single" ], - "linebreak-style": [ 2, "unix" ], - "semi": [ 2, "always" ], - "no-unused-vars": [ 2, { - "vars": "all", - "args": "none" - } ], - "spaced-comment": [ 2, "always" ] - }, - "env": { - "node": true, - "mocha": true, - "browser": true, - "jasmine": true - }, - "globals": { - "fixtures": true - }, - "extends": "eslint:recommended" -} \ No newline at end of file + "extends": [ + "airbnb-base", + "plugin:flowtype/recommended", + "prettier", + "prettier/flowtype" + ], + "plugins": [ + "flowtype", + "prettier" + ], + "env": { + "jest": true + }, + "rules": { + "eqeqeq": ["off"], + "no-bitwise": ["off"], + "class-methods-use-this": ["off"], + "import/prefer-default-export": ["off"], + "arrow-body-style": ["error", "as-needed"], + "no-use-before-define": ["error", { "functions": false }], + "no-param-reassign": ["error", { "props": false }], + "no-unused-vars": ["error", { + "args": "none" + }], + "prettier/prettier": ["error", { + "singleQuote": true, + "parser": "flow", + "tabWidth": 4 + }] + } +} diff --git a/.flowconfig b/.flowconfig new file mode 100644 index 0000000..48b0e2a --- /dev/null +++ b/.flowconfig @@ -0,0 +1,10 @@ +[ignore] +./lib + +[include] + +[libs] + +[options] +emoji=true +unsafe.enable_getters_and_setters=true diff --git a/.gitignore b/.gitignore index 091195f..c98ca5d 100644 --- a/.gitignore +++ b/.gitignore @@ -27,3 +27,4 @@ build/Release node_modules .tmp +lib/ diff --git a/.travis.yml b/.travis.yml index bafe922..4eb1aed 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,4 +1,9 @@ sudo: false language: node_js node_js: - - "stable" \ No newline at end of file + - "stable" +script: + - npm run test + - npm run lint +after_script: + - npm run flow diff --git a/README.md b/README.md index 60f176e..b705099 100644 --- a/README.md +++ b/README.md @@ -4,100 +4,92 @@ [![Linux Build Status](https://travis-ci.org/SamyPesse/gitkit-js.svg?branch=master)](https://travis-ci.org/SamyPesse/gitkit-js) [![Windows Build status](https://ci.appveyor.com/api/projects/status/63nlflxcwmb2pue6?svg=true)](https://ci.appveyor.com/project/SamyPesse/gitkit-js) -Pure JavaScript implementation of Git backed by immutable models and promises. +GitKit is a pure JavaScript implementation of Git backed by immutable models and promises. -The goal is to provide both a low and high level API for manipulating Git repositories: read files, commit changes, edit working index, clone, push, fetch, etc. +The goal of this project is not to be used in real-world projects, but instead to provides a readable (the code is typed using flow) JS implementation of the Git-backend with a high-level API for manipulating repositories: read files, commit changes, clone, push, etc. See [Support](#support) for details about supported operations. This library can work both in the browser and Node.js. ## Installation ``` -$ npm install gitkit +$ yarn add gitkit ``` -## Usage - -#### API Basics - -State of the Git repository is represented as a single immutable `Repository` object. Read and write access to the repository is done using a `FS` driver, the implementation of the fs depends on the plaftrom (`NativeFS` for Node.js/Native, `LocalStorageFS` or `MemoryFS` for the browser). - -```js -var GitKit = require('gitkit'); -var NativeFS = require('gitkit/lib/fs/native'); - -// Prepare the filesystem -var fs = NativeFS(process.cwd()); +## CLI -// Create a repository instance -var repo = GitKit.Repository.createWithFS(fs, isBare); -``` +GitKit implements a CLI with the goal of being compatible with the official Git command. -##### Clone a remote repository +⚠️ **Do not use it:** GitKit is for testing-only. ```js -// Create a transport instance for the GitHub repository -var transport = new GitKit.HTTPTransport('https://github.com/GitbookIO/gitbook.git'); - -GitKit.TransferUtils.clone(repo, transport) -.then(function(newRepo) { - // Clone succeed! -}, function(err) { - // Clone failed -}) +$ npm install gitkit --global +$ gitkit clone https://github.com/GitbookIO/gitkit.git ./repo ``` -##### List branches - -`GitKit.BranchUtils.list` returns a promise listing branches as a list of strings. - -```js -GitKit.BranchUtils.list(repo) - .then(function(branches) { ... }) -``` - -##### Get current branch - -`GitKit.BranchUtils.getCurrent` returns a promise resolved with the name of the current active branch. - -```js -GitKit.BranchUtils.getCurrent(repo) - .then(function(branch) { ... }) -``` - -##### List files in repository - -`GitKit.WorkingIndex` provides a set of methods to work with the working index. - -```js -GitKit.WorkingIndex.readFromRepo(repo) - .then(function(workingIndex) { - var entries = workingIndex.getEntries(); - }); -``` - -##### List changes not staged for commit - -`GitKit.ChangesUtils` provides a set of methods to work with pending changes. - -```js -GitKit.ChangesUtils.list(repo) - .then(function(changes) { ... }); -``` +## Usage -##### Commit changes +State of the Git repository is represented as a single immutable `Repository` instance. Read and write access to the repository is done using a `FS` driver, the implementation of the fs depends on the platform (`NativeFS` for Node.js, `LocalStorageFS` or `MemoryFS` for the browser). ```js -var author = GitKit.Person.create('Bob', 'bob@gmail.com'); -var message = 'My First commit'; - -GitKit.CommitUtils.createForChanges(repo, author, message, changes) - .then(function(newRepo) { ... }); +import GitKit, { Repository } from 'gitkit'; + +import NativeFS from 'gitkit/lib/fs/native'; + +// Create a filesystem to access the repository on the disk: +const fs = new NativeFS(process.cwd()); + +// Create a repository: +const repo = new Repository({ fs }); +const gitkit = new GitKit(repo); + +// Commit some changes +gitkit + .writeFile('README.md', 'Hello world') + .then(() => gitkit.addFile('README.md')) + .then(() => gitkit.commit({ + message: 'Update README', + author: Author.createFromPerson( + Person.create({ + name: 'John Doe', + email: 'john.doe@gmail.com' + }) + ) + })); ``` -##### More example and documentation coming soon! - -I'll publish a better documentation for this library soon. +## Support + +| Description | Status | +| --------- |:-----------:| +| Initialize a new repository | ✅ | +| **References** | | +| Listing refs (branches, tags), both from packed-refs or refs folder | ✅ | +| Create a new reference | ❌ | +| **Branches** | | +| Read current HEAD | ✅ | +| Checkout a branch (update HEAD and working files) | ❌ | +| **Index** | | +| Listing files in the `.git/index` | ✅ | +| Add new file in the index | ❌ | +| Update the index from the file in the repository | ❌ | +| **Trees** | | +| List all entries in a tree | ✅ | +| Create a new tree | ❌ | +| **Blobs** | | +| Read a blob by its sha | ✅ | +| Create a new blob | ❌ | +| **Commits** | | +| Read a commit by its sha | ✅ | +| Walk the commits history | ✅ | +| Create a new commit | ❌ | +| **Clone / Fetch** | | +| Discovery with the remote repository | ✅ | +| Fetch a reference | ❌ | +| Clone a new repository | ❌ | +| **Transports** | | +| HTTP / HTTPS | ✅ | +| SSH | ❌ | ## Thanks diff --git a/bin/add.js b/bin/add.js deleted file mode 100644 index b7262cb..0000000 --- a/bin/add.js +++ /dev/null @@ -1,8 +0,0 @@ -var Git = require('../'); -var command = require('./command'); - -module.exports = command('add [file]', function(repo, args) { - var filePath = args[0]; - - return Git.WorkingUtils.add(repo, filePath); -}); diff --git a/bin/branch.js b/bin/branch.js deleted file mode 100644 index 5f09701..0000000 --- a/bin/branch.js +++ /dev/null @@ -1,17 +0,0 @@ -/* eslint-disable no-console */ - -var Q = require('q'); -var Git = require('../'); -var command = require('./command'); - -module.exports = command('branch', function(repo, args) { - return Q.all([ - Git.BranchUtils.getCurrent(repo), - Git.BranchUtils.list(repo) - ]) - .spread(function(currentBranch, branches) { - branches.forEach(function(branch) { - console.log(currentBranch == branch? '*' : ' ', branch); - }); - }); -}); diff --git a/bin/cat-file.js b/bin/cat-file.js deleted file mode 100644 index dee990b..0000000 --- a/bin/cat-file.js +++ /dev/null @@ -1,51 +0,0 @@ -/* eslint-disable no-console */ - -var git = require('../'); -var command = require('./command'); - -function printBlob(blob) { - console.log(blob.getContent().toString('utf8')); -} - -function printTree(tree) { - var entries = tree.getEntries(); - - entries.forEach(function(entry) { - console.log(entry.getMode() + ' ' + - entry.getType() + ' ' + - entry.getSha() + '\t' + - entry.getPath()); - }); -} - -function printCommit(commit) { - var parents = commit.getParents(); - - console.log('tree', commit.getTree()); - - parents.forEach(function(parent) { - console.log('parent', parent); - }); - - console.log('author', commit.getAuthor().toString()); - console.log('committer', commit.getCommitter().toString()); - console.log(''); - console.log(commit.getMessage()); -} - -module.exports = command('cat-file [sha]', function(repo, args) { - var sha = args[0]; - - return git.GitObject.readFromRepo(repo, sha) - .then(function(obj) { - if (obj.isBlob()) { - printBlob(git.Blob.createFromObject(obj)); - } else if (obj.isTree()) { - printTree(git.Tree.createFromObject(obj)); - } else if (obj.isCommit()) { - printCommit(git.Commit.createFromObject(obj)); - } else { - throw new Error('Unknow object type'); - } - }); -}); diff --git a/bin/command.js b/bin/command.js deleted file mode 100644 index b7e7eac..0000000 --- a/bin/command.js +++ /dev/null @@ -1,17 +0,0 @@ - -/* - Create and prepare a command - - @param {String} description - @param {Function} func - @return {Object} -*/ -function createCommand(description, func, opts) { - return { - description: description, - exec: func, - options: opts || [] - }; -} - -module.exports = createCommand; diff --git a/bin/commit.js b/bin/commit.js deleted file mode 100644 index a2b27f1..0000000 --- a/bin/commit.js +++ /dev/null @@ -1,28 +0,0 @@ -/* eslint-disable no-console */ - -var Git = require('../'); -var command = require('./command'); - -// WIP -module.exports = command('commit', function(repo, args, kwargs) { - return Git.ChangesUtils.list(repo) - .then(function(changes) { - // Only apply tracked changes - changes = changes.filter(function(change) { - return change.isTracked(); - }); - - // todo - var author = null; - var message = kwargs.message; - - return Git.CommitUtils.createForChanges(repo, author, message, changes); - }); -}, [ - { - name: 'message', - shortcut: 'm', - description: 'Message for the commit', - required: true - } -]); diff --git a/bin/gitkit.js b/bin/gitkit.js deleted file mode 100755 index 74d8888..0000000 --- a/bin/gitkit.js +++ /dev/null @@ -1,67 +0,0 @@ -#!/usr/bin/env node -/* eslint-disable no-console */ - -var Q = require('q'); -var is = require('is'); -var program = require('commander'); - -var pkg = require('../package.json'); -var Git = require('../lib'); -var FS = require('../lib/fs/node'); - -var COMMANDS = [ - require('./log'), - require('./ls-tree'), - require('./ls-files'), - require('./cat-file'), - require('./branch'), - require('./status'), - require('./commit'), - require('./add'), - require('./init') -]; - -var fs = new FS(process.cwd()); - -program - .option('--debug', 'Enable debugging') - .version(pkg.version); - -COMMANDS.forEach(function(command) { - var cmd = program.command(command.description); - - command.options.forEach(function(opt) { - cmd = cmd.option('--'+opt.name+' ['+opt.name+']', opt.description); - }); - - cmd.action(function() { - var args = Array.prototype.slice.call(arguments, 0, -1); - var kwargs; - - Q() - .then(function() { - kwargs = command.options.reduce(function(out, opt) { - out[opt.name] = command.options[opt.name]; - - if (is.undefined(out[opt.name]) && opt.required) { - throw new Error('Parameter "' + opt.name + '" is required'); - } - - return out; - }, {}); - - return Git.RepoUtils.prepare(fs); - }) - .then(function(repo) { - return command.exec(repo, args, kwargs); - }) - .fail(function(err) { - console.error(program.debug? err.stack : err.message); - process.exit(1); - }); - }); -}); - -if(program.parse(process.argv).args.length == 0 && process.argv.length === 2) { - program.help(); -} diff --git a/bin/init.js b/bin/init.js deleted file mode 100644 index 6ccd810..0000000 --- a/bin/init.js +++ /dev/null @@ -1,10 +0,0 @@ -/* eslint-disable no-console */ - -var Git = require('../'); -var command = require('./command'); - -module.exports = command('init', function(repo, args) { - repo = repo.set('bare', false); - - return Git.RepoUtils.init(repo); -}); diff --git a/bin/log.js b/bin/log.js deleted file mode 100644 index eaa1bc1..0000000 --- a/bin/log.js +++ /dev/null @@ -1,25 +0,0 @@ -/* eslint-disable no-console */ - -var Git = require('../'); -var command = require('./command'); - -module.exports = command('log', function(repo, args) { - var MAX = 100; - var i = 0; - - function printCommit(sha, commit, depth) { - i++; - - console.log('commit', sha); - console.log(commit.getAuthor().toString()); - console.log(commit.getMessage()); - console.log(''); - - if (i >= MAX) return false; - } - - return Git.CommitUtils.getHead(repo) - .then(function(baseSHA) { - return Git.CommitUtils.walk(repo, baseSHA, printCommit); - }); -}); diff --git a/bin/ls-files.js b/bin/ls-files.js deleted file mode 100644 index f9e7c73..0000000 --- a/bin/ls-files.js +++ /dev/null @@ -1,20 +0,0 @@ -/* eslint-disable no-console */ - -var git = require('../'); -var command = require('./command'); - -function printEntry(entry) { - console.log( - '' + entry.getMode(), - entry.getSha(), - entry.getPath() - ); -} - -module.exports = command('ls-files', function(repo, args) { - return git.WorkingIndex.readFromRepo(repo) - .then(function(workingIndex) { - var entries = workingIndex.getEntries(); - entries.forEach(printEntry); - }); -}); diff --git a/bin/ls-tree.js b/bin/ls-tree.js deleted file mode 100644 index 2ad474c..0000000 --- a/bin/ls-tree.js +++ /dev/null @@ -1,14 +0,0 @@ -/* eslint-disable no-console */ - -var git = require('../'); -var command = require('./command'); - -function printEntry(filePath, entry) { - console.log(entry.getMode(), entry.getType(), entry.getSha(), filePath); -} - -module.exports = command('ls-tree [sha]', function(repo, args) { - var sha = args[0]; - - return git.TreeUtils.walk(repo, sha, printEntry); -}); diff --git a/bin/status.js b/bin/status.js deleted file mode 100644 index 503cea9..0000000 --- a/bin/status.js +++ /dev/null @@ -1,43 +0,0 @@ -/* eslint-disable no-console */ - -var color = require('bash-color'); - -var Git = require('../'); -var command = require('./command'); - -var TRACKED = 'tracked'; -var UNTRACKED = 'untracked'; - -module.exports = command('status', function(repo, args) { - return Git.ChangesUtils.list(repo) - .then(function(changes) { - var groups = changes.groupBy(function(change) { - return (change.isTracked()? TRACKED : UNTRACKED); - }); - - if (groups.has(TRACKED)) { - console.log('Changes not staged for commit:'); - - groups.get(TRACKED).forEach(function(change) { - var type = change.getType(); - var file = change.getFile(); - - console.log('\t', color.red( type + ':\t' + file.getPath())); - }); - - console.log(''); - } - - if (groups.has(UNTRACKED)) { - console.log('Untracked files:'); - - groups.get(UNTRACKED).forEach(function(change) { - var file = change.getFile(); - - console.log('\t', color.red(file.getPath())); - }); - - console.log(''); - } - }); -}); diff --git a/examples/clone.js b/examples/clone.js deleted file mode 100644 index df12a3c..0000000 --- a/examples/clone.js +++ /dev/null @@ -1,40 +0,0 @@ -/* eslint-disable no-console */ -var path = require('path'); - -var GitKit = require('../'); -var NodeFS = require('../lib/fs/node'); - -// Create a transport instance for the GitHub repository -var transport = new GitKit.HTTPTransport('https://github.com/GitbookIO/gitbook.git'); - -// Create an FS to write the output repository -var fs = new NodeFS(path.resolve(__dirname, '../.tmp/test-clone')); - -// Create a repository instance -var repo = GitKit.Repository.createWithFS(fs); - -// Initialize the repository -GitKit.RepoUtils.init(repo) - - // Clone using the HTTP transport - .then(function() { - return GitKit.TransferUtils.clone(repo, transport); - }) - - .then( - // Success - function() { - console.log('Clone succeed!'); - }, - - // Error - function(err) { - console.log('Clone failed:'); - console.log(err.stack || err); - }, - - // Progress - function(line) { - console.log(line.getMessage()); - } - ); diff --git a/lib/BranchUtils/getCurrent.js b/lib/BranchUtils/getCurrent.js deleted file mode 100644 index b79274d..0000000 --- a/lib/BranchUtils/getCurrent.js +++ /dev/null @@ -1,17 +0,0 @@ -var Head = require('../models/head'); - -/** - * Get name of current branch - * - * @param {Repository} repo - * @return {Promise} - */ -function getCurrentBranch(repo) { - return Head.readFromRepo(repo) - .then(function(head) { - var refName = head.getRef(); - return refName.replace('refs/heads/', ''); - }); -} - -module.exports = getCurrentBranch; diff --git a/lib/BranchUtils/index.js b/lib/BranchUtils/index.js deleted file mode 100644 index 4d318de..0000000 --- a/lib/BranchUtils/index.js +++ /dev/null @@ -1,5 +0,0 @@ - -module.exports = { - list: require('./list'), - getCurrent: require('./getCurrent') -}; diff --git a/lib/BranchUtils/list.js b/lib/BranchUtils/list.js deleted file mode 100644 index 89148dc..0000000 --- a/lib/BranchUtils/list.js +++ /dev/null @@ -1,19 +0,0 @@ -var FileUtils = require('../FileUtils'); - -/** - * List branches in a repository - * - * @param {Repository} repo - * @return {Promise>} - */ -function listBranches(repo) { - var fs = repo.getFS(); - var baseFolder = repo.getGitPath('refs/heads'); - - return FileUtils.list(fs, baseFolder) - .then(function(files) { - return files.keySeq(); - }); -} - -module.exports = listBranches; diff --git a/lib/ChangesUtils/compare.js b/lib/ChangesUtils/compare.js deleted file mode 100644 index fd5e735..0000000 --- a/lib/ChangesUtils/compare.js +++ /dev/null @@ -1,47 +0,0 @@ -var Immutable = require('immutable'); -var Change = require('../models/change'); -var File = require('../models/file'); - -/** - * Compare the working index and the current state of the repository. - * - * @param {WorkingIndex} workingIndex - * @param {OrderedMap} files - * @return {OrderedMap} - */ -function compareChanges(workingIndex, files) { - var changes = Immutable.OrderedMap(); - var entries = workingIndex.getEntries(); - - // Files not present in the index: UNTRACKED - files.forEach(function(file) { - var filePath = file.getPath(); - var entry = entries.get(filePath); - if (entry) return; - - changes = changes.set( - filePath, - Change.createForFile(Change.TYPES.UNTRACKED, file) - ); - }); - - // Entries no longer present in the folder - entries.forEach(function(entry) { - var entryPath = entry.getPath(); - var file = files.get(entryPath); - if (file) return; - - changes = changes.set( - entryPath, - Change.createForFile( - Change.TYPES.REMOVED, - File.createFromIndexEntry(entry) - ) - ); - }); - - return changes; -} - - -module.exports = compareChanges; diff --git a/lib/ChangesUtils/index.js b/lib/ChangesUtils/index.js deleted file mode 100644 index d38693d..0000000 --- a/lib/ChangesUtils/index.js +++ /dev/null @@ -1,4 +0,0 @@ - -module.exports = { - list: require('./list') -}; diff --git a/lib/ChangesUtils/list.js b/lib/ChangesUtils/list.js deleted file mode 100644 index fa75ef3..0000000 --- a/lib/ChangesUtils/list.js +++ /dev/null @@ -1,22 +0,0 @@ -var Promise = require('q'); -var WorkingIndex = require('../models/workingIndex'); -var FileUtils = require('../FileUtils'); -var compareChanges = require('./compare'); - -/** - * Compare the working index and the current state of the repository. - * - * @param {Repository} repo - * @param {} - * @return {Promsie>} - */ -function listChanges(repo) { - return Promise.all([ - WorkingIndex.readFromRepo(repo), - FileUtils.listAll(repo) - ]) - .spread(compareChanges); -} - - -module.exports = listChanges; diff --git a/lib/CommitUtils/createForChanges.js b/lib/CommitUtils/createForChanges.js deleted file mode 100644 index d494d76..0000000 --- a/lib/CommitUtils/createForChanges.js +++ /dev/null @@ -1,59 +0,0 @@ -var Head = require('../models/head'); -var Commit = require('../models/commit'); -var Ref = require('../models/ref'); -var TreeUtils = require('../TreeUtils'); - -/** - * Create a new tree and commit for changes. - * - * @param {Tree} - * @param {Author} - * @param {String} message - * @param {Map} entries - * @return {String} - */ -function createForChanges(repo, author, message, changes) { - var parent, currentRefPath; - - // Get current commit - return Head.readFromRepo(repo) - .then(function(head) { - currentRefPath = head.getRef(); - return Ref.readFromRepo(repo, currentRefPath); - }) - - .then(function(ref) { - parent = ref.getCommit(); - - return Commit.readFromRepo(repo, parent); - }) - - // Write new tree - .then(function(commit) { - return TreeUtils.applyChanges(repo, commit.getTree(), changes); - }) - - // Create the new commit - .then(function(treeSha) { - var commit = Commit.create({ - author: author, - committer: author, - message: message, - parents: [parent], - tree: treeSha - }); - - // Write the commit in the reposirory - return Commit.writeToRepo(repo, commit); - }) - - // Update the reference - .then(function(commitSha) { - var ref = Ref.createForCommit(commitSha); - - // Write the reference - return Ref.writeToRepo(repo, currentRefPath, ref); - }); -} - -module.exports = createForChanges; diff --git a/lib/CommitUtils/getHead.js b/lib/CommitUtils/getHead.js deleted file mode 100644 index 0aa0534..0000000 --- a/lib/CommitUtils/getHead.js +++ /dev/null @@ -1,21 +0,0 @@ -var Head = require('../models/head'); -var Ref = require('../models/ref'); - -/** - * Get head commit for a repository - * - * @param {Repository} - * @return {Promise} - */ -function getHead(repo) { - return Head.readFromRepo(repo) - .then(function(head) { - var refName = head.getRef(); - return Ref.readFromRepo(repo, refName); - }) - .then(function(ref) { - return ref.getCommit(); - }); -} - -module.exports = getHead; diff --git a/lib/CommitUtils/index.js b/lib/CommitUtils/index.js deleted file mode 100644 index df904fa..0000000 --- a/lib/CommitUtils/index.js +++ /dev/null @@ -1,6 +0,0 @@ - -module.exports = { - getHead: require('./getHead'), - walk: require('./walk'), - createForChanges: require('./createForChanges') -}; diff --git a/lib/CommitUtils/walk.js b/lib/CommitUtils/walk.js deleted file mode 100644 index 6e58bda..0000000 --- a/lib/CommitUtils/walk.js +++ /dev/null @@ -1,31 +0,0 @@ -var Promise = require('q'); -var Commit = require('../models/commit'); - -/** - * Iterate over commits, stop when iter returns false - * - * @param {Repository} - * @param {String} base - * @param {Function(sha, commit, depth)} iter - */ -function walk(repo, base, iter, depth) { - depth = depth || 0; - - - return Commit.readFromRepo(repo, base) - .then(function(commit) { - if (iter(base, commit, depth) === false) { - return; - } - - var parents = commit.getParents(); - - return parents.reduce(function(prev, parent, i) { - return prev.then(function() { - return walk(repo, parent, iter, depth + i); - }); - }, Promise()); - }); -} - -module.exports = walk; diff --git a/lib/FileUtils/getIgnoreFilter.js b/lib/FileUtils/getIgnoreFilter.js deleted file mode 100644 index 7a2ec6a..0000000 --- a/lib/FileUtils/getIgnoreFilter.js +++ /dev/null @@ -1,33 +0,0 @@ -var is = require('is'); -var ignore = require('ignore'); - -/** - * Create a filter function - * - * @param {Repository} repo - * @return {Promise} - */ -function getIgnoreFilter(repo) { - var ig = ignore(); - - ig.add([ - // Skip all git files - '.git/*', - - // Skip OS X meta data - '.DS_Store' - ]); - - return repo.readFile('.gitignore') - .then(function(content) { - ig.add(content.toString('utf8')); - var filterPath = ig.createFilter(); - - return function(file) { - file = is.string(file)? file : file.getPath(); - return filterPath(file); - }; - }); -} - -module.exports = getIgnoreFilter; diff --git a/lib/FileUtils/index.js b/lib/FileUtils/index.js deleted file mode 100644 index 63fab97..0000000 --- a/lib/FileUtils/index.js +++ /dev/null @@ -1,6 +0,0 @@ - -module.exports = { - listAll: require('./listAll'), - list: require('./list'), - getIgnoreFilter: require('./getIgnoreFilter') -}; diff --git a/lib/FileUtils/list.js b/lib/FileUtils/list.js deleted file mode 100644 index f632a9d..0000000 --- a/lib/FileUtils/list.js +++ /dev/null @@ -1,54 +0,0 @@ -var Q = require('q'); -var path = require('path'); -var Immutable = require('immutable'); - -/** - * List all files in a repository - * - * @param {FS} fs - * @param {String} base - * @param {Function} filter - * @return {OrderedMap} - */ -function list(fs, base, filter) { - base = base || ''; - - return fs.readDir(base) - .then(function(files) { - return files.reduce(function(prev, fileName) { - return prev.then(function(result) { - var filePath = path.join(base, fileName); - - return fs.statFile(filePath) - .then(function(file) { - if (filter && filter(file) === false) { - return result; - } - - if (file.isDirectory()) { - return list(fs, filePath, filter) - .then(function(_files) { - _files = Immutable.OrderedMap( - _files.map(function(f) { - return [ - path.relative(base, f.getPath()), - f - ]; - }).values() - ); - - return result.merge(_files); - }); - } - - return result.set( - path.relative(base, filePath), - file - ); - }); - }); - }, Q(new Immutable.OrderedMap())); - }); -} - -module.exports = list; diff --git a/lib/FileUtils/listAll.js b/lib/FileUtils/listAll.js deleted file mode 100644 index 2441584..0000000 --- a/lib/FileUtils/listAll.js +++ /dev/null @@ -1,19 +0,0 @@ -var getIgnoreFilter = require('./getIgnoreFilter'); -var list = require('./list'); - -/** - * List all files not ignored in a repository. - * - * @param {Repository} - * @return {Promise>} - */ -function listAll(repo) { - var fs = repo.getFS(); - - return getIgnoreFilter(repo) - .then(function(filter) { - return list(fs, '', filter); - }); -} - -module.exports = listAll; diff --git a/lib/RepoUtils/checkout.js b/lib/RepoUtils/checkout.js deleted file mode 100644 index ad90dee..0000000 --- a/lib/RepoUtils/checkout.js +++ /dev/null @@ -1,97 +0,0 @@ -var Q = require('q'); -var TreeUtils = require('../TreeUtils'); -var Commit = require('../models/commit'); -var Ref = require('../models/ref'); -var Blob = require('../models/blob'); -var Head = require('../models/head'); -var ProgressLine = require('../models/progressLine'); - - -/* - To do for checkout: - - - Write all blob for the commit's tree to working dir - - Write content - - Update file's stat - - Recreate workingIndex -*/ - - -/** - * Checkout a tree, for each entry, write it to the repo - * - * @param {Repository} - * @param {String} - * @return {Promise} - */ -function checkoutTree(repo, treeSHA) { - if (repo.isBare()) { - return Q.reject(new Error('Can\'t checkout in a bare repository')); - } - - var d = Q.defer(); - - TreeUtils.walk(repo, treeSHA, function(filename, entry) { - var sha = entry.getSha(); - - d.notify( - ProgressLine.WriteFile(filename) - ); - - // Read the blob - return Blob.readFromRepo(repo, sha) - - // Write it as a file - .then(function(blobContent) { - return repo.writeFile(filename, blobContent); - }) - - // TODO: edit stat of file - .then(function() { - - }); - }) - - // Update working index - .then(function() { - // todo - }) - - // Resolve - .then(d.resolve, d.reject); - - return d.promise; -} - -/** - * Checkout a commit|ref - * - * @param {Repository} - * @param {String} refName - * @return {Promise} - */ -function checkout(repo, refName) { - if (repo.isBare()) { - return Q.reject(new Error('Can\'t checkout in a bare repository')); - } - - // Read ref - return Ref.readFromRepo(repo, refName) - .then(function(ref) { - return Commit.readFromRepo(repo, ref.getCommit()); - }) - - // Update the working tree - .then(function(commit) { - return checkoutTree(repo, commit.getTree()); - }) - - // Update HEAD - .then(function() { - var head = Head.createForRef(refName); - return Head.writeToRepo(repo, head); - }); -} - - -module.exports = checkout; diff --git a/lib/RepoUtils/getAllRefs.js b/lib/RepoUtils/getAllRefs.js deleted file mode 100644 index e58972b..0000000 --- a/lib/RepoUtils/getAllRefs.js +++ /dev/null @@ -1,30 +0,0 @@ -var Q = require('q'); -var Immutable = require('immutable'); - -var listAllRefs = require('./listAllRefs'); -var Ref = require('../models/ref'); - -/** - * Get all refs in a repository - * - * @param {Repository} repo - * @return {OrderedMap} - */ -function getAllRefs(repo) { - return listAllRefs(repo) - .then(function(refs) { - var base = new Immutable.OrderedMap(); - - return refs.reduce(function(prev, refName) { - return prev.then(function(out) { - return Ref.readFromRepoByName(repo, refName) - .then(function(ref) { - return out.set(refName, ref); - }); - }); - - }, Q(base)); - }); -} - -module.exports = getAllRefs; diff --git a/lib/RepoUtils/index.js b/lib/RepoUtils/index.js deleted file mode 100644 index 4737ef3..0000000 --- a/lib/RepoUtils/index.js +++ /dev/null @@ -1,11 +0,0 @@ -/** @module RepoUtils */ - -module.exports = { - init: require('./init'), - isBare: require('./isBare'), - checkout: require('./checkout'), - prepare: require('./prepare'), - listAllRefs: require('./listAllRefs'), - getAllRefs: require('./getAllRefs'), - writeObjectStream: require('./writeObjectStream') -}; diff --git a/lib/RepoUtils/init.js b/lib/RepoUtils/init.js deleted file mode 100644 index e0e2a04..0000000 --- a/lib/RepoUtils/init.js +++ /dev/null @@ -1,46 +0,0 @@ -var Q = require('q'); -var path = require('path'); - -var Head = require('../models/head'); - -/** - * Initialize a repository as empty - * - * @param {Repository} - * @return {Promise} - */ -function init(repo) { - var fs = repo.getFS(); - var isBare = repo.isBare(); - var base = isBare? '' : '.git'; - - var head = Head.createForRef('refs/heads/master'); - - return Head.readFromRepo(repo) - .then(function() { - throw new Error('Directory is already a git repository'); - }, function() { - return Q(); - }) - .then(function() { - return fs.mkdir(base) - - // Create directories - .then(function() { - return Q.all([ - fs.mkdir(path.join(base, 'objects')), - fs.mkdir(path.join(base, 'refs/heads')), - fs.mkdir(path.join(base, 'hooks')) - ]); - }) - - // Write files - .then(function() { - return Q.all([ - Head.writeToRepo(repo, head) - ]); - }); - }); -} - -module.exports = init; diff --git a/lib/RepoUtils/isBare.js b/lib/RepoUtils/isBare.js deleted file mode 100644 index 004287e..0000000 --- a/lib/RepoUtils/isBare.js +++ /dev/null @@ -1,18 +0,0 @@ -var Promise = require('q'); - -/** - * Detect if a repository is bare or not - * - * @param {FS} - * @return {Promise} - */ -function isBare(fs) { - return fs.statFile('.git') - .then(function() { - return false; - }, function() { - return Promise(true); - }); -} - -module.exports = isBare; diff --git a/lib/RepoUtils/listAllRefs.js b/lib/RepoUtils/listAllRefs.js deleted file mode 100644 index b11beed..0000000 --- a/lib/RepoUtils/listAllRefs.js +++ /dev/null @@ -1,18 +0,0 @@ -var FileUtils = require('../FileUtils'); - -/** - * List all refs in a repository - * @param {Repository} repo - * @return {List} - */ -function listAllRefs(repo) { - var fs = repo.getFS(); - var baseFolder = repo.getGitPath('refs'); - - return FileUtils.list(fs, baseFolder) - .then(function(files) { - return files.keySeq(); - }); -} - -module.exports = listAllRefs; diff --git a/lib/RepoUtils/prepare.js b/lib/RepoUtils/prepare.js deleted file mode 100644 index e30fc45..0000000 --- a/lib/RepoUtils/prepare.js +++ /dev/null @@ -1,16 +0,0 @@ -var Repository = require('../models/repo'); -var isBare = require('./isBare'); - -/** - * Prepare a repository for an fs - * @param {FS} - * @return {Promise} - */ -function prepare(fs) { - return isBare(fs) - .then(function(bare) { - return Repository.createWithFS(fs, bare); - }); -} - -module.exports = prepare; diff --git a/lib/RepoUtils/writeObjectStream.js b/lib/RepoUtils/writeObjectStream.js deleted file mode 100644 index 4efa0c5..0000000 --- a/lib/RepoUtils/writeObjectStream.js +++ /dev/null @@ -1,17 +0,0 @@ -var through = require('through2'); -var GitObject = require('../models/object'); - -/** - * Write a stream of GitObject to a repository - * - * @param {Repository} - * @return {Stream} - */ -function writeObjectStream(repo) { - return through.obj(function(obj, enc, callback) { - GitObject.writeToRepo(repo, obj) - .nodeify(callback); - }); -} - -module.exports = writeObjectStream; diff --git a/lib/TransferUtils/__tests__/applyDelta.js b/lib/TransferUtils/__tests__/applyDelta.js deleted file mode 100644 index 0d6237e..0000000 --- a/lib/TransferUtils/__tests__/applyDelta.js +++ /dev/null @@ -1,17 +0,0 @@ -var Buffer = require('buffer').Buffer; -var applyDelta = require('../applyDelta'); - -var DELTA = 'mwKaApA1KGQyNWNhMjVhYWJmOTkzZTg1MjdkN2I4NGFhNjMxODkxZjJlZWVmNDiRXUAFMDY5NTORokouMDY5NTMzIC0wODAwCgphZGQgYXNzZXJ0LmVuZCgpIHRvIHV0aWxzIHRlc3RzCg=='; -var BASE = 'dHJlZSA0YWRlOTdjMTgxMjNjNTdhNTBlM2I5OTIzZDA1M2ZkMmRiNGY2MDVmCnBhcmVudCA3N2ZlMTg0OTRiNGI3ZWJmMGVjY2E0ZDBjY2Y4NTA3M2ZiMjFiZGZiCmF1dGhvciBDaHJpcyBEaWNraW5zb24gPGNocmlzdG9waGVyLnMuZGlja2luc29uQGdtYWlsLmNvbT4gMTM1NjY1NDMwMyAtMDgwMApjb21taXR0ZXIgQ2hyaXMgRGlja2luc29uIDxjaHJpc3RvcGhlci5zLmRpY2tpbnNvbkBnbWFpbC5jb20+IDEzNTY2NTQzMDMgLTA4MDAKCmJ1bXAgdmVyc2lvbiBmb3IgY2kudGVzdGxpbmcuY29tCg=='; -var OUTPUT = 'dHJlZSA0YWRlOTdjMTgxMjNjNTdhNTBlM2I5OTIzZDA1M2ZkMmRiNGY2MDVmCnBhcmVudCBkMjVjYTI1YWFiZjk5M2U4NTI3ZDdiODRhYTYzMTg5MWYyZWVlZjQ4CmF1dGhvciBDaHJpcyBEaWNraW5zb24gPGNocmlzdG9waGVyLnMuZGlja2luc29uQGdtYWlsLmNvbT4gMTM1NjA2OTUzMyAtMDgwMApjb21taXR0ZXIgQ2hyaXMgRGlja2luc29uIDxjaHJpc3RvcGhlci5zLmRpY2tpbnNvbkBnbWFpbC5jb20+IDEzNTYwNjk1MzMgLTA4MDAKCmFkZCBhc3NlcnQuZW5kKCkgdG8gdXRpbHMgdGVzdHMK'; - - -describe('applyDelta', function() { - it('should correctly apply delta', function() { - var delta = new Buffer(DELTA, 'base64'); - var base = new Buffer(BASE, 'base64'); - - var out = applyDelta(base, delta); - expect(out.toString('base64'), OUTPUT); - }); -}); diff --git a/lib/TransferUtils/__tests__/parseDiscovery.js b/lib/TransferUtils/__tests__/parseDiscovery.js deleted file mode 100644 index ab532ee..0000000 --- a/lib/TransferUtils/__tests__/parseDiscovery.js +++ /dev/null @@ -1,36 +0,0 @@ -var parseDiscovery = require('../parseDiscovery'); - -describe('parseDiscovery', function() { - it('should parse capabilities and refs', function() { - var stream = fixtures.createReadStream('discovery-http-output'); - - return parseDiscovery( - stream - ) - .then(function(out) { - expect(out).toExist(); - expect(out.capabilities).toExist(); - expect(out.capabilities).toEqual([ - 'multi_ack', - 'thin-pack', - 'side-band', - 'side-band-64k', - 'ofs-delta', - 'shallow', - 'no-progress', - 'include-tag', - 'multi_ack_detailed', - 'no-done', - 'symref=HEAD:refs/heads/master', - 'agent=git/2:2.6.5+github-1394-g163a735' - ]); - - expect(out.refs).toExist(); - expect(out.refs.size).toBe(422); - - expect(out.refs.get('HEAD').getCommit()).toBe('ba182ce8e430f08bd8c60865b9c853875a3d6483'); - expect(out.refs.get('refs/heads/master').getCommit()).toBe('ba182ce8e430f08bd8c60865b9c853875a3d6483'); - expect(out.refs.get('refs/tags/2.5.1').getCommit()).toBe('3388be9db6c6ae2173652df20eb98f36186c1f9a'); - }); - }); -}); diff --git a/lib/TransferUtils/__tests__/parsePack.js b/lib/TransferUtils/__tests__/parsePack.js deleted file mode 100644 index 5739827..0000000 --- a/lib/TransferUtils/__tests__/parsePack.js +++ /dev/null @@ -1,21 +0,0 @@ -var parsePack = require('../parsePack'); - -describe('parsePack', function() { - it('should output all objects', function(done) { - var packCount = 0; - - fixtures.createReadStream('pack') - .pipe(parsePack()) - .on('data', function(line) { - packCount++; - }) - .on('error', function(err) { - done(err); - }) - .on('finish', function() { - expect(packCount).toBe(481); - - done(); - }); - }); -}); diff --git a/lib/TransferUtils/__tests__/parsePktLines.js b/lib/TransferUtils/__tests__/parsePktLines.js deleted file mode 100644 index 687d3eb..0000000 --- a/lib/TransferUtils/__tests__/parsePktLines.js +++ /dev/null @@ -1,58 +0,0 @@ -var intoStream = require('into-stream'); - -var parsePktLines = require('../parsePktLines'); -var parsePktLineMeta = require('../parsePktLineMeta'); - -var data = [ - '001bhi\0ofs-delta hat party\n', - '0007hi\n', - '0032want 0000000000000000000000000000000000000000\n', - '0000' -]; - -var results = [ - {data:'hi\n', type:'pkt-line', caps:['ofs-delta', 'hat', 'party']}, - {data:'hi\n', type:'pkt-line', caps:['ofs-delta', 'hat', 'party']}, - {data:'want 0000000000000000000000000000000000000000\n', type:'pkt-line', caps:['ofs-delta', 'hat', 'party']}, - {data:'', type:'pkt-flush', caps:['ofs-delta', 'hat', 'party']} -]; - -describe('parsePktLines and parsePktLineMeta', function() { - it('should output all lines', function(done) { - var lineIdx = 0; - - intoStream(data) - .pipe(parsePktLines()) - .pipe(parsePktLineMeta()) - .on('data', function(line) { - var expectLine = results[lineIdx]; - - expect(line.toString('utf8')).toBe(expectLine.data); - expect(line.type).toBe(expectLine.type); - expect(line.caps).toEqual(expectLine.caps); - - lineIdx++; - }) - .on('finish', function() { - expect(lineIdx).toBe(results.length); - - done(); - }); - }); - - it('should parse line from a discovery', function(done) { - var count = 0; - - fixtures.createReadStream('discovery-http-output') - .pipe(parsePktLines()) - .on('data', function(buf) { - count++; - }) - .on('finish', function() { - expect(count).toBe(425); - - done(); - }); - }); -}); - diff --git a/lib/TransferUtils/__tests__/parseUploadPack.js b/lib/TransferUtils/__tests__/parseUploadPack.js deleted file mode 100644 index b1cc989..0000000 --- a/lib/TransferUtils/__tests__/parseUploadPack.js +++ /dev/null @@ -1,23 +0,0 @@ -var parseUploadPack = require('../parseUploadPack'); - -describe('parseUploadPack', function() { - this.timeout(3000000); - - it('should output all lines', function(done) { - var objectCount = 0; - - fixtures.createReadStream('pack-http-output') - .pipe(parseUploadPack()) - .on('data', function() { - objectCount++; - }) - .on('error', function(err) { - done(err); - }) - .on('finish', function() { - expect(objectCount).toBe(12545); - - done(); - }); - }); -}); diff --git a/lib/TransferUtils/applyDelta.js b/lib/TransferUtils/applyDelta.js deleted file mode 100644 index 61d6273..0000000 --- a/lib/TransferUtils/applyDelta.js +++ /dev/null @@ -1,80 +0,0 @@ -var Buffer = require('buffer').Buffer; -var varint = require('varint'); - -/* - This code is mostly based on https://github.com/chrisdickinson/git-apply-delta -*/ - -/** - * Apply delta from packfile to a buffer. - * @param {Buffer} base - * @param {Buffer} delta - * @return {Buffer} - */ -function applyDelta(base, delta) { - var baseSize; - var outputSize; - var command; - var idx = 0, len = 0, outIdx = 0; - - var OFFSET_BUFFER = new Buffer(4); - var LENGTH_BUFFER = new Buffer(4); - - baseSize = varint.decode(delta); - delta = delta.slice(varint.decode.bytes); - - if (baseSize !== base.length) { - throw new Error('Base doesn\'t match expected size: ' + baseSize + ' != '+base.length); - } - - outputSize = varint.decode(delta); - delta = delta.slice(varint.decode.bytes); - - var output = new Buffer(outputSize); - - // Apply deltas - len = delta.length; - - while(idx < len) { - command = delta[idx++]; - command & 0x80 ? copy() : insert(); - } - - function copy() { - OFFSET_BUFFER.writeUInt32LE(0, 0); - LENGTH_BUFFER.writeUInt32LE(0, 0); - - var check = 1, length, offset, x; - - for (x = 0; x < 4; ++x) { - if (command & check) { - OFFSET_BUFFER[3 - x] = delta[idx++]; - } - check <<= 1; - } - - for (x = 0; x < 3; ++x) { - if (command & check) { - LENGTH_BUFFER[3 - x] = delta[idx++]; - } - check <<= 1; - } - LENGTH_BUFFER[0] = 0; - - length = LENGTH_BUFFER.readUInt32BE(0) || 0x10000; - offset = OFFSET_BUFFER.readUInt32BE(0); - - base.copy(output, outIdx, offset, offset + length); - outIdx += length; - } - - function insert() { - delta.copy(output, outIdx, idx, command + idx); - idx += command; - outIdx += command; - } - - return output; -} - -module.exports = applyDelta; diff --git a/lib/TransferUtils/clone.js b/lib/TransferUtils/clone.js deleted file mode 100644 index 8f11a58..0000000 --- a/lib/TransferUtils/clone.js +++ /dev/null @@ -1,22 +0,0 @@ -var RepoUtils = require('../RepoUtils'); -var fetch = require('./fetch'); - -/** - * Clone a repository. - * See http://stackoverflow.com/a/16428258 - * - * @param {Repository} - * @param {Transport} - * @return {Promise} - */ -function clone(repo, transport, opts) { - return fetch(repo, transport, opts) - - // Finally checkout master - .then(function(ref) { - if (repo.isBare()) return; - return RepoUtils.checkout(repo, ref); - }); -} - -module.exports = clone; diff --git a/lib/TransferUtils/encodePktLines.js b/lib/TransferUtils/encodePktLines.js deleted file mode 100644 index d8f58b5..0000000 --- a/lib/TransferUtils/encodePktLines.js +++ /dev/null @@ -1,28 +0,0 @@ -var pad = require('pad'); -var is = require('is'); -var Buffer = require('buffer').Buffer; - -/** - * Encode a list of buffer into a buffer of pkt-lines - * - * @param {List} - * @return {Buffer} - */ -function encodePktLines(lines) { - return lines.reduce(function(out, line) { - if (is.string(line)) { - line = new Buffer(line, 'utf8'); - } - - var lineLength = line.length + 4; - lineLength = pad(4, lineLength.toString(16), '0'); - - return Buffer.concat([ - out, - new Buffer(lineLength, 'utf8'), - line - ]); - }, Buffer('')); -} - -module.exports = encodePktLines; diff --git a/lib/TransferUtils/fetch.js b/lib/TransferUtils/fetch.js deleted file mode 100644 index e30ea9b..0000000 --- a/lib/TransferUtils/fetch.js +++ /dev/null @@ -1,58 +0,0 @@ -var Q = require('q'); -var Immutable = require('immutable'); - -var RepoUtils = require('../RepoUtils'); -var fetchDiscovery = require('./fetchDiscovery'); -var fetchRef = require('./fetchRef'); -var Ref = require('../models/ref'); - -var HEAD_REFNAME = 'HEAD'; - -var FetchOptions = Immutable.Record({ - // Name of the remote - remoteName: String('origin') -}); - - -/** - * Fetch a repository - * - * @param {Repository} - * @param {Transport} - * @return {Promise} - */ -function fetch(repo, transport, opts) { - opts = FetchOptions(opts); - - return Q.all([ - // Fetch the list of refs and capabilities of the server - fetchDiscovery(transport), - - // List refs we have - RepoUtils.getAllRefs(repo) - ]) - .spread(function(discovery, refs) { - var availableRefs = discovery.refs; - - // Find HEAD commit - var headCommit = availableRefs.get(HEAD_REFNAME); - - // Find name of HEAD ref - var headRefName = availableRefs.findKey(function(ref, refName) { - return ( - refName != HEAD_REFNAME && - Immutable.is(ref, headCommit) - ); - }); - var headRef = discovery.refs.get(headRefName); - - return fetchRef(repo, transport, headRef, refs) - - // Write the ref - .then(function() { - return Ref.writeToRepo(repo, headRefName, headRef); - }); - }); -} - -module.exports = fetch; diff --git a/lib/TransferUtils/fetchDiscovery.js b/lib/TransferUtils/fetchDiscovery.js deleted file mode 100644 index 519d02d..0000000 --- a/lib/TransferUtils/fetchDiscovery.js +++ /dev/null @@ -1,14 +0,0 @@ -var parseDiscovery = require('./parseDiscovery'); - -/* - * Fetch refs and capabilities from a remote repository - * - * @param {Transport} - * @return {Promise} - */ -function fetchDiscovery(transport) { - return transport.getWithUploadPack('info/refs') - .then(parseDiscovery); -} - -module.exports = fetchDiscovery; diff --git a/lib/TransferUtils/fetchRef.js b/lib/TransferUtils/fetchRef.js deleted file mode 100644 index dafbe63..0000000 --- a/lib/TransferUtils/fetchRef.js +++ /dev/null @@ -1,78 +0,0 @@ -var Promise = require('q'); - -var parseUploadPack = require('./parseUploadPack'); -var encodePktLines = require('./encodePktLines'); -var RepoUtils = require('../RepoUtils'); -var ProgressLine = require('../models/progressLine'); - -/** - * Return a buffer to send for requesting a ref - * - * @param {Ref} - * @param {List} - * @return {Buffer} - */ -function refWantRequest(wantRef, haveRefs) { - var lines = [ - 'want ' + wantRef.getCommit() + ' multi_ack_detailed side-band-64k thin-pack ofs-delta', - '' - ]; - - haveRefs.forEach(function(haveRef) { - lines.push('have ' + haveRef.getCommit() + '\n'); - }); - - lines.push('done'); - - return encodePktLines(lines); -} - -/** - * Fetch a ref from the server - * - * @param {Repository} - * @param {Transport} transport - * @param {Ref} wantRef - * @param {List} haveRefs - * @return {Promise} - */ -function fetchRef(repo, transport, wantRef, haveRefs) { - var request = refWantRequest(wantRef, haveRefs); - - // Fetch the objects by calling git-upload-pack - return transport.uploadPack(request) - - .then(function(res) { - var d = Promise.defer(); - var countObjects = 0; - var objectIndex = 0; - - res - .pipe(parseUploadPack({ - onCount: function(count) { - countObjects = count; - } - })) - - .pipe( - RepoUtils.writeObjectStream(repo) - .on('data', function() { - objectIndex++; - - d.notify(ProgressLine.FetchObject(objectIndex, countObjects)); - }) - ) - - .on('error', function(err) { - d.reject(err); - }) - .on('finish', function() { - d.resolve(); - }); - - - return d.promise; - }); -} - -module.exports = fetchRef; diff --git a/lib/TransferUtils/index.js b/lib/TransferUtils/index.js deleted file mode 100644 index 650c84c..0000000 --- a/lib/TransferUtils/index.js +++ /dev/null @@ -1,5 +0,0 @@ - -module.exports = { - clone: require('./clone'), - fetch: require('./fetch') -}; diff --git a/lib/TransferUtils/parseDiscovery.js b/lib/TransferUtils/parseDiscovery.js deleted file mode 100644 index 54a47a0..0000000 --- a/lib/TransferUtils/parseDiscovery.js +++ /dev/null @@ -1,58 +0,0 @@ -var Promise = require('q'); -var Immutable = require('immutable'); - -var Ref = require('../models/ref'); -var parsePktLines = require('./parsePktLines'); -var parsePktLineMeta = require('./parsePktLineMeta'); - -/** - * Parses the response to /info/refs?service=git-upload-pack, which contains ids for - * refs/heads and a capability listing for this git HTTP server. - * - * The returned stream emits data for each line. - * It also emit a "capabilities" event - * - * @param {Stream} input - * @return {Promise} - */ -function parseDiscovery(input) { - var d = Promise.defer(); - var capabilities; - var lineIndex = 0; - var refs = new Immutable.OrderedMap(); - - input.pipe(parsePktLines()) - .pipe(parsePktLineMeta()) - .on('data', function(line) { - lineIndex++; - - if (line.caps && !capabilities) { - capabilities = line.caps; - } - - if (lineIndex === 1 || line.type !== parsePktLineMeta.TYPES.LINE) { - return; - } - - var content = line.toString('utf8').trim(); - var parts = content.split(' '); - - refs = refs.set( - parts[1], - Ref.createForCommit(parts[0]) - ); - }) - .on('error', function(err) { - d.reject(err); - }) - .on('end', function() { - d.resolve({ - capabilities: capabilities, - refs: refs - }); - }); - - return d.promise; -} - -module.exports = parseDiscovery; diff --git a/lib/TransferUtils/parsePack.js b/lib/TransferUtils/parsePack.js deleted file mode 100644 index 0984456..0000000 --- a/lib/TransferUtils/parsePack.js +++ /dev/null @@ -1,265 +0,0 @@ -var pako = require('pako'); -var Dissolve = require('dissolve'); -var uint8ToBuffer = require('typedarray-to-buffer'); - -var GitObject = require('../models/object'); -var applyDelta = require('./applyDelta'); - -var TYPES = { - COMMIT: 1, - TREE: 2, - BLOB: 3, - TAG: 4, - OFS_DELTA: 6, - REF_DELTA: 7 -}; - -/** - * Parse header of an object entry - * @param {Dissolve} - * @return {Dissolve} - */ -function parseObjectHeader(parser) { - return parser.uint8be('byte') - .tap(function() { - var byte = this.vars.byte; - var left = 4; - - this.vars.type = (byte >> 4) & 7; - this.vars.size = byte & 0xf; - - this.loop(function(end) { - if (!(this.vars.byte & 0x80)) { - return end(); - } - - this.uint8be('byte') - .tap(function() { - this.vars.size |= (this.vars.byte & 0x7f) << left; - left += 7; - }); - }) - .tap(function() { - this.vars.size = this.vars.size >>> 0; - }); - }); -} - -/** - * Parse inflate content for blob, commit and tree. - * Also index the content for this specific index. - * - * @param {Dissolve} - * @return {Dissolve} - */ -function parseInflateContent(parser) { - var inflator = new pako.Inflate(); - - // Iterate while we found end of zip content - return parser.loop(function(end) { - this.buffer('byte', 1) - .tap(function() { - var byte = this.vars.byte; - - var ab = new Uint8Array(1); - ab.fill(byte[0]); - - inflator.push(ab); - - if (inflator.ended) { - if (inflator.err) { - this.emit('error', new Error(inflator.msg)); - } - - this.vars.content = uint8ToBuffer(inflator.result); - - end(); - } - }); - }); -} - -/** - * Parse a REF delta - * - * @param {Dissolve} - * @return {Dissolve} - */ -function parseRefDelta(parser) { - return parser.string('ref', 20) - .then(function() { - throw new Error('Not yet implemented'); - }); -} - -/** - * Parse OFS delta's header - * - * @param {Dissolve} - * @return {Dissolve} - */ -function parseOFSDeltaHeader(parser) { - return parser.uint8be('byte') - .tap(function() { - var byte = this.vars.byte; - - this.vars.rv = byte & 0x7f; - - this.loop(function(end) { - if (!(this.vars.byte & 0x80)) { - return end(); - } - - this.uint8be('byte') - .tap(function() { - this.vars.rv++; - this.vars.rv <<= 7; - this.vars.rv |= this.vars.byte & 0x7f; - }); - }); - }); -} - -/** - * Parse OFS delta, apply delta and return with vars "type" and "content" - * - * @param {Dissolve} - * @return {Dissolve} - */ -function parseOFSDelta(parser) { - return parseOFSDeltaHeader(parser) - // Extract delta content - .tap(function() { - parseInflateContent(this); - }) - - // Apply delta to content - .tap(function() { - var rv = this.vars.rv; - var baseOffset = this.vars.objectOffset; - var offset = baseOffset - rv; - - // Get referenced type/content - var base = this.gitContentIndex[offset]; - var type = this.gitObjectTypes[offset]; - - if (base) { - // Apply delta on content - var delta = this.vars.content; - var output = applyDelta(base, delta); - - // Export as variables - this.vars.content = output; - this.vars.type = type; - } else { - throw new Error('Content for ref is not indexed'); - } - }); -} - -/** - * Parse an object entry - * - * @param {Dissolve} - * @return {Dissolve} - */ -function parseObject(parser) { - return parser.tap(function() { - this.vars = { - objectOffset: this.offset - }; - - parseObjectHeader(parser); - }) - .tap(function() { - if (this.vars.type < 5) { - parseInflateContent(this); - } else if (this.vars.type === TYPES.OFS_DELTA) { - parseOFSDelta(this); - } else if (this.vars.type === TYPES.REF_DELTA) { - parseRefDelta(this); - } else { - return this.emit('error', new Error('Invalid entry type in pack')); - } - }) - - // Index content for this offset - // todo: index content by sha for REF_DELTA - .tap(function() { - var objectOffset = this.vars.objectOffset; - - this.gitObjectTypes = this.gitObjectTypes || {}; - this.gitContentIndex = this.gitContentIndex || {}; - - this.gitObjectTypes[objectOffset] = this.vars.type; - this.gitContentIndex[objectOffset] = this.vars.content; - }); -} - -/** - * Parse a packfile - * - * @return {Stream} - */ -function parsePack(opts) { - return Dissolve({ - objectMode: true - }) - .string('signature', 4) - .uint32be('version') - .uint32be('count') - .tap(function() { - var header = this.vars; - - // Check header - if (header.signature !== 'PACK') { - this.emit('error', new Error('Invalid pack signature')); - return; - } - - if (opts && opts.onCount) { - opts.onCount(header.count); - } - - var objectCount = 0; - - this.loop(function(endObjectLoop) { - parseObject(this) - .tap(function() { - var buf = this.vars.content; - var type = this.vars.type; - - if (!type || !buf) return; - - var objectType; - - if (type == TYPES.COMMIT) { - objectType = GitObject.TYPES.COMMIT; - } else if (type == TYPES.BLOB) { - objectType = GitObject.TYPES.BLOB; - } else if (type == TYPES.TREE) { - objectType = GitObject.TYPES.TREE; - } else { - throw new Error('Unknow type: ' + type); - } - - // Emit GitObject - var obj = new GitObject({ - type: objectType, - content: buf - }); - - this.emit('data', obj); - }) - .tap(function() { - objectCount++; - - if (objectCount === header.count) { - return endObjectLoop(); - } - }); - }); - }); -} - -module.exports = parsePack; diff --git a/lib/TransferUtils/parsePktLineMeta.js b/lib/TransferUtils/parsePktLineMeta.js deleted file mode 100644 index 0e3484f..0000000 --- a/lib/TransferUtils/parsePktLineMeta.js +++ /dev/null @@ -1,120 +0,0 @@ -var through = require('through2'); - -var TYPES = { - FLUSH: 'pkt-flush', - LINE: 'pkt-line', - PACKFILE: 'packfile', - PROGRESS: 'progress', - ERROR: 'error' -}; - -function divineClientCapabilities(buf) { - for (var i = 0, len = buf.length; i < len; ++i) { - if(buf[i] === 0) { - break; - } - } - - if (i === len) { - return null; - } - - return { - idx: i, - caps: buf.slice(i+1, buf.length - 1).toString('utf8').split(' ') - }; -} - -function divineServerCapabilities(buf) { - var i, len; - var isFetch = buf.slice(0, 4).toString() === 'want'; - - if (isFetch) { - for (i = 45, len = buf.length; i < len; ++i) { - if(buf[i] === 32) { - break; - } - } - } else { - for (i = 0, len = buf.length; i < len; ++i) { - if(buf[i] === 0) { - break; - } - } - } - - if(i === len) { - return null; - } - - return { - idx: i, - caps: buf.slice(i+1, buf.length - 1).toString('utf8').split(' ') - }; -} - - -/** - * Parse metadata (type and caps) from a stream of pkt-lines. - * When capabilities are found, each lines after will have a "caps" property. - * - * @param {Boolean} serverMode: true if results come from the server - * @return {Stream} - */ -function parsePktLineMeta(serverMode) { - var caps = null; - var lineIndex = 0; - - serverMode = !!serverMode; - var divineCapabilities = serverMode? divineServerCapabilities : divineClientCapabilities; - - // On which line shuld we look for capabilities? - var checkCapsOn = serverMode ? 1 : 0; - - return through.obj(function(line, enc, callback) { - var resultType; - var result = line; - - // Detect type - if (line.length === 0) { - resultType = TYPES.FLUSH; - } else { - var peek = line[0]; - resultType = peek === 1 ? TYPES.PACKFILE : - peek === 2 ? TYPES.PROGRESS : - peek === 3 ? TYPES.ERROR : TYPES.LINE; - } - - // Should we look for capabilities - if(!caps && lineIndex === checkCapsOn) { - caps = divineCapabilities(result) ; - if (caps) { - result = result.slice(0, caps.idx + 1); - result[result.length - 1] = 0x0A; - caps = caps.caps; - } - } - - // Remove first byte to define type - if (resultType !== TYPES.LINE) { - result = result.slice(1); - } - - result.type = resultType; - result.caps = caps; - - lineIndex++; - - if (resultType === TYPES.FLUSH) { - lineIndex = 0; - caps = null; - } else if (resultType === TYPES.PROGRESS || resultType === TYPES.ERROR) { - this.emit(resultType, result); - } - - callback(null, result); - }); -} - -module.exports = parsePktLineMeta; -module.exports.TYPES = TYPES; diff --git a/lib/TransferUtils/parsePktLines.js b/lib/TransferUtils/parsePktLines.js deleted file mode 100644 index 474810f..0000000 --- a/lib/TransferUtils/parsePktLines.js +++ /dev/null @@ -1,29 +0,0 @@ -var Dissolve = require('dissolve'); -var Buffer = require('buffer').Buffer; - -/** - * Parse a list of pkt-line - * - * @return {Stream} - */ -function parsePktLines() { - return Dissolve() - .loop(function(end) { - this.buffer('lineLength', 4) - .tap(function() { - var lineLength = parseInt(this.vars.lineLength.toString(), 16); - - if (lineLength === 0) { - this.push(new Buffer('')); - return; - } - - this.buffer('line', lineLength - 4) - .tap(function() { - this.push(this.vars.line); - }); - }); - }); -} - -module.exports = parsePktLines; diff --git a/lib/TransferUtils/parseUploadPack.js b/lib/TransferUtils/parseUploadPack.js deleted file mode 100644 index b4a514b..0000000 --- a/lib/TransferUtils/parseUploadPack.js +++ /dev/null @@ -1,37 +0,0 @@ -var throughFilter = require('through2-filter'); -var combine = require('stream-combiner2'); - -var parsePktLines = require('./parsePktLines'); -var parsePack = require('./parsePack'); -var parsePktLineMeta = require('./parsePktLineMeta'); - -/** - * Parse an upload result into a list of packs - * @return {Stream} - */ -function parseUploadPack(opts) { - var filter = throughFilter.obj(function(line) { - return line.type == parsePktLineMeta.TYPES.PACKFILE; - }); - - return combine.obj( - // Parse as lines - parsePktLines(), - - // Parse metatdata of lines - parsePktLineMeta(), - - // Filter packs - filter, - - // Parse pack as objects - parsePack(opts), - - // Not sure why... But without this filter, .on('data') doesn't work - throughFilter.obj(function() { - return true; - }) - ); -} - -module.exports = parseUploadPack; diff --git a/lib/TreeUtils/addEntry.js b/lib/TreeUtils/addEntry.js deleted file mode 100644 index 11ea302..0000000 --- a/lib/TreeUtils/addEntry.js +++ /dev/null @@ -1,19 +0,0 @@ - -/** - * Add an entry to an existing tree - * @param {Tree} - * @param {TreeEntry} - * @return {Tree} - */ -function addEntry(tree, entry) { - var entries = tree.getEntries(); - - entries = entries.set( - entry.getPath(), - entry - ); - - return tree.set('entries', entries); -} - -module.exports = addEntry; diff --git a/lib/TreeUtils/applyChanges.js b/lib/TreeUtils/applyChanges.js deleted file mode 100644 index 0f1fe87..0000000 --- a/lib/TreeUtils/applyChanges.js +++ /dev/null @@ -1,25 +0,0 @@ -// var Tree = require('../models/tree'); - -/** - * Apply changes to a tree - * @param {Repository} - * @param {String} - * @param {Map} changes - * @return newTreeSha - */ -function applyChanges(repo, treeSha, changes) { - /* var toApply = changes.filter(function(change, filePath) { - var parts = filePath.split('/'); - return parts.length === 1; - }); - - - return Tree.readFromRepo(repo, treeSha) - .then(function(tree) { - var entries = tree.getEntries(); - - // TODO - }); */ -} - -module.exports = applyChanges; diff --git a/lib/TreeUtils/index.js b/lib/TreeUtils/index.js deleted file mode 100644 index 5bec92c..0000000 --- a/lib/TreeUtils/index.js +++ /dev/null @@ -1,6 +0,0 @@ - -module.exports = { - addEntry: require('./addEntry'), - walk: require('./walk'), - applyChanges: require('./applyChanges') -}; diff --git a/lib/TreeUtils/walk.js b/lib/TreeUtils/walk.js deleted file mode 100644 index 15e6265..0000000 --- a/lib/TreeUtils/walk.js +++ /dev/null @@ -1,32 +0,0 @@ -var Promise = require('q'); -var path = require('path'); -var Tree = require('../models/tree'); - -/** - * Iterate over all tree entries - * @param {Repository} - * @param {String} base - * @param {Function(filepath, entry)} iter - */ -function walk(repo, base, iter, baseName) { - baseName = baseName || ''; - - return Tree.readFromRepo(repo, base) - .then(function(tree) { - var entries = tree.getEntries(); - - return entries.reduce(function(prev, entry, filename) { - return prev.then(function() { - var completeFilename = path.join(baseName, filename); - - if (!entry.isTree()) { - return iter(completeFilename, entry); - } else { - return walk(repo, entry.getSha(), iter, completeFilename); - } - }); - }, Promise()); - }); -} - -module.exports = walk; diff --git a/lib/WorkingUtils/add.js b/lib/WorkingUtils/add.js deleted file mode 100644 index cc60ff9..0000000 --- a/lib/WorkingUtils/add.js +++ /dev/null @@ -1,41 +0,0 @@ -var Promise = require('q'); -var Immutable = require('immutable'); - -var WorkingIndex = require('../models/workingIndex'); -var createEntry = require('./createEntry'); - -/** - * Add a file to the working index. It's equivalent to "git add" - * - * @param {Repository} repo - * @param {File|String} file - * @return {Promise} - */ -function addToIndex(repo, file) { - return Promise.all([ - // Read current working index - WorkingIndex.readFromRepo(repo), - - // Prepare new entry for this file - createEntry(repo, file) - ]) - .spread(function(workingIndex, newEntry) { - var entries = workingIndex.getEntries(); - var currentEntry = entries.get(newEntry.getPath()); - - // Nothing changed? - if (Immutable.is(currentEntry, newEntry)) { - return workingIndex; - } - - // Add entry - entries = entries.set(newEntry.getPath(), newEntry); - - workingIndex = workingIndex.set('entries', entries); - - // Write file for index - return WorkingIndex.writeToRepo(repo, workingIndex); - }); -} - -module.exports = addToIndex; diff --git a/lib/WorkingUtils/createEntry.js b/lib/WorkingUtils/createEntry.js deleted file mode 100644 index fddb511..0000000 --- a/lib/WorkingUtils/createEntry.js +++ /dev/null @@ -1,38 +0,0 @@ -var Promise = require('q'); -var is = require('is'); -var IndexEntry = require('../models/indexEntry'); -var sha1 = require('../utils/sha1'); - -/** - * Create an IndexEntry from a file. - * - * @param {Repository} repo - * @param {File|String} file - * @return {Promise} - */ -function createEntry(repo, file) { - return Promise() - - // Stat the file - .then(function() { - if (is.string(file)) { - return repo.statFile(file); - } else { - return file; - } - }) - - // Read file content - .then(function(fileStat) { - return repo.readFile(fileStat.getPath()) - .then(function(buf) { - var sha = sha1.encode(buf); - - return new IndexEntry({ - sha: sha - }); - }); - }); -} - -module.exports = createEntry; diff --git a/lib/WorkingUtils/index.js b/lib/WorkingUtils/index.js deleted file mode 100644 index 72e480a..0000000 --- a/lib/WorkingUtils/index.js +++ /dev/null @@ -1,5 +0,0 @@ - -module.exports = { - add: require('./add'), - createEntry: require('./createEntry') -}; diff --git a/lib/fs/memory.js b/lib/fs/memory.js deleted file mode 100644 index 073d512..0000000 --- a/lib/fs/memory.js +++ /dev/null @@ -1,12 +0,0 @@ -var MemoryFileSystem = require('memory-fs'); -var NodeFS = require('./node'); - -/** - * Create a in-mmeory filesystem that can be used in both Node.js and the browser - */ -function MemoryFS() { - var fs = new MemoryFileSystem(); - return new NodeFS(fs, '/'); -}; - -module.exports = MemoryFS; diff --git a/lib/fs/native.js b/lib/fs/native.js deleted file mode 100644 index 050e4e3..0000000 --- a/lib/fs/native.js +++ /dev/null @@ -1,4 +0,0 @@ -var fs = require('fs'); -var NodeFS = require('./node'); - -module.exports = NodeFS.bind(null, fs); diff --git a/lib/fs/node.js b/lib/fs/node.js deleted file mode 100644 index 57ae920..0000000 --- a/lib/fs/node.js +++ /dev/null @@ -1,98 +0,0 @@ -var Immutable = require('immutable'); -var Promise = require('q'); -var path = require('path'); -var mkdirp = require('mkdirp'); -var bindAll = require('bind-all'); - -var File = require('../models/file'); - -function FS(fs, root) { - if (!(this instanceof FS)) { - return new FS(fs, root); - } - - this.fs = bindAll(fs); - this.root = root; -} - -// Absolute path for a file -FS.prototype.path = function(filePath) { - return path.resolve(this.root, filePath); -}; - -/** - * Read a directory - * @param {String} - * @return {Promise>} - */ -FS.prototype.readDir = function readDir(dirpath) { - return Promise.nfcall(this.fs.readdir, this.path(dirpath)) - .then(Immutable.List); -}; - -/** - * Get details about a file - * @param {String} - * @return {Promise} - */ -FS.prototype.statFile = function statFile(filePath) { - return Promise.nfcall(this.fs.stat, this.path(filePath)) - .then(function(stat) { - return new File({ - path: filePath, - contentSize: stat.size, - mode: String(stat.mode), - type: stat.isDirectory()? File.TYPES.DIRECTORY : File.TYPES.FILE - }); - }); -}; - -/** - * Read content of a file - * @param {String} - * @return {Promise} - */ -FS.prototype.read = function read(filePath) { - return Promise.nfcall(this.fs.readFile, this.path(filePath)); -}; - -/** - * Write content of a file - * @param {String} - * @param {Buffer} - * @return {Promise} - */ -FS.prototype.write = function write(filePath, buf) { - var that = this; - - return this.mkdir(path.dirname(filePath)) - .then(function() { - return Promise.nfcall(that.fs.writeFile, that.path(filePath), buf); - }); -}; - -/** - * Remove a file - * @param {String} - * @return {Promise} - */ -FS.prototype.unlink = function unlink(filePath) { - return Promise.nfcall(this.fs.unlink, this.path(filePath)); -}; - -/** - * Create a directory - * @param {String} - * @return {Promise} - */ -FS.prototype.mkdir = function mkdir(filePath) { - if (this.fs.mkdirp) { - return Promise.nfcall(this.fs.mkdirp, this.path(filePath)); - } - - return Promise.nfcall(mkdirp, this.path(filePath), { - fs: this.fs - }); -}; - -module.exports = FS; diff --git a/lib/index.js b/lib/index.js deleted file mode 100644 index d0c47ed..0000000 --- a/lib/index.js +++ /dev/null @@ -1,29 +0,0 @@ - - -module.exports = { - // Models - Repository: require('./models/repo'), - GitObject: require('./models/object'), - Commit: require('./models/commit'), - Author: require('./models/author'), - Blob: require('./models/blob'), - Ref: require('./models/ref'), - Head: require('./models/head'), - Tree: require('./models/tree'), - WorkingIndex: require('./models/workingIndex'), - Person: require('./models/person'), - ProgressLine: require('./models/progressLine'), - - // Transports - HTTPTransport: require('./transport/http'), - - // Utils - CommitUtils: require('./CommitUtils'), - TreeUtils: require('./TreeUtils'), - BranchUtils: require('./BranchUtils'), - FileUtils: require('./FileUtils'), - WorkingUtils: require('./WorkingUtils'), - ChangesUtils: require('./ChangesUtils'), - RepoUtils: require('./RepoUtils'), - TransferUtils: require('./TransferUtils') -}; diff --git a/lib/models/__tests__/blob.js b/lib/models/__tests__/blob.js deleted file mode 100644 index 51f336e..0000000 --- a/lib/models/__tests__/blob.js +++ /dev/null @@ -1,11 +0,0 @@ -var Blob = require('../blob'); - -describe('Blob', function() { - describe('.createFromString', function() { - it('should create a blob', function() { - var blob = Blob.createFromString('Hello World'); - expect(blob).toBeA(Blob); - }); - }); -}); - diff --git a/lib/models/__tests__/object.js b/lib/models/__tests__/object.js deleted file mode 100644 index 2b5c57b..0000000 --- a/lib/models/__tests__/object.js +++ /dev/null @@ -1,13 +0,0 @@ -var GitObject = require('../object'); - -describe('Object', function() { - describe('.createFromBuffer', function() { - it('should read tree', function() { - var buf = fixtures.read('object-tree'); - var obj = GitObject.createFromZip(buf); - - expect(obj.getContent().length).toBe(352); - expect(obj.getType()).toBe(GitObject.TYPES.TREE); - }); - }); -}); diff --git a/lib/models/__tests__/tree.js b/lib/models/__tests__/tree.js deleted file mode 100644 index 2eac12a..0000000 --- a/lib/models/__tests__/tree.js +++ /dev/null @@ -1,14 +0,0 @@ -var GitObject = require('../object'); -var Tree = require('../tree'); - -describe('Tree', function() { - it('.createFromBuffer', function() { - var buf = fixtures.read('object-tree'); - - var obj = GitObject.createFromZip(buf); - var tree = Tree.createFromBuffer(obj.getContent()); - - var entries = tree.getEntries(); - expect(entries.size).toBe(10); - }); -}); diff --git a/lib/models/__tests__/workingIndex.js b/lib/models/__tests__/workingIndex.js deleted file mode 100644 index 01546b1..0000000 --- a/lib/models/__tests__/workingIndex.js +++ /dev/null @@ -1,14 +0,0 @@ -var WorkingIndex = require('../workingIndex'); - -describe('WorkingIndex', function() { - it('.createFromBuffer', function() { - var buf = fixtures.read('index'); - var wk = WorkingIndex.createFromBuffer(buf); - - var version = wk.getVersion(); - var entries = wk.getEntries(); - - expect(version).toBe(2); - expect(entries.size).toBe(94); - }); -}); diff --git a/lib/models/author.js b/lib/models/author.js deleted file mode 100644 index f70e3df..0000000 --- a/lib/models/author.js +++ /dev/null @@ -1,74 +0,0 @@ -var Immutable = require('immutable'); -var pad = require('pad'); - -var parseAuthor = require('../utils/parseAuthor'); - -var Author = Immutable.Record({ - name: String(), - email: String(), - timestamp: Number(), - timezone: String() -}); - -Author.prototype.getName = function() { - return this.get('name'); -}; - -Author.prototype.getEmail = function() { - return this.get('email'); -}; - -Author.prototype.getTimestamp = function() { - return this.get('timestamp'); -}; - -Author.prototype.getTimezone = function() { - return this.get('timezone'); -}; - -/** - * Convert an author to a string - * @return {String} - */ -Author.prototype.toString = function() { - return [ - this.name, - '<' + this.email + '>', - this.timestamp, - this.timezone - ].join(' '); -}; - -/** - * Parse and create an author instance - * @param {String} content - * @return {Author|null} - */ -Author.createFromString = function createFromString(str) { - var match = parseAuthor(str); - if (!match) return null; - - return new Author(match); -}; - -/** - * Create an author from a person - * @param {Person} person - * @param {Date} date - * @return {Author} - */ -Author.createFromPerson = function createFromPerson(person, date) { - var offset = new Date().getTimezoneOffset(); - offset = ((offset<0? '+':'-') + - pad('' + parseInt(Math.abs(offset/60)), 2) + - pad('' + Math.abs(offset%60), 2)); - - return new Author({ - name: person.getName(), - email: person.getEmail(), - timezone: offset, - timestamp: Number(date.getTime()/1000) - }); -}; - -module.exports = Author; diff --git a/lib/models/blob.js b/lib/models/blob.js deleted file mode 100644 index 07f1dfe..0000000 --- a/lib/models/blob.js +++ /dev/null @@ -1,93 +0,0 @@ -var Immutable = require('immutable'); -var Buffer = require('buffer').Buffer; - -var GitObject = require('./object'); - -var Blob = Immutable.Record({ - content: Buffer('') -}); - -/** - * Return the content as a buffer of this blob - * @return {Buffer} - */ -Blob.prototype.getContent = function() { - return this.get('content'); -}; - -/** - * Return the size of this blob - * @return {Number} - */ -Blob.prototype.getContentSize = function() { - return this.getContent().length; -}; - -/** - * Transform this blob into a git object - * @return {GitObject} - */ -Blob.prototype.toGitObject = function() { - return GitObject.create(GitObject.TYPES.BLOB, this.getContent()); -}; - -/** - * Create a blob from a Buffer - * @param {Buffer} content - * @return {Blob} - */ -Blob.createFromBuffer = function(content) { - return new Blob({ - content: content - }); -}; - -/** - * Create a blob from a String - * @param {String} content - * @return {Blob} - */ -Blob.createFromString = function(content) { - return Blob.createFromBuffer(new Buffer(content, 'utf8')); -}; - -/** - * Create a blob from a GitObject - * @param {GitObject} obj - * @return {Blob} - */ -Blob.createFromObject = function(obj) { - return Blob.createFromBuffer(obj.getContent()); -}; - -/** - * Read a blob by its sha from a repository - * @param {Repository} repo - * @param {String} sha - * @return {Promise} - */ -Blob.readFromRepo = function(repo, sha) { - return GitObject.readFromRepo(repo, sha) - .then(function(obj) { - if (!obj.isBlob()) { - throw new Error('Object "' + sha + '" is not a blob'); - } - - return Blob.createFromObject(obj); - }); -}; - -/** - * Write a blob in a repository. - * It returns the new sha of this blob. - * - * @param {Repository} repo - * @param {Blob} blob - * @return {Promise} - */ -Blob.writeToRepo = function(repo, blob) { - var gitObject = blob.toGitObject(); - return GitObject.writeToRepo(gitObject); -}; - -module.exports = Blob; diff --git a/lib/models/change.js b/lib/models/change.js deleted file mode 100644 index 52dd428..0000000 --- a/lib/models/change.js +++ /dev/null @@ -1,49 +0,0 @@ -var Immutable = require('immutable'); -var File = require('./file'); -var Blob = require('./blob'); - -var TYPES = { - MODIFIED: 'modified', - CREATED: 'created', - REMOVED: 'removed', - UNTRACKED: 'untracked' -}; - -var Change = Immutable.Record({ - file: File(), - type: String(TYPES.MODIFIED), - blob: Blob() -}); - -Change.prototype.getFile = function() { - return this.get('file'); -}; - -Change.prototype.getBlob = function() { - return this.get('blob'); -}; - -Change.prototype.getType = function() { - return this.get('type'); -}; - -Change.prototype.isTracked = function() { - return this.getType() !== TYPES.UNTRACKED; -}; - -/** - * Create a new change from a file and a type - * - * @param {String} type - * @parsm {File} file - * @return {Change} - */ -Change.createForFile = function(type, file) { - return new Change({ - type: type, - file: file - }); -}; - -module.exports = Change; -module.exports.TYPES = TYPES; diff --git a/lib/models/commit.js b/lib/models/commit.js deleted file mode 100644 index 5f6e003..0000000 --- a/lib/models/commit.js +++ /dev/null @@ -1,107 +0,0 @@ -var Immutable = require('immutable'); - -var Author = require('./author'); -var GitObject = require('./object'); -var parseCommit = require('../utils/parseCommit'); - -var Commit = Immutable.Record({ - author: Author(), - committer: Author(), - tree: String(), - parents: Immutable.List(), - message: String() -}); - -Commit.prototype.getAuthor = function() { - return this.get('author'); -}; - -Commit.prototype.getCommitter = function() { - return this.get('committer'); -}; - -Commit.prototype.getTree = function() { - return this.get('tree'); -}; - -Commit.prototype.getParents = function() { - return this.get('parents'); -}; - -Commit.prototype.getMessage = function() { - return this.get('message'); -}; - -/** - * Parse a commit from a String - * - * @param {String} content - * @return {Commit} - */ -Commit.createFromString = function(content) { - var info = parseCommit(content); - - return new Commit({ - author: Author(info.author), - committer: Author(info.committer), - tree: info.tree, - parents: Immutable.List(info.parents), - message: info.message - }); -}; - -/** - * Parse a commit from a Buffer - * - * @param {Buffer} content - * @return {Commit} - */ -Commit.createFromBuffer = function(content) { - return Commit.createFromString(content.toString('utf8')); -}; - -/** - * Parse a commit from a GitObject - * - * @param {GitObject} obj - * @return {Commit} - */ -Commit.createFromObject = function(obj) { - return Commit.createFromBuffer(obj.getContent()); -}; - -/** - * Create a new commit from metadat - * - * @param {Object} - * @return {Commit} - */ -Commit.create = function(obj) { - return new Commit({ - author: obj.author, - committer: obj.committer, - tree: obj.tree, - parents: Immutable.List(obj.parents), - message: obj.message - }); -}; - -/* - * Read a commit object by its sha from a repository - * - * @param {Repository} repo - * @param {String} sha - * @return {Promise} - */ -Commit.readFromRepo = function(repo, sha) { - return GitObject.readFromRepo(repo, sha) - .then(function(obj) { - if (!obj.isCommit()) { - throw new Error('Object "' + sha + '" is not a commit'); - } - - return Commit.createFromObject(obj); - }); -}; - -module.exports = Commit; diff --git a/lib/models/file.js b/lib/models/file.js deleted file mode 100644 index 7b277a8..0000000 --- a/lib/models/file.js +++ /dev/null @@ -1,50 +0,0 @@ -var Immutable = require('immutable'); - -var TYPES = { - DIRECTORY: 'dir', - FILE: 'file' -}; - -var File = Immutable.Record({ - path: String(), - type: String(TYPES.FILE), - mode: String(), - contentSize: Number() -}); - -File.prototype.getPath = function() { - return this.get('path'); -}; - -File.prototype.getType = function() { - return this.get('type'); -}; - -File.prototype.getMode = function() { - return this.get('mode'); -}; - -File.prototype.getContentSize = function() { - return this.get('contentSize'); -}; - -File.prototype.isDirectory = function() { - return this.getType() === TYPES.DIRECTORY; -}; - -/* - * Create a file from a IndexEntry - * - * @param {IndexEntry} - * @return {File} - */ -File.createFromIndexEntry = function(entry) { - return new File({ - path: entry.getPath(), - mode: entry.getMode(), - contentSize: entry.getEntrySize() - }); -}; - -module.exports = File; -module.exports.TYPES = TYPES; diff --git a/lib/models/head.js b/lib/models/head.js deleted file mode 100644 index 8ad86b7..0000000 --- a/lib/models/head.js +++ /dev/null @@ -1,78 +0,0 @@ -var Immutable = require('immutable'); - -var parseMap = require('../utils/parseMap'); - -var Head = Immutable.Record({ - ref: String() -}); - -Head.prototype.getRef = function() { - return this.get('ref'); -}; - -/** - * Output the head as a string - * @return {String} - */ -Head.prototype.toString = function() { - return 'ref: ' + this.getRef() + '\n'; -}; - -/** - * Parse a ref from a String - * @param {String} content - * @return {Head} - */ -Head.createFromString = function(content) { - var map = parseMap(content); - return Head.createForRef(map.get('ref')); -}; - -/** - * Create a head for a ref - * @param {String} ref - * @return {Head} - */ -Head.createForRef = function(ref) { - return new Head({ - ref: ref - }); -}; - -/** - * Parse a head from a Buffer - * @param {Buffer} content - * @return {Head} - */ -Head.createFromBuffer = function(content) { - return Head.createFromString(content.toString('utf8')); -}; - -/** - * Read a head from a repository using its path - * @param {Repository} repo - * @param {String} filename - * @return {Promise} - */ -Head.readFromRepo = function(repo, filename) { - filename = filename || 'HEAD'; - - return repo.readGitFile(filename) - .then(Head.createFromBuffer); -}; - -/** - * Write a head to a repository - * - * @param {Repository} repo - * @param {String} filename - * @return {Promise} - */ -Head.writeToRepo = function(repo, head, filename) { - filename = filename || 'HEAD'; - var headContent = head.toString(); - - return repo.writeGitFile(filename, new Buffer(headContent, 'utf8')); -}; - -module.exports = Head; diff --git a/lib/models/indexEntry.js b/lib/models/indexEntry.js deleted file mode 100644 index a4839eb..0000000 --- a/lib/models/indexEntry.js +++ /dev/null @@ -1,167 +0,0 @@ -var Immutable = require('immutable'); -var Concentrate = require('concentrate'); -var Dissolve = require('dissolve'); -var Buffer = require('buffer').Buffer; - -var IndexEntry = Immutable.Record({ - ctime: Date(), - mtime: Date(), - dev: Number(), - ino: Number(), - mode: Number(), - uid: Number(), - gid: Number(), - entrySize: Number(), - flags: Number(), - extendedFlags: Number(), - sha: String(), - path: String() -}); - -IndexEntry.prototype.getDev = function() { - return this.get('dev'); -}; - -IndexEntry.prototype.getIno = function() { - return this.get('ino'); -}; - -IndexEntry.prototype.getUid = function() { - return this.get('uid'); -}; - -IndexEntry.prototype.getGid = function() { - return this.get('gid'); -}; - -IndexEntry.prototype.getCTime = function() { - return this.get('ctime'); -}; - -IndexEntry.prototype.getMTime = function() { - return this.get('mtime'); -}; - -IndexEntry.prototype.getMode = function() { - return this.get('mode'); -}; - -IndexEntry.prototype.getPath = function() { - return this.get('path'); -}; - -IndexEntry.prototype.getSha = function() { - return this.get('sha'); -}; - -IndexEntry.prototype.getFlags = function() { - return this.get('flags'); -}; - -IndexEntry.prototype.getExtendedFlags = function() { - return this.get('extendedFlags'); -}; - -IndexEntry.prototype.getEntrySize = function() { - return this.get('entrySize'); -}; - -/** - * Output an IndexEntry as a buffer - * @return {Buffer} - */ -IndexEntry.prototype.toBuffer = function(version) { - var output = Concentrate() - - // ctime - .uint32be(this.getCTime().getTime()) - .uint32be(0) - - // mtime - .uint32be(this.getMTime().getTime()) - .uint32be(0) - - .uint32be(this.getDev()) - .uint32be(this.getIno()) - .uint32be(this.getUid()) - .uint32be(this.getGid()) - .uint32be(this.getEntrySize()) - .buffer((new Buffer(this.getSha())).toString('hex')) - .uint16be(this.getFlags()); - - - if (version >= 3) output = output.uint16be(this.getExtendedFlags()); - output = output.string(this.getPath()); - - var buf = output.result(); - var padlen = (8 - (buf.length % 8)) || 8; - - return Buffer.concat([ - buf, - Buffer.alloc(padlen, 0) - ]); -}; - -/** - * Parse from a buffer - * @param {Buffer} - * @return {Object{Buffer, IndexEntry}} - */ -IndexEntry.createFromBuffer = function(buf, version) { - var parser = Dissolve() - .uint32be('ctime') - .uint32be('ctime_nano') - .uint32be('mtime') - .uint32be('mtime_nano') - .uint32be('dev') - .uint32be('ino') - .uint32be('mode') - .uint32be('uid') - .uint32be('gid') - .uint32be('entrySize') - .buffer('sha', 20) - .uint16be('flags') - .tap(function() { - if (version < 3) return; - - this.uint16be('extendedFlags'); - }) - - // Read path - .tap(function() { - this.vars.path = ''; - - this.loop(function(end) { - this.buffer('pathc', 1); - this.tap(function() { - if (this.vars.pathc[0] == 0) end(); - else { - this.vars.path += this.vars.pathc.toString('utf8'); - } - }); - }); - }) - .tap(function() { - var offset = this.offset; - var padlen = (8 - (offset % 8)) || 8; - this.buffer('null', padlen); - }) - .tap(function() { - this.vars.ctime = new Date(this.vars.ctime); - this.vars.mtime = new Date(this.vars.mtime); - this.vars.sha = this.vars.sha.toString('hex'); - delete this.vars.pathc; - delete this.vars.null; - delete this.vars.ctime_nano; - delete this.vars.mtime_nano; - }); - - parser.write(buf); - - return { - buffer: buf.slice(parser.offset), - entry: new IndexEntry(parser.vars) - }; -}; - -module.exports = IndexEntry; diff --git a/lib/models/object.js b/lib/models/object.js deleted file mode 100644 index 9e9d7a8..0000000 --- a/lib/models/object.js +++ /dev/null @@ -1,155 +0,0 @@ -var Immutable = require('immutable'); -var Buffer = require('buffer').Buffer; -var path = require('path'); - -var zlib = require('../utils/zlib'); -var sha1 = require('../utils/sha1'); - -var TYPES = { - TREE: 'tree', - BLOB: 'blob', - COMMIT: 'commit' -}; - -var GitObject = Immutable.Record({ - type: String(), - content: Buffer('') -}); - -GitObject.prototype.getType = function() { - return this.get('type'); -}; - -GitObject.prototype.getContent = function() { - return this.get('content'); -}; - -GitObject.prototype.isTree = function() { - return this.getType() === TYPES.TREE; -}; - -GitObject.prototype.isBlob = function() { - return this.getType() === TYPES.BLOB; -}; - -GitObject.prototype.isCommit = function() { - return this.getType() === TYPES.COMMIT; -}; - - -/** - * Calcul the sha of this object - * @return {String} - */ -GitObject.prototype.getSha = function() { - return sha1.encode(this.getAsBuffer()); -}; - -/** - * Return this git object as a buffer - * @param {Buffer} - */ -GitObject.prototype.getAsBuffer = function() { - var type = this.getType(); - var content = this.getContent(); - - var nullBuf = new Buffer(1); - nullBuf.fill(0); - - return Buffer.concat([ - (new Buffer(type + ' ' + content.length, 'utf8')), - nullBuf, - content - ]); -}; - -/** - * Return path to an object by its sha - * @paran {String} sha - * @return {String} - */ -GitObject.getPath = function(sha) { - return path.join('objects', sha.slice(0, 2), sha.slice(2)); -}; - -/** - * Read an object from a repository using its SHA - * @param {Repository} repo - * @paran {String} sha - * @return {Promise} - */ -GitObject.readFromRepo = function(repo, sha) { - var objectPath = GitObject.getPath(sha); - - return repo.readGitFile(objectPath) - .then(GitObject.createFromZip); -}; - -/** - * Create a Git object from a zip content - * @param {Buffer} content - * @return {GitObject} - */ -GitObject.createFromZip = function(content) { - return GitObject.createFromBuffer( - zlib.unzip(content) - ); -}; - -/** - * Create a Git object from a zip content - * @param {Buffer} content - * @return {GitObject} - */ -GitObject.createFromBuffer = function(content) { - var nullChar = content.indexOf(0); - - // Parse object header - var header = content.slice(0, nullChar).toString(); - var type = header.split(' ')[0]; - - // Extract content - var innerContent = content.slice(nullChar + 1); - - return new GitObject({ - type: type, - content: innerContent - }); -}; - -/** - * Write a git object in a repository. - * It returns the new sha of this object. - * @param {Repository} repo - * @param {GitObject} obj - * @return {Promise} - */ -GitObject.writeToRepo = function(repo, obj) { - // Output object as a buffer - var content = obj.getAsBuffer(); - - // Calcul sha1 for the buffer - var sha = sha1.encode(content); - - // Calcul path for this sha - var objectPath = GitObject.getPath(sha); - - // Zip and write the buffer - return repo.writeGitFile(objectPath, zlib.zip(content)) - .thenResolve(sha); -}; - -/** - * Create a Git object from a content and a type - * @param {Buffer} content - * @return {GitObject} - */ -GitObject.create = function(type, content) { - return new GitObject({ - type: type, - content: content - }); -}; - -module.exports = GitObject; -module.exports.TYPES = TYPES; diff --git a/lib/models/person.js b/lib/models/person.js deleted file mode 100644 index de77f00..0000000 --- a/lib/models/person.js +++ /dev/null @@ -1,31 +0,0 @@ -var Immutable = require('immutable'); - -var Person = Immutable.Record({ - name: String(), - email: String() -}); - -Person.prototype.getName = function() { - return this.get('name'); -}; - -Person.prototype.getEmail = function() { - return this.get('email'); -}; - - -/* - * Create a person using a name and email. - * - * @param {String} name - * @param {String} email - * @return {Person} - */ -Person.create = function(name, email) { - return new Person({ - name: name, - email: email - }); -}; - -module.exports = Person; diff --git a/lib/models/progressLine.js b/lib/models/progressLine.js deleted file mode 100644 index 2053cdf..0000000 --- a/lib/models/progressLine.js +++ /dev/null @@ -1,80 +0,0 @@ -var is = require('is'); -var Immutable = require('immutable'); - -/* - ProgressLine is an utility send to promise's progress - to normalize logging in the output. -*/ - -var TYPES = { - // Is writing a file on the disk - FILES_WRITE: 'files:write', - - // Is fetching an object from a remote repository - FETCH_OBJECT: 'fetch:object' -}; - - -var ProgressLine = Immutable.Record({ - type: String(), - props: Immutable.Map() -}); - -ProgressLine.prototype.getType = function() { - return this.get('type'); -}; - -ProgressLine.prototype.getProps = function() { - return this.get('props'); -}; - -ProgressLine.prototype.getMessage = function() { - var type = this.getType(); - var props = this.getProps(); - - var message = 'Unknown'; - var progress; - - if (type === TYPES.FILES_WRITE) { - message = 'Writing file "' + props.get('filename') + '"'; - } else if (type === TYPES.FETCH_OBJECT) { - message = 'Fetching object ' + props.get('index') + '/' + props.get('total'); - progress = props.get('index') / props.get('total'); - } - - if (is.number(progress)) { - message = '[' + (progress * 100).toFixed(0) + '%] ' + message; - } - - return message; -}; - -/* - * Create a progress line for a promise - * @return {Object} - */ -function createProgressLine(type, props) { - return new ProgressLine({ - type: type, - props: new Immutable.Map(props) - }); -} - -function createFilesWrite(filename) { - return createProgressLine(TYPES.FILES_WRITE, { - filename: filename - }); -} - -function createFetchObject(objIndex, objTotal) { - return createProgressLine(TYPES.FETCH_OBJECT, { - index: objIndex, - total: objTotal - }); -} - -module.exports = createProgressLine; -module.exports.TYPES = TYPES; - -module.exports.WriteFile = createFilesWrite; -module.exports.FetchObject = createFetchObject; diff --git a/lib/models/ref.js b/lib/models/ref.js deleted file mode 100644 index 92eb528..0000000 --- a/lib/models/ref.js +++ /dev/null @@ -1,99 +0,0 @@ -var Immutable = require('immutable'); -var path = require('path'); - -var Ref = Immutable.Record({ - commit: String() -}); - -Ref.prototype.getCommit = function() { - return this.get('commit'); -}; - -/** - * Output the reference as a string - * @return {String} - */ -Ref.prototype.toString = function() { - return this.getCommit() + '\n'; -}; - -/** - * Output the reference as a buffer - * @return {Buffer} - */ -Ref.prototype.toBuffer = function() { - return new Buffer(this.toString(), 'utf8'); -}; - -/** - * Return path to a ref by its sha - * @paran {String} name - * @return {String} - */ -Ref.getPath = function(name) { - return path.join('refs', name); -}; - -/** - * Parse a ref from a String - * @param {String} content - * @return {Ref} - */ -Ref.createFromString = function(content) { - var sha = content.trim(); - return Ref.createForCommit(sha); -}; - -/** - * Parse a ref from a Buffer - * @param {Buffer} content - * @return {Ref} - */ -Ref.createFromBuffer = function(content) { - return Ref.createFromString(content.toString('utf8')); -}; - -/** - * Create ref using a commit sha - * @param {String} sha - * @return {Ref} - */ -Ref.createForCommit = function(sha) { - return new Ref({ - commit: sha - }); -}; - -/** - * Read a ref from a repository using its name - * @param {Repository} repo - * @paran {String} name - * @return {Promise} - */ -Ref.readFromRepoByName = function(repo, name) { - var refPath = Ref.getPath(name); - return Ref.readFromRepo(repo, refPath); -}; - -/** - * Read a ref from a repository using its filename - * @param {Repository} repo - * @paran {String} refPath - * @return {Promise} - */ -Ref.readFromRepo = function(repo, refPath) { - return repo.readGitFile(refPath) - .then(Ref.createFromBuffer); -}; - -/** - * Read a ref from a repository using its filename - * @param {Repository} repo - * @paran {String} refPath - * @return {Promise} - */ -Ref.writeToRepo = function(repo, refPath, ref) { - return repo.writeGitFile(refPath, ref.toBuffer()); -}; - -module.exports = Ref; diff --git a/lib/models/repo.js b/lib/models/repo.js deleted file mode 100644 index 4c6509f..0000000 --- a/lib/models/repo.js +++ /dev/null @@ -1,126 +0,0 @@ -var Immutable = require('immutable'); -var path = require('path'); - -var Repository = Immutable.Record({ - bare: Boolean(false), - fs: null -}); - -Repository.prototype.isBare = function() { - return this.get('bare'); -}; - -Repository.prototype.getFS = function() { - return this.get('fs'); -}; - -/** - * Read a file from the repository - * - * @param {String} - * @return {Buffer} - */ -Repository.prototype.readFile = function(filepath) { - return this.getFS().read(filepath); -}; - -/** - * Write a file from the repository - * - * @param {String} - * @param {Buffer} - * @return {Buffer} - */ -Repository.prototype.writeFile = function(filepath, buf) { - return this.getFS().write(filepath, buf); -}; - -/** - * Get details about a file - * - * @param {String} - * @return {File} - */ -Repository.prototype.statFile = function(filepath) { - return this.getFS().statFile(filepath); -}; - -/** - * Read a directory from the repository - * - * @param {String} - * @return {List} - */ -Repository.prototype.readDir = function(filepath) { - return this.getFS().readDir(filepath); -}; - -/** - * Get absolute path toa file related to the git content - * - * @param {String} - * @return {String} - */ -Repository.prototype.getGitPath = function(filepath) { - return this.isBare()? filepath : path.join('.git', filepath); -}; - -/** - * Read a git file from the repository (file in the '.git' directory) - * - * @param {String} - * @return {Buffer} - */ -Repository.prototype.readGitFile = function(filepath) { - filepath = this.getGitPath(filepath); - return this.readFile(filepath); -}; - -/** - * Write a git file from the repository (file in the '.git' directory) - * - * @param {String} - * @param {Buffer} - * @return {Buffer} - */ -Repository.prototype.writeGitFile = function(filepath, buf) { - filepath = this.getGitPath(filepath); - return this.writeFile(filepath, buf); -}; - -/** - * Stat a git file from the repository (file in the '.git' directory) - * - * @param {String} - * @return {File} - */ -Repository.prototype.statGitFile = function(filepath) { - filepath = this.getGitPath(filepath); - return this.statFile(filepath); -}; - -/** - * List content of a directory (inside the .git) - * - * @param {String} - * @return {List} - */ -Repository.prototype.readGitDir = function(filepath) { - filepath = this.getGitPath(filepath); - return this.readDir(filepath); -}; - -/** - * Create a repository with an fs instance - * - * @param {String} - * @return {Repository} - */ -Repository.createWithFS = function(fs, isBare) { - return new Repository({ - bare: isBare, - fs: fs - }); -}; - -module.exports = Repository; diff --git a/lib/models/tree.js b/lib/models/tree.js deleted file mode 100644 index c7797cd..0000000 --- a/lib/models/tree.js +++ /dev/null @@ -1,92 +0,0 @@ -var is = require('is'); -var Immutable = require('immutable'); -var Dissolve = require('dissolve'); -var Buffer = require('buffer').Buffer; - -var TreeEntry = require('./treeEntry'); -var GitObject = require('./object'); -var scan = require('../utils/scan'); - -var Tree = Immutable.Record({ - entries: Immutable.OrderedMap() -}); - -Tree.prototype.getEntries = function() { - return this.get('entries'); -}; - -/** - * Parse a tree from a Buffer - * - * @param {Buffer} content - * @return {Tree} - */ -Tree.createFromBuffer = function(content) { - var entriesMap = {}; - var parser = Dissolve() - .loop(function() { - scan(this, 'mode', ' '); - scan(this, 'path', new Buffer([0])); - this.buffer('sha', 20); - - this.tap(function() { - entriesMap[this.vars.path] = new TreeEntry({ - path: this.vars.path, - mode: this.vars.mode, - sha: this.vars.sha.toString('hex') - }); - }); - }); - - parser.write(content); - - return new Tree({ - entries: Immutable.OrderedMap(entriesMap) - }); -}; - -/** - * Parse a tree from a GitObject - * - * @param {GitObject} obj - * @return {Tree} - */ -Tree.createFromObject = function(obj) { - return Tree.createFromBuffer(obj.getContent()); -}; - -/** - * Create a tree using a list of entries - * - * @param {Array|List} arr - * @return {Tree} - */ -Tree.create = function(arr) { - if (!is.array(arr)) arr = arr.toJS(); - - return new Tree({ - entries: Immutable.OrderedMap(arr.map(function(entry) { - return [entry.getPath(), entry]; - })) - }); -}; - -/** - * Read a tree object by its sha from a repository - * - * @param {Repository} repo - * @param {String} sha - * @return {Promise} - */ -Tree.readFromRepo = function(repo, sha) { - return GitObject.readFromRepo(repo, sha) - .then(function(obj) { - if (!obj.isTree()) { - throw new Error('Object "' + sha + '" is not a tree'); - } - - return Tree.createFromObject(obj); - }); -}; - -module.exports = Tree; diff --git a/lib/models/treeEntry.js b/lib/models/treeEntry.js deleted file mode 100644 index 8fe0e56..0000000 --- a/lib/models/treeEntry.js +++ /dev/null @@ -1,57 +0,0 @@ -var Immutable = require('immutable'); - -var TYPES = { - BLOB: 'blob', - TREE: 'tree' -}; - -/* - Represents an entry in the git tree. -*/ -var TreeEntry = Immutable.Record({ - path: String(), - mode: String(), - sha: String() -}); - -TreeEntry.prototype.getPath = function() { - return this.get('path'); -}; - -TreeEntry.prototype.getMode = function() { - return this.get('mode'); -}; - -TreeEntry.prototype.getSha = function() { - return this.get('sha'); -}; - -TreeEntry.prototype.getType = function() { - return this.getMode() === 16384? TYPES.TREE : TYPES.BLOB; -}; - -TreeEntry.prototype.isTree = function() { - return this.getType() == 'tree'; -}; - -TreeEntry.prototype.isBlob = function() { - return this.getType() == 'blob'; -}; - -/** - * Create a new tree ntry for a blob - * - * @param {String} - * @return {TreeEntry} - */ -TreeEntry.createForBlob = function(filename, blobSha, mode) { - return new TreeEntry({ - type: TYPES.BLOB, - path: filename, - mode: mode || '', - sha: blobSha - }); -}; - -module.exports = TreeEntry; -module.exports.TYPES = TYPES; diff --git a/lib/models/workingIndex.js b/lib/models/workingIndex.js deleted file mode 100644 index d9ba583..0000000 --- a/lib/models/workingIndex.js +++ /dev/null @@ -1,110 +0,0 @@ -var Immutable = require('immutable'); -var Concentrate = require('concentrate'); - -var IndexEntry = require('./indexEntry'); - -var SIGNATURE = 'DIRC'; - -var WorkingIndex = Immutable.Record({ - version: Number(), - entries: Immutable.OrderedMap() -}); - -WorkingIndex.prototype.getEntries = function() { - return this.get('entries'); -}; - -WorkingIndex.prototype.getVersion = function() { - return this.get('version'); -}; - -/** - * Output as a buffer - * @return {Buffer} - */ -WorkingIndex.prototype.toBuffer = function() { - var entries = this.getEntries(); - var version = this.getVersion(); - - var output = Concentrate() - .string(SIGNATURE) - .uint32be(version) - .uint32be(entries.size); - - entries.each(function(entry) { - output.buffer(entry.toBuffer(version)); - }); - - return output.result; -}; - -/** - * Parse a working inex from a Buffer - * - * @param {Buffer} content - * @return {WorkingIndex} - */ -WorkingIndex.createFromBuffer = function(content) { - var version, nEntries; - var entriesBuf; - var entries = Immutable.OrderedMap(); - - var header = content.slice(0, 12); - - // Validate header signature - var signature = header.slice(0, 4); - if (signature.toString() !== SIGNATURE) { - throw new Error('Invalid working index header'); - } - - // Read the version - version = header.readInt32BE(4); - - // Read the number of entries - nEntries = header.readInt32BE(8); - - entriesBuf = content.slice(12); - for (var i = 0; i < (nEntries - 1); i++) { - var result = IndexEntry.createFromBuffer(entriesBuf, version); - entriesBuf = result.buffer; - entries = entries.set( - result.entry.getPath(), - result.entry - ); - } - - return new WorkingIndex({ - version: version, - entries: entries - }); -}; - -/** - * Read a WorkingIndex from a repository using its filename - * - * @param {Repository} repo - * @paran {String} refPath - * @return {Promise} - */ -WorkingIndex.readFromRepo = function(repo, fileName) { - fileName = fileName || 'index'; - - return repo.readGitFile(fileName) - .then(WorkingIndex.createFromBuffer); -}; - -/** - * Write a working index in a repository. - * - * @param {Repository} repo - * @param {WorkingIndex} obj - * @return {Promise} - */ -WorkingIndex.writeToRepo = function(repo, workingIndex, fileName) { - fileName = fileName || 'index'; - - return repo.writeGitFile(fileName, workingIndex.toBuffer()); -}; - - -module.exports = WorkingIndex; diff --git a/lib/transport/http.js b/lib/transport/http.js deleted file mode 100644 index 00d46ed..0000000 --- a/lib/transport/http.js +++ /dev/null @@ -1,125 +0,0 @@ -var Promise = require('q'); -var http = require('http'); -var https = require('https'); -var url = require('url'); -var querystring = require('querystring'); - -var pkg = require('../../package.json'); - -var SERVICE_UPLOAD_PACK = 'git-upload-pack'; -// var SERVICE_RECEIVE_PACK = 'git-receive-pack'; - -/** - * HTTP/HTTPS Transport for the smart Git protocol. - * - * @param {String} uri - * @param {Object} auth - */ -function HTTPTransport(uri, auth) { - if (!this instanceof HTTPTransport) { - return new HTTPTransport(uri, auth); - } - - this.uri = uri; - this.auth = auth; -} - -/** - * Open and prepare the connection - * @return {Promise} - */ -HTTPTransport.prototype.open = function() { - return Promise(); -}; - -/** - * Close and cleanup the connection - * @return {Promise} - */ -HTTPTransport.prototype.close = function() { - return Promise(); -}; - -/** - * Execute an HTTP request - * @param {String} method - * @param {String} uri - * @param {Object} opts - * @return {Promise} - */ -HTTPTransport.prototype.request = function(method, uri, opts) { - var d = Promise.defer(); - opts = opts || {}; - opts.headers = opts.headers || {}; - - // Set authorization headers - if (this.auth) { - opts.headers['Authorization'] = 'Basic ' + new Buffer(this.auth.user + ':' + this.auth.password) - .toString('base64'); - } - - // Set user agent - opts.headers['User-Agent'] = pkg.name + '/' + pkg.version; - - // Parse url - var parsed = url.parse(this.uri + '/' + uri); - - // Setup agent - var agent = parsed.protocol == 'https:'? https : http; - - var req = agent.request({ - protocol: parsed.protocol, - hostname: parsed.hostname, - port: parsed.port, - path: parsed.path, - method: method, - headers: opts.headers - }, function (res) { - d.resolve(res); - }); - - // Send request - req.end(opts.body); - - return d.promise; -}; - -/** - * Get a pack from the server using "git-upload-pack" - * @param {String} resource - * @return {Promise} - */ -HTTPTransport.prototype.getWithUploadPack = function(resource, data) { - resource = resource + '?' + querystring.stringify({ - service: SERVICE_UPLOAD_PACK - }); - - return this.request('GET', resource, { - headers: { - 'Content-Type': 'application/x-git-upload-pack-request' - } - }) - .then(function(res) { - if (res.headers['content-type'] != 'application/x-'+SERVICE_UPLOAD_PACK+'-advertisement') { - throw new Error('Invalid content type when fetching pack'); - } - - return res; - }); -}; - -/** - * Get a pack from the server using "git-upload-pack" - * @param {Buffer} data - * @return {Promise} - */ -HTTPTransport.prototype.uploadPack = function(data) { - return this.request('POST', SERVICE_UPLOAD_PACK, { - body: data, - headers: { - 'Content-Type': 'application/x-git-upload-pack-request' - } - }); -}; - -module.exports = HTTPTransport; diff --git a/lib/utils/parseAuthor.js b/lib/utils/parseAuthor.js deleted file mode 100644 index 3e9aeec..0000000 --- a/lib/utils/parseAuthor.js +++ /dev/null @@ -1,21 +0,0 @@ -var AUTHOR_RE = /(.*) <([^>]+)> (\d+) ([+-]{1}\d{4})/; - -/** - * Parse an author line - * - * @param {String} str - * @return {Object} - */ -function parseAuthor(str) { - var match = AUTHOR_RE.exec(str); - if (!match) return null; - - return { - name: match[1].replace(/(^\s+|\s+$)/, ''), - email: match[2], - timestamp: parseInt(match[3], 10), - timezone: match[4] - }; -} - -module.exports = parseAuthor; diff --git a/lib/utils/parseCommit.js b/lib/utils/parseCommit.js deleted file mode 100644 index c3cbe3a..0000000 --- a/lib/utils/parseCommit.js +++ /dev/null @@ -1,77 +0,0 @@ -var parseAuthor = require('./parseAuthor'); - -/** - * Parse a line for an author in a commit - * - * @param {String} line - * @return {Object} - */ -function parseAuthorLine(line, info) { - if (line.indexOf('author') !== -1) { - info.author = parseAuthor(line.replace('author ', '')) - if (info) { - return true; - } - } - if (line.indexOf('committer') !== -1) { - info.committer = parseAuthor(line.replace('committer ', '')) - if (info.committer) { - return true; - } - } - - return false; -} - -/** - * Parse a commit into a map of infos - * - * @param {String} data - * @return {Object} - */ -function parseCommit(data) { - var matched = ''; - var line = ''; - var commit = { - parents: [] - }; - data = data.split('\n'); - - var i = 0; - - // parse commit data - while (line = data[i].trim()) { - if (!commit.tree && (matched = line.match(/^tree ([0-9a-fA-F]{40})$/))) { - commit.tree = matched[1]; - } else if (matched = line.match(/^parent ([0-9a-fA-F]{40})$/)) { - commit.parents.push(matched[1]); - } else if (matched = /^\s+git\-svn\-id:\s(.+)$/.exec(line)) { - var svn = matched[1].split(/[@\s]/); - commit.svn = { - repo : svn[0], - rev : Number(svn[1]), - uuid : svn[2] - }; - } else if (!parseAuthorLine(line, commit) && /^\s+(\S.+)/.exec(line)) { - // empty line means commit message begins - break; - } - - i++; - } - - // slicing from i + 1 to to remove the \n at the beginning of the commit title - var commitMessage = data.slice(i + 1).map(function(line) { - // remove 'git spaces' from the beginning of each line - return line.replace(/^ {4}/, '').replace(/\r$/, ''); - }); - - commit.title = commitMessage[0]; - commit.description = commitMessage.slice(2).join('\n'); - - commit.message = commit.title + (commit.description? '\n' + commit.description : ''); - - return commit; -} - -module.exports = parseCommit; diff --git a/lib/utils/parseMap.js b/lib/utils/parseMap.js deleted file mode 100644 index ba9203e..0000000 --- a/lib/utils/parseMap.js +++ /dev/null @@ -1,23 +0,0 @@ -var Immutable = require('immutable'); - -/** - * Parse a map - * Exemple: .git/HEAD or .git/refs/remotes/origin/HEAD - * - * @param {String} - * @return {Map} - */ -function parseMap(str) { - var o = {}; - - str.split('/n').forEach(function(line) { - var parts = line.split(':'); - if (parts.length == 1) return; - - o[parts[0]] = parts.slice(1).join(':').trim(); - }); - - return new Immutable.Map(o); -} - -module.exports = parseMap; diff --git a/lib/utils/scan.js b/lib/utils/scan.js deleted file mode 100644 index 35d48e6..0000000 --- a/lib/utils/scan.js +++ /dev/null @@ -1,28 +0,0 @@ -var Buffer = require('buffer').Buffer; - -/** - * Utility for dissolve to scan for a buffer - * - * See https://github.com/deoxxa/dissolve/issues/21 - */ -function scan(parser, name, search) { - search = new Buffer(search, 'utf8'); - var result = new Buffer(''); - - return parser.loop(function(end) { - this.buffer('tmpSearch', 1) - .tap(function() { - var c = this.vars.tmpSearch; - result = Buffer.concat([result, c]); - - var toSearch = result.slice(-search.length); - if (toSearch.compare(search) === 0) { - delete this.vars.tmpSearch; - this.vars[name] = result.slice(0, -search.length); - end(); - } - }); - }); -} - -module.exports = scan; diff --git a/lib/utils/sha1.js b/lib/utils/sha1.js deleted file mode 100644 index 1087b9c..0000000 --- a/lib/utils/sha1.js +++ /dev/null @@ -1,26 +0,0 @@ -var crypto = require('crypto'); - -/** - * Validates a SHA in hexadecimal - * @param {String}, - * @return {Boolean} - */ -function validateSha(str) { - return (/[0-9a-f]{40}/).test(str); -} - -/** - * Encode content as sha - * @param {String|Buffer} s - * @return {String} - */ -function encode(s) { - var shasum = crypto.createHash('sha1'); - shasum.update(s); - return shasum.digest('hex'); -} - -module.exports = { - is: validateSha, - encode: encode -}; diff --git a/lib/utils/zlib.js b/lib/utils/zlib.js deleted file mode 100644 index 28ecb0b..0000000 --- a/lib/utils/zlib.js +++ /dev/null @@ -1,32 +0,0 @@ -var pako = require('pako'); -var uint8ToBuffer = require('typedarray-to-buffer'); -var bufferToUint8 = require('buffer-to-uint8array'); - -/** - * Decompress a gzip buffer - * @param {Buffer} - * @return {Buffer} - */ -function unzip(buf) { - var input = bufferToUint8(buf); - var output = pako.inflate(input); - - return uint8ToBuffer(output); -} - -/** - * Compress a buffer - * @param {Buffer} - * @return {Buffer} - */ -function zip(buf) { - var input = bufferToUint8(buf); - var output = pako.deflate(input); - - return uint8ToBuffer(output); -} - -module.exports = { - zip: zip, - unzip: unzip -}; \ No newline at end of file diff --git a/package.json b/package.json index 6f7a7a2..3238b6d 100644 --- a/package.json +++ b/package.json @@ -10,40 +10,49 @@ "url": "https://github.com/SamyPesse/gitkit.js.git" }, "dependencies": { - "axios": "^0.9.1", - "bash-color": "0.0.3", - "binary": "^0.3.0", - "bind-all": "^1.0.0", "buffer-to-uint8array": "^1.1.0", - "commander": "2.9.0", + "commander": "^2.9.0", "concentrate": "^0.2.3", + "debug": "^2.6.8", "dissolve": "deoxxa/dissolve#4fd1556d0199a27dd0a6f9f22cb6648194301454", - "http-browserify": "^1.7.0", - "ignore": "^3.1.1", - "immutable": "^3.7.6", - "is": "^3.1.0", - "memory-fs": "^0.3.0", - "mkdir": "0.0.2", - "pad": "^1.0.0", - "pako": "^1.0.1", - "q": "^1.4.1", + "event-stream": "^3.3.4", + "immutable": "^3.8.1", + "ini": "^1.3.4", + "memory-fs": "^0.4.1", + "mkdirp": "^0.5.1", + "network-byte-order": "^0.2.0", + "pad": "^1.1.0", + "pako": "^1.0.5", "stream-combiner2": "^1.1.1", - "through2": "^2.0.1", - "through2-filter": "^2.0.0", + "through2": "^2.0.3", "typedarray-to-buffer": "^3.1.2", - "varint": "^4.0.0" + "varint": "^5.0.0" }, "devDependencies": { - "eslint": "^2.13.0", - "expect": "^1.19.0", - "into-stream": "^2.0.0", - "mocha": "2.3.4" + "babel-cli": "^6.24.1", + "babel-eslint": "^7.2.3", + "babel-preset-es2015": "^6.24.1", + "babel-preset-flow": "^6.23.0", + "babel-preset-stage-0": "^6.24.1", + "eslint": "^4.0.0", + "eslint-config-airbnb-base": "^11.2.0", + "eslint-config-prettier": "^2.2.0", + "eslint-plugin-flowtype": "^2.34.0", + "eslint-plugin-import": "^2.3.0", + "eslint-plugin-prettier": "^2.1.2", + "flow-bin": "^0.48.0", + "jest": "^20.0.4", + "prettier": "^1.4.4" }, "scripts": { - "test": "./node_modules/.bin/mocha ./testing/setup.js ./lib/**/*/__tests__/*.js --bail --reporter=list", - "lint": "eslint ." + "flow": "flow", + "test": "jest ./src/", + "build": "babel src/ -d lib/", + "prepublish": "yarn run build", + "lint": "eslint ./src", + "fix": "eslint --fix ./src" }, "bin": { - "gitkit": "./bin/gitkit.js" + "gitkit": "./lib/cli/gitkit.js" } } diff --git a/src/GitKit.js b/src/GitKit.js new file mode 100644 index 0000000..482e6ef --- /dev/null +++ b/src/GitKit.js @@ -0,0 +1,67 @@ +/* @flow */ + +import Debug from 'debug'; +import Transforms from './transforms'; + +import type { Repository, GitObject, Ref, Blob, Tree, Commit } from './models'; +import type { SHA } from './types/SHA'; + +const debug = Debug('gitkit:transform'); + +class GitKit { + repo: Repository; + + constructor(repo: Repository) { + this.repo = repo; + } + + readObject: (sha: SHA) => Promise; + readTree: (sha: SHA) => Promise; + readCommit: (sha: SHA) => Promise; + readBlob: (sha: SHA) => Promise; + readRecursiveTree: (sha: SHA) => Promise; + addObjects: (objects: GitObject[]) => Promise; + addObject: (object: GitObject) => Promise; + addBlob: (blob: Blob) => Promise; + addTree: (tree: Tree) => Promise; + addCommit: (commit: Commit) => Promise; + + walkCommits: ( + sha: SHA, + iter: (commit: Commit, sha: SHA) => ?boolean + ) => Promise<*>; + + walkTree: ( + sha: SHA, + iter: (entry: TreeEntry, filepath: string) => * + ) => Promise<*>; + + writeFile: (filename: string, content: Buffer | string) => Promise<*>; + mkdir: (filename: string) => Promise<*>; + unlinkFile: (filename: string) => Promise<*>; + + // Refs + readHEAD: () => Promise<*>; + indexRefs: () => Promise<*>; + updateRef: (name: string, ref: Ref) => GitKit; + + // Working index + readWorkingIndex: () => Promise<*>; + addFile: (filename: string) => Promise<*>; + + readConfig: () => Promise<*>; + flushConfig: () => Promise<*>; + addRemote: (name: string, url: string) => Promise<*>; +} + +/* + * Bind all transforms. + */ +Object.keys(Transforms).forEach(type => { + GitKit.prototype[type] = function transform(...args) { + debug(type, { args }); + return Transforms[type](this, ...args); + }; +}); + +export default GitKit; diff --git a/src/__tests__/module.js b/src/__tests__/module.js new file mode 100644 index 0000000..05921ff --- /dev/null +++ b/src/__tests__/module.js @@ -0,0 +1,5 @@ +/* eslint-disable global-require */ + +test('it should be importable', () => { + require('../'); +}); diff --git a/src/cli/add.js b/src/cli/add.js new file mode 100644 index 0000000..c250973 --- /dev/null +++ b/src/cli/add.js @@ -0,0 +1,15 @@ +/** @flow */ +/* eslint-disable no-console */ + +import type GitKit from '../'; + +function add(gitkit: GitKit, [filename]: string[]): Promise<*> { + return gitkit.readWorkingIndex().then(() => gitkit.addFile(filename)); +} + +export default { + name: 'add [file]', + description: 'Add file contents to the index', + exec: add, + options: [] +}; diff --git a/src/cli/branch.js b/src/cli/branch.js new file mode 100644 index 0000000..2e07eab --- /dev/null +++ b/src/cli/branch.js @@ -0,0 +1,29 @@ +/** @flow */ +/* eslint-disable no-console */ + +import type GitKit from '../'; + +/* + * Log the list of branches. + */ +function logBranches(gitkit: GitKit): Promise<*> { + return gitkit.readHEAD().then(() => gitkit.indexRefs()).then(() => { + const { head } = gitkit.repo; + const { branches } = gitkit.repo.refs; + + branches.forEach((ref, branchName) => { + console.log( + `${head.isPointingToBranch(branchName) + ? '*' + : ' '} ${branchName}` + ); + }); + }); +} + +export default { + name: 'branch', + description: 'List, create, or delete branches', + exec: logBranches, + options: [] +}; diff --git a/src/cli/cat-file.js b/src/cli/cat-file.js new file mode 100644 index 0000000..70287cd --- /dev/null +++ b/src/cli/cat-file.js @@ -0,0 +1,91 @@ +/** @flow */ +/* eslint-disable no-console */ + +import { Blob, Commit, Tree } from '../'; +import type GitKit from '../'; + +type Kwargs = { + pretty: boolean, + size: boolean, + type: boolean +}; + +function prettyBlob(blob: Blob) { + console.log(blob.content.toString('utf8')); +} + +function prettyCommit(commit: Commit) { + console.log(`tree ${commit.tree}`); + commit.parents.forEach(parent => console.log(`parent ${parent}`)); + console.log(`author ${commit.author}`); + console.log(`committer ${commit.committer}`); + console.log(`\n${commit.message}`); +} + +function prettyTree(tree: Tree) { + tree.entries.forEach((entry, filepath) => + console.log(`${entry.mode}\t${entry.type}\t${entry.sha}\t${filepath}`) + ); +} + +/* + * Print an object from the database. + */ +function catFile( + gitkit: GitKit, + [sha]: string[], + { pretty, type, size }: Kwargs +): Promise<*> { + if (!sha) { + throw new Error('Missing argument "sha"'); + } + + return gitkit + .indexObjects() + .then(() => gitkit.readObject(sha)) + .then(object => { + if (type) { + console.log(object.type); + } else if (size) { + console.log(object.length); + } else if (pretty) { + if (object.isBlob) { + prettyBlob(Blob.createFromObject(object)); + } else if (object.isCommit) { + prettyCommit(Commit.createFromObject(object)); + } else if (object.isTree) { + prettyTree(Tree.createFromObject(object)); + } + } + }); +} + +export default { + name: 'cat-file [sha]', + description: + 'Provide content or type and size information for repository objects', + exec: catFile, + options: [ + { + type: 'boolean', + name: 'pretty', + shortcut: 'p', + description: "pretty-print object's content", + default: true + }, + { + type: 'boolean', + name: 'type', + shortcut: 't', + description: 'show object type', + default: false + }, + { + type: 'boolean', + name: 'size', + shortcut: 's', + description: 'show object size', + default: false + } + ] +}; diff --git a/src/cli/fetch.js b/src/cli/fetch.js new file mode 100644 index 0000000..5241b41 --- /dev/null +++ b/src/cli/fetch.js @@ -0,0 +1,16 @@ +/** @flow */ +/* eslint-disable no-console */ + +import type GitKit from '../'; + +/* + * Download objects and refs from another repository + */ +function fetch(gitkit: GitKit): Promise<*> {} + +export default { + name: 'fetch [repository] [refspec]', + description: 'Download objects and refs from another repository', + exec: fetch, + options: [] +}; diff --git a/src/cli/gitkit.js b/src/cli/gitkit.js new file mode 100755 index 0000000..b984127 --- /dev/null +++ b/src/cli/gitkit.js @@ -0,0 +1,94 @@ +#!/usr/bin/env node +/* eslint-disable no-console */ + +import program from 'commander'; + +import GitKit, { Repository } from '../'; +import NativeFS from '../fs/NativeFS'; + +import pkg from '../../package.json'; + +// Commands +import add from './add'; +import lsTree from './ls-tree'; +import lsFiles from './ls-files'; +import logCommits from './log'; +import branch from './branch'; +import tag from './tag'; +import showRef from './show-ref'; +import catFile from './cat-file'; +import fetch from './fetch'; +import remote from './remote'; +import init from './init'; +import status from './status'; + +program.version(pkg.version).option('--debug', 'Enable error debugging'); + +[ + init, + status, + add, + catFile, + branch, + tag, + logCommits, + lsTree, + lsFiles, + showRef, + fetch, + remote +].forEach(({ name, description, exec, options = [] }) => { + let command = program.command(name).description(description); + + options.forEach(opt => { + command = command.option( + `${opt.shortcut + ? `-${opt.shortcut}, ` + : ''}--${opt.name}${opt.type != 'boolean' + ? ` [${opt.name}]` + : ''}`, + opt.description + ); + }); + + command.action((...fnargs) => { + Promise.resolve() + .then(() => { + const repo = new Repository({ + fs: new NativeFS(process.cwd()) + }); + const gitkit = new GitKit(repo); + + const args = fnargs.slice(0, -1); + const kwargs = options.reduce((out, opt) => { + const value = command[opt.name]; + const isDefined = typeof value !== 'undefined'; + + if (isDefined && opt.isRequired) { + throw new Error(`Parameter "${opt.name}" is required`); + } + + return { + ...out, + [opt.name]: isDefined ? value : opt.default + }; + }, {}); + + return exec(gitkit, args, kwargs); + }) + .catch(err => { + console.error( + process.env.NODE_ENV == 'development' || process.env.DEBUG + ? err.stack + : err.message + ); + process.exit(1); + }); + }); +}); + +program.parse(process.argv); + +if (program.args.length == 0) { + program.help(); +} diff --git a/src/cli/init.js b/src/cli/init.js new file mode 100644 index 0000000..986cbd3 --- /dev/null +++ b/src/cli/init.js @@ -0,0 +1,46 @@ +/** @flow */ +/* eslint-disable no-console */ + +import path from 'path'; +import GitKit, { Repository } from '../'; +import NativeFS from '../fs/NativeFS'; + +type Kwargs = { + bare: boolean +}; + +function init( + _gitkit: GitKit, + [relativeDir = '']: string[], + { bare }: Kwargs +): Promise<*> { + const directory = path.resolve(process.cwd(), relativeDir); + const repo = new Repository({ + fs: new NativeFS(directory), + isBare: bare + }); + const gitkit = new GitKit(repo); + + return gitkit.init().then(() => { + console.log( + `Initialized empty Git repository in ${bare + ? directory + : path.join(directory, '.git')}` + ); + }); +} + +export default { + name: 'init [directory]', + description: + 'Create an empty Git repository or reinitialize an existing one', + exec: init, + options: [ + { + type: 'boolean', + name: 'bare', + describe: 'Create a bare repository.', + default: false + } + ] +}; diff --git a/src/cli/log.js b/src/cli/log.js new file mode 100644 index 0000000..76f0a98 --- /dev/null +++ b/src/cli/log.js @@ -0,0 +1,65 @@ +/** @flow */ +/* eslint-disable no-console */ + +import type GitKit, { Commit } from '../'; + +type Kwargs = { + max: number +}; + +/* + * Print a commit. + */ +function printCommit(commit: Commit, sha: string) { + console.log(`commit ${sha}`); + console.log(`Author: ${commit.author.name} <${commit.author.email}>`); + + if (commit.author.email != commit.committer.email) { + console.log( + `Committer: ${commit.author.name} <${commit.author.email}>` + ); + } + + console.log(`\n\t${commit.message}\n`); +} + +/* + * Log the commits history + */ +function logCommits( + gitkit: GitKit, + args: string[], + { max }: Kwargs +): Promise<*> { + let count = 0; + + return gitkit + .indexObjects() + .then(() => gitkit.readHEAD()) + .then(() => gitkit.indexRefs()) + .then(() => { + const { headCommit } = gitkit.repo; + + return gitkit.walkCommits(headCommit, (commit, commitSHA) => { + printCommit(commit, commitSHA); + + count += 1; + return count < max; + }); + }); +} + +export default { + name: 'log', + description: 'Show commit logs', + exec: logCommits, + options: [ + { + type: 'number', + name: 'max', + shortcut: 'm', + describe: 'Max number of commits to log (default is 100).', + default: 100 + } + ] +}; diff --git a/src/cli/ls-files.js b/src/cli/ls-files.js new file mode 100644 index 0000000..8f0a8ce --- /dev/null +++ b/src/cli/ls-files.js @@ -0,0 +1,25 @@ +/** @flow */ +/* eslint-disable no-console */ + +import type GitKit from '../'; + +/* + * Log the list of refs in the repository + */ +function lsFiles(gitkit: GitKit): Promise<*> { + return gitkit.readWorkingIndex().then(() => { + const { workingIndex } = gitkit.repo; + + workingIndex.entries.forEach(file => { + console.log(`${file.path}`); + }); + }); +} + +export default { + name: 'ls-files', + description: + 'Show information about files in the index and the working tree', + exec: lsFiles, + options: [] +}; diff --git a/src/cli/ls-tree.js b/src/cli/ls-tree.js new file mode 100644 index 0000000..124202f --- /dev/null +++ b/src/cli/ls-tree.js @@ -0,0 +1,48 @@ +/** @flow */ +/* eslint-disable no-console */ + +import type GitKit, { TreeEntry } from '../'; + +type Kwargs = { + recursive: boolean +}; + +/* + * Print a tree entry. + */ +function printEntry(entry: TreeEntry, filepath: string) { + console.log(`${entry.mode}\t${entry.type}\t${entry.sha}\t${filepath}`); +} + +/* + * List all files in the repository. + */ +function lsTree( + gitkit: GitKit, + [sha]: string[], + { recursive }: Kwargs +): Promise<*> { + return gitkit.indexObjects().then(() => { + if (recursive) { + return gitkit.walkTree(sha, printEntry); + } + return gitkit.readTree(sha).then(tree => { + tree.entries.forEach(printEntry); + }); + }); +} + +export default { + name: 'ls-tree [sha]', + description: 'List the contents of a tree object', + exec: lsTree, + options: [ + { + type: 'boolean', + name: 'recursive', + shortcut: 'r', + description: 'Recurse into sub-trees.', + default: false + } + ] +}; diff --git a/src/cli/remote.js b/src/cli/remote.js new file mode 100644 index 0000000..1d81cf4 --- /dev/null +++ b/src/cli/remote.js @@ -0,0 +1,24 @@ +/** @flow */ +/* eslint-disable no-console */ + +import type GitKit from '../'; + +/* + * Log the list of remotes. + */ +function listRemotes(gitkit: GitKit): Promise<*> { + return gitkit.readConfig().then(() => { + const { config } = gitkit.repo; + + config.remotes.forEach((remote, name) => { + console.log(name); + }); + }); +} + +export default { + name: 'remote', + description: 'Manage set of tracked repositories', + exec: listRemotes, + options: [] +}; diff --git a/src/cli/show-ref.js b/src/cli/show-ref.js new file mode 100644 index 0000000..88d4c3e --- /dev/null +++ b/src/cli/show-ref.js @@ -0,0 +1,24 @@ +/** @flow */ +/* eslint-disable no-console */ + +import type GitKit from '../'; + +/* + * Log the list of refs in the repository + */ +function showRef(gitkit: GitKit): Promise<*> { + return gitkit.indexRefs().then(() => { + const { refs } = gitkit.repo; + + refs.refs.forEach((ref, refName) => { + console.log(`${ref.commit || ref.ref} ${refName}`); + }); + }); +} + +export default { + name: 'show-ref', + description: 'List references in a local repository', + exec: showRef, + options: [] +}; diff --git a/src/cli/status.js b/src/cli/status.js new file mode 100644 index 0000000..8885061 --- /dev/null +++ b/src/cli/status.js @@ -0,0 +1,50 @@ +/** @flow */ +/* eslint-disable no-console */ + +import type GitKit from '../'; + +function status(gitkit: GitKit): Promise<*> { + return gitkit + .readWorkingIndex() + .then(() => gitkit.readHEAD()) + .then(() => gitkit.indexRefs()) + .then(() => gitkit.indexObjects()) + .then(() => gitkit.readCommit(gitkit.repo.headCommit)) + .then(commit => gitkit.readRecursiveTree(commit.tree)) + .then(tree => { + const { workingIndex } = gitkit.repo; + + const indexedFiles = workingIndex.entries.keySeq().toSet(); + const commitedFiles = tree.entries.keySeq().toSet(); + + const allFiles = indexedFiles.union(commitedFiles); + + const deleted = []; + const added = []; + const modified = []; + + allFiles.forEach(file => { + const indexedFile = workingIndex.entries.get(file); + const commitedFile = tree.entries.get(file); + + if (!commitedFile) { + added.push(file); + } else if (!indexedFile) { + deleted.push(file); + } else if (commitedFile.sha != indexedFile.sha) { + modified.push(file); + } + }); + + console.log('deleted', deleted); + console.log('added', added); + console.log('modified', modified); + }); +} + +export default { + name: 'status', + description: 'Show the working tree status', + exec: status, + options: [] +}; diff --git a/src/cli/tag.js b/src/cli/tag.js new file mode 100644 index 0000000..1e165d1 --- /dev/null +++ b/src/cli/tag.js @@ -0,0 +1,24 @@ +/** @flow */ +/* eslint-disable no-console */ + +import type GitKit from '../'; + +/* + * Log the list of branches. + */ +function logTags(gitkit: GitKit): Promise<*> { + return gitkit.indexRefs().then(() => { + const { refs } = gitkit.repo; + + refs.tags.forEach((ref, tagName) => { + console.log(`${tagName}`); + }); + }); +} + +export default { + name: 'tag', + description: 'Create, list, delete tags', + exec: logTags, + options: [] +}; diff --git a/src/fs/GenericFS.js b/src/fs/GenericFS.js new file mode 100644 index 0000000..7285aea --- /dev/null +++ b/src/fs/GenericFS.js @@ -0,0 +1,119 @@ +/** @flow */ + +import path from 'path'; +import { OrderedMap } from 'immutable'; +import type { List } from 'immutable'; + +export type FileType = 'file' | 'dir'; + +export type FileStat = { + path: string, + size: number, + mode: string, + type: FileType, + ctime: Date, + mtime: Date, + dev: number, + ino: number, + uid: number, + gid: number +}; + +class GenericFS { + /* + * List files in a folder. + */ + readDir(dirpath: string): Promise> { + return Promise.reject(new Error('Not implemented')); + } + + /* + * Get infos about a file. + */ + stat(filepath: string): Promise { + return Promise.reject(new Error('Not implemented')); + } + + /* + * Read the entire file. + */ + read(filepath: string): Promise { + return Promise.reject(new Error('Not implemented')); + } + + /* + * Read specific position of a file. + * The basic implementation is to read the entire buffer and slice it. + * Custom FS can implement an optimized version. + */ + readAt(filepath: string, offset: number, length: number): Promise { + return this.read(filepath).then(buffer => + buffer.slice(offset, offset + length) + ); + } + + /* + * Write a file. + */ + write(filepath: string, content: Buffer): Promise<*> { + return Promise.reject(new Error('Not implemented')); + } + + /* + * Create a directory. + */ + mkdir(dirpath: string): Promise<*> { + return Promise.reject(new Error('Not implemented')); + } + + /* + * Delete a file. + */ + unlink(filepath: string): Promise<*> { + return Promise.reject(new Error('Not implemented')); + } + + /* + * Check if a file exists + */ + exists(file: string): Promise { + return this.stat(file).then(() => true, err => Promise.resolve(false)); + } + + /* + * List all files in a tree. + */ + readTree( + dirpath: string = '', + { + prefix = dirpath + }: { + prefix?: string + } = {} + ): Promise> { + return this.readDir(dirpath).then(files => + files.reduce( + (prev, file) => + prev.then((accu: OrderedMap) => { + const filepath = path.join(dirpath, file); + + return this.stat(filepath).then(stat => { + if (stat.type == 'dir') { + return this.readTree(filepath, { + prefix + }).then(out => accu.merge(out)); + } + + return accu.set( + path.relative(prefix, filepath), + stat + ); + }); + }), + Promise.resolve(new OrderedMap()) + ) + ); + } +} + +export default GenericFS; diff --git a/src/fs/MemoryFS.js b/src/fs/MemoryFS.js new file mode 100644 index 0000000..ae3ffef --- /dev/null +++ b/src/fs/MemoryFS.js @@ -0,0 +1,16 @@ +/** @flow */ + +import MemoryFileSystem from 'memory-fs'; +import NodeFS from './NodeFS'; + +/* + * Filesystem stored in memory. + */ + +class MemoryFS extends NodeFS { + constructor() { + super(new MemoryFileSystem(), '/'); + } +} + +export default MemoryFS; diff --git a/src/fs/NativeFS.js b/src/fs/NativeFS.js new file mode 100644 index 0000000..5bddeca --- /dev/null +++ b/src/fs/NativeFS.js @@ -0,0 +1,16 @@ +/** @flow */ + +import fs from 'fs'; +import NodeFS from './NodeFS'; + +/* + * FS when running GitKit in node.js. + */ + +class NativeFS extends NodeFS { + constructor(root: string) { + super(fs, root); + } +} + +export default NativeFS; diff --git a/src/fs/NodeFS.js b/src/fs/NodeFS.js new file mode 100644 index 0000000..13ca921 --- /dev/null +++ b/src/fs/NodeFS.js @@ -0,0 +1,144 @@ +/** @flow */ + +import path from 'path'; +import mkdirp from 'mkdirp'; +import { List } from 'immutable'; +import GenericFS from './GenericFS'; + +import type { FileStat } from './GenericFS'; + +/* + * Interface for a Node compatible API. + */ + +class NodeFS extends GenericFS { + fs: *; + root: string; + + constructor(fs: *, root: string) { + super(); + this.fs = fs; + this.root = root; + } + + resolve(file: string): string { + return path.resolve(this.root, file); + } + + /* + * List files in a folder. + */ + readDir(file: string): Promise> { + return new Promise((resolve, reject) => { + this.fs.readdir(this.resolve(file), (err, files) => { + if (err) { + reject(err); + } else { + resolve(List(files)); + } + }); + }); + } + + /* + * Get infos about a file. + */ + stat(file: string): Promise { + return new Promise((resolve, reject) => { + this.fs.stat(this.resolve(file), (err, stat) => { + if (err) { + reject(err); + } else { + resolve({ + path: file, + size: stat.size, + mode: String(stat.mode), + type: stat.isDirectory() ? 'dir' : 'file', + ctime: stat.ctime, + mtime: stat.mtime, + dev: stat.dev, + ino: stat.ino, + uid: stat.uid, + gid: stat.gid + }); + } + }); + }); + } + + /* + * Read a file. + */ + read(file: string): Promise { + return new Promise((resolve, reject) => { + this.fs.readFile(this.resolve(file), (err, content) => { + if (err) { + reject(err); + } else { + resolve(content); + } + }); + }); + } + + /* + * Write a file. + */ + write(file: string, content: Buffer): Promise<*> { + const filepath = this.resolve(file); + const dirpath = path.dirname(file); + + return this.mkdir(dirpath).then( + () => + new Promise((resolve, reject) => { + this.fs.writeFile(filepath, content, err => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }) + ); + } + + /* + * Delete a file. + */ + unlink(file: string): Promise<*> { + return new Promise((resolve, reject) => { + this.fs.unlink(this.resolve(file), err => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + } + + /* + * Create a directory. + */ + mkdir(dir: string): Promise<*> { + const dirpath = this.resolve(dir); + + return new Promise((resolve, reject) => { + mkdirp( + dirpath, + { + fs: this.fs + }, + err => { + if (err) { + reject(err); + } else { + resolve(); + } + } + ); + }); + } +} + +export default NodeFS; diff --git a/src/fs/__tests__/MemoryFS.js b/src/fs/__tests__/MemoryFS.js new file mode 100644 index 0000000..7287b44 --- /dev/null +++ b/src/fs/__tests__/MemoryFS.js @@ -0,0 +1,42 @@ +import MemoryFS from '../MemoryFS'; + +let fs; + +beforeAll(() => { + fs = new MemoryFS(); +}); + +describe('.write', () => { + test('it should create the file if does not exist', () => + fs + .write('README.md', new Buffer('Hello world', 'utf8')) + .then(() => + expect( + fs.read('README.md').then(buf => buf.toString('utf8')) + ).resolves.toBe('Hello world') + )); + + test('it should create a file in a directory', () => + fs + .write('folder/README.md', new Buffer('Hello world 2', 'utf8')) + .then(() => + expect( + fs + .read('folder/README.md') + .then(buf => buf.toString('utf8')) + ).resolves.toBe('Hello world 2') + )); +}); + +describe('.read', () => { + test('it should throw for a non existing file', () => + expect(fs.read('README_NOTEXIST.md')).rejects.toBeDefined()); +}); + +describe('.readAt', () => { + test('it should return the right buffer', () => { + expect( + fs.readAt('README.md', 6, 5).then(buf => buf.toString('utf8')) + ).resolves.toBe('world'); + }); +}); diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..6f77699 --- /dev/null +++ b/src/index.js @@ -0,0 +1,52 @@ +/** @flow */ + +import GitKit from './GitKit'; + +import { + Author, + Blob, + BranchConfig, + Commit, + Config, + FetchDiscovery, + GitObject, + Head, + IndexEntry, + ObjectsIndex, + PackFile, + PackFileIndex, + PackIndexOffset, + Person, + Ref, + RefsIndex, + RemoteConfig, + Repository, + Tree, + TreeEntry, + WorkingIndex +} from './models'; + +export default GitKit; +export { + Author, + Blob, + BranchConfig, + Commit, + Config, + FetchDiscovery, + GitObject, + Head, + IndexEntry, + ObjectsIndex, + PackFile, + PackFileIndex, + PackIndexOffset, + Person, + Ref, + RefsIndex, + RemoteConfig, + Repository, + Tree, + TreeEntry, + WorkingIndex +}; diff --git a/src/models/Author.js b/src/models/Author.js new file mode 100644 index 0000000..428445a --- /dev/null +++ b/src/models/Author.js @@ -0,0 +1,59 @@ +/** @flow */ + +import pad from 'pad'; +import { Record } from 'immutable'; + +import type Person from './Person'; + +const AUTHOR_RE = /(.*) <([^>]+)> (\d+) ([+-]{1}\d{4})/; + +const DEFAULTS: { + name: string, + email: string, + timezone: string, + timestamp: number +} = { + name: '', + email: '', + timezone: '', + timestamp: 0 +}; + +class Author extends Record(DEFAULTS) { + static createFromString(str): ?Author { + const match = AUTHOR_RE.exec(str); + if (!match) { + return null; + } + + return new Author({ + name: match[1].replace(/(^\s+|\s+$)/, ''), + email: match[2], + timestamp: parseInt(match[3], 10), + timezone: match[4] + }); + } + + static createFromPerson(person: Person): Author { + const date = new Date(); + const offset = date.getTimezoneOffset(); + const timezone = + (offset < 0 ? '+' : '-') + + pad(`${parseInt(Math.abs(offset / 60), 10)}`, 2) + + pad(`${Math.abs(offset % 60)}`, 2); + + return new Author({ + name: person.name, + email: person.email, + timezone, + timestamp: Number(date.getTime() / 1000) + }); + } + + toString(): string { + return `${this.name} <${this.email}> ${this.timestamp} ${this + .timezone}`; + } +} + +export default Author; diff --git a/src/models/Blob.js b/src/models/Blob.js new file mode 100644 index 0000000..d7611c6 --- /dev/null +++ b/src/models/Blob.js @@ -0,0 +1,48 @@ +/** @flow */ + +import { Record } from 'immutable'; +import GitObject from './GitObject'; + +import type { GitObjectSerializable } from './GitObject'; + +const DEFAULTS: { + content: Buffer +} = { + content: new Buffer('') +}; + +class Blob extends Record(DEFAULTS) implements GitObjectSerializable { + toGitObject(): GitObject { + return new GitObject({ + type: 'blob', + content: this.content + }); + } + + /* + * Create a blob from a git object. + */ + static createFromObject(o: GitObject): Blob { + return new Blob({ + content: o.content + }); + } + + /* + * Create a blob from a buffer. + */ + static createFromBuffer(content: Buffer): Blob { + return new Blob({ + content + }); + } + + /* + * Create a blob from a string. + */ + static createFromString(content: string): Blob { + return Blob.createFromBuffer(new Buffer(content, 'utf8')); + } +} + +export default Blob; diff --git a/src/models/BranchConfig.js b/src/models/BranchConfig.js new file mode 100644 index 0000000..dc3da7b --- /dev/null +++ b/src/models/BranchConfig.js @@ -0,0 +1,19 @@ +/** @flow */ + +import { Record } from 'immutable'; + +/* + * A branch in the config. + */ + +const DEFAULTS: { + remote: string, + merge: string +} = { + remote: '', + merge: '' +}; + +class BranchConfig extends Record(DEFAULTS) {} + +export default BranchConfig; diff --git a/src/models/Commit.js b/src/models/Commit.js new file mode 100644 index 0000000..e65c9d6 --- /dev/null +++ b/src/models/Commit.js @@ -0,0 +1,92 @@ +/** @flow */ + +import { Record, List } from 'immutable'; +import Author from './Author'; +import GitObject from './GitObject'; + +import type { GitObjectSerializable } from './GitObject'; +import type { SHA } from '../types/SHA'; + +const DEFAULTS: { + author: Author, + committer: Author, + tree: SHA, + parents: List, + message: string +} = { + author: new Author(), + committer: new Author(), + tree: '', + parents: new List(), + message: '' +}; + +class Commit extends Record(DEFAULTS) implements GitObjectSerializable { + toGitObject(): GitObject { + return new GitObject({ + type: 'commit', + content: this.toBuffer() + }); + } + + toBuffer(): Buffer { + return new Buffer(this.toString(), 'utf8'); + } + + toString(): string { + return `tree ${this.tree} +${this.parents.map(parent => `parent ${parent}`).join('\n')} +author ${this.author.toString()} +committer ${this.committer.toString()} + +${this.message}`; + } + + /* + * Parse a commit from string content of the git object. + */ + static createFromString(content: string): Commit { + const lines = content.split('\n'); + + const separator = lines.findIndex(line => !line.trim()); + const metaLines = lines.slice(0, separator); + + const meta = metaLines.reduce((accu, line) => { + const match = line.match(/(\S+)\s(.*)/); + + if (!match) { + return accu; + } + + const key = match[1]; + const value = match[2]; + + return { + ...accu, + [key]: (accu[key] || []).concat([value]) + }; + }, {}); + + // Extract message + const messageLines = lines.slice(separator + 1, -1); + const message = messageLines.join('\n'); + + return new Commit({ + message, + tree: meta.tree[0], + parents: List(meta.parent), + author: Author.createFromString(meta.author), + committer: Author.createFromString(meta.committer) + }); + } + + static createFromBuffer(content: Buffer): Commit { + return Commit.createFromString(content.toString('utf8')); + } + + static createFromObject(o: GitObject): Commit { + return Commit.createFromBuffer(o.content); + } +} + +export default Commit; diff --git a/src/models/Config.js b/src/models/Config.js new file mode 100644 index 0000000..ab90598 --- /dev/null +++ b/src/models/Config.js @@ -0,0 +1,115 @@ +/** @flow */ + +import ini from 'ini'; +import { Record, Map, OrderedMap } from 'immutable'; +import RemoteConfig from './RemoteConfig'; +import BranchConfig from './BranchConfig'; +import type Repository from './Repository'; + +/* + * Model to represent the parsing of the .git/config file. + */ + +const DEFAULTS: { + core: Map, + remotes: OrderedMap, + branches: OrderedMap +} = { + core: new Map(), + remotes: new OrderedMap(), + branches: new OrderedMap() +}; + +class Config extends Record(DEFAULTS) { + /* + * Return a string version of the config. + */ + toString() { + const { core, remotes, branches } = this; + + const raw = { + core: core.toJS() + }; + + remotes.forEach((remote, name) => { + raw[`remote "${name}"`] = remote.toJS(); + }); + + branches.forEach((branch, name) => { + raw[`branch "${name}"`] = branch.toJS(); + }); + + return ini.stringify(raw); + } + + /* + * Write the config to the disk. + */ + writeToRepo(repo: Repository): Promise<*> { + const { fs } = repo; + const configPath = repo.resolveGitFile('config'); + + return fs.write(configPath, new Buffer(this.toString(), 'utf8')); + } + + /* + * Add a new remote. + */ + addRemote(name: string, url: string): Config { + const { remotes } = this; + const remote = new RemoteConfig({ + url, + fetch: `+refs/heads/*:refs/remotes/${name}/*` + }); + + return this.merge({ + remotes: remotes.set(name, remote) + }); + } + + /* + * Parse the git config from a string. + */ + static createFromString(content: string): Config { + const raw = ini.parse(content); + let branches = new OrderedMap(); + let remotes = new OrderedMap(); + + Object.keys(raw).forEach(key => { + const match = /(\S+) "(.*)"/.exec(key); + if (!match) { + return; + } + + const prop = match[1]; + const name = match[2]; + const value = raw[key]; + + if (prop == 'branch') { + branches = branches.set(name, new BranchConfig(value)); + } else if (prop == 'remote') { + remotes = remotes.set(name, new RemoteConfig(value)); + } + }); + + return new Config({ + core: new Map(raw.core || {}), + branches, + remotes + }); + } + + /* + * Read the config from the repository. + */ + static readFromRepository(repo: Repository): Promise { + const { fs } = repo; + const configPath = repo.resolveGitFile('config'); + + return fs + .read(configPath) + .then(buffer => Config.createFromString(buffer.toString('utf8'))); + } +} + +export default Config; diff --git a/src/models/FetchDiscovery.js b/src/models/FetchDiscovery.js new file mode 100644 index 0000000..1ffc4bd --- /dev/null +++ b/src/models/FetchDiscovery.js @@ -0,0 +1,66 @@ +/** @flow */ + +import { Record, OrderedMap, List } from 'immutable'; +import Ref from './Ref'; + +import type { Transport } from '../transports'; +import { createDiscoveryParser } from '../transfer'; + +/* + * Model to represent the discovery between the server + * and the client during a fetch/clone. + * + * Parses the response to /info/refs?service=git-upload-pack, which contains ids for + * refs/heads and a capability listing for this git HTTP server. + */ + +const DEFAULTS: { + refs: OrderedMap, + capabilities: List +} = { + refs: new OrderedMap(), + capabilities: List() +}; + +class FetchDiscovery extends Record(DEFAULTS) { + /* + * Fetch discovery from the server using a transport. + */ + static fetch(transport: Transport): Promise { + return transport.getWithUploadPack('info/refs').then( + res => + new Promise((resolve, reject) => { + let capabilities; + let refs = new OrderedMap(); + + res + .pipe( + createDiscoveryParser({ + onCapabilities: caps => { + capabilities = capabilities || caps; + } + }) + ) + .on( + 'data', + ({ name, ref }: { name: string, ref: Ref }) => { + refs = refs.set(name, ref); + } + ) + .on('error', err => { + reject(err); + }) + .on('end', () => { + resolve( + new FetchDiscovery({ + refs, + capabilities: List(capabilities) + }) + ); + }); + }) + ); + } +} + +export default FetchDiscovery; diff --git a/src/models/GitObject.js b/src/models/GitObject.js new file mode 100644 index 0000000..0284a18 --- /dev/null +++ b/src/models/GitObject.js @@ -0,0 +1,111 @@ +/** @flow */ + +import path from 'path'; +import { Record } from 'immutable'; + +import sha1 from '../utils/sha1'; +import zlib from '../utils/zlib'; + +import type { SHA } from '../types/SHA'; + +export type GitObjectType = 'blob' | 'tree' | 'commit'; + +const DEFAULTS: { + type: GitObjectType, + content: Buffer +} = { + type: 'blob', + content: new Buffer('') +}; + +class GitObject extends Record(DEFAULTS) { + get sha(): SHA { + return sha1.encode(this.getAsBuffer()); + } + + get length(): number { + return this.content.length; + } + + get path(): string { + return GitObject.getPath(this.sha); + } + + get isTree(): boolean { + return this.type == 'tree'; + } + + get isBlob(): boolean { + return this.type == 'blob'; + } + + get isCommit(): boolean { + return this.type == 'commit'; + } + + /* + * Get entire buffer to represent this object. + */ + getAsBuffer(): Buffer { + const { type, content } = this; + + const nullBuf = new Buffer(1); + nullBuf.fill(0); + + return Buffer.concat([ + new Buffer(`${type} ${content.length}`, 'utf8'), + nullBuf, + content + ]); + } + + /* + * Get entire buffer that should be writen on the disk. + */ + getAsZippedBuffer(): Buffer { + return zlib.zip(this.getAsBuffer()); + } + + /* + * Get path in a git repository for an object. + */ + static getPath(sha: string): string { + return path.join('objects', sha.slice(0, 2), sha.slice(2)); + } + + /* + * Create a git object from a buffer (dezipped). + */ + static createFromBuffer(buffer: Buffer): GitObject { + const nullChar = buffer.indexOf(0); + + // Parse object header + const header = buffer.slice(0, nullChar).toString(); + const type = header.split(' ')[0]; + + // Extract content + const content = buffer.slice(nullChar + 1); + + return new GitObject({ + type, + content + }); + } + + /* + * The Git object is zipped on the disk. + */ + static createFromZip(buffer: Buffer): GitObject { + return GitObject.createFromBuffer(zlib.unzip(buffer)); + } +} + +/* + * An interface for class which are serializable as Git objects. + */ +export interface GitObjectSerializable { + static createFromObject(o: GitObject): T, + toGitObject(): GitObject +} + +export default GitObject; diff --git a/src/models/Head.js b/src/models/Head.js new file mode 100644 index 0000000..1e5b5f2 --- /dev/null +++ b/src/models/Head.js @@ -0,0 +1,29 @@ +/** @flow */ + +import Ref from './Ref'; +import type Repository from './Repository'; + +class Head extends Ref { + /* + * A detached head is when the HEAD is pointing to a commit + * instead of a ref. + * + * https://git-scm.com/docs/git-checkout#_detached_head + */ + get isDetached(): boolean { + return !!this.commit; + } + + /* + * Read the HEAD from the repository. + * The HEAD reference is not stored in the packed-refs + */ + static readFromRepository(repo: Repository): Promise { + const { fs } = repo; + const indexpath = repo.resolveGitFile('HEAD'); + + return fs.read(indexpath).then(buffer => Head.createFromBuffer(buffer)); + } +} + +export default Head; diff --git a/src/models/IndexEntry.js b/src/models/IndexEntry.js new file mode 100644 index 0000000..f3ad6f0 --- /dev/null +++ b/src/models/IndexEntry.js @@ -0,0 +1,162 @@ +/** @flow */ + +import { Record } from 'immutable'; +import Concentrate from 'concentrate'; +import Dissolve from 'dissolve'; + +import type { FileStat } from '../fs/GenericFS'; +import { htonl } from '../utils/buffer'; + +/* + * Model to represent an entry in the git index. + * + * https://github.com/git/git/blob/master/Documentation/technical/index-format.txt + */ + +const DEFAULTS: { + ctime: Date, + mtime: Date, + dev: number, + ino: number, + mode: number, + uid: number, + gid: number, + fileSize: number, + flags: number, + extendedFlags: number, + sha: string, + path: string +} = { + ctime: new Date(), + mtime: new Date(), + dev: 0, + ino: 0, + mode: 0, + uid: 0, + gid: 0, + fileSize: 0, + flags: 0, + extendedFlags: 0, + sha: '', + path: '' +}; + +class IndexEntry extends Record(DEFAULTS) { + /* + * Convert this index entry to a buffer that can be written to + * the working index. + */ + toBuffer(version: number = 3): Buffer { + let output = Concentrate() + // ctime + .uint32be(this.ctime.getTime() / 1000) + .uint32be(0) + // mtime + .uint32be(this.mtime.getTime() / 1000) + .uint32be(0) + .buffer(htonl(this.dev)) + .buffer(htonl(this.ino)) + .uint32be(this.mode) + .buffer(htonl(this.uid)) + .buffer(htonl(this.gid)) + .buffer(htonl(this.fileSize)) + .string(new Buffer(this.sha).toString('hex')) + .uint16be(this.flags); + + if (version >= 3) { + output = output.uint16be(this.extendedFlags); + } + + output = output.string(this.path); + + const buf = output.result(); + const padlen = 8 - buf.length % 8 || 8; + + return Buffer.concat([buf, Buffer.alloc(padlen, 0)]); + } + + /* + * Create a parser to parse an index entry. + * It emits the event "entry" and set the vars; entry. + */ + static createParser( + version: number, + parser: Dissolve = Dissolve() + ): Dissolve { + const baseOffset = parser.offset; + + parser + .uint32be('ctime') + .uint32be('ctime_nano') + .uint32be('mtime') + .uint32be('mtime_nano') + .uint32be('dev') + .uint32be('ino') + .uint32be('mode') + .uint32be('uid') + .uint32be('gid') + .uint32be('fileSize') + .buffer('sha', 20) + .uint8('flags') + .uint8('pathLength') + .tap(() => { + if (version < 3) { + return; + } + + parser.uint16be('extendedFlags'); + }) + // Read path + .tap(() => { + parser.string('path', parser.vars.pathLength); + }) + .tap(() => { + const { offset } = parser; + const relativeOffset = offset - baseOffset; + const padlen = 8 - relativeOffset % 8 || 8; + parser.skip(padlen); + }) + .tap(() => { + const entry = new IndexEntry({ + ctime: new Date(parser.vars.ctime * 1000), + mtime: new Date(parser.vars.mtime * 1000), + sha: parser.vars.sha.toString('hex'), + path: parser.vars.path, + dev: parser.vars.dev, + ino: parser.vars.ino, + fileSize: parser.vars.fileSize, + uid: parser.vars.uid, + gid: parser.vars.gid, + flags: parser.vars.flags, + extendedFlags: parser.vars.extendedFlags || 0 + }); + + parser.vars.entry = entry; + parser.emit('entry', entry); + }); + + return parser; + } + + /* + * Create an index entry from the stat result of a file. + */ + static createFromFileStat(stat: FileStat, sha: string): IndexEntry { + return new IndexEntry({ + sha, + mode: stat.mode, + fileSize: stat.size, + path: stat.path, + ctime: stat.ctime, + mtime: stat.mtime, + dev: stat.dev, + ino: stat.ino, + uid: stat.uid, + gid: stat.gid, + flags: 0, + extendedFlags: 0 + }); + } +} + +export default IndexEntry; diff --git a/src/models/ObjectsIndex.js b/src/models/ObjectsIndex.js new file mode 100644 index 0000000..a63c192 --- /dev/null +++ b/src/models/ObjectsIndex.js @@ -0,0 +1,222 @@ +/** @flow */ + +import path from 'path'; +import Debug from 'debug'; +import { Record, Map } from 'immutable'; +import GitObject from './GitObject'; +import Tree from './Tree'; +import Blob from './Blob'; +import Commit from './Commit'; +import PackFileIndex from './PackFileIndex'; +import PackFile from './PackFile'; + +import type { GitObjectType, GitObjectSerializable } from './GitObject'; +import type Repository from './Repository'; +import type { SHA } from '../types/SHA'; + +/* + * Utility model to lookup an object in the repository. + * + * Git objects can be stored: + * - In a packfile, we need to index all packfiles to lookup the object + * - In a .git/objects/... file + */ + +const debug = Debug('gitkit:objects'); + +const TYPES: { [GitObjectType]: GitObjectSerializable } = { + blob: Blob, + commit: Commit, + tree: Tree +}; + +const DEFAULTS: { + // Map from packfile path to its index + packfiles: Map, + // Objects cache from packfile/files/new + objects: Map +} = { + packfiles: Map(), + objects: Map() +}; + +class ObjectsIndex extends Record(DEFAULTS) { + /* + * Get an object. + */ + getObject(sha: SHA): GitObject { + const { objects } = this; + const object = objects.get(sha); + + if (!object) { + throw new Error(`object "${sha}" not found`); + } + + return object; + } + + getObjectOfType(sha: SHA, type: GitObjectType): Blob | Commit | Tree { + const object = this.getObject(sha); + + if (object.type != type) { + throw new Error(`"${sha}" is not a ${type} but a ${object.type}`); + } + + return TYPES[type].createFromObject(object); + } + + getBlob(sha: SHA): Blob { + return this.getObjectOfType(sha, 'blob'); + } + + getTree(sha: SHA): Tree { + return this.getObjectOfType(sha, 'tree'); + } + + getCommit(sha: SHA): Commit { + return this.getObjectOfType(sha, 'commit'); + } + + /* + * Check if we have already read an object. + */ + hasObject(sha: SHA): boolean { + const { objects } = this; + return objects.has(sha); + } + + /* + * Add a git object to the cache. + */ + addObject(object: GitObject): ?GitObject { + const { objects } = this; + const { sha } = object; + + debug(`index object ${sha}`); + return this.merge({ + objects: objects.set(sha, object) + }); + } + + /* + * Get packfile containing a git object. + */ + getPackFor(sha: SHA): ?string { + const { packfiles } = this; + return packfiles.findKey(index => index.hasObject(sha)); + } + + /* + * Return true if object is in a packfile. + */ + isPacked(sha: SHA): boolean { + return !!this.getPackFor(sha); + } + + /* + * Read a git object from a repository, and stores it in the index. + */ + readObject(repo: Repository, sha: SHA): Promise { + if (this.hasObject(sha)) { + return Promise.resolve(this); + } + + return this.isPacked(sha) + ? this.readObjectFromPack(repo, sha) + : this.readObjectFromFiles(repo, sha); + } + + /* + * Read a git object from the files in .git/objects + */ + readObjectFromFiles(repo: Repository, sha: SHA): Promise { + const { fs } = repo; + + debug(`read object ${sha} from file`); + return fs + .read(repo.resolveGitFile(GitObject.getPath(sha))) + .then(buffer => GitObject.createFromZip(buffer)) + .then(object => this.addObject(object)); + } + + /* + * Read a git object from a packfile. + */ + readObjectFromPack(repo: Repository, sha: SHA): Promise { + const { fs } = repo; + const packFilename = this.getPackFor(sha); + + if (!packFilename) { + return Promise.reject(new Error(`No packfile contains ${sha}`)); + } + + // TODO: avoid reading the whole packfile each time. + debug(`read object ${sha} from packfile ${packFilename}`); + return fs + .read(packFilename) + .then(buffer => PackFile.createFromBuffer(buffer)) + .then(packfile => packfile.getObject(sha)) + .then(object => { + if (!object) { + throw new Error('Error while reading object from packfile'); + } + + return this.addObject(object); + }); + } + + /* + * Write an object to the disk. + */ + writeObjectToRepository(repo: Repository, object: GitObject): Promise<*> { + const { fs } = repo; + const buffer = object.getAsZippedBuffer(); + const objectPath = repo.resolveGitFile(object.path); + + debug(`write object ${objectPath} to disk`); + return fs.write(objectPath, buffer); + } + + /* + * Index packfiles from repository. + * It list all packfile index, read them, and index them. + */ + static indexFromRepository(repo: Repository): Promise { + const { fs } = repo; + + return fs + .readTree(repo.resolveGitFile('objects/pack/')) + .then(files => + files.reduce((prev, file) => { + const filepath = file.path; + + if (path.extname(filepath) !== '.idx') { + return prev; + } + + const baseName = path.basename(filepath, '.idx'); + const pack = files.find( + packfile => + path.extname(packfile.path) == '.pack' && + path.basename(packfile.path, '.pack') == baseName + ); + + if (!pack) { + return prev; + } + + return prev.then(packfiles => + fs + .read(filepath) + .then(buffer => + PackFileIndex.createFromBuffer(buffer) + ) + .then(index => packfiles.set(pack.path, index)) + ); + }, Promise.resolve(Map())) + ) + .then(packfiles => new ObjectsIndex({ packfiles })); + } +} + +export default ObjectsIndex; diff --git a/src/models/PackFile.js b/src/models/PackFile.js new file mode 100644 index 0000000..70b12e7 --- /dev/null +++ b/src/models/PackFile.js @@ -0,0 +1,404 @@ +/** @flow */ + +import { Record, Map } from 'immutable'; +import Dissolve from 'dissolve'; +import { Inflate } from 'pako'; +import varint from 'varint'; +import uint8ToBuffer from 'typedarray-to-buffer'; +import GitObject from './GitObject'; + +import type { SHA } from '../types/SHA'; + +/* + * Model to represent the content of a packfile. + * + * https://github.com/git/git/blob/master/Documentation/technical/pack-format.txt + */ + +const ENTRY_TYPES = { + COMMIT: 1, + TREE: 2, + BLOB: 3, + TAG: 4, + OFS_DELTA: 6, + REF_DELTA: 7 +}; + +const TYPES_FOROBJECT = { + [ENTRY_TYPES.COMMIT]: 'commit', + [ENTRY_TYPES.TREE]: 'tree', + [ENTRY_TYPES.BLOB]: 'blob', + [ENTRY_TYPES.TAG]: 'tag' +}; + +const DEFAULTS: { + version: string, + objects: Map +} = { + version: '', + objects: new Map() +}; + +class PackFile extends Record(DEFAULTS) { + /* + * Get an object. + */ + getObject(sha: SHA): ?GitObject { + const { objects } = this; + return objects.get(sha); + } + + /* + * Read an entire pack file from a buffer. + */ + static createFromBuffer(buffer: Buffer): ?PackFile { + const parser = PackFile.createStreamReader(); + let result; + + parser.on('pack', pack => { + result = pack; + }); + + parser.write(buffer); + + return result; + } + + /* + * Read a packfile from a stream of data. + * + * It emits an event "object" for each GitObject found. + * It emits an event "pack" with the complete PackFile at the end. + */ + static createStreamReader(): WritableStream { + const parser = Dissolve({ + objectMode: true + }); + + return parsePack(parser).tap(() => { + const pack = new PackFile({ + version: parser.vars.version, + objects: new Map(parser.vars.objects) + }); + parser.emit('pack', pack); + }); + } +} + +/* + * Parse header of an object in the pack. + * It sets the vars: size and type. + */ +function parseObjectHeader(parser: Dissolve): Dissolve { + return parser.uint8be('byte').tap(() => { + const byte = parser.vars.byte; + let left = 4; + + parser.vars.type = (byte >> 4) & 7; + parser.vars.size = byte & 0xf; + + parser + .loop(end => { + if (!(parser.vars.byte & 0x80)) { + end(); + return; + } + + parser.uint8be('byte').tap(() => { + parser.vars.size |= (parser.vars.byte & 0x7f) << left; + left += 7; + }); + }) + .tap(() => { + parser.vars.size >>>= 0; + }); + }); +} + +/* + * Parse inflate content for blob, commit and tree. + * Also index the content for this specific index. + * + * It sets the vars: content. + */ +function parseInflateContent(parser: Dissolve): Dissolve { + const inflator = new Inflate(); + + // Iterate while we found end of zip content + return parser.loop(end => { + parser.buffer('byte', 1).tap(() => { + const byte = parser.vars.byte; + + const ab = new Uint8Array(1); + ab.fill(byte[0]); + + inflator.push(ab); + + if (inflator.ended) { + if (inflator.err) { + parser.emit('error', new Error(inflator.msg)); + } + + parser.vars.content = uint8ToBuffer(inflator.result); + + end(); + } + }); + }); +} + +/* + * Parse a REF delta. + */ +function parseRefDelta(parser: Dissolve): Dissolve { + return parser.string('ref', 20).then(() => { + throw new Error('Not yet implemented'); + }); +} + +/* + * Parse OFS delta's header. + */ +function parseOFSDeltaHeader(parser: Dissolve): Dissolve { + return parser.uint8be('byte').tap(() => { + const byte = parser.vars.byte; + + parser.vars.rv = byte & 0x7f; + + parser.loop(end => { + if (!(parser.vars.byte & 0x80)) { + end(); + return; + } + + parser.uint8be('byte').tap(() => { + parser.vars.rv += 1; + parser.vars.rv <<= 7; + parser.vars.rv |= parser.vars.byte & 0x7f; + }); + }); + }); +} + +/* + * Parse OFS delta, apply delta and return with vars "type" and "content". + */ +function parseOFSDelta(parser: Dissolve): Dissolve { + return ( + parseOFSDeltaHeader(parser) + // Extract delta content + .tap(() => { + parseInflateContent(parser); + }) + // Apply delta to content + .tap(() => { + const { objectOffset, rv } = parser.vars; + const offset = objectOffset - rv; + + // Get referenced type/content + const origin = parser.packObjects[offset]; + + if (origin) { + // Apply delta on content + const delta = parser.vars.content; + const output = applyDelta(origin.content, delta); + + // Export as variables + parser.vars.content = output; + parser.vars.type = origin.type; + } else { + throw new Error( + `Content for OFS_DELTA is not indexed: ${offset} - ${JSON.stringify( + Object.keys(parser.packObjects) + )} ` + ); + } + }) + ); +} + +/* + * Parse an object entry. + * + * It sets the vars: object. + */ +function parseObject(parser: Dissolve): Dissolve { + return ( + parser + .tap(() => { + parser.vars = { + objectOffset: parser.offset + }; + + parseObjectHeader(parser); + }) + .tap(() => { + if (parser.vars.type < 5) { + parseInflateContent(parser); + } else if (parser.vars.type === ENTRY_TYPES.OFS_DELTA) { + parseOFSDelta(parser); + } else if (parser.vars.type === ENTRY_TYPES.REF_DELTA) { + parseRefDelta(parser); + } else { + parser.emit( + 'error', + new Error('Invalid entry type in pack') + ); + } + }) + // Index content for this offset + // TODO: index content by sha for REF_DELTA + .tap(() => { + const { content, type, objectOffset } = parser.vars; + + if (!type || !content) { + return; + } + + // Index the object for this offset so that it can be accessed + // for delta. + parser.packObjects = parser.packObjects || {}; + parser.packObjects[objectOffset] = { type, content }; + + if (!TYPES_FOROBJECT[type]) { + throw new Error(`Unknow type: ${type}`); + } + + const object = new GitObject({ + type: TYPES_FOROBJECT[type], + content + }); + + parser.vars.object = object; + }) + ); +} + +/* + * Parse an entire packfile. + * + * it sets the vars "version" and "objects" + */ +function parsePack(parser: Dissolve): Dissolve { + return parser + .string('signature', 4) + .uint32be('version') + .uint32be('count') + .tap(() => { + const { signature, version, count } = parser.vars; + + // Check header + if (signature !== 'PACK') { + parser.emit('error', new Error('Invalid pack signature')); + return; + } + + let objectIndex = 0; + const objects: { [string]: GitObject } = {}; + + parser + .loop(stopLoop => { + parseObject(parser).tap(() => { + const { object } = parser.vars; + objects[object.sha] = object; + + parser.emit('object', object); + + objectIndex += 1; + if (objectIndex == count) { + stopLoop(); + } + }); + }) + .tap(() => { + parser.vars = { + version, + objects + }; + }); + }); +} + +/* + * Apply delta from packfile to a buffer. + * This code is mostly based on https://github.com/chrisdickinson/git-apply-delta + */ +function applyDelta(base: Buffer, deltaContent: Buffer): Buffer { + let command; + let idx = 0; + let len = 0; + let outIdx = 0; + let delta = deltaContent; + + const OFFSET_BUFFER = new Buffer(4); + const LENGTH_BUFFER = new Buffer(4); + + const baseSize = varint.decode(delta); + delta = delta.slice(varint.decode.bytes); + + if (baseSize !== base.length) { + throw new Error( + `Base doesn't match expected size: ${baseSize} != ${base.length}` + ); + } + + const outputSize = varint.decode(delta); + delta = delta.slice(varint.decode.bytes); + + const output = new Buffer(outputSize); + + // Apply deltas + len = delta.length; + + while (idx < len) { + command = delta[idx]; + idx += 1; + + if (command & 0x80) { + copy(); + } else { + insert(); + } + } + + function copy() { + OFFSET_BUFFER.writeUInt32LE(0, 0); + LENGTH_BUFFER.writeUInt32LE(0, 0); + + let check = 1; + let x; + + for (x = 0; x < 4; x += 1) { + if (command & check) { + OFFSET_BUFFER[3 - x] = delta[idx]; + idx += 1; + } + check <<= 1; + } + + for (x = 0; x < 3; x += 1) { + if (command & check) { + LENGTH_BUFFER[3 - x] = delta[idx]; + idx += 1; + } + check <<= 1; + } + LENGTH_BUFFER[0] = 0; + + const length = LENGTH_BUFFER.readUInt32BE(0) || 0x10000; + const offset = OFFSET_BUFFER.readUInt32BE(0); + + base.copy(output, outIdx, offset, offset + length); + outIdx += length; + } + + function insert() { + delta.copy(output, outIdx, idx, command + idx); + idx += command; + outIdx += command; + } + + return output; +} + +export default PackFile; diff --git a/src/models/PackFileIndex.js b/src/models/PackFileIndex.js new file mode 100644 index 0000000..508ec9f --- /dev/null +++ b/src/models/PackFileIndex.js @@ -0,0 +1,193 @@ +/** @flow */ + +import { Record, OrderedMap } from 'immutable'; +import Dissolve from 'dissolve'; + +import PackIndexOffset from './PackIndexOffset'; +import type { SHA } from '../types/SHA'; + +/* + * Model to represent the index of a packfile. + * It can be used to easily lookup an object in a packfile. + * And locate objects accross multiple packfiles (see ObjectsIndex). + * + * https://github.com/git/git/blob/master/Documentation/technical/pack-format.txt + */ + +const V2_MAGIC = 0xff744f63; + +const DEFAULTS: { + version: number, + objects: OrderedMap +} = { + version: 2, + objects: new OrderedMap() +}; + +class PackFileIndex extends Record(DEFAULTS) { + /* + * Check if an object is in this packfile. + */ + hasObject(sha: SHA): boolean { + const { objects } = this; + return objects.has(sha); + } + + /* + * Read an entire pack file index from a buffer. + */ + static createFromBuffer(buffer: Buffer): ?PackFileIndex { + const parser = PackFileIndex.createStreamReader(); + let result; + + parser.on('index', index => { + result = index; + }); + + parser.write(buffer); + + return result; + } + + /* + * Read a packfile index from a stream of data. + * + * It emits an event "object" for each GitObject found. + * It emits an event "pack" with the complete PackFile at the end. + */ + static createStreamReader(): WritableStream { + const parser = Dissolve({ + objectMode: true + }); + + return parsePackIndex(parser).tap(() => { + const { shas, crcs, offsets, version } = parser.vars; + + const index = new PackFileIndex({ + version, + objects: new OrderedMap( + shas.map((sha, i) => [ + sha, + new PackIndexOffset({ + crc: crcs[i], + offset: offsets[i] + }) + ]) + ) + }); + parser.emit('index', index); + }); + } +} + +/* + * Parse an entire packfile index. + * + * it sets the vars "version" and "objects" + */ +function parsePackIndex(parser: Dissolve): Dissolve { + return parser.uint32be('magic').tap(() => { + if (parser.vars.magic !== V2_MAGIC) { + parser.emit('error', new Error("Can't parse version 1 packfile")); + return; + } + + parser.uint32be('version'); + parseFanoutTable(parser); + parseObjectSHAs(parser); + parseObjectCRC32(parser); + parseObjectOffsets(parser); + }); +} + +/* + * Parse the fanout table. + */ +function parseFanoutTable(parser: Dissolve): Dissolve { + return parser.buffer('fanout', 1020).uint32be('expected'); +} + +/* + * Parse the object shas. + * It create a variable "shas": Array. + */ +function parseObjectSHAs(parser: Dissolve): Dissolve { + return parser.tap(() => { + const { expected } = parser.vars; + let i = 0; + + parser.vars.shas = []; + + parser.loop(end => { + if (i == expected) { + end(); + return; + } + + parser.buffer('sha', 20).tap(() => { + const sha = parser.vars.sha.toString('hex'); + parser.vars.shas.push(sha); + + i += 1; + }); + }); + }); +} + +/* + * Parse the object CRC32s. + * It create a variable "crcs": Array. + */ +function parseObjectCRC32(parser: Dissolve): Dissolve { + return parser.tap(() => { + const { expected } = parser.vars; + let i = 0; + + parser.vars.crcs = []; + + parser.loop(end => { + if (i == expected) { + end(); + return; + } + + parser.int32('crc').tap(() => { + const { crc } = parser.vars; + parser.vars.crcs.push(crc); + + i += 1; + }); + }); + }); +} + +/* + * Parse the object offsets. + * It create a variable "offsets": Array. + * + * TODO: support large offset. + */ +function parseObjectOffsets(parser: Dissolve): Dissolve { + return parser.tap(() => { + const { expected } = parser.vars; + let i = 0; + + parser.vars.offsets = []; + + parser.loop(end => { + if (i == expected) { + end(); + return; + } + + parser.uint32be('offset').tap(() => { + const { offset } = parser.vars; + parser.vars.offsets.push(offset); + + i += 1; + }); + }); + }); +} + +export default PackFileIndex; diff --git a/src/models/PackIndexOffset.js b/src/models/PackIndexOffset.js new file mode 100644 index 0000000..a11396a --- /dev/null +++ b/src/models/PackIndexOffset.js @@ -0,0 +1,19 @@ +/** @flow */ + +import { Record } from 'immutable'; + +/* + * An index for an object found while parsing a PackFileIndex. + */ + +const DEFAULTS: { + offset: number, + crc: string +} = { + offset: 0, + crc: '' +}; + +class PackIndexOffset extends Record(DEFAULTS) {} + +export default PackIndexOffset; diff --git a/src/models/Person.js b/src/models/Person.js new file mode 100644 index 0000000..e57afbb --- /dev/null +++ b/src/models/Person.js @@ -0,0 +1,15 @@ +/** @flow */ + +import { Record } from 'immutable'; + +const DEFAULTS: { + name: string, + email: string +} = { + name: '', + email: '' +}; + +class Person extends Record(DEFAULTS) {} + +export default Person; diff --git a/src/models/Ref.js b/src/models/Ref.js new file mode 100644 index 0000000..c8f9fcd --- /dev/null +++ b/src/models/Ref.js @@ -0,0 +1,69 @@ +/** @flow */ + +import { Record } from 'immutable'; + +import type { SHA } from '../types/SHA'; + +const DEFAULTS: { + ref: ?string, + commit: ?SHA +} = { + ref: null, + commit: null +}; + +class Ref extends Record(DEFAULTS) { + /* + * Check if this ref points to a branch name "branch". + */ + isPointingToBranch(branch: string): boolean { + return this.ref == `refs/heads/${branch}`; + } + + toString(): string { + if (this.commit) { + return `${this.commit}\n`; + } + return `ref: ${this.ref}\n`; + } + + toBuffer(): Buffer { + return new Buffer(this.toString(), 'utf8'); + } + + /* + * Write the ref as a file. + */ + writeToRepo(repo: Repository, filename: string): Promise<*> { + const { fs } = repo; + const content = this.toBuffer(); + const filepath = repo.resolveGitFile(filename); + + return fs.write(filepath, content); + } + + /* + * Create a Ref instance from the content of a ref file. + */ + static createFromBuffer(buffer: Buffer): Ref { + return this.createFromString(buffer.toString('utf8')); + } + + static createFromString(content: string): Ref { + const trimmed = content.trim(); + + // Are we matching a ref ? + const match = trimmed.match(/ref:\s+(\S+)/); + if (match) { + return new Ref({ + ref: match[1] + }); + } + + return new Ref({ + commit: trimmed + }); + } +} + +export default Ref; diff --git a/src/models/RefsIndex.js b/src/models/RefsIndex.js new file mode 100644 index 0000000..d2fd285 --- /dev/null +++ b/src/models/RefsIndex.js @@ -0,0 +1,161 @@ +/** @flow */ + +import { Record, OrderedMap } from 'immutable'; +import Ref from './Ref'; + +import type Repository from './Repository'; + +const PACKED_FILE = 'packed-refs'; + +/* + * A listing of refs that can be created from: + * 1. Read the .git/refs folder + * 2. Read the .git/packed-refs + */ + +const DEFAULTS: { + refs: OrderedMap +} = { + refs: new OrderedMap() +}; + +class RefsIndex extends Record(DEFAULTS) { + get branches(): OrderedMap { + return this.prefixWith('refs/heads/'); + } + get tags(): OrderedMap { + return this.prefixWith('refs/tags/'); + } + + /* + * Get a ref by its name. + */ + getRef(name: string): ?Ref { + const { refs } = this; + return refs.get(name); + } + + /* + * Update a reference. + */ + setRef(name: string, ref: Ref): RefsIndex { + const { refs } = this; + + return this.merge({ + refs: refs.set(name, ref) + }); + } + + /* + * Filter refs by a prefix, it removes the prefix from the key. + */ + prefixWith(prefix: string): OrderedMap { + const { refs } = this; + + return refs + .filter((ref, refName) => refName.indexOf(prefix) === 0) + .mapKeys(refName => refName.slice(prefix.length)); + } + + /* + * Read the index of refs from the repository. + * It list both refs in the .git/refs directory, and decode the refs in + * the .git/packed-refs + */ + static indexFromRepository(repo: Repository): Promise { + return RefsIndex.hasPackedRefs(repo) + .then(hasPackedRefs => + Promise.all([ + hasPackedRefs + ? RefsIndex.readPackedFromRepository(repo) + : new RefsIndex(), + RefsIndex.readRefsFromRepository(repo) + ]) + ) + .then( + ([packed, files]) => + new RefsIndex({ + refs: packed.refs.merge(files.refs) + }) + ); + } + + /* + * Read the refs file from the repository. + */ + static readRefsFromRepository(repo: Repository): Promise { + const { fs } = repo; + const refspath = repo.resolveGitFile('refs'); + + return fs + .readTree(refspath, { + prefix: repo.resolveGitFile('./') + }) + .then(files => + files.reduce( + (prev, stat, filepath) => + prev.then(accu => + fs + .read(repo.resolveGitFile(filepath)) + .then(buf => + accu.set( + filepath, + Ref.createFromBuffer(buf) + ) + ) + ), + Promise.resolve(new OrderedMap()) + ) + ) + .then(refs => new RefsIndex({ refs })); + } + + /* + * Read the packed index from the repository. + */ + static readPackedFromRepository(repo: Repository): Promise { + const { fs } = repo; + const filepath = repo.resolveGitFile(PACKED_FILE); + + return fs + .read(filepath) + .then(buf => RefsIndex.createFromPack(buf.toString('utf8'))); + } + + /* + * Check if the repository has a packed-refs file. + */ + static hasPackedRefs(repo: Repository): Promise { + const { fs } = repo; + const filepath = repo.resolveGitFile(PACKED_FILE); + + return fs.exists(filepath); + } + + /* + * Create the refs index from the content of a packed-refs file. + */ + static createFromPack(content: string): Ref { + const regex = /^([0-9a-f]{40})\s+(\S+)$/gm; + let matches; + let refs = new OrderedMap(); + + // eslint-disable-next-line no-cond-assign + while ((matches = regex.exec(content)) !== null) { + if (matches.index === regex.lastIndex) { + regex.lastIndex += 1; + } + + const commit = matches[1]; + const refName = matches[2]; + + refs = refs.set(refName, new Ref({ commit })); + } + + return new RefsIndex({ + refs + }); + } +} + +export default RefsIndex; diff --git a/src/models/RemoteConfig.js b/src/models/RemoteConfig.js new file mode 100644 index 0000000..5b08ac3 --- /dev/null +++ b/src/models/RemoteConfig.js @@ -0,0 +1,19 @@ +/** @flow */ + +import { Record } from 'immutable'; + +/* + * A remote in the config. + */ + +const DEFAULTS: { + url: string, + fetch: string +} = { + url: '', + fetch: '' +}; + +class RemoteConfig extends Record(DEFAULTS) {} + +export default RemoteConfig; diff --git a/src/models/Repository.js b/src/models/Repository.js new file mode 100644 index 0000000..fa1b052 --- /dev/null +++ b/src/models/Repository.js @@ -0,0 +1,124 @@ +/** @flow */ + +import path from 'path'; +import { Record } from 'immutable'; +import GenericFS from '../fs/GenericFS'; + +import Config from './Config'; +import WorkingIndex from './WorkingIndex'; +import ObjectsIndex from './ObjectsIndex'; +import RefsIndex from './RefsIndex'; +import Head from './Head'; +import type { SHA } from '../types/SHA'; + +const DEFAULTS: { + isBare: boolean, + fs: GenericFS, + // The .git/config + config: Config, + // Head reference + head: Head, + // Index of working files (only for non-bare repos) + workingIndex: WorkingIndex, + // Index to read/edit git objects + objects: ObjectsIndex, + // Index to read/edit references + refs: RefsIndex +} = { + isBare: false, + fs: new GenericFS(), + config: new Config(), + head: new Head(), + workingIndex: new WorkingIndex(), + objects: new ObjectsIndex(), + refs: new RefsIndex() +}; + +class Repository extends Record(DEFAULTS) { + /* + * Return sha of head commit. + */ + get headCommit(): SHA { + const { head, refs } = this; + + if (!head.commit && !head.ref) { + throw new Error('Invalid HEAD'); + } + + if (head.isDetached) { + return head.commit; + } + + const ref = refs.getRef(head.ref); + + if (!ref) { + throw new Error(`Ref from HEAD "${head.ref}" not found`); + } + + return ref.commit; + } + + /* + * Resolve a file from the .git folder. + */ + resolveGitFile(file: string): string { + const { isBare } = this; + return isBare ? file : path.join('.git', file); + } + + /* + * Read a Git object by its sha. + */ + readObject(sha: SHA): Promise { + return this.objects.readObject(this, sha).then(objects => + this.merge({ + objects + }) + ); + } + + /* + * Read the HEAD. + */ + readHEAD(): Promise { + return Head.readFromRepository(this).then(head => this.merge({ head })); + } + + /* + * Read the config. + */ + readConfig(): Promise { + return Config.readFromRepository(this).then(config => + this.merge({ config }) + ); + } + + /* + * Read the working index. + */ + readWorkingIndex(): Promise { + return WorkingIndex.readFromRepository(this).then(workingIndex => + this.merge({ workingIndex }) + ); + } + + /* + * Index all refs. + */ + indexRefs(): Promise { + return RefsIndex.indexFromRepository(this).then(refs => + this.merge({ refs }) + ); + } + + /* + * Index all objects + */ + indexObjects(): Promise { + return ObjectsIndex.indexFromRepository(this).then(objects => + this.merge({ objects }) + ); + } +} + +export default Repository; diff --git a/src/models/Tree.js b/src/models/Tree.js new file mode 100644 index 0000000..a78e024 --- /dev/null +++ b/src/models/Tree.js @@ -0,0 +1,60 @@ +/** @flow */ + +import { Record, OrderedMap } from 'immutable'; +import Dissolve from 'dissolve'; +import GitObject from './GitObject'; +import TreeEntry from './TreeEntry'; +import { scan } from '../utils/buffer'; + +import type { GitObjectSerializable } from './GitObject'; + +const DEFAULTS: { + entries: OrderedMap +} = { + entries: new OrderedMap() +}; + +class Tree extends Record(DEFAULTS) implements GitObjectSerializable { + /* + * Parse a tree listing from a buffer. + */ + static createFromBuffer(buffer: Buffer): Tree { + let entries = new OrderedMap(); + + const parser = Dissolve(); + + parser.loop(() => { + scan(parser, 'mode', ' '); + scan(parser, 'path', new Buffer([0])); + parser.buffer('sha', 20); + + parser.tap(() => { + const entryPath = parser.vars.path.toString('utf8'); + + entries = entries.set( + entryPath, + new TreeEntry({ + path: entryPath, + mode: parseInt(parser.vars.mode.toString('utf8'), 10), + sha: parser.vars.sha.toString('hex') + }) + ); + }); + }); + + parser.write(buffer); + + return new Tree({ + entries + }); + } + + /* + * Create a blob from a git object. + */ + static createFromObject(o: GitObject): Tree { + return Tree.createFromBuffer(o.content); + } +} + +export default Tree; diff --git a/src/models/TreeEntry.js b/src/models/TreeEntry.js new file mode 100644 index 0000000..63a6923 --- /dev/null +++ b/src/models/TreeEntry.js @@ -0,0 +1,36 @@ +/** @flow */ + +import { Record } from 'immutable'; + +import type { SHA } from '../types/SHA'; + +type TreeEntryType = 'blob' | 'tree'; + +const DEFAULTS: { + path: string, + mode: number, + sha: SHA +} = { + path: '', + mode: 0, + sha: '' +}; + +class TreeEntry extends Record(DEFAULTS) { + get type(): TreeEntryType { + if (this.mode == 40000) { + return 'tree'; + } + return 'blob'; + } + + get isTree(): boolean { + return this.type == 'tree'; + } + + get isBlob(): boolean { + return this.type == 'blob'; + } +} + +export default TreeEntry; diff --git a/src/models/WorkingIndex.js b/src/models/WorkingIndex.js new file mode 100644 index 0000000..f27b723 --- /dev/null +++ b/src/models/WorkingIndex.js @@ -0,0 +1,159 @@ +/** @flow */ + +import Debug from 'debug'; +import { Record, OrderedMap } from 'immutable'; +import Dissolve from 'dissolve'; +import Concentrate from 'concentrate'; +import IndexEntry from './IndexEntry'; +import sha1 from '../utils/sha1'; +import type Repository from './Repository'; + +/* + * Model to represent the index of the git repository + * + * https://github.com/git/git/blob/master/Documentation/technical/index-format.txt + */ + +const SIGNATURE = 'DIRC'; +const debug = Debug('gitkit:workingIndex'); + +const DEFAULTS: { + version: number, + entries: OrderedMap +} = { + version: 2, + entries: new OrderedMap() +}; + +class WorkingIndex extends Record(DEFAULTS) { + /* + * Add or update an entry. + */ + addEntry(entry: IndexEntry): WorkingIndex { + const { entries } = this; + + return this.merge({ + entries: entries.set(entry.path, entry) + }); + } + + /* + * Convert this index to a buffer that can be written. + */ + toBuffer(): Buffer { + const { version, entries } = this; + + const output = Concentrate() + .string(SIGNATURE) + .uint32be(version) + .uint32be(entries.size); + + entries.forEach(entry => { + output.buffer(entry.toBuffer(version)); + }); + + const inner = output.result(); + const innerSHA = sha1.encode(inner); + + return Buffer.concat([inner, new Buffer(innerSHA, 'hex')]); + } + + /* + * Write the index file in the repository. + */ + writeToRepo(repo: Repository): Promise<*> { + const { fs } = repo; + const content = this.toBuffer(); + const filepath = repo.resolveGitFile('index'); + + debug('write the index file'); + return fs.write(filepath, content); + } + + /* + * Read from the repositpry. + */ + static readFromRepository(repo: Repository): Promise { + const { fs } = repo; + const indexpath = repo.resolveGitFile('index'); + + debug(`read index file from ${indexpath}`); + return fs.read(indexpath).then( + buffer => WorkingIndex.createFromBuffer(buffer), + // The index file may not exist + error => { + debug('no index file found in the repository'); + return Promise.resolve(new WorkingIndex()); + } + ); + } + + /* + * Create a working index from a buffer. + */ + static createFromBuffer(buffer: Buffer): WorkingIndex { + const parser = WorkingIndex.createParser(); + let result = new WorkingIndex(); + + parser.on('index', (index: WorkingIndex) => { + result = index; + }); + + parser.write(buffer); + + return result; + } + + /* + * Create a parser for a working index. + * + * It emits events: + * - "entry" for each file entry + * - "index" for the entire WorkingIndex + */ + static createParser(): WritableStream { + const parser = Dissolve(); + + parser + .buffer('signature', 4) + .int32be('version') + .int32be('count') + .tap(() => { + const { signature, count, version } = parser.vars; + let entries = new OrderedMap(); + + if (signature.toString() != SIGNATURE) { + this.emit(new Error('Invalid signature for index file')); + return; + } + + let i = 0; + + parser + .loop(end => { + IndexEntry.createParser(version, parser).tap(() => { + const { entry } = parser.vars; + entries = entries.set(entry.path, entry); + + i += 1; + if (i === count) { + end(); + } + }); + }) + .tap(() => { + parser.emit( + 'index', + new WorkingIndex({ + version, + entries + }) + ); + }); + }); + + return parser; + } +} + +export default WorkingIndex; diff --git a/src/models/__tests__/Author.js b/src/models/__tests__/Author.js new file mode 100644 index 0000000..f5e0745 --- /dev/null +++ b/src/models/__tests__/Author.js @@ -0,0 +1,14 @@ +import Author from '../Author'; + +describe('createFromString', () => { + test('it should parse all fields', () => { + const author = Author.createFromString( + 'Samy Pesse 1466463181 +0200' + ); + + expect(author.name).toBe('Samy Pesse'); + expect(author.email).toBe('samypesse@gmail.com'); + expect(author.timestamp).toBe(1466463181); + expect(author.timezone).toBe('+0200'); + }); +}); diff --git a/src/models/__tests__/Commit.js b/src/models/__tests__/Commit.js new file mode 100644 index 0000000..e8e5083 --- /dev/null +++ b/src/models/__tests__/Commit.js @@ -0,0 +1,43 @@ +import path from 'path'; +import fs from 'fs'; + +import Author from '../Author'; +import Commit from '../Commit'; +import GitObject from '../GitObject'; + +describe('.createFromObject', () => { + let commit; + + beforeAll(() => { + const dataPath = path.join( + __dirname, + 'data/commit-bbe781d8f1cdffd26e504af8c66e097ad7dc8003' + ); + const buf = fs.readFileSync(dataPath); + const obj = GitObject.createFromZip(buf); + + commit = Commit.createFromObject(obj); + }); + + it('should parse right message', () => { + expect(commit.message).toBe('Add path for git objects'); + }); + + it('should parse tree sha', () => { + expect(commit.tree).toBe('266e71686f9a0fc884477274bfab6448ce04a7a3'); + }); + + it('should parse parents list', () => { + expect(commit.parents.toJS()).toEqual([ + 'f38b2026ad9ef2c9bbd7041d4feaf97bf6632b7f' + ]); + }); + + it('should parse the author/committer', () => { + expect(commit.author).toBeInstanceOf(Author); + expect(commit.committer).toBeInstanceOf(Author); + + expect(commit.author.name).toBe('Samy Pessé'); + expect(commit.author.email).toBe('samypesse@gmail.com'); + }); +}); diff --git a/src/models/__tests__/Config.js b/src/models/__tests__/Config.js new file mode 100644 index 0000000..3e7a08f --- /dev/null +++ b/src/models/__tests__/Config.js @@ -0,0 +1,51 @@ +import Config from '../Config'; + +describe('createFromString', () => { + const config = Config.createFromString(`[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true +[remote "origin"] + url = https://github.com/SamyPesse/gitkit-js.git + fetch = +refs/heads/*:refs/remotes/origin/* +[branch "master"] + remote = origin + merge = refs/heads/master +[lfs "https://github.com/SamyPesse/gitkit-js.git/info/lfs"] + access = basic +[branch "es6-flow"] + remote = origin + merge = refs/heads/es6-flow`); + + test('it should parse the core (boolean)', () => { + expect(config.core.get('bare')).toBe(false); + expect(config.core.get('filemode')).toBe(true); + }); + + test('it should parse the core (string)', () => { + expect(config.core.get('repositoryformatversion')).toBe('0'); + }); + + test('it should list all branches', () => { + expect(config.branches.size).toBe(2); + + const master = config.branches.get('master'); + expect(master).toBeDefined(); + expect(master.remote).toEqual('origin'); + expect(master.merge).toEqual('refs/heads/master'); + }); + + test('it should list all remotes', () => { + expect(config.remotes.size).toBe(1); + + const remote = config.remotes.get('origin'); + expect(remote).toBeDefined(); + expect(remote.url).toEqual( + 'https://github.com/SamyPesse/gitkit-js.git' + ); + expect(remote.fetch).toEqual('+refs/heads/*:refs/remotes/origin/*'); + }); +}); diff --git a/src/models/__tests__/FetchDiscovery.js b/src/models/__tests__/FetchDiscovery.js new file mode 100644 index 0000000..be96b62 --- /dev/null +++ b/src/models/__tests__/FetchDiscovery.js @@ -0,0 +1,25 @@ +import FetchDiscovery from '../FetchDiscovery'; +import HTTPTransport from '../../transports/HTTPTransport'; + +describe('fetch', () => { + let discovery; + + beforeAll(() => { + const transport = new HTTPTransport( + 'https://github.com/SamyPesse/gitkit-js.git' + ); + + return FetchDiscovery.fetch(transport).then( + result => (discovery = result) + ); + }); + + it('should parse capabilities', () => { + expect(discovery.capabilities.toJS()).toContain('multi_ack'); + }); + + it('should list refs', () => { + expect(discovery.refs.has('HEAD')).toEqual(true); + expect(discovery.refs.has('refs/tags/0.1.0')).toEqual(true); + }); +}); diff --git a/src/models/__tests__/GitObject.js b/src/models/__tests__/GitObject.js new file mode 100644 index 0000000..0812683 --- /dev/null +++ b/src/models/__tests__/GitObject.js @@ -0,0 +1,57 @@ +import path from 'path'; +import fs from 'fs'; + +import GitObject from '../GitObject'; + +describe('.sha', () => { + test('it should encode it correctly', () => { + const o = new GitObject({ + type: 'blob', + content: new Buffer('Hello world', 'utf8') + }); + + expect(o.sha).toBe('70c379b63ffa0795fdbfbc128e5a2818397b7ef8'); + }); +}); + +describe('.path', () => { + test('it should return correct path', () => { + const o = new GitObject({ + type: 'blob', + content: new Buffer('Hello', 'utf8') + }); + + expect(o.sha).toBe('5ab2f8a4323abafb10abb68657d9d39f1a775057'); + expect(o.path).toBe( + 'objects/5a/b2f8a4323abafb10abb68657d9d39f1a775057' + ); + }); +}); + +describe('.createFromZip', () => { + it('should detect type "tree"', () => { + const dataPath = path.join( + __dirname, + 'data/tree-2bd3640faa3f7e0c7a644c9ca475b30b62e9e62c' + ); + const buf = fs.readFileSync(dataPath); + + const obj = GitObject.createFromZip(buf); + + expect(obj.type).toBe('tree'); + expect(obj.length).toBe(446); + }); + + it('should detect type "commit"', () => { + const dataPath = path.join( + __dirname, + 'data/commit-bbe781d8f1cdffd26e504af8c66e097ad7dc8003' + ); + const buf = fs.readFileSync(dataPath); + + const obj = GitObject.createFromZip(buf); + + expect(obj.type).toBe('commit'); + expect(obj.length).toBe(239); + }); +}); diff --git a/src/models/__tests__/Head.js b/src/models/__tests__/Head.js new file mode 100644 index 0000000..790f68f --- /dev/null +++ b/src/models/__tests__/Head.js @@ -0,0 +1,19 @@ +import Head from '../Head'; + +describe('isDetached', () => { + test('it should be true when pointing to a commit', () => { + const head = new Head({ + commit: '8a3493eea604510d7ba532cce73bf0fb68c6db8f' + }); + + expect(head.isDetached).toBe(true); + }); + + test('it should be false when pointing to a ref', () => { + const head = new Head({ + ref: 'refs/heads/master' + }); + + expect(head.isDetached).toBe(false); + }); +}); diff --git a/src/models/__tests__/IndexEntry.js b/src/models/__tests__/IndexEntry.js new file mode 100644 index 0000000..6b539c5 --- /dev/null +++ b/src/models/__tests__/IndexEntry.js @@ -0,0 +1,13 @@ +import IndexEntry from '../IndexEntry'; + +describe('toBuffer', () => { + test('it generate the right buffer', () => { + const entry = new IndexEntry({ + path: 'README.md', + fileSize: 10, + sha: '802992c4220de19a90767f3000a79a31b98d0df7' + }); + + const buffer = entry.toBuffer(2); + }); +}); diff --git a/src/models/__tests__/PackFile.js b/src/models/__tests__/PackFile.js new file mode 100644 index 0000000..14c7896 --- /dev/null +++ b/src/models/__tests__/PackFile.js @@ -0,0 +1,39 @@ +import path from 'path'; +import fs from 'fs'; + +import PackFile from '../PackFile'; + +describe('.createFromBuffer', () => { + let pack; + + beforeAll(() => { + const dataPath = path.join(__dirname, 'data/pack-version2.pack'); + const buf = fs.readFileSync(dataPath); + + pack = PackFile.createFromBuffer(buf); + }); + + it('should parse correct version', () => { + expect(pack.version).toBe(2); + }); + + it('should extract all objects', () => { + expect(pack.objects.size).toBe(481); + }); + + it('should correctly extract not deltified object', () => { + const tree = pack.objects.get( + '5e0a8cda1925e50779633a9137ecbae032c10010' + ); + expect(tree).toBeDefined(); + expect(tree.type).toBe('tree'); + }); + + it('should correctly extract deltified object', () => { + const blob = pack.objects.get( + 'edf18bf82ff1fc90588a48e036706f49e045f1d1' + ); + expect(blob).toBeDefined(); + expect(blob.type).toBe('blob'); + }); +}); diff --git a/src/models/__tests__/PackFileIndex.js b/src/models/__tests__/PackFileIndex.js new file mode 100644 index 0000000..9232f9c --- /dev/null +++ b/src/models/__tests__/PackFileIndex.js @@ -0,0 +1,55 @@ +import path from 'path'; +import fs from 'fs'; + +import PackFileIndex from '../PackFileIndex'; + +describe('.createFromBuffer', () => { + let index; + + beforeAll(() => { + const dataPath = path.join(__dirname, 'data/pack-version2.idx'); + const buf = fs.readFileSync(dataPath); + + index = PackFileIndex.createFromBuffer(buf); + }); + + it('should parse correct version', () => { + expect(index.version).toBe(2); + }); + + it('should list all objects', () => { + expect(index.objects.size).toBe(481); + }); + + it('should parse the CRC (1)', () => { + const obj = index.objects.get( + 'fcb5fc5bb3552658b1d29448fa435c99387fe99c' + ); + expect(obj).toBeDefined(); + expect(obj.crc).toBe(-1699773302); + }); + + it('should parse the CRC (2)', () => { + const obj = index.objects.get( + 'fe9fdab42e582b503e978f4747e6dad68ec9c416' + ); + expect(obj).toBeDefined(); + expect(obj.crc).toBe(1923035264); + }); + + it('should parse the offset (1)', () => { + const obj = index.objects.get( + 'f6b3ef3db34f7bb01760a439a900fa30c27b8df9' + ); + expect(obj).toBeDefined(); + expect(obj.offset).toBe(47833); + }); + + it('should parse the offset (2)', () => { + const obj = index.objects.get( + 'fad8f5c2bbfc62c530786a7a3ab8eaceaa1f21f8' + ); + expect(obj).toBeDefined(); + expect(obj.offset).toBe(31935); + }); +}); diff --git a/src/models/__tests__/Ref.js b/src/models/__tests__/Ref.js new file mode 100644 index 0000000..0633f8e --- /dev/null +++ b/src/models/__tests__/Ref.js @@ -0,0 +1,30 @@ +import Ref from '../Ref'; + +describe('createFromString', () => { + test('it should parse ref HEAD', () => { + const head = Ref.createFromString('ref: refs/heads/master\n'); + + expect(head.ref).toBe('refs/heads/master'); + expect(head.commit).toBe(null); + }); + + test('it should parse detached HEAD', () => { + const head = Ref.createFromString( + '8a3493eea604510d7ba532cce73bf0fb68c6db8f\n' + ); + + expect(head.commit).toBe('8a3493eea604510d7ba532cce73bf0fb68c6db8f'); + expect(head.ref).toBe(null); + }); +}); + +describe('createFromBuffer', () => { + test('it should parse ref HEAD', () => { + const head = Ref.createFromBuffer( + new Buffer('ref: refs/heads/master\n', 'utf8') + ); + + expect(head.ref).toBe('refs/heads/master'); + expect(head.commit).toBe(null); + }); +}); diff --git a/src/models/__tests__/RefsIndex.js b/src/models/__tests__/RefsIndex.js new file mode 100644 index 0000000..b94ed8b --- /dev/null +++ b/src/models/__tests__/RefsIndex.js @@ -0,0 +1,36 @@ +import RefsIndex from '../RefsIndex'; + +let index; + +describe('createFromPack', () => { + test('it should parse without error', () => { + index = RefsIndex.createFromPack(`# pack-refs with: peeled fully-peeled +931e905e0d5d250f4a8129600c390800cde77c08 refs/heads/es6-flow +277a149659ce5caa44db4da52ffca7332f31316a refs/heads/master +b5a03273eb4f3ba3e4feb5bdf3a34e1b889f67c7 refs/remotes/origin/es6-flow +6caa820b76605daddeeecac07d3289079d477ae0 refs/remotes/origin/examples/webapp +9363f8200d75ca91d359d457541df9a01d00544f refs/remotes/origin/flow +277a149659ce5caa44db4da52ffca7332f31316a refs/remotes/origin/master +aae050d2042be9115d2545dfa9ee3e8be65c9974 refs/tags/0.1.0 +`); + }); + + test('it should parse all refs', () => { + expect(index.refs.size).toBe(7); + }); + + test('it should correctly map', () => { + const ref = index.refs.get('refs/heads/master'); + expect(ref).toBeDefined(); + expect(ref.commit).toBe('277a149659ce5caa44db4da52ffca7332f31316a'); + }); +}); + +describe('branches', () => { + test('it should only list local branches', () => { + const { branches } = index; + + expect(branches.size).toBe(2); + expect(branches.keySeq().toArray()).toEqual(['es6-flow', 'master']); + }); +}); diff --git a/src/models/__tests__/Tree.js b/src/models/__tests__/Tree.js new file mode 100644 index 0000000..7ea391d --- /dev/null +++ b/src/models/__tests__/Tree.js @@ -0,0 +1,40 @@ +import path from 'path'; +import fs from 'fs'; + +import Tree from '../Tree'; +import GitObject from '../GitObject'; + +describe('.createFromObject', () => { + let tree; + + beforeAll(() => { + const dataPath = path.join( + __dirname, + 'data/tree-2bd3640faa3f7e0c7a644c9ca475b30b62e9e62c' + ); + const buf = fs.readFileSync(dataPath); + const obj = GitObject.createFromZip(buf); + + tree = Tree.createFromObject(obj); + }); + + it('should decode the right number of entries', () => { + expect(tree.entries.size).toBe(12); + }); + + it('should detect the mode of entries', () => { + const treeEntry = tree.entries.get('src'); + const blobEntry = tree.entries.get('package.json'); + + expect(treeEntry.mode).toBe(40000); + expect(blobEntry.mode).toBe(100644); + }); + + it('should detect the type of entries', () => { + const blobs = tree.entries.filter(entry => entry.isBlob); + const trees = tree.entries.filter(entry => entry.isTree); + + expect(blobs.size).toBe(11); + expect(trees.size).toBe(1); + }); +}); diff --git a/src/models/__tests__/WorkingIndex.js b/src/models/__tests__/WorkingIndex.js new file mode 100644 index 0000000..f78cce0 --- /dev/null +++ b/src/models/__tests__/WorkingIndex.js @@ -0,0 +1,29 @@ +import path from 'path'; +import fs from 'fs'; + +import WorkingIndex from '../WorkingIndex'; + +describe('.createFromBuffer', () => { + let index; + + beforeAll(() => { + const dataPath = path.join(__dirname, 'data/index'); + const buf = fs.readFileSync(dataPath); + + index = WorkingIndex.createFromBuffer(buf); + }); + + it('should parse correct version', () => { + expect(index.version).toBe(2); + }); + + it('should extract all entries', () => { + expect(index.entries.size).toBe(55); + }); + + it('should have the right files', () => { + expect(index.entries.has('.babelrc')).toBe(true); + expect(index.entries.has('yarn.lock')).toBe(true); + expect(index.entries.has('src/models/Author.js')).toBe(true); + }); +}); diff --git a/src/models/__tests__/data/commit-bbe781d8f1cdffd26e504af8c66e097ad7dc8003 b/src/models/__tests__/data/commit-bbe781d8f1cdffd26e504af8c66e097ad7dc8003 new file mode 100644 index 0000000..40bd0f0 --- /dev/null +++ b/src/models/__tests__/data/commit-bbe781d8f1cdffd26e504af8c66e097ad7dc8003 @@ -0,0 +1 @@ +xQj1 DSP^!B{BO &.Gsb73𘙲vt*""Yo%FDM2!Ƣevz`sBI9W8U4KhO;|J=amir~-k"޻]Za~˸/-pON \ No newline at end of file diff --git a/src/models/__tests__/data/index b/src/models/__tests__/data/index new file mode 100644 index 0000000..5a97d4b Binary files /dev/null and b/src/models/__tests__/data/index differ diff --git a/src/models/__tests__/data/pack-version2.idx b/src/models/__tests__/data/pack-version2.idx new file mode 100644 index 0000000..9f53d51 Binary files /dev/null and b/src/models/__tests__/data/pack-version2.idx differ diff --git a/testing/data/pack b/src/models/__tests__/data/pack-version2.pack similarity index 100% rename from testing/data/pack rename to src/models/__tests__/data/pack-version2.pack diff --git a/src/models/__tests__/data/tree-2bd3640faa3f7e0c7a644c9ca475b30b62e9e62c b/src/models/__tests__/data/tree-2bd3640faa3f7e0c7a644c9ca475b30b62e9e62c new file mode 100644 index 0000000..8e03f86 Binary files /dev/null and b/src/models/__tests__/data/tree-2bd3640faa3f7e0c7a644c9ca475b30b62e9e62c differ diff --git a/src/models/index.js b/src/models/index.js new file mode 100644 index 0000000..023aeab --- /dev/null +++ b/src/models/index.js @@ -0,0 +1,47 @@ +/** @flow */ + +import Author from './Author'; +import Blob from './Blob'; +import BranchConfig from './BranchConfig'; +import Commit from './Commit'; +import Config from './Config'; +import FetchDiscovery from './FetchDiscovery'; +import GitObject from './GitObject'; +import Head from './Head'; +import IndexEntry from './IndexEntry'; +import ObjectsIndex from './ObjectsIndex'; +import PackFile from './PackFile'; +import PackFileIndex from './PackFileIndex'; +import PackIndexOffset from './PackIndexOffset'; +import Person from './Person'; +import Ref from './Ref'; +import RefsIndex from './RefsIndex'; +import RemoteConfig from './RemoteConfig'; +import Repository from './Repository'; +import Tree from './Tree'; +import TreeEntry from './TreeEntry'; +import WorkingIndex from './WorkingIndex'; + +export { + Author, + Blob, + BranchConfig, + Commit, + Config, + FetchDiscovery, + GitObject, + Head, + IndexEntry, + ObjectsIndex, + PackFile, + PackFileIndex, + PackIndexOffset, + Person, + Ref, + RefsIndex, + RemoteConfig, + Repository, + Tree, + TreeEntry, + WorkingIndex +}; diff --git a/src/transfer/discovery.js b/src/transfer/discovery.js new file mode 100644 index 0000000..b39eb7b --- /dev/null +++ b/src/transfer/discovery.js @@ -0,0 +1,59 @@ +/* @flow */ +/* eslint-disable array-callback-return */ + +import es from 'event-stream'; +import combine from 'stream-combiner2'; + +import Ref from '../models/Ref'; + +import { + LINEMETA_TYPES, + createPackLineParser, + createPackLineMetaParser +} from './lines'; + +/* + * Parse an upload result into a list of packs. + * It emits "data" for each { name: string, ref: Ref } + */ +function createDiscoveryParser({ + onCapabilities = () => {} +}: { + onCapabilities: (caps: string[]) => * +}): WritableStream { + let lineIndex = 0; + + return combine.obj( + // Parse as lines + createPackLineParser(), + // Parse metatdata of lines + createPackLineMetaParser(), + // Read infos from lines. + es.map((line, callback) => { + lineIndex += 1; + + if (line.caps) { + onCapabilities(line.caps); + } + + if (lineIndex === 1 || line.type !== LINEMETA_TYPES.LINE) { + callback(); + return; + } + + const content = line.toString('utf8').trim(); + const parts = content.split(' '); + + const refName = parts[1]; + const sha = parts[0]; + const ref = new Ref({ commit: sha }); + + callback(null, { + name: refName, + ref + }); + }) + ); +} + +export { createDiscoveryParser }; diff --git a/src/transfer/index.js b/src/transfer/index.js new file mode 100644 index 0000000..086f191 --- /dev/null +++ b/src/transfer/index.js @@ -0,0 +1,15 @@ +import { createUploadPackParser } from './uploadPacks'; +import { + LINEMETA_TYPES, + createPackLineParser, + createPackLineMetaParser +} from './lines'; +import { createDiscoveryParser } from './discovery'; + +export { + LINEMETA_TYPES, + createPackLineParser, + createPackLineMetaParser, + createUploadPackParser, + createDiscoveryParser +}; diff --git a/src/transfer/lines.js b/src/transfer/lines.js new file mode 100644 index 0000000..0543fac --- /dev/null +++ b/src/transfer/lines.js @@ -0,0 +1,157 @@ +/** @flow */ +/* eslint-disable array-callback-return */ + +import Dissolve from 'dissolve'; +import es from 'event-stream'; + +const LINEMETA_TYPES = { + FLUSH: 'pkt-flush', + LINE: 'pkt-line', + PACKFILE: 'packfile', + PROGRESS: 'progress', + ERROR: 'error' +}; + +/* + * Create a parser for pkt lines. + */ +function createPackLineParser(): Dissolve { + const parser = Dissolve(); + + parser.loop(end => { + parser.buffer('lineLength', 4).tap(() => { + const lineLength = parseInt(parser.vars.lineLength.toString(), 16); + + if (lineLength === 0) { + parser.push(new Buffer('')); + return; + } + + parser.buffer('line', lineLength - 4).tap(() => { + parser.push(parser.vars.line); + }); + }); + }); + + return parser; +} + +/* + * Create a a parser for line metadatas + */ +function createPackLineMetaParser(isServer: boolean = false): Dissolve { + let caps = null; + let lineIndex = 0; + + const divineCapabilities = isServer + ? divineServerCapabilities + : divineClientCapabilities; + + // On which line shuld we look for capabilities? + const checkCapsOn = isServer ? 1 : 0; + + return es.map((line, callback) => { + let resultType; + let result = line; + + // Detect type + if (line.length === 0) { + resultType = LINEMETA_TYPES.FLUSH; + } else { + const peek = line[0]; + + if (peek == 1) { + resultType = LINEMETA_TYPES.PACKFILE; + } else if (peek == 2) { + resultType = LINEMETA_TYPES.PROGRESS; + } else if (peek == 3) { + resultType = LINEMETA_TYPES.ERROR; + } else { + resultType = LINEMETA_TYPES.LINE; + } + } + + // Should we look for capabilities + if (!caps && lineIndex === checkCapsOn) { + caps = divineCapabilities(result); + if (caps) { + result = result.slice(0, caps.idx + 1); + result[result.length - 1] = 0x0a; + caps = caps.caps; + } + } + + // Remove first byte to define type + if (resultType !== LINEMETA_TYPES.LINE) { + result = result.slice(1); + } + + result.type = resultType; + result.caps = caps; + + lineIndex += 1; + + if (resultType === LINEMETA_TYPES.FLUSH) { + lineIndex = 0; + caps = null; + } else if ( + resultType === LINEMETA_TYPES.PROGRESS || + resultType === LINEMETA_TYPES.ERROR + ) { + this.emit(resultType, result); + } + + callback(null, result); + }); +} + +function divineClientCapabilities(buf) { + let i; + let len; + + for (i = 0, len = buf.length; i < len; i += 1) { + if (buf[i] === 0) { + break; + } + } + + if (i === len) { + return null; + } + + return { + idx: i, + caps: buf.slice(i + 1, buf.length - 1).toString('utf8').split(' ') + }; +} + +function divineServerCapabilities(buf) { + let i; + let len; + const isFetch = buf.slice(0, 4).toString() === 'want'; + + if (isFetch) { + for (i = 45, len = buf.length; i < len; i += 1) { + if (buf[i] === 32) { + break; + } + } + } else { + for (i = 0, len = buf.length; i < len; i += 1) { + if (buf[i] === 0) { + break; + } + } + } + + if (i === len) { + return null; + } + + return { + idx: i, + caps: buf.slice(i + 1, buf.length - 1).toString('utf8').split(' ') + }; +} + +export { LINEMETA_TYPES, createPackLineParser, createPackLineMetaParser }; diff --git a/src/transfer/uploadPacks.js b/src/transfer/uploadPacks.js new file mode 100644 index 0000000..09c597f --- /dev/null +++ b/src/transfer/uploadPacks.js @@ -0,0 +1,34 @@ +/* @flow */ + +import es from 'event-stream'; +import combine from 'stream-combiner2'; + +import { + LINEMETA_TYPES, + createPackLineParser, + createPackLineMetaParser +} from './lines'; + +/* + * Parse an upload result into a list of packs + */ +function createUploadPackParser(opts): WritableStream { + return combine.obj( + // Parse as lines + createPackLineParser(), + // Parse metatdata of lines + createPackLineMetaParser(), + // Filter packs + es.map((line, callback) => { + if (line.type == LINEMETA_TYPES.PACKFILE) { + callback(null, line); + } else { + callback(); + } + }), + // Parse pack as objects + parsePack(opts) + ); +} + +export { createUploadPackParser }; diff --git a/src/transforms/config.js b/src/transforms/config.js new file mode 100644 index 0000000..db27958 --- /dev/null +++ b/src/transforms/config.js @@ -0,0 +1,39 @@ +/** @flow */ + +import type GitKit from '../GitKit'; + +/* + * Transforms to the configuration. + */ + +const Transforms = {}; + +/* + * Read the config. + */ +Transforms.readConfig = (gitkit: GitKit): Promise<*> => + gitkit.repo.readConfig().then(repo => (gitkit.repo = repo)); + +/* + * Write the config to the disk. + */ +Transforms.flushConfig = (gitkit: GitKit): Promise<*> => { + const { repo } = gitkit; + return repo.config.writeToRepo(repo); +}; + +/* + * Add a new remote. + */ +Transforms.addRemote = (gitkit: GitKit, name: string, url: string): GitKit => { + const { repo } = gitkit; + const { config } = repo; + + gitkit.repo = repo.merge({ + config: config.addRemote(name, url) + }); + + return gitkit; +}; + +export default Transforms; diff --git a/src/transforms/files.js b/src/transforms/files.js new file mode 100644 index 0000000..917eda2 --- /dev/null +++ b/src/transforms/files.js @@ -0,0 +1,30 @@ +/** @flow */ + +import type GitKit from '../GitKit'; + +/* + * Transforms to edit files. + */ + +const Transforms = {}; + +Transforms.writeFile = ( + gitkit: GitKit, + filename: string, + content: Buffer | string +): Promise<*> => { + const { fs } = gitkit.repo; + return fs.write(filename, content); +}; + +Transforms.mkdir = (gitkit: GitKit, dirname: string): Promise<*> => { + const { fs } = gitkit.repo; + return fs.mkdir(dirname); +}; + +Transforms.unlinkFile = (gitkit: GitKit, filename: string): Promise<*> => { + const { fs } = gitkit.repo; + return fs.unlinkFile(filename); +}; + +export default Transforms; diff --git a/src/transforms/index.js b/src/transforms/index.js new file mode 100644 index 0000000..875f99a --- /dev/null +++ b/src/transforms/index.js @@ -0,0 +1,21 @@ +/* @flow */ + +import InitTransforms from './init'; +import ObjectsTransforms from './objects'; +import RefsTransforms from './refs'; +import RemotesTransforms from './remotes'; +import FilesTransforms from './files'; +import WorkingTransforms from './working'; +import ConfigTransforms from './config'; + +const Transforms = { + ...InitTransforms, + ...ObjectsTransforms, + ...RefsTransforms, + ...RemotesTransforms, + ...FilesTransforms, + ...WorkingTransforms, + ...ConfigTransforms +}; + +export default Transforms; diff --git a/src/transforms/init.js b/src/transforms/init.js new file mode 100644 index 0000000..48c7b82 --- /dev/null +++ b/src/transforms/init.js @@ -0,0 +1,44 @@ +/** @flow */ + +import path from 'path'; +import { Head } from '../'; +import type GitKit from '../GitKit'; + +/* + * Transforms to init a repo. + */ + +const Transforms = {}; + +/* + * Initialize the repository. + */ +Transforms.init = (gitkit: GitKit): Promise<*> => { + const base = gitkit.repo.isBare ? '' : '.git'; + + return ( + gitkit + .readHEAD() + .then( + () => { + throw new Error('Directory is already a git repository'); + }, + () => Promise.resolve() + ) + .then(() => gitkit.mkdir(base)) + .then(() => + Promise.all([ + gitkit.mkdir(path.join(base, 'objects')), + gitkit.mkdir(path.join(base, 'refs/heads')), + gitkit.mkdir(path.join(base, 'hooks')) + ]) + ) + // Write files + .then(() => { + const head = new Head({ ref: 'refs/heads/master' }); + return head.writeToRepo(gitkit.repo, 'HEAD'); + }) + ); +}; + +export default Transforms; diff --git a/src/transforms/objects.js b/src/transforms/objects.js new file mode 100644 index 0000000..a2ade76 --- /dev/null +++ b/src/transforms/objects.js @@ -0,0 +1,134 @@ +/** @flow */ + +import path from 'path'; +import { OrderedMap } from 'immutable'; +import { Blob, Tree, Commit } from '../models'; +import type { GitObject, TreeEntry } from '../models'; +import type { SHA } from '../types/SHA'; +import type GitKit from '../GitKit'; + +/* + * Low-level operations for objects. + */ + +const Transforms = {}; + +Transforms.indexObjects = (gitkit: GitKit): Promise<*> => + gitkit.repo.indexObjects().then(repo => (gitkit.repo = repo)); + +/* + * Read an object from the database. + */ +Transforms.readObject = (gitkit: GitKit, sha: SHA): Promise => + gitkit.repo.readObject(sha).then(repo => { + gitkit.repo = repo; + return repo.objects.getObject(sha); + }); + +Transforms.readCommit = (gitkit: GitKit, sha: SHA): Promise => + gitkit.readObject(sha).then(object => Commit.createFromObject(object)); + +Transforms.readTree = (gitkit: GitKit, sha: SHA): Promise => + gitkit.readObject(sha).then(object => Tree.createFromObject(object)); + +Transforms.readBlob = (gitkit: GitKit, sha: SHA): Promise => + gitkit.readObject(sha).then(object => Blob.createFromObject(object)); + +/* + * Create a new git object. + */ +Transforms.addObjects = (gitkit: GitKit, toAdd: GitObject): Promise<*> => { + const { objects } = gitkit.repo; + + return toAdd + .reduce( + (prev, object) => + prev.then(_objects => { + const { sha } = object; + + if (_objects.hasObject(sha)) { + return _objects; + } + + return _objects + .writeObjectToRepository(gitkit.repo, object) + .then(() => _objects.addObject(object)); + }), + Promise.resolve(objects) + ) + .then(() => toAdd); +}; + +Transforms.addObject = ( + gitkit: GitKit, + object: GitObject +): Promise => + gitkit.addObjects([object]).then(objects => objects[0]); + +Transforms.addBlob = (gitkit: GitKit, blob: Blob): Promise => + gitkit.addObject(blob.toGitObject()); + +Transforms.addTree = (gitkit: GitKit, tree: Tree): Promise => + gitkit.addObject(tree.toGitObject()); + +Transforms.addCommit = (gitkit: GitKit, commit: Commit): Promise => + gitkit.addObject(commit.toGitObject()); + +/* + * Iterate over commits. + */ +Transforms.walkCommits = ( + gitkit: GitKit, + sha: SHA, + iter: (commit: Commit, sha: SHA) => ?boolean +): Promise<*> => + gitkit.readCommit(sha).then(commit => { + if (iter(commit, sha) == false) { + return false; + } + + return commit.parents.reduce( + (prev, parent) => prev.then(() => gitkit.walkCommits(parent, iter)), + Promise.resolve() + ); + }); +/* + * Iterate over tree. + */ +Transforms.walkTree = ( + gitkit: GitKit, + sha: SHA, + iter: (entry: TreeEntry, filepath: string) => *, + baseName: string = '' +): Promise<*> => + gitkit.readTree(sha).then(tree => { + const { entries } = tree; + + return entries.reduce( + (prev, entry) => + prev.then(() => { + const filepath = path.join(baseName, entry.path); + if (!entry.isTree) { + return iter(entry, filepath); + } + return gitkit.walkTree(entry.sha, iter, filepath); + }), + Promise.resolve() + ); + }); + +/* + * Read an entire tree. + * The returned Tree is not valid since it contains entire paths and only files entries. + */ +Transforms.readRecursiveTree = (gitkit: GitKit, sha: SHA): Promise => { + let entries = new OrderedMap(); + + return gitkit + .walkTree(sha, (entry, filepath) => { + entries = entries.set(filepath, entry); + }) + .then(() => new Tree({ entries })); +}; + +export default Transforms; diff --git a/src/transforms/refs.js b/src/transforms/refs.js new file mode 100644 index 0000000..5fec039 --- /dev/null +++ b/src/transforms/refs.js @@ -0,0 +1,35 @@ +/** @flow */ + +import type { Ref } from '../models'; +import type GitKit from '../GitKit'; + +/* + * Transforms to manipulate refs (branches and tags) + */ + +const Transforms = {}; + +/* + * Read the HEAD. + */ +Transforms.readHEAD = (gitkit: GitKit): Promise<*> => + gitkit.repo.readHEAD().then(repo => (gitkit.repo = repo)); + +Transforms.indexRefs = (gitkit: GitKit): Promise<*> => + gitkit.repo.indexRefs().then(repo => (gitkit.repo = repo)); + +/* + * Create a new ref. + */ +Transforms.updateRef = (gitkit: GitKit, name: string, ref: Ref): GitKit => { + const { repo } = gitkit; + const { refs } = repo; + + gitkit.repo = repo.merge({ + refs: refs.setRef(name, ref) + }); + + return gitkit; +}; + +export default Transforms; diff --git a/src/transforms/remotes.js b/src/transforms/remotes.js new file mode 100644 index 0000000..17108a4 --- /dev/null +++ b/src/transforms/remotes.js @@ -0,0 +1,71 @@ +/** @flow */ + +import type { List } from 'immutable'; +import { FetchDiscovery } from '../models'; +import type { Ref } from '../models'; +import type { Transport } from '../transports'; +import type GitKit from '../GitKit'; + +/* + * Transforms to do remote operations. + */ + +const Transforms = {}; + +/* + * Clone a remote repository. + */ +Transforms.clone = (gitkit: GitKit, transport: Transport) => {}; + +/* + * Fetch from a remote transport. + */ +Transforms.fetch = ( + gitkit: GitKit, + transport: Transport, + // Ref to fetch/update + refName: string = 'HEAD' +) => + FetchDiscovery.fetch(transport).then(discovery => { + const remoteRef = discovery.refs.get(refName); + const localRef = gitkit.repo.refs.getRef(refName); + + if (!remoteRef) { + throw new Error(`Couldn't find remote ref ${refName}`); + } + }); + +/* + * Fetch a specific ref from the server. + */ +function fetchRef( + transform: Transform, + transport: Transport, + wantRef: Ref, + haveRefs: List +) { + const request = genRefWantRequest(wantRef, haveRefs); + + // Fetch the objects by calling git-upload-pack + return transport.uploadPack(request); +} + +/* + * Return a buffer to send for requesting a ref. + */ +function genRefWantRequest(wantRef: Ref, haveRefs: List): Buffer { + const lines = [ + `want ${wantRef.commit} multi_ack_detailed side-band-64k thin-pack ofs-delta`, + '' + ]; + + haveRefs.forEach(haveRef => { + lines.push(`have ${haveRef.commit}\n`); + }); + + lines.push('done'); + + return encodePktLines(lines); +} + +export default Transforms; diff --git a/src/transforms/working.js b/src/transforms/working.js new file mode 100644 index 0000000..2288791 --- /dev/null +++ b/src/transforms/working.js @@ -0,0 +1,41 @@ +/** @flow */ + +import type GitKit from '../GitKit'; +import { Blob, IndexEntry } from '../models'; + +/* + * Transforms to edit the working index. + */ + +const Transforms = {}; + +Transforms.readWorkingIndex = (gitkit: GitKit): Promise<*> => + gitkit.repo.readWorkingIndex().then(repo => (gitkit.repo = repo)); + +/* + * Equivalent to the "git add" command + */ +Transforms.addFile = (gitkit: GitKit, filename: string): Promise<*> => { + const { fs } = gitkit.repo; + let { workingIndex } = gitkit.repo; + + return Promise.all([ + fs.stat(filename), + fs.read(filename) + ]).then(([stat, content]) => { + const blob = Blob.createFromBuffer(content); + + return gitkit.addBlob(blob).then(({ sha }) => { + const entry = IndexEntry.createFromFileStat(stat, sha); + workingIndex = workingIndex.addEntry(entry); + + gitkit.repo = gitkit.repo.merge({ + workingIndex + }); + + return workingIndex.writeToRepo(gitkit.repo); + }); + }); +}; + +export default Transforms; diff --git a/src/transports/HTTPTransport.js b/src/transports/HTTPTransport.js new file mode 100644 index 0000000..29b08c5 --- /dev/null +++ b/src/transports/HTTPTransport.js @@ -0,0 +1,112 @@ +/** @flow */ + +import querystring from 'querystring'; +import url from 'url'; +import http from 'http'; +import https from 'https'; +import type stream from 'stream'; +import Transport from './Transport'; +import pkg from '../../package.json'; + +export type HTTPAuth = { + user: string, + password: ?string +}; + +type HTTPMethod = 'GET' | 'POST'; + +type HTTPOptions = { + headers: ?{ [string]: string }, + body: ?Buffer +}; + +const SERVICE_UPLOAD_PACK = 'git-upload-pack'; + +class HTTPTransport extends Transport { + baseURL: string; + auth: ?HTTPAuth; + + constructor(baseURL: string, auth: ?HTTPAuth) { + super(); + + this.baseURL = baseURL; + this.auth = auth; + } + + request( + method: HTTPMethod, + resource: string, + options: HTTPOptions = {} + ): Promise { + return new Promise((resolve, reject) => { + if (this.auth) { + options.headers.Authorization = `Basic ${new Buffer( + `${this.auth.user}:${this.auth.password}` + ).toString('base64')}`; + } + + options.headers['User-Agent'] = `${pkg.name}/${pkg.version}`; + + // Parse url + const parsed = url.parse(`${this.baseURL}/${resource}`); + + // Setup agent + const agent = parsed.protocol == 'https:' ? https : http; + + const req = agent.request( + { + method, + protocol: parsed.protocol, + hostname: parsed.hostname, + port: parsed.port, + path: parsed.path, + headers: options.headers + }, + res => { + resolve(res); + } + ); + + // Send request + req.end(options.body); + }); + } + + /* + * Get a pack from the server using "git-upload-pack" + */ + getWithUploadPack(baseResource: string): Promise { + const resource = `${baseResource}?${querystring.stringify({ + service: SERVICE_UPLOAD_PACK + })}`; + + return this.request('GET', resource, { + headers: { + 'Content-Type': 'application/x-git-upload-pack-request' + } + }).then(res => { + if ( + res.headers['content-type'] != + `application/x-${SERVICE_UPLOAD_PACK}-advertisement` + ) { + throw new Error('Invalid content type when fetching pack'); + } + + return res; + }); + } + + /* + * Upload a pack to the server. + */ + postUploadPack(resource: string, body: Buffer): Promise { + return this.request('POST', SERVICE_UPLOAD_PACK, { + body, + headers: { + 'Content-Type': 'application/x-git-upload-pack-request' + } + }); + } +} + +export default HTTPTransport; diff --git a/src/transports/Transport.js b/src/transports/Transport.js new file mode 100644 index 0000000..fdba7d2 --- /dev/null +++ b/src/transports/Transport.js @@ -0,0 +1,32 @@ +/** @flow */ + +import type stream from 'stream'; + +class Transport { + open(): Promise<*> { + return Promise.resolve(); + } + + close(): Promise<*> { + return Promise.resolve(); + } + + /* + * Get a pack from the server using "git-upload-pack" + */ + getWithUploadPack(resource: string): Promise { + return Promise.reject(new Error('Not implemented')); + } + + /* + * Upload a pack to the server. + */ + postUploadPack( + resource: string, + content: Buffer + ): Promise { + return Promise.reject(new Error('Not implemented')); + } +} + +export default Transport; diff --git a/src/transports/index.js b/src/transports/index.js new file mode 100644 index 0000000..1404268 --- /dev/null +++ b/src/transports/index.js @@ -0,0 +1,5 @@ +/* @flow */ + +import Transport from './Transport'; + +export { Transport }; diff --git a/src/types/SHA.js b/src/types/SHA.js new file mode 100644 index 0000000..b0027fa --- /dev/null +++ b/src/types/SHA.js @@ -0,0 +1,3 @@ +/** @flow */ + +export type SHA = string; diff --git a/src/utils/buffer.js b/src/utils/buffer.js new file mode 100644 index 0000000..655523a --- /dev/null +++ b/src/utils/buffer.js @@ -0,0 +1,45 @@ +/** @flow */ + +import type Dissolve from 'dissolve'; +import nbo from 'network-byte-order'; + +/* + * Utility for dissolve to scan for a buffer + * + * See https://github.com/deoxxa/dissolve/issues/21 + */ +function scan( + parser: Dissolve, + name: string, + search: string | Buffer +): Dissolve { + const searchFor = new Buffer(search, 'utf8'); + let result = new Buffer(''); + + return parser.loop(end => { + parser.buffer('tmpSearch', 1).tap(() => { + const c = parser.vars.tmpSearch; + result = Buffer.concat([result, c]); + + const toSearch = result.slice(-searchFor.length); + if (toSearch.compare(searchFor) === 0) { + delete parser.vars.tmpSearch; + parser.vars[name] = result.slice(0, -searchFor.length); + end(); + } + }); + }); +} + +/* + * Converts the given unsigned 32-bit (long) integer from host byte order + * to network byte order (Little-Endian to Big-Endian). + */ +function htonl(value: Number): Buffer { + const b = Buffer.alloc(4); + nbo.htonl(b, 0, value); + + return b; +} + +export { scan, htonl }; diff --git a/src/utils/sha1.js b/src/utils/sha1.js new file mode 100644 index 0000000..78d7ef2 --- /dev/null +++ b/src/utils/sha1.js @@ -0,0 +1,26 @@ +/** @flow */ + +const crypto = require('crypto'); + +const sha1 = { + is: validateSha, + encode +}; + +/* + * Validates a SHA in hexadecimal. + */ +function validateSha(str: string): boolean { + return /[0-9a-f]{40}/.test(str); +} + +/* + * Encode content as sha. + */ +function encode(s: Buffer): string { + const shasum = crypto.createHash('sha1'); + shasum.update(s); + return shasum.digest('hex'); +} + +export default sha1; diff --git a/src/utils/zlib.js b/src/utils/zlib.js new file mode 100644 index 0000000..600cb21 --- /dev/null +++ b/src/utils/zlib.js @@ -0,0 +1,29 @@ +/** @flow */ +import pako from 'pako'; +import uint8ToBuffer from 'typedarray-to-buffer'; +import bufferToUint8 from 'buffer-to-uint8array'; + +/* + * Decompress a gzip buffer. + */ +function unzip(buf: Buffer): Buffer { + const input = bufferToUint8(buf); + const output = pako.inflate(input); + + return uint8ToBuffer(output); +} + +/** + * Compress a buffer. + */ +function zip(buf: Buffer): Buffer { + const input = bufferToUint8(buf); + const output = pako.deflate(input); + + return uint8ToBuffer(output); +} + +export default { + zip, + unzip +}; diff --git a/testing/data/discovery-http-output b/testing/data/discovery-http-output deleted file mode 100644 index ee28a8c..0000000 Binary files a/testing/data/discovery-http-output and /dev/null differ diff --git a/testing/data/index b/testing/data/index deleted file mode 100644 index a15ea01..0000000 Binary files a/testing/data/index and /dev/null differ diff --git a/testing/data/index.js b/testing/data/index.js deleted file mode 100644 index 776f67a..0000000 --- a/testing/data/index.js +++ /dev/null @@ -1,39 +0,0 @@ -var fs = require('fs'); -var path = require('path'); - -/* - Return path to a fixture - - @param {String} - @return {String} -*/ -function fixturePath(name) { - return path.resolve(__dirname, name); -} - - -/* - Read a fixture as a stream - - @param {String} - @return {Stream} -*/ -function createReadStream(name) { - return fs.createReadStream(fixturePath(name)); -} - -/* - Read a fixture as a buffer - - @param {String} - @return {Buffer} -*/ -function read(name) { - return fs.readFileSync(fixturePath(name)); -} - -module.exports = { - path: fixturePath, - read: read, - createReadStream: createReadStream -}; diff --git a/testing/data/object-tree b/testing/data/object-tree deleted file mode 100644 index 0a6bc7e..0000000 Binary files a/testing/data/object-tree and /dev/null differ diff --git a/testing/data/pack-http-output b/testing/data/pack-http-output deleted file mode 100644 index 7188eb5..0000000 Binary files a/testing/data/pack-http-output and /dev/null differ diff --git a/testing/fixtures.js b/testing/fixtures.js deleted file mode 100644 index e5a4d9f..0000000 --- a/testing/fixtures.js +++ /dev/null @@ -1,39 +0,0 @@ -var fs = require('fs'); -var path = require('path'); - -/* - Return path to a fixture - - @param {String} - @return {String} -*/ -function fixturePath(name) { - return path.resolve(__dirname, 'data', name); -} - - -/* - Read a fixture as a stream - - @param {String} - @return {Stream} -*/ -function createReadStream(name) { - return fs.createReadStream(fixturePath(name)); -} - -/* - Read a fixture as a buffer - - @param {String} - @return {Buffer} -*/ -function read(name) { - return fs.readFileSync(fixturePath(name)); -} - -module.exports = { - path: fixturePath, - read: read, - createReadStream: createReadStream -}; diff --git a/testing/setup.js b/testing/setup.js deleted file mode 100644 index 1a8f935..0000000 --- a/testing/setup.js +++ /dev/null @@ -1,6 +0,0 @@ -var expect = require('expect'); -var fixtures = require('./fixtures'); - -global.expect = expect; -global.fixtures = fixtures; - diff --git a/yarn.lock b/yarn.lock new file mode 100644 index 0000000..f63f77e --- /dev/null +++ b/yarn.lock @@ -0,0 +1,3506 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +abab@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/abab/-/abab-1.0.3.tgz#b81de5f7274ec4e756d797cd834f303642724e5d" + +abbrev@1: + version "1.1.0" + resolved "https://registry.yarnpkg.com/abbrev/-/abbrev-1.1.0.tgz#d0554c2256636e2f56e7c2e5ad183f859428d81f" + +acorn-globals@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-3.1.0.tgz#fd8270f71fbb4996b004fa880ee5d46573a731bf" + dependencies: + acorn "^4.0.4" + +acorn-jsx@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-3.0.1.tgz#afdf9488fb1ecefc8348f6fb22f464e32a58b36b" + dependencies: + acorn "^3.0.4" + +acorn@^3.0.4: + version "3.3.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-3.3.0.tgz#45e37fb39e8da3f25baee3ff5369e2bb5f22017a" + +acorn@^4.0.4: + version "4.0.13" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-4.0.13.tgz#105495ae5361d697bd195c825192e1ad7f253787" + +acorn@^5.0.1: + version "5.0.3" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.0.3.tgz#c460df08491463f028ccb82eab3730bf01087b3d" + +ajv-keywords@^1.0.0: + version "1.5.1" + resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-1.5.1.tgz#314dd0a4b3368fad3dfcdc54ede6171b886daf3c" + +ajv@^4.7.0, ajv@^4.9.1: + version "4.11.8" + resolved "https://registry.yarnpkg.com/ajv/-/ajv-4.11.8.tgz#82ffb02b29e662ae53bdc20af15947706739c536" + dependencies: + co "^4.6.0" + json-stable-stringify "^1.0.1" + +align-text@^0.1.1, align-text@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" + dependencies: + kind-of "^3.0.2" + longest "^1.0.1" + repeat-string "^1.5.2" + +amdefine@>=0.0.4: + version "1.0.1" + resolved "https://registry.yarnpkg.com/amdefine/-/amdefine-1.0.1.tgz#4a5282ac164729e93619bcfd3ad151f817ce91f5" + +ansi-escapes@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-1.4.0.tgz#d3a8a83b319aa67793662b13e761c7911422306e" + +ansi-escapes@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-2.0.0.tgz#5bae52be424878dd9783e8910e3fc2922e83c81b" + +ansi-regex@^2.0.0, ansi-regex@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df" + +ansi-styles@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe" + +ansi-styles@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.1.0.tgz#09c202d5c917ec23188caa5c9cb9179cd9547750" + dependencies: + color-convert "^1.0.0" + +anymatch@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-1.3.0.tgz#a3e52fa39168c825ff57b0248126ce5a8ff95507" + dependencies: + arrify "^1.0.0" + micromatch "^2.1.5" + +append-transform@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/append-transform/-/append-transform-0.4.0.tgz#d76ebf8ca94d276e247a36bad44a4b74ab611991" + dependencies: + default-require-extensions "^1.0.0" + +aproba@^1.0.3: + version "1.1.2" + resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.1.2.tgz#45c6629094de4e96f693ef7eab74ae079c240fc1" + +are-we-there-yet@~1.1.2: + version "1.1.4" + resolved "https://registry.yarnpkg.com/are-we-there-yet/-/are-we-there-yet-1.1.4.tgz#bb5dca382bb94f05e15194373d16fd3ba1ca110d" + dependencies: + delegates "^1.0.0" + readable-stream "^2.0.6" + +argparse@^1.0.7: + version "1.0.9" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" + dependencies: + sprintf-js "~1.0.2" + +arr-diff@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-2.0.0.tgz#8f3b827f955a8bd669697e4a4256ac3ceae356cf" + dependencies: + arr-flatten "^1.0.1" + +arr-flatten@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/arr-flatten/-/arr-flatten-1.0.3.tgz#a274ed85ac08849b6bd7847c4580745dc51adfb1" + +array-equal@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/array-equal/-/array-equal-1.0.0.tgz#8c2a5ef2472fd9ea742b04c77a75093ba2757c93" + +array-union@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" + dependencies: + array-uniq "^1.0.1" + +array-uniq@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/array-uniq/-/array-uniq-1.0.3.tgz#af6ac877a25cc7f74e058894753858dfdb24fdb6" + +array-unique@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/array-unique/-/array-unique-0.2.1.tgz#a1d97ccafcbc2625cc70fadceb36a50c58b01a53" + +arrify@^1.0.0, arrify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/arrify/-/arrify-1.0.1.tgz#898508da2226f380df904728456849c1501a4b0d" + +asn1@~0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/asn1/-/asn1-0.2.3.tgz#dac8787713c9966849fc8180777ebe9c1ddf3b86" + +assert-plus@1.0.0, assert-plus@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-1.0.0.tgz#f12e0f3c5d77b0b1cdd9146942e4e96c1e4dd525" + +assert-plus@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/assert-plus/-/assert-plus-0.2.0.tgz#d74e1b87e7affc0db8aadb7021f3fe48101ab234" + +async-each@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/async-each/-/async-each-1.0.1.tgz#19d386a1d9edc6e7c1c85d388aedbcc56d33602d" + +async@^1.4.0: + version "1.5.2" + resolved "https://registry.yarnpkg.com/async/-/async-1.5.2.tgz#ec6a61ae56480c0c3cb241c95618e20892f9672a" + +async@^2.1.4: + version "2.4.1" + resolved "https://registry.yarnpkg.com/async/-/async-2.4.1.tgz#62a56b279c98a11d0987096a01cc3eeb8eb7bbd7" + dependencies: + lodash "^4.14.0" + +asynckit@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" + +aws-sign2@~0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.6.0.tgz#14342dd38dbcc94d0e5b87d763cd63612c0e794f" + +aws4@^1.2.1: + version "1.6.0" + resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e" + +babel-cli@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-cli/-/babel-cli-6.24.1.tgz#207cd705bba61489b2ea41b5312341cf6aca2283" + dependencies: + babel-core "^6.24.1" + babel-polyfill "^6.23.0" + babel-register "^6.24.1" + babel-runtime "^6.22.0" + commander "^2.8.1" + convert-source-map "^1.1.0" + fs-readdir-recursive "^1.0.0" + glob "^7.0.0" + lodash "^4.2.0" + output-file-sync "^1.1.0" + path-is-absolute "^1.0.0" + slash "^1.0.0" + source-map "^0.5.0" + v8flags "^2.0.10" + optionalDependencies: + chokidar "^1.6.1" + +babel-code-frame@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-code-frame/-/babel-code-frame-6.22.0.tgz#027620bee567a88c32561574e7fd0801d33118e4" + dependencies: + chalk "^1.1.0" + esutils "^2.0.2" + js-tokens "^3.0.0" + +babel-core@^6.0.0, babel-core@^6.24.1: + version "6.25.0" + resolved "https://registry.yarnpkg.com/babel-core/-/babel-core-6.25.0.tgz#7dd42b0463c742e9d5296deb3ec67a9322dad729" + dependencies: + babel-code-frame "^6.22.0" + babel-generator "^6.25.0" + babel-helpers "^6.24.1" + babel-messages "^6.23.0" + babel-register "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.25.0" + babel-traverse "^6.25.0" + babel-types "^6.25.0" + babylon "^6.17.2" + convert-source-map "^1.1.0" + debug "^2.1.1" + json5 "^0.5.0" + lodash "^4.2.0" + minimatch "^3.0.2" + path-is-absolute "^1.0.0" + private "^0.1.6" + slash "^1.0.0" + source-map "^0.5.0" + +babel-eslint@^7.2.3: + version "7.2.3" + resolved "https://registry.yarnpkg.com/babel-eslint/-/babel-eslint-7.2.3.tgz#b2fe2d80126470f5c19442dc757253a897710827" + dependencies: + babel-code-frame "^6.22.0" + babel-traverse "^6.23.1" + babel-types "^6.23.0" + babylon "^6.17.0" + +babel-generator@^6.18.0, babel-generator@^6.25.0: + version "6.25.0" + resolved "https://registry.yarnpkg.com/babel-generator/-/babel-generator-6.25.0.tgz#33a1af70d5f2890aeb465a4a7793c1df6a9ea9fc" + dependencies: + babel-messages "^6.23.0" + babel-runtime "^6.22.0" + babel-types "^6.25.0" + detect-indent "^4.0.0" + jsesc "^1.3.0" + lodash "^4.2.0" + source-map "^0.5.0" + trim-right "^1.0.1" + +babel-helper-bindify-decorators@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-bindify-decorators/-/babel-helper-bindify-decorators-6.24.1.tgz#14c19e5f142d7b47f19a52431e52b1ccbc40a330" + dependencies: + babel-runtime "^6.22.0" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-builder-binary-assignment-operator-visitor@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-builder-binary-assignment-operator-visitor/-/babel-helper-builder-binary-assignment-operator-visitor-6.24.1.tgz#cce4517ada356f4220bcae8a02c2b346f9a56664" + dependencies: + babel-helper-explode-assignable-expression "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-call-delegate@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-call-delegate/-/babel-helper-call-delegate-6.24.1.tgz#ece6aacddc76e41c3461f88bfc575bd0daa2df8d" + dependencies: + babel-helper-hoist-variables "^6.24.1" + babel-runtime "^6.22.0" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-define-map@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-define-map/-/babel-helper-define-map-6.24.1.tgz#7a9747f258d8947d32d515f6aa1c7bd02204a080" + dependencies: + babel-helper-function-name "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + lodash "^4.2.0" + +babel-helper-explode-assignable-expression@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-explode-assignable-expression/-/babel-helper-explode-assignable-expression-6.24.1.tgz#f25b82cf7dc10433c55f70592d5746400ac22caa" + dependencies: + babel-runtime "^6.22.0" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-explode-class@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-explode-class/-/babel-helper-explode-class-6.24.1.tgz#7dc2a3910dee007056e1e31d640ced3d54eaa9eb" + dependencies: + babel-helper-bindify-decorators "^6.24.1" + babel-runtime "^6.22.0" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-function-name@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-function-name/-/babel-helper-function-name-6.24.1.tgz#d3475b8c03ed98242a25b48351ab18399d3580a9" + dependencies: + babel-helper-get-function-arity "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-get-function-arity@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-get-function-arity/-/babel-helper-get-function-arity-6.24.1.tgz#8f7782aa93407c41d3aa50908f89b031b1b6853d" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-hoist-variables@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-hoist-variables/-/babel-helper-hoist-variables-6.24.1.tgz#1ecb27689c9d25513eadbc9914a73f5408be7a76" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-optimise-call-expression@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-optimise-call-expression/-/babel-helper-optimise-call-expression-6.24.1.tgz#f7a13427ba9f73f8f4fa993c54a97882d1244257" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-helper-regex@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-regex/-/babel-helper-regex-6.24.1.tgz#d36e22fab1008d79d88648e32116868128456ce8" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + lodash "^4.2.0" + +babel-helper-remap-async-to-generator@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-remap-async-to-generator/-/babel-helper-remap-async-to-generator-6.24.1.tgz#5ec581827ad723fecdd381f1c928390676e4551b" + dependencies: + babel-helper-function-name "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helper-replace-supers@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helper-replace-supers/-/babel-helper-replace-supers-6.24.1.tgz#bf6dbfe43938d17369a213ca8a8bf74b6a90ab1a" + dependencies: + babel-helper-optimise-call-expression "^6.24.1" + babel-messages "^6.23.0" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-helpers@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-helpers/-/babel-helpers-6.24.1.tgz#3471de9caec388e5c850e597e58a26ddf37602b2" + dependencies: + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-jest@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-20.0.3.tgz#e4a03b13dc10389e140fc645d09ffc4ced301671" + dependencies: + babel-core "^6.0.0" + babel-plugin-istanbul "^4.0.0" + babel-preset-jest "^20.0.3" + +babel-messages@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-messages/-/babel-messages-6.23.0.tgz#f3cdf4703858035b2a2951c6ec5edf6c62f2630e" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-check-es2015-constants@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-check-es2015-constants/-/babel-plugin-check-es2015-constants-6.22.0.tgz#35157b101426fd2ffd3da3f75c7d1e91835bbf8a" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-istanbul@^4.0.0: + version "4.1.4" + resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-4.1.4.tgz#18dde84bf3ce329fddf3f4103fae921456d8e587" + dependencies: + find-up "^2.1.0" + istanbul-lib-instrument "^1.7.2" + test-exclude "^4.1.1" + +babel-plugin-jest-hoist@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-20.0.3.tgz#afedc853bd3f8dc3548ea671fbe69d03cc2c1767" + +babel-plugin-syntax-async-functions@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-functions/-/babel-plugin-syntax-async-functions-6.13.0.tgz#cad9cad1191b5ad634bf30ae0872391e0647be95" + +babel-plugin-syntax-async-generators@^6.5.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-async-generators/-/babel-plugin-syntax-async-generators-6.13.0.tgz#6bc963ebb16eccbae6b92b596eb7f35c342a8b9a" + +babel-plugin-syntax-class-constructor-call@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-constructor-call/-/babel-plugin-syntax-class-constructor-call-6.18.0.tgz#9cb9d39fe43c8600bec8146456ddcbd4e1a76416" + +babel-plugin-syntax-class-properties@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-class-properties/-/babel-plugin-syntax-class-properties-6.13.0.tgz#d7eb23b79a317f8543962c505b827c7d6cac27de" + +babel-plugin-syntax-decorators@^6.13.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-decorators/-/babel-plugin-syntax-decorators-6.13.0.tgz#312563b4dbde3cc806cee3e416cceeaddd11ac0b" + +babel-plugin-syntax-do-expressions@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-do-expressions/-/babel-plugin-syntax-do-expressions-6.13.0.tgz#5747756139aa26d390d09410b03744ba07e4796d" + +babel-plugin-syntax-dynamic-import@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-dynamic-import/-/babel-plugin-syntax-dynamic-import-6.18.0.tgz#8d6a26229c83745a9982a441051572caa179b1da" + +babel-plugin-syntax-exponentiation-operator@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-exponentiation-operator/-/babel-plugin-syntax-exponentiation-operator-6.13.0.tgz#9ee7e8337290da95288201a6a57f4170317830de" + +babel-plugin-syntax-export-extensions@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-export-extensions/-/babel-plugin-syntax-export-extensions-6.13.0.tgz#70a1484f0f9089a4e84ad44bac353c95b9b12721" + +babel-plugin-syntax-flow@^6.18.0: + version "6.18.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-flow/-/babel-plugin-syntax-flow-6.18.0.tgz#4c3ab20a2af26aa20cd25995c398c4eb70310c8d" + +babel-plugin-syntax-function-bind@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-function-bind/-/babel-plugin-syntax-function-bind-6.13.0.tgz#48c495f177bdf31a981e732f55adc0bdd2601f46" + +babel-plugin-syntax-object-rest-spread@^6.8.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-object-rest-spread/-/babel-plugin-syntax-object-rest-spread-6.13.0.tgz#fd6536f2bce13836ffa3a5458c4903a597bb3bf5" + +babel-plugin-syntax-trailing-function-commas@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-syntax-trailing-function-commas/-/babel-plugin-syntax-trailing-function-commas-6.22.0.tgz#ba0360937f8d06e40180a43fe0d5616fff532cf3" + +babel-plugin-transform-async-generator-functions@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-generator-functions/-/babel-plugin-transform-async-generator-functions-6.24.1.tgz#f058900145fd3e9907a6ddf28da59f215258a5db" + dependencies: + babel-helper-remap-async-to-generator "^6.24.1" + babel-plugin-syntax-async-generators "^6.5.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-async-to-generator@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-async-to-generator/-/babel-plugin-transform-async-to-generator-6.24.1.tgz#6536e378aff6cb1d5517ac0e40eb3e9fc8d08761" + dependencies: + babel-helper-remap-async-to-generator "^6.24.1" + babel-plugin-syntax-async-functions "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-class-constructor-call@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-constructor-call/-/babel-plugin-transform-class-constructor-call-6.24.1.tgz#80dc285505ac067dcb8d6c65e2f6f11ab7765ef9" + dependencies: + babel-plugin-syntax-class-constructor-call "^6.18.0" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-class-properties@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-class-properties/-/babel-plugin-transform-class-properties-6.24.1.tgz#6a79763ea61d33d36f37b611aa9def81a81b46ac" + dependencies: + babel-helper-function-name "^6.24.1" + babel-plugin-syntax-class-properties "^6.8.0" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-decorators@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-decorators/-/babel-plugin-transform-decorators-6.24.1.tgz#788013d8f8c6b5222bdf7b344390dfd77569e24d" + dependencies: + babel-helper-explode-class "^6.24.1" + babel-plugin-syntax-decorators "^6.13.0" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-types "^6.24.1" + +babel-plugin-transform-do-expressions@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-do-expressions/-/babel-plugin-transform-do-expressions-6.22.0.tgz#28ccaf92812d949c2cd1281f690c8fdc468ae9bb" + dependencies: + babel-plugin-syntax-do-expressions "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-arrow-functions@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-arrow-functions/-/babel-plugin-transform-es2015-arrow-functions-6.22.0.tgz#452692cb711d5f79dc7f85e440ce41b9f244d221" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-block-scoped-functions@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoped-functions/-/babel-plugin-transform-es2015-block-scoped-functions-6.22.0.tgz#bbc51b49f964d70cb8d8e0b94e820246ce3a6141" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-block-scoping@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-block-scoping/-/babel-plugin-transform-es2015-block-scoping-6.24.1.tgz#76c295dc3a4741b1665adfd3167215dcff32a576" + dependencies: + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + lodash "^4.2.0" + +babel-plugin-transform-es2015-classes@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-classes/-/babel-plugin-transform-es2015-classes-6.24.1.tgz#5a4c58a50c9c9461e564b4b2a3bfabc97a2584db" + dependencies: + babel-helper-define-map "^6.24.1" + babel-helper-function-name "^6.24.1" + babel-helper-optimise-call-expression "^6.24.1" + babel-helper-replace-supers "^6.24.1" + babel-messages "^6.23.0" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-computed-properties@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-computed-properties/-/babel-plugin-transform-es2015-computed-properties-6.24.1.tgz#6fe2a8d16895d5634f4cd999b6d3480a308159b3" + dependencies: + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-destructuring@^6.22.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-destructuring/-/babel-plugin-transform-es2015-destructuring-6.23.0.tgz#997bb1f1ab967f682d2b0876fe358d60e765c56d" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-duplicate-keys@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-duplicate-keys/-/babel-plugin-transform-es2015-duplicate-keys-6.24.1.tgz#73eb3d310ca969e3ef9ec91c53741a6f1576423e" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-for-of@^6.22.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-for-of/-/babel-plugin-transform-es2015-for-of-6.23.0.tgz#f47c95b2b613df1d3ecc2fdb7573623c75248691" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-function-name@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-function-name/-/babel-plugin-transform-es2015-function-name-6.24.1.tgz#834c89853bc36b1af0f3a4c5dbaa94fd8eacaa8b" + dependencies: + babel-helper-function-name "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-literals@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-literals/-/babel-plugin-transform-es2015-literals-6.22.0.tgz#4f54a02d6cd66cf915280019a31d31925377ca2e" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-modules-amd@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-amd/-/babel-plugin-transform-es2015-modules-amd-6.24.1.tgz#3b3e54017239842d6d19c3011c4bd2f00a00d154" + dependencies: + babel-plugin-transform-es2015-modules-commonjs "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-modules-commonjs@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-commonjs/-/babel-plugin-transform-es2015-modules-commonjs-6.24.1.tgz#d3e310b40ef664a36622200097c6d440298f2bfe" + dependencies: + babel-plugin-transform-strict-mode "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-modules-systemjs@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-systemjs/-/babel-plugin-transform-es2015-modules-systemjs-6.24.1.tgz#ff89a142b9119a906195f5f106ecf305d9407d23" + dependencies: + babel-helper-hoist-variables "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-modules-umd@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-modules-umd/-/babel-plugin-transform-es2015-modules-umd-6.24.1.tgz#ac997e6285cd18ed6176adb607d602344ad38468" + dependencies: + babel-plugin-transform-es2015-modules-amd "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + +babel-plugin-transform-es2015-object-super@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-object-super/-/babel-plugin-transform-es2015-object-super-6.24.1.tgz#24cef69ae21cb83a7f8603dad021f572eb278f8d" + dependencies: + babel-helper-replace-supers "^6.24.1" + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-parameters@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-parameters/-/babel-plugin-transform-es2015-parameters-6.24.1.tgz#57ac351ab49caf14a97cd13b09f66fdf0a625f2b" + dependencies: + babel-helper-call-delegate "^6.24.1" + babel-helper-get-function-arity "^6.24.1" + babel-runtime "^6.22.0" + babel-template "^6.24.1" + babel-traverse "^6.24.1" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-shorthand-properties@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-shorthand-properties/-/babel-plugin-transform-es2015-shorthand-properties-6.24.1.tgz#24f875d6721c87661bbd99a4622e51f14de38aa0" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-spread@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-spread/-/babel-plugin-transform-es2015-spread-6.22.0.tgz#d6d68a99f89aedc4536c81a542e8dd9f1746f8d1" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-sticky-regex@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-sticky-regex/-/babel-plugin-transform-es2015-sticky-regex-6.24.1.tgz#00c1cdb1aca71112cdf0cf6126c2ed6b457ccdbc" + dependencies: + babel-helper-regex "^6.24.1" + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-plugin-transform-es2015-template-literals@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-template-literals/-/babel-plugin-transform-es2015-template-literals-6.22.0.tgz#a84b3450f7e9f8f1f6839d6d687da84bb1236d8d" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-typeof-symbol@^6.22.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-typeof-symbol/-/babel-plugin-transform-es2015-typeof-symbol-6.23.0.tgz#dec09f1cddff94b52ac73d505c84df59dcceb372" + dependencies: + babel-runtime "^6.22.0" + +babel-plugin-transform-es2015-unicode-regex@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-es2015-unicode-regex/-/babel-plugin-transform-es2015-unicode-regex-6.24.1.tgz#d38b12f42ea7323f729387f18a7c5ae1faeb35e9" + dependencies: + babel-helper-regex "^6.24.1" + babel-runtime "^6.22.0" + regexpu-core "^2.0.0" + +babel-plugin-transform-exponentiation-operator@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-exponentiation-operator/-/babel-plugin-transform-exponentiation-operator-6.24.1.tgz#2ab0c9c7f3098fa48907772bb813fe41e8de3a0e" + dependencies: + babel-helper-builder-binary-assignment-operator-visitor "^6.24.1" + babel-plugin-syntax-exponentiation-operator "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-export-extensions@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-export-extensions/-/babel-plugin-transform-export-extensions-6.22.0.tgz#53738b47e75e8218589eea946cbbd39109bbe653" + dependencies: + babel-plugin-syntax-export-extensions "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-flow-strip-types@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-flow-strip-types/-/babel-plugin-transform-flow-strip-types-6.22.0.tgz#84cb672935d43714fdc32bce84568d87441cf7cf" + dependencies: + babel-plugin-syntax-flow "^6.18.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-function-bind@^6.22.0: + version "6.22.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-function-bind/-/babel-plugin-transform-function-bind-6.22.0.tgz#c6fb8e96ac296a310b8cf8ea401462407ddf6a97" + dependencies: + babel-plugin-syntax-function-bind "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-object-rest-spread@^6.22.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-object-rest-spread/-/babel-plugin-transform-object-rest-spread-6.23.0.tgz#875d6bc9be761c58a2ae3feee5dc4895d8c7f921" + dependencies: + babel-plugin-syntax-object-rest-spread "^6.8.0" + babel-runtime "^6.22.0" + +babel-plugin-transform-regenerator@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-regenerator/-/babel-plugin-transform-regenerator-6.24.1.tgz#b8da305ad43c3c99b4848e4fe4037b770d23c418" + dependencies: + regenerator-transform "0.9.11" + +babel-plugin-transform-strict-mode@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-plugin-transform-strict-mode/-/babel-plugin-transform-strict-mode-6.24.1.tgz#d5faf7aa578a65bbe591cf5edae04a0c67020758" + dependencies: + babel-runtime "^6.22.0" + babel-types "^6.24.1" + +babel-polyfill@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.23.0.tgz#8364ca62df8eafb830499f699177466c3b03499d" + dependencies: + babel-runtime "^6.22.0" + core-js "^2.4.0" + regenerator-runtime "^0.10.0" + +babel-preset-es2015@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-preset-es2015/-/babel-preset-es2015-6.24.1.tgz#d44050d6bc2c9feea702aaf38d727a0210538939" + dependencies: + babel-plugin-check-es2015-constants "^6.22.0" + babel-plugin-transform-es2015-arrow-functions "^6.22.0" + babel-plugin-transform-es2015-block-scoped-functions "^6.22.0" + babel-plugin-transform-es2015-block-scoping "^6.24.1" + babel-plugin-transform-es2015-classes "^6.24.1" + babel-plugin-transform-es2015-computed-properties "^6.24.1" + babel-plugin-transform-es2015-destructuring "^6.22.0" + babel-plugin-transform-es2015-duplicate-keys "^6.24.1" + babel-plugin-transform-es2015-for-of "^6.22.0" + babel-plugin-transform-es2015-function-name "^6.24.1" + babel-plugin-transform-es2015-literals "^6.22.0" + babel-plugin-transform-es2015-modules-amd "^6.24.1" + babel-plugin-transform-es2015-modules-commonjs "^6.24.1" + babel-plugin-transform-es2015-modules-systemjs "^6.24.1" + babel-plugin-transform-es2015-modules-umd "^6.24.1" + babel-plugin-transform-es2015-object-super "^6.24.1" + babel-plugin-transform-es2015-parameters "^6.24.1" + babel-plugin-transform-es2015-shorthand-properties "^6.24.1" + babel-plugin-transform-es2015-spread "^6.22.0" + babel-plugin-transform-es2015-sticky-regex "^6.24.1" + babel-plugin-transform-es2015-template-literals "^6.22.0" + babel-plugin-transform-es2015-typeof-symbol "^6.22.0" + babel-plugin-transform-es2015-unicode-regex "^6.24.1" + babel-plugin-transform-regenerator "^6.24.1" + +babel-preset-flow@^6.23.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-preset-flow/-/babel-preset-flow-6.23.0.tgz#e71218887085ae9a24b5be4169affb599816c49d" + dependencies: + babel-plugin-transform-flow-strip-types "^6.22.0" + +babel-preset-jest@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-20.0.3.tgz#cbacaadecb5d689ca1e1de1360ebfc66862c178a" + dependencies: + babel-plugin-jest-hoist "^20.0.3" + +babel-preset-stage-0@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-preset-stage-0/-/babel-preset-stage-0-6.24.1.tgz#5642d15042f91384d7e5af8bc88b1db95b039e6a" + dependencies: + babel-plugin-transform-do-expressions "^6.22.0" + babel-plugin-transform-function-bind "^6.22.0" + babel-preset-stage-1 "^6.24.1" + +babel-preset-stage-1@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-preset-stage-1/-/babel-preset-stage-1-6.24.1.tgz#7692cd7dcd6849907e6ae4a0a85589cfb9e2bfb0" + dependencies: + babel-plugin-transform-class-constructor-call "^6.24.1" + babel-plugin-transform-export-extensions "^6.22.0" + babel-preset-stage-2 "^6.24.1" + +babel-preset-stage-2@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-preset-stage-2/-/babel-preset-stage-2-6.24.1.tgz#d9e2960fb3d71187f0e64eec62bc07767219bdc1" + dependencies: + babel-plugin-syntax-dynamic-import "^6.18.0" + babel-plugin-transform-class-properties "^6.24.1" + babel-plugin-transform-decorators "^6.24.1" + babel-preset-stage-3 "^6.24.1" + +babel-preset-stage-3@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-preset-stage-3/-/babel-preset-stage-3-6.24.1.tgz#836ada0a9e7a7fa37cb138fb9326f87934a48395" + dependencies: + babel-plugin-syntax-trailing-function-commas "^6.22.0" + babel-plugin-transform-async-generator-functions "^6.24.1" + babel-plugin-transform-async-to-generator "^6.24.1" + babel-plugin-transform-exponentiation-operator "^6.24.1" + babel-plugin-transform-object-rest-spread "^6.22.0" + +babel-register@^6.24.1: + version "6.24.1" + resolved "https://registry.yarnpkg.com/babel-register/-/babel-register-6.24.1.tgz#7e10e13a2f71065bdfad5a1787ba45bca6ded75f" + dependencies: + babel-core "^6.24.1" + babel-runtime "^6.22.0" + core-js "^2.4.0" + home-or-tmp "^2.0.0" + lodash "^4.2.0" + mkdirp "^0.5.1" + source-map-support "^0.4.2" + +babel-runtime@^6.18.0, babel-runtime@^6.22.0: + version "6.23.0" + resolved "https://registry.yarnpkg.com/babel-runtime/-/babel-runtime-6.23.0.tgz#0a9489f144de70efb3ce4300accdb329e2fc543b" + dependencies: + core-js "^2.4.0" + regenerator-runtime "^0.10.0" + +babel-template@^6.16.0, babel-template@^6.24.1, babel-template@^6.25.0: + version "6.25.0" + resolved "https://registry.yarnpkg.com/babel-template/-/babel-template-6.25.0.tgz#665241166b7c2aa4c619d71e192969552b10c071" + dependencies: + babel-runtime "^6.22.0" + babel-traverse "^6.25.0" + babel-types "^6.25.0" + babylon "^6.17.2" + lodash "^4.2.0" + +babel-traverse@^6.18.0, babel-traverse@^6.23.1, babel-traverse@^6.24.1, babel-traverse@^6.25.0: + version "6.25.0" + resolved "https://registry.yarnpkg.com/babel-traverse/-/babel-traverse-6.25.0.tgz#2257497e2fcd19b89edc13c4c91381f9512496f1" + dependencies: + babel-code-frame "^6.22.0" + babel-messages "^6.23.0" + babel-runtime "^6.22.0" + babel-types "^6.25.0" + babylon "^6.17.2" + debug "^2.2.0" + globals "^9.0.0" + invariant "^2.2.0" + lodash "^4.2.0" + +babel-types@^6.18.0, babel-types@^6.19.0, babel-types@^6.23.0, babel-types@^6.24.1, babel-types@^6.25.0: + version "6.25.0" + resolved "https://registry.yarnpkg.com/babel-types/-/babel-types-6.25.0.tgz#70afb248d5660e5d18f811d91c8303b54134a18e" + dependencies: + babel-runtime "^6.22.0" + esutils "^2.0.2" + lodash "^4.2.0" + to-fast-properties "^1.0.1" + +babylon@^6.13.0, babylon@^6.17.0, babylon@^6.17.2: + version "6.17.3" + resolved "https://registry.yarnpkg.com/babylon/-/babylon-6.17.3.tgz#1327d709950b558f204e5352587fd0290f8d8e48" + +balanced-match@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" + +bcrypt-pbkdf@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.1.tgz#63bc5dcb61331b92bc05fd528953c33462a06f8d" + dependencies: + tweetnacl "^0.14.3" + +binary-extensions@^1.0.0: + version "1.8.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-1.8.0.tgz#48ec8d16df4377eae5fa5884682480af4d95c774" + +bl@^1.0.0: + version "1.2.1" + resolved "https://registry.yarnpkg.com/bl/-/bl-1.2.1.tgz#cac328f7bee45730d404b692203fcb590e172d5e" + dependencies: + readable-stream "^2.0.5" + +block-stream@*: + version "0.0.9" + resolved "https://registry.yarnpkg.com/block-stream/-/block-stream-0.0.9.tgz#13ebfe778a03205cfe03751481ebb4b3300c126a" + dependencies: + inherits "~2.0.0" + +boom@2.x.x: + version "2.10.1" + resolved "https://registry.yarnpkg.com/boom/-/boom-2.10.1.tgz#39c8918ceff5799f83f9492a848f625add0c766f" + dependencies: + hoek "2.x.x" + +brace-expansion@^1.1.7: + version "1.1.8" + resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.8.tgz#c07b211c7c952ec1f8efd51a77ef0d1d3990a292" + dependencies: + balanced-match "^1.0.0" + concat-map "0.0.1" + +braces@^1.8.2: + version "1.8.5" + resolved "https://registry.yarnpkg.com/braces/-/braces-1.8.5.tgz#ba77962e12dff969d6b76711e914b737857bf6a7" + dependencies: + expand-range "^1.8.1" + preserve "^0.2.0" + repeat-element "^1.1.2" + +browser-resolve@^1.11.2: + version "1.11.2" + resolved "https://registry.yarnpkg.com/browser-resolve/-/browser-resolve-1.11.2.tgz#8ff09b0a2c421718a1051c260b32e48f442938ce" + dependencies: + resolve "1.1.7" + +bser@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/bser/-/bser-1.0.2.tgz#381116970b2a6deea5646dd15dd7278444b56169" + dependencies: + node-int64 "^0.4.0" + +bser@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/bser/-/bser-2.0.0.tgz#9ac78d3ed5d915804fd87acb158bc797147a1719" + dependencies: + node-int64 "^0.4.0" + +buffer-to-uint8array@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/buffer-to-uint8array/-/buffer-to-uint8array-1.1.0.tgz#cf6f41287c022f458da752c391c1a8d535ec5f72" + +builtin-modules@^1.0.0, builtin-modules@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/builtin-modules/-/builtin-modules-1.1.1.tgz#270f076c5a72c02f5b65a47df94c5fe3a278892f" + +caller-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/caller-path/-/caller-path-0.1.0.tgz#94085ef63581ecd3daa92444a8fe94e82577751f" + dependencies: + callsites "^0.2.0" + +callsites@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-0.2.0.tgz#afab96262910a7f33c19a5775825c69f34e350ca" + +callsites@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50" + +camelcase@^1.0.2: + version "1.2.1" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-1.2.1.tgz#9bb5304d2e0b56698b2c758b08a3eaa9daa58a39" + +camelcase@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-3.0.0.tgz#32fc4b9fcdaf845fcdf7e73bb97cac2261f0ab0a" + +caseless@~0.12.0: + version "0.12.0" + resolved "https://registry.yarnpkg.com/caseless/-/caseless-0.12.0.tgz#1b681c21ff84033c826543090689420d187151dc" + +center-align@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/center-align/-/center-align-0.1.3.tgz#aa0d32629b6ee972200411cbd4461c907bc2b7ad" + dependencies: + align-text "^0.1.3" + lazy-cache "^1.0.3" + +chalk@^1.0.0, chalk@^1.1.0, chalk@^1.1.1, chalk@^1.1.3: + version "1.1.3" + resolved "https://registry.yarnpkg.com/chalk/-/chalk-1.1.3.tgz#a8115c55e4a702fe4d150abd3872822a7e09fc98" + dependencies: + ansi-styles "^2.2.1" + escape-string-regexp "^1.0.2" + has-ansi "^2.0.0" + strip-ansi "^3.0.0" + supports-color "^2.0.0" + +chokidar@^1.6.1: + version "1.7.0" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-1.7.0.tgz#798e689778151c8076b4b360e5edd28cda2bb468" + dependencies: + anymatch "^1.3.0" + async-each "^1.0.0" + glob-parent "^2.0.0" + inherits "^2.0.1" + is-binary-path "^1.0.0" + is-glob "^2.0.0" + path-is-absolute "^1.0.0" + readdirp "^2.0.0" + optionalDependencies: + fsevents "^1.0.0" + +ci-info@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-1.0.0.tgz#dc5285f2b4e251821683681c381c3388f46ec534" + +circular-json@^0.3.1: + version "0.3.1" + resolved "https://registry.yarnpkg.com/circular-json/-/circular-json-0.3.1.tgz#be8b36aefccde8b3ca7aa2d6afc07a37242c0d2d" + +cli-cursor@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-cursor/-/cli-cursor-2.1.0.tgz#b35dac376479facc3e94747d41d0d0f5238ffcb5" + dependencies: + restore-cursor "^2.0.0" + +cli-width@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cli-width/-/cli-width-2.1.0.tgz#b234ca209b29ef66fc518d9b98d5847b00edf00a" + +cliui@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-2.1.0.tgz#4b475760ff80264c762c3a1719032e91c7fea0d1" + dependencies: + center-align "^0.1.1" + right-align "^0.1.1" + wordwrap "0.0.2" + +cliui@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/cliui/-/cliui-3.2.0.tgz#120601537a916d29940f934da3b48d585a39213d" + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + wrap-ansi "^2.0.0" + +co@^4.6.0: + version "4.6.0" + resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184" + +code-point-at@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/code-point-at/-/code-point-at-1.1.0.tgz#0d070b4d043a5bea33a2f1a40e2edb3d9a4ccf77" + +color-convert@^1.0.0: + version "1.9.0" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.0.tgz#1accf97dd739b983bf994d56fec8f95853641b7a" + dependencies: + color-name "^1.1.1" + +color-name@^1.1.1: + version "1.1.2" + resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.2.tgz#5c8ab72b64bd2215d617ae9559ebb148475cf98d" + +combined-stream@^1.0.5, combined-stream@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" + dependencies: + delayed-stream "~1.0.0" + +commander@^2.8.1, commander@^2.9.0: + version "2.9.0" + resolved "https://registry.yarnpkg.com/commander/-/commander-2.9.0.tgz#9c99094176e12240cb22d6c5146098400fe0f7d4" + dependencies: + graceful-readlink ">= 1.0.0" + +concat-map@0.0.1: + version "0.0.1" + resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" + +concat-stream@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/concat-stream/-/concat-stream-1.6.0.tgz#0aac662fd52be78964d5532f694784e70110acf7" + dependencies: + inherits "^2.0.3" + readable-stream "^2.2.2" + typedarray "^0.0.6" + +concentrate@^0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/concentrate/-/concentrate-0.2.3.tgz#f97a0206521c2d7c45171b64cfc4d6f8b6302542" + +console-control-strings@^1.0.0, console-control-strings@~1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" + +contains-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" + +content-type-parser@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/content-type-parser/-/content-type-parser-1.0.1.tgz#c3e56988c53c65127fb46d4032a3a900246fdc94" + +convert-source-map@^1.1.0, convert-source-map@^1.4.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.5.0.tgz#9acd70851c6d5dfdd93d9282e5edf94a03ff46b5" + +core-js@^2.4.0: + version "2.4.1" + resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.4.1.tgz#4de911e667b0eae9124e34254b53aea6fc618d3e" + +core-util-is@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" + +cryptiles@2.x.x: + version "2.0.5" + resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-2.0.5.tgz#3bdfecdc608147c1c67202fa291e7dca59eaa3b8" + dependencies: + boom "2.x.x" + +cssom@0.3.x, "cssom@>= 0.3.2 < 0.4.0": + version "0.3.2" + resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.2.tgz#b8036170c79f07a90ff2f16e22284027a243848b" + +"cssstyle@>= 0.2.37 < 0.3.0": + version "0.2.37" + resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-0.2.37.tgz#541097234cb2513c83ceed3acddc27ff27987d54" + dependencies: + cssom "0.3.x" + +dashdash@^1.12.0: + version "1.14.1" + resolved "https://registry.yarnpkg.com/dashdash/-/dashdash-1.14.1.tgz#853cfa0f7cbe2fed5de20326b8dd581035f6e2f0" + dependencies: + assert-plus "^1.0.0" + +debug@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.2.0.tgz#f87057e995b1a1f6ae6a4960664137bc56f039da" + dependencies: + ms "0.7.1" + +debug@^2.1.1, debug@^2.2.0, debug@^2.6.3, debug@^2.6.8: + version "2.6.8" + resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.8.tgz#e731531ca2ede27d188222427da17821d68ff4fc" + dependencies: + ms "2.0.0" + +decamelize@^1.0.0, decamelize@^1.1.1: + version "1.2.0" + resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" + +deep-extend@~0.4.0: + version "0.4.2" + resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.4.2.tgz#48b699c27e334bf89f10892be432f6e4c7d34a7f" + +deep-is@~0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" + +default-require-extensions@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/default-require-extensions/-/default-require-extensions-1.0.0.tgz#f37ea15d3e13ffd9b437d33e1a75b5fb97874cb8" + dependencies: + strip-bom "^2.0.0" + +del@^2.0.2: + version "2.2.2" + resolved "https://registry.yarnpkg.com/del/-/del-2.2.2.tgz#c12c981d067846c84bcaf862cff930d907ffd1a8" + dependencies: + globby "^5.0.0" + is-path-cwd "^1.0.0" + is-path-in-cwd "^1.0.0" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + rimraf "^2.2.8" + +delayed-stream@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" + +delegates@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a" + +detect-indent@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/detect-indent/-/detect-indent-4.0.0.tgz#f76d064352cdf43a1cb6ce619c4ee3a9475de208" + dependencies: + repeating "^2.0.0" + +diff@^3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-3.2.0.tgz#c9ce393a4b7cbd0b058a725c93df299027868ff9" + +dissolve@deoxxa/dissolve#4fd1556d0199a27dd0a6f9f22cb6648194301454: + version "0.3.3" + resolved "https://codeload.github.com/deoxxa/dissolve/tar.gz/4fd1556d0199a27dd0a6f9f22cb6648194301454" + dependencies: + bl "^1.0.0" + readable-stream "^2.0.0" + +doctrine@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" + dependencies: + esutils "^2.0.2" + isarray "^1.0.0" + +doctrine@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-2.0.0.tgz#c73d8d2909d22291e1a007a395804da8b665fe63" + dependencies: + esutils "^2.0.2" + isarray "^1.0.0" + +duplexer2@~0.1.0: + version "0.1.4" + resolved "https://registry.yarnpkg.com/duplexer2/-/duplexer2-0.1.4.tgz#8b12dab878c0d69e3e7891051662a32fc6bddcc1" + dependencies: + readable-stream "^2.0.2" + +duplexer@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/duplexer/-/duplexer-0.1.1.tgz#ace6ff808c1ce66b57d1ebf97977acb02334cfc1" + +ecc-jsbn@~0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/ecc-jsbn/-/ecc-jsbn-0.1.1.tgz#0fc73a9ed5f0d53c38193398523ef7e543777505" + dependencies: + jsbn "~0.1.0" + +"errno@>=0.1.1 <0.2.0-0", errno@^0.1.3: + version "0.1.4" + resolved "https://registry.yarnpkg.com/errno/-/errno-0.1.4.tgz#b896e23a9e5e8ba33871fc996abd3635fc9a1c7d" + dependencies: + prr "~0.0.0" + +error-ex@^1.2.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.1.tgz#f855a86ce61adc4e8621c3cda21e7a7612c3a8dc" + dependencies: + is-arrayish "^0.2.1" + +escape-string-regexp@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.2.tgz#4dbc2fe674e71949caf3fb2695ce7f2dc1d9a8d1" + +escape-string-regexp@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4" + +escodegen@^1.6.1: + version "1.8.1" + resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.8.1.tgz#5a5b53af4693110bebb0867aa3430dd3b70a1018" + dependencies: + esprima "^2.7.1" + estraverse "^1.9.1" + esutils "^2.0.2" + optionator "^0.8.1" + optionalDependencies: + source-map "~0.2.0" + +eslint-config-airbnb-base@^11.2.0: + version "11.2.0" + resolved "https://registry.yarnpkg.com/eslint-config-airbnb-base/-/eslint-config-airbnb-base-11.2.0.tgz#19a9dc4481a26f70904545ec040116876018f853" + +eslint-config-prettier@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-2.2.0.tgz#ca47663852789a75c10feba673e802cc1eff085f" + dependencies: + get-stdin "^5.0.1" + +eslint-import-resolver-node@^0.2.0: + version "0.2.3" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.2.3.tgz#5add8106e8c928db2cba232bcd9efa846e3da16c" + dependencies: + debug "^2.2.0" + object-assign "^4.0.1" + resolve "^1.1.6" + +eslint-module-utils@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.0.0.tgz#a6f8c21d901358759cdc35dbac1982ae1ee58bce" + dependencies: + debug "2.2.0" + pkg-dir "^1.0.0" + +eslint-plugin-flowtype@^2.34.0: + version "2.34.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-flowtype/-/eslint-plugin-flowtype-2.34.0.tgz#b9875f314652e5081623c9d2b18a346bbb759c09" + dependencies: + lodash "^4.15.0" + +eslint-plugin-import@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.3.0.tgz#37c801e0ada0e296cbdf20c3f393acb5b52af36b" + dependencies: + builtin-modules "^1.1.1" + contains-path "^0.1.0" + debug "^2.2.0" + doctrine "1.5.0" + eslint-import-resolver-node "^0.2.0" + eslint-module-utils "^2.0.0" + has "^1.0.1" + lodash.cond "^4.3.0" + minimatch "^3.0.3" + read-pkg-up "^2.0.0" + +eslint-plugin-prettier@^2.1.2: + version "2.1.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-2.1.2.tgz#4b90f4ee7f92bfbe2e926017e1ca40eb628965ea" + dependencies: + fast-diff "^1.1.1" + jest-docblock "^20.0.1" + +eslint-scope@^3.7.1: + version "3.7.1" + resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-3.7.1.tgz#3d63c3edfda02e06e01a452ad88caacc7cdcb6e8" + dependencies: + esrecurse "^4.1.0" + estraverse "^4.1.1" + +eslint@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-4.0.0.tgz#7277c01437fdf41dccd168d5aa0e49b75ca1f260" + dependencies: + babel-code-frame "^6.22.0" + chalk "^1.1.3" + concat-stream "^1.6.0" + debug "^2.6.8" + doctrine "^2.0.0" + eslint-scope "^3.7.1" + espree "^3.4.3" + esquery "^1.0.0" + estraverse "^4.2.0" + esutils "^2.0.2" + file-entry-cache "^2.0.0" + glob "^7.1.2" + globals "^9.17.0" + ignore "^3.3.3" + imurmurhash "^0.1.4" + inquirer "^3.0.6" + is-my-json-valid "^2.16.0" + is-resolvable "^1.0.0" + js-yaml "^3.8.4" + json-stable-stringify "^1.0.1" + levn "^0.3.0" + lodash "^4.17.4" + mkdirp "^0.5.1" + natural-compare "^1.4.0" + optionator "^0.8.2" + path-is-inside "^1.0.2" + pluralize "^4.0.0" + progress "^2.0.0" + require-uncached "^1.0.3" + strip-json-comments "~2.0.1" + table "^4.0.1" + text-table "~0.2.0" + +espree@^3.4.3: + version "3.4.3" + resolved "https://registry.yarnpkg.com/espree/-/espree-3.4.3.tgz#2910b5ccd49ce893c2ffffaab4fd8b3a31b82374" + dependencies: + acorn "^5.0.1" + acorn-jsx "^3.0.0" + +esprima@^2.7.1: + version "2.7.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" + +esprima@^3.1.1: + version "3.1.3" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" + +esquery@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.0.tgz#cfba8b57d7fba93f17298a8a006a04cda13d80fa" + dependencies: + estraverse "^4.0.0" + +esrecurse@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.1.0.tgz#4713b6536adf7f2ac4f327d559e7756bff648220" + dependencies: + estraverse "~4.1.0" + object-assign "^4.0.1" + +estraverse@^1.9.1: + version "1.9.3" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-1.9.3.tgz#af67f2dc922582415950926091a4005d29c9bb44" + +estraverse@^4.0.0, estraverse@^4.1.1, estraverse@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13" + +estraverse@~4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.1.1.tgz#f6caca728933a850ef90661d0e17982ba47111a2" + +esutils@^2.0.2: + version "2.0.2" + resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b" + +event-stream@^3.3.4: + version "3.3.4" + resolved "https://registry.yarnpkg.com/event-stream/-/event-stream-3.3.4.tgz#4ab4c9a0f5a54db9338b4c34d86bfce8f4b35571" + dependencies: + duplexer "~0.1.1" + from "~0" + map-stream "~0.1.0" + pause-stream "0.0.11" + split "0.3" + stream-combiner "~0.0.4" + through "~2.3.1" + +exec-sh@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.2.0.tgz#14f75de3f20d286ef933099b2ce50a90359cef10" + dependencies: + merge "^1.1.3" + +expand-brackets@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/expand-brackets/-/expand-brackets-0.1.5.tgz#df07284e342a807cd733ac5af72411e581d1177b" + dependencies: + is-posix-bracket "^0.1.0" + +expand-range@^1.8.1: + version "1.8.2" + resolved "https://registry.yarnpkg.com/expand-range/-/expand-range-1.8.2.tgz#a299effd335fe2721ebae8e257ec79644fc85337" + dependencies: + fill-range "^2.1.0" + +extend@~3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" + +external-editor@^2.0.4: + version "2.0.4" + resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-2.0.4.tgz#1ed9199da9cbfe2ef2f7a31b2fde8b0d12368972" + dependencies: + iconv-lite "^0.4.17" + jschardet "^1.4.2" + tmp "^0.0.31" + +extglob@^0.3.1: + version "0.3.2" + resolved "https://registry.yarnpkg.com/extglob/-/extglob-0.3.2.tgz#2e18ff3d2f49ab2765cec9023f011daa8d8349a1" + dependencies: + is-extglob "^1.0.0" + +extsprintf@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.0.2.tgz#e1080e0658e300b06294990cc70e1502235fd550" + +fast-diff@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/fast-diff/-/fast-diff-1.1.1.tgz#0aea0e4e605b6a2189f0e936d4b7fbaf1b7cfd9b" + +fast-levenshtein@~2.0.4: + version "2.0.6" + resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" + +fb-watchman@^1.8.0: + version "1.9.2" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-1.9.2.tgz#a24cf47827f82d38fb59a69ad70b76e3b6ae7383" + dependencies: + bser "1.0.2" + +fb-watchman@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.0.tgz#54e9abf7dfa2f26cd9b1636c588c1afc05de5d58" + dependencies: + bser "^2.0.0" + +figures@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/figures/-/figures-2.0.0.tgz#3ab1a2d2a62c8bfb431a0c94cb797a2fce27c962" + dependencies: + escape-string-regexp "^1.0.5" + +file-entry-cache@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-2.0.0.tgz#c392990c3e684783d838b8c84a45d8a048458361" + dependencies: + flat-cache "^1.2.1" + object-assign "^4.0.1" + +filename-regex@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/filename-regex/-/filename-regex-2.0.1.tgz#c1c4b9bee3e09725ddb106b75c1e301fe2f18b26" + +fileset@^2.0.2: + version "2.0.3" + resolved "https://registry.yarnpkg.com/fileset/-/fileset-2.0.3.tgz#8e7548a96d3cc2327ee5e674168723a333bba2a0" + dependencies: + glob "^7.0.3" + minimatch "^3.0.3" + +fill-range@^2.1.0: + version "2.2.3" + resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-2.2.3.tgz#50b77dfd7e469bc7492470963699fe7a8485a723" + dependencies: + is-number "^2.1.0" + isobject "^2.0.0" + randomatic "^1.1.3" + repeat-element "^1.1.2" + repeat-string "^1.5.2" + +find-up@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-1.1.2.tgz#6b2e9822b1a2ce0a60ab64d610eccad53cb24d0f" + dependencies: + path-exists "^2.0.0" + pinkie-promise "^2.0.0" + +find-up@^2.0.0, find-up@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7" + dependencies: + locate-path "^2.0.0" + +flat-cache@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-1.2.2.tgz#fa86714e72c21db88601761ecf2f555d1abc6b96" + dependencies: + circular-json "^0.3.1" + del "^2.0.2" + graceful-fs "^4.1.2" + write "^0.2.1" + +flow-bin@^0.48.0: + version "0.48.0" + resolved "https://registry.yarnpkg.com/flow-bin/-/flow-bin-0.48.0.tgz#72d075143524358db8901525e3c784dc13a7c7ee" + +for-in@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80" + +for-own@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/for-own/-/for-own-0.1.5.tgz#5265c681a4f294dabbf17c9509b6763aa84510ce" + dependencies: + for-in "^1.0.1" + +forever-agent@~0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91" + +form-data@~2.1.1: + version "2.1.4" + resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.1.4.tgz#33c183acf193276ecaa98143a69e94bfee1750d1" + dependencies: + asynckit "^0.4.0" + combined-stream "^1.0.5" + mime-types "^2.1.12" + +from@~0: + version "0.1.7" + resolved "https://registry.yarnpkg.com/from/-/from-0.1.7.tgz#83c60afc58b9c56997007ed1a768b3ab303a44fe" + +fs-readdir-recursive@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs-readdir-recursive/-/fs-readdir-recursive-1.0.0.tgz#8cd1745c8b4f8a29c8caec392476921ba195f560" + +fs.realpath@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" + +fsevents@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-1.1.2.tgz#3282b713fb3ad80ede0e9fcf4611b5aa6fc033f4" + dependencies: + nan "^2.3.0" + node-pre-gyp "^0.6.36" + +fstream-ignore@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/fstream-ignore/-/fstream-ignore-1.0.5.tgz#9c31dae34767018fe1d249b24dada67d092da105" + dependencies: + fstream "^1.0.0" + inherits "2" + minimatch "^3.0.0" + +fstream@^1.0.0, fstream@^1.0.10, fstream@^1.0.2: + version "1.0.11" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" + dependencies: + graceful-fs "^4.1.2" + inherits "~2.0.0" + mkdirp ">=0.5 0" + rimraf "2" + +function-bind@^1.0.2: + version "1.1.0" + resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.0.tgz#16176714c801798e4e8f2cf7f7529467bb4a5771" + +gauge@~2.7.3: + version "2.7.4" + resolved "https://registry.yarnpkg.com/gauge/-/gauge-2.7.4.tgz#2c03405c7538c39d7eb37b317022e325fb018bf7" + dependencies: + aproba "^1.0.3" + console-control-strings "^1.0.0" + has-unicode "^2.0.0" + object-assign "^4.1.0" + signal-exit "^3.0.0" + string-width "^1.0.1" + strip-ansi "^3.0.1" + wide-align "^1.1.0" + +generate-function@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/generate-function/-/generate-function-2.0.0.tgz#6858fe7c0969b7d4e9093337647ac79f60dfbe74" + +generate-object-property@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/generate-object-property/-/generate-object-property-1.2.0.tgz#9c0e1c40308ce804f4783618b937fa88f99d50d0" + dependencies: + is-property "^1.0.0" + +get-caller-file@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-1.0.2.tgz#f702e63127e7e231c160a80c1554acb70d5047e5" + +get-stdin@^5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/get-stdin/-/get-stdin-5.0.1.tgz#122e161591e21ff4c52530305693f20e6393a398" + +getpass@^0.1.1: + version "0.1.7" + resolved "https://registry.yarnpkg.com/getpass/-/getpass-0.1.7.tgz#5eff8e3e684d569ae4cb2b1282604e8ba62149fa" + dependencies: + assert-plus "^1.0.0" + +glob-base@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/glob-base/-/glob-base-0.3.0.tgz#dbb164f6221b1c0b1ccf82aea328b497df0ea3c4" + dependencies: + glob-parent "^2.0.0" + is-glob "^2.0.0" + +glob-parent@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-2.0.0.tgz#81383d72db054fcccf5336daa902f182f6edbb28" + dependencies: + is-glob "^2.0.0" + +glob@^7.0.0, glob@^7.0.3, glob@^7.0.5, glob@^7.1.1, glob@^7.1.2: + version "7.1.2" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.2.tgz#c19c9df9a028702d678612384a6552404c636d15" + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + +globals@^9.0.0, globals@^9.17.0: + version "9.18.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" + +globby@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/globby/-/globby-5.0.0.tgz#ebd84667ca0dbb330b99bcfc68eac2bc54370e0d" + dependencies: + array-union "^1.0.1" + arrify "^1.0.0" + glob "^7.0.3" + object-assign "^4.0.1" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.4: + version "4.1.11" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" + +"graceful-readlink@>= 1.0.0": + version "1.0.1" + resolved "https://registry.yarnpkg.com/graceful-readlink/-/graceful-readlink-1.0.1.tgz#4cafad76bc62f02fa039b2f94e9a3dd3a391a725" + +growly@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/growly/-/growly-1.3.0.tgz#f10748cbe76af964b7c96c93c6bcc28af120c081" + +handlebars@^4.0.3: + version "4.0.10" + resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.10.tgz#3d30c718b09a3d96f23ea4cc1f403c4d3ba9ff4f" + dependencies: + async "^1.4.0" + optimist "^0.6.1" + source-map "^0.4.4" + optionalDependencies: + uglify-js "^2.6" + +har-schema@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-1.0.5.tgz#d263135f43307c02c602afc8fe95970c0151369e" + +har-validator@~4.2.1: + version "4.2.1" + resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-4.2.1.tgz#33481d0f1bbff600dd203d75812a6a5fba002e2a" + dependencies: + ajv "^4.9.1" + har-schema "^1.0.5" + +has-ansi@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/has-ansi/-/has-ansi-2.0.0.tgz#34f5049ce1ecdf2b0649af3ef24e45ed35416d91" + dependencies: + ansi-regex "^2.0.0" + +has-flag@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-1.0.0.tgz#9d9e793165ce017a00f00418c43f942a7b1d11fa" + +has-unicode@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/has-unicode/-/has-unicode-2.0.1.tgz#e0e6fe6a28cf51138855e086d1691e771de2a8b9" + +has@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/has/-/has-1.0.1.tgz#8461733f538b0837c9361e39a9ab9e9704dc2f28" + dependencies: + function-bind "^1.0.2" + +hawk@~3.1.3: + version "3.1.3" + resolved "https://registry.yarnpkg.com/hawk/-/hawk-3.1.3.tgz#078444bd7c1640b0fe540d2c9b73d59678e8e1c4" + dependencies: + boom "2.x.x" + cryptiles "2.x.x" + hoek "2.x.x" + sntp "1.x.x" + +hoek@2.x.x: + version "2.16.3" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-2.16.3.tgz#20bb7403d3cea398e91dc4710a8ff1b8274a25ed" + +home-or-tmp@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/home-or-tmp/-/home-or-tmp-2.0.0.tgz#e36c3f2d2cae7d746a857e38d18d5f32a7882db8" + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.1" + +hosted-git-info@^2.1.4: + version "2.4.2" + resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.4.2.tgz#0076b9f46a270506ddbaaea56496897460612a67" + +html-encoding-sniffer@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-1.0.1.tgz#79bf7a785ea495fe66165e734153f363ff5437da" + dependencies: + whatwg-encoding "^1.0.1" + +http-signature@~1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.1.1.tgz#df72e267066cd0ac67fb76adf8e134a8fbcf91bf" + dependencies: + assert-plus "^0.2.0" + jsprim "^1.2.2" + sshpk "^1.7.0" + +iconv-lite@0.4.13: + version "0.4.13" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.13.tgz#1f88aba4ab0b1508e8312acc39345f36e992e2f2" + +iconv-lite@^0.4.17: + version "0.4.18" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.18.tgz#23d8656b16aae6742ac29732ea8f0336a4789cf2" + +ignore@^3.3.3: + version "3.3.3" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-3.3.3.tgz#432352e57accd87ab3110e82d3fea0e47812156d" + +immutable@^3.8.1: + version "3.8.1" + resolved "https://registry.yarnpkg.com/immutable/-/immutable-3.8.1.tgz#200807f11ab0f72710ea485542de088075f68cd2" + +imurmurhash@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea" + +inflight@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9" + dependencies: + once "^1.3.0" + wrappy "1" + +inherits@2, inherits@^2.0.1, inherits@^2.0.3, inherits@~2.0.0, inherits@~2.0.1: + version "2.0.3" + resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" + +ini@^1.3.4, ini@~1.3.0: + version "1.3.4" + resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.4.tgz#0537cb79daf59b59a1a517dff706c86ec039162e" + +inquirer@^3.0.6: + version "3.1.1" + resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-3.1.1.tgz#87621c4fba4072f48a8dd71c9f9df6f100b2d534" + dependencies: + ansi-escapes "^2.0.0" + chalk "^1.0.0" + cli-cursor "^2.1.0" + cli-width "^2.0.0" + external-editor "^2.0.4" + figures "^2.0.0" + lodash "^4.3.0" + mute-stream "0.0.7" + run-async "^2.2.0" + rx-lite "^4.0.8" + rx-lite-aggregates "^4.0.8" + string-width "^2.0.0" + strip-ansi "^3.0.0" + through "^2.3.6" + +invariant@^2.2.0: + version "2.2.2" + resolved "https://registry.yarnpkg.com/invariant/-/invariant-2.2.2.tgz#9e1f56ac0acdb6bf303306f338be3b204ae60360" + dependencies: + loose-envify "^1.0.0" + +invert-kv@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/invert-kv/-/invert-kv-1.0.0.tgz#104a8e4aaca6d3d8cd157a8ef8bfab2d7a3ffdb6" + +is-arrayish@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d" + +is-binary-path@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-1.0.1.tgz#75f16642b480f187a711c814161fd3a4a7655898" + dependencies: + binary-extensions "^1.0.0" + +is-buffer@^1.1.5: + version "1.1.5" + resolved "https://registry.yarnpkg.com/is-buffer/-/is-buffer-1.1.5.tgz#1f3b26ef613b214b88cbca23cc6c01d87961eecc" + +is-builtin-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-builtin-module/-/is-builtin-module-1.0.0.tgz#540572d34f7ac3119f8f76c30cbc1b1e037affbe" + dependencies: + builtin-modules "^1.0.0" + +is-ci@^1.0.10: + version "1.0.10" + resolved "https://registry.yarnpkg.com/is-ci/-/is-ci-1.0.10.tgz#f739336b2632365061a9d48270cd56ae3369318e" + dependencies: + ci-info "^1.0.0" + +is-dotfile@^1.0.0: + version "1.0.3" + resolved "https://registry.yarnpkg.com/is-dotfile/-/is-dotfile-1.0.3.tgz#a6a2f32ffd2dfb04f5ca25ecd0f6b83cf798a1e1" + +is-equal-shallow@^0.1.3: + version "0.1.3" + resolved "https://registry.yarnpkg.com/is-equal-shallow/-/is-equal-shallow-0.1.3.tgz#2238098fc221de0bcfa5d9eac4c45d638aa1c534" + dependencies: + is-primitive "^2.0.0" + +is-extendable@^0.1.1: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-extendable/-/is-extendable-0.1.1.tgz#62b110e289a471418e3ec36a617d472e301dfc89" + +is-extglob@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-1.0.0.tgz#ac468177c4943405a092fc8f29760c6ffc6206c0" + +is-finite@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-finite/-/is-finite-1.0.2.tgz#cc6677695602be550ef11e8b4aa6305342b6d0aa" + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz#ef9e31386f031a7f0d643af82fde50c457ef00cb" + dependencies: + number-is-nan "^1.0.0" + +is-fullwidth-code-point@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz#a3b30a5c4f199183167aaab93beefae3ddfb654f" + +is-glob@^2.0.0, is-glob@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-2.0.1.tgz#d096f926a3ded5600f3fdfd91198cb0888c2d863" + dependencies: + is-extglob "^1.0.0" + +is-my-json-valid@^2.16.0: + version "2.16.0" + resolved "https://registry.yarnpkg.com/is-my-json-valid/-/is-my-json-valid-2.16.0.tgz#f079dd9bfdae65ee2038aae8acbc86ab109e3693" + dependencies: + generate-function "^2.0.0" + generate-object-property "^1.1.0" + jsonpointer "^4.0.0" + xtend "^4.0.0" + +is-number@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-2.1.0.tgz#01fcbbb393463a548f2f466cce16dece49db908f" + dependencies: + kind-of "^3.0.2" + +is-number@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/is-number/-/is-number-3.0.0.tgz#24fd6201a4782cf50561c810276afc7d12d71195" + dependencies: + kind-of "^3.0.2" + +is-path-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-cwd/-/is-path-cwd-1.0.0.tgz#d225ec23132e89edd38fda767472e62e65f1106d" + +is-path-in-cwd@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-in-cwd/-/is-path-in-cwd-1.0.0.tgz#6477582b8214d602346094567003be8a9eac04dc" + dependencies: + is-path-inside "^1.0.0" + +is-path-inside@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-1.0.0.tgz#fc06e5a1683fbda13de667aff717bbc10a48f37f" + dependencies: + path-is-inside "^1.0.1" + +is-posix-bracket@^0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4" + +is-primitive@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/is-primitive/-/is-primitive-2.0.0.tgz#207bab91638499c07b2adf240a41a87210034575" + +is-promise@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.1.0.tgz#79a2a9ece7f096e80f36d2b2f3bc16c1ff4bf3fa" + +is-property@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/is-property/-/is-property-1.0.2.tgz#57fe1c4e48474edd65b09911f26b1cd4095dda84" + +is-resolvable@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-resolvable/-/is-resolvable-1.0.0.tgz#8df57c61ea2e3c501408d100fb013cf8d6e0cc62" + dependencies: + tryit "^1.0.1" + +is-typedarray@^1.0.0, is-typedarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a" + +is-utf8@^0.2.0: + version "0.2.1" + resolved "https://registry.yarnpkg.com/is-utf8/-/is-utf8-0.2.1.tgz#4b0da1442104d1b336340e80797e865cf39f7d72" + +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" + +isexe@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10" + +isobject@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/isobject/-/isobject-2.1.0.tgz#f065561096a3f1da2ef46272f815c840d87e0c89" + dependencies: + isarray "1.0.0" + +isstream@~0.1.2: + version "0.1.2" + resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" + +istanbul-api@^1.1.1: + version "1.1.9" + resolved "https://registry.yarnpkg.com/istanbul-api/-/istanbul-api-1.1.9.tgz#2827920d380d4286d857d57a2968a841db8a7ec8" + dependencies: + async "^2.1.4" + fileset "^2.0.2" + istanbul-lib-coverage "^1.1.1" + istanbul-lib-hook "^1.0.7" + istanbul-lib-instrument "^1.7.2" + istanbul-lib-report "^1.1.1" + istanbul-lib-source-maps "^1.2.1" + istanbul-reports "^1.1.1" + js-yaml "^3.7.0" + mkdirp "^0.5.1" + once "^1.4.0" + +istanbul-lib-coverage@^1.0.1, istanbul-lib-coverage@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-1.1.1.tgz#73bfb998885299415c93d38a3e9adf784a77a9da" + +istanbul-lib-hook@^1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/istanbul-lib-hook/-/istanbul-lib-hook-1.0.7.tgz#dd6607f03076578fe7d6f2a630cf143b49bacddc" + dependencies: + append-transform "^0.4.0" + +istanbul-lib-instrument@^1.4.2, istanbul-lib-instrument@^1.7.2: + version "1.7.2" + resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-1.7.2.tgz#6014b03d3470fb77638d5802508c255c06312e56" + dependencies: + babel-generator "^6.18.0" + babel-template "^6.16.0" + babel-traverse "^6.18.0" + babel-types "^6.18.0" + babylon "^6.13.0" + istanbul-lib-coverage "^1.1.1" + semver "^5.3.0" + +istanbul-lib-report@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-1.1.1.tgz#f0e55f56655ffa34222080b7a0cd4760e1405fc9" + dependencies: + istanbul-lib-coverage "^1.1.1" + mkdirp "^0.5.1" + path-parse "^1.0.5" + supports-color "^3.1.2" + +istanbul-lib-source-maps@^1.1.0, istanbul-lib-source-maps@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-1.2.1.tgz#a6fe1acba8ce08eebc638e572e294d267008aa0c" + dependencies: + debug "^2.6.3" + istanbul-lib-coverage "^1.1.1" + mkdirp "^0.5.1" + rimraf "^2.6.1" + source-map "^0.5.3" + +istanbul-reports@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-1.1.1.tgz#042be5c89e175bc3f86523caab29c014e77fee4e" + dependencies: + handlebars "^4.0.3" + +jest-changed-files@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-20.0.3.tgz#9394d5cc65c438406149bef1bf4d52b68e03e3f8" + +jest-cli@^20.0.4: + version "20.0.4" + resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-20.0.4.tgz#e532b19d88ae5bc6c417e8b0593a6fe954b1dc93" + dependencies: + ansi-escapes "^1.4.0" + callsites "^2.0.0" + chalk "^1.1.3" + graceful-fs "^4.1.11" + is-ci "^1.0.10" + istanbul-api "^1.1.1" + istanbul-lib-coverage "^1.0.1" + istanbul-lib-instrument "^1.4.2" + istanbul-lib-source-maps "^1.1.0" + jest-changed-files "^20.0.3" + jest-config "^20.0.4" + jest-docblock "^20.0.3" + jest-environment-jsdom "^20.0.3" + jest-haste-map "^20.0.4" + jest-jasmine2 "^20.0.4" + jest-message-util "^20.0.3" + jest-regex-util "^20.0.3" + jest-resolve-dependencies "^20.0.3" + jest-runtime "^20.0.4" + jest-snapshot "^20.0.3" + jest-util "^20.0.3" + micromatch "^2.3.11" + node-notifier "^5.0.2" + pify "^2.3.0" + slash "^1.0.0" + string-length "^1.0.1" + throat "^3.0.0" + which "^1.2.12" + worker-farm "^1.3.1" + yargs "^7.0.2" + +jest-config@^20.0.4: + version "20.0.4" + resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-20.0.4.tgz#e37930ab2217c913605eff13e7bd763ec48faeea" + dependencies: + chalk "^1.1.3" + glob "^7.1.1" + jest-environment-jsdom "^20.0.3" + jest-environment-node "^20.0.3" + jest-jasmine2 "^20.0.4" + jest-matcher-utils "^20.0.3" + jest-regex-util "^20.0.3" + jest-resolve "^20.0.4" + jest-validate "^20.0.3" + pretty-format "^20.0.3" + +jest-diff@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-20.0.3.tgz#81f288fd9e675f0fb23c75f1c2b19445fe586617" + dependencies: + chalk "^1.1.3" + diff "^3.2.0" + jest-matcher-utils "^20.0.3" + pretty-format "^20.0.3" + +jest-docblock@^20.0.1, jest-docblock@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-20.0.3.tgz#17bea984342cc33d83c50fbe1545ea0efaa44712" + +jest-environment-jsdom@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-20.0.3.tgz#048a8ac12ee225f7190417713834bb999787de99" + dependencies: + jest-mock "^20.0.3" + jest-util "^20.0.3" + jsdom "^9.12.0" + +jest-environment-node@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-20.0.3.tgz#d488bc4612af2c246e986e8ae7671a099163d403" + dependencies: + jest-mock "^20.0.3" + jest-util "^20.0.3" + +jest-haste-map@^20.0.4: + version "20.0.4" + resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-20.0.4.tgz#653eb55c889ce3c021f7b94693f20a4159badf03" + dependencies: + fb-watchman "^2.0.0" + graceful-fs "^4.1.11" + jest-docblock "^20.0.3" + micromatch "^2.3.11" + sane "~1.6.0" + worker-farm "^1.3.1" + +jest-jasmine2@^20.0.4: + version "20.0.4" + resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-20.0.4.tgz#fcc5b1411780d911d042902ef1859e852e60d5e1" + dependencies: + chalk "^1.1.3" + graceful-fs "^4.1.11" + jest-diff "^20.0.3" + jest-matcher-utils "^20.0.3" + jest-matchers "^20.0.3" + jest-message-util "^20.0.3" + jest-snapshot "^20.0.3" + once "^1.4.0" + p-map "^1.1.1" + +jest-matcher-utils@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-20.0.3.tgz#b3a6b8e37ca577803b0832a98b164f44b7815612" + dependencies: + chalk "^1.1.3" + pretty-format "^20.0.3" + +jest-matchers@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-matchers/-/jest-matchers-20.0.3.tgz#ca69db1c32db5a6f707fa5e0401abb55700dfd60" + dependencies: + jest-diff "^20.0.3" + jest-matcher-utils "^20.0.3" + jest-message-util "^20.0.3" + jest-regex-util "^20.0.3" + +jest-message-util@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-20.0.3.tgz#6aec2844306fcb0e6e74d5796c1006d96fdd831c" + dependencies: + chalk "^1.1.3" + micromatch "^2.3.11" + slash "^1.0.0" + +jest-mock@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-20.0.3.tgz#8bc070e90414aa155c11a8d64c869a0d5c71da59" + +jest-regex-util@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-20.0.3.tgz#85bbab5d133e44625b19faf8c6aa5122d085d762" + +jest-resolve-dependencies@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-20.0.3.tgz#6e14a7b717af0f2cb3667c549de40af017b1723a" + dependencies: + jest-regex-util "^20.0.3" + +jest-resolve@^20.0.4: + version "20.0.4" + resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-20.0.4.tgz#9448b3e8b6bafc15479444c6499045b7ffe597a5" + dependencies: + browser-resolve "^1.11.2" + is-builtin-module "^1.0.0" + resolve "^1.3.2" + +jest-runtime@^20.0.4: + version "20.0.4" + resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-20.0.4.tgz#a2c802219c4203f754df1404e490186169d124d8" + dependencies: + babel-core "^6.0.0" + babel-jest "^20.0.3" + babel-plugin-istanbul "^4.0.0" + chalk "^1.1.3" + convert-source-map "^1.4.0" + graceful-fs "^4.1.11" + jest-config "^20.0.4" + jest-haste-map "^20.0.4" + jest-regex-util "^20.0.3" + jest-resolve "^20.0.4" + jest-util "^20.0.3" + json-stable-stringify "^1.0.1" + micromatch "^2.3.11" + strip-bom "3.0.0" + yargs "^7.0.2" + +jest-snapshot@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-20.0.3.tgz#5b847e1adb1a4d90852a7f9f125086e187c76566" + dependencies: + chalk "^1.1.3" + jest-diff "^20.0.3" + jest-matcher-utils "^20.0.3" + jest-util "^20.0.3" + natural-compare "^1.4.0" + pretty-format "^20.0.3" + +jest-util@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-20.0.3.tgz#0c07f7d80d82f4e5a67c6f8b9c3fe7f65cfd32ad" + dependencies: + chalk "^1.1.3" + graceful-fs "^4.1.11" + jest-message-util "^20.0.3" + jest-mock "^20.0.3" + jest-validate "^20.0.3" + leven "^2.1.0" + mkdirp "^0.5.1" + +jest-validate@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-20.0.3.tgz#d0cfd1de4f579f298484925c280f8f1d94ec3cab" + dependencies: + chalk "^1.1.3" + jest-matcher-utils "^20.0.3" + leven "^2.1.0" + pretty-format "^20.0.3" + +jest@^20.0.4: + version "20.0.4" + resolved "https://registry.yarnpkg.com/jest/-/jest-20.0.4.tgz#3dd260c2989d6dad678b1e9cc4d91944f6d602ac" + dependencies: + jest-cli "^20.0.4" + +js-tokens@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7" + +js-yaml@^3.7.0, js-yaml@^3.8.4: + version "3.8.4" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.8.4.tgz#520b4564f86573ba96662af85a8cafa7b4b5a6f6" + dependencies: + argparse "^1.0.7" + esprima "^3.1.1" + +jsbn@~0.1.0: + version "0.1.1" + resolved "https://registry.yarnpkg.com/jsbn/-/jsbn-0.1.1.tgz#a5e654c2e5a2deb5f201d96cefbca80c0ef2f513" + +jschardet@^1.4.2: + version "1.4.2" + resolved "https://registry.yarnpkg.com/jschardet/-/jschardet-1.4.2.tgz#2aa107f142af4121d145659d44f50830961e699a" + +jsdom@^9.12.0: + version "9.12.0" + resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-9.12.0.tgz#e8c546fffcb06c00d4833ca84410fed7f8a097d4" + dependencies: + abab "^1.0.3" + acorn "^4.0.4" + acorn-globals "^3.1.0" + array-equal "^1.0.0" + content-type-parser "^1.0.1" + cssom ">= 0.3.2 < 0.4.0" + cssstyle ">= 0.2.37 < 0.3.0" + escodegen "^1.6.1" + html-encoding-sniffer "^1.0.1" + nwmatcher ">= 1.3.9 < 2.0.0" + parse5 "^1.5.1" + request "^2.79.0" + sax "^1.2.1" + symbol-tree "^3.2.1" + tough-cookie "^2.3.2" + webidl-conversions "^4.0.0" + whatwg-encoding "^1.0.1" + whatwg-url "^4.3.0" + xml-name-validator "^2.0.1" + +jsesc@^1.3.0: + version "1.3.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-1.3.0.tgz#46c3fec8c1892b12b0833db9bc7622176dbab34b" + +jsesc@~0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-0.5.0.tgz#e7dee66e35d6fc16f710fe91d5cf69f70f08911d" + +json-schema@0.2.3: + version "0.2.3" + resolved "https://registry.yarnpkg.com/json-schema/-/json-schema-0.2.3.tgz#b480c892e59a2f05954ce727bd3f2a4e882f9e13" + +json-stable-stringify@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz#9a759d39c5f2ff503fd5300646ed445f88c4f9af" + dependencies: + jsonify "~0.0.0" + +json-stringify-safe@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz#1296a2d58fd45f19a0f6ce01d65701e2c735b6eb" + +json5@^0.5.0: + version "0.5.1" + resolved "https://registry.yarnpkg.com/json5/-/json5-0.5.1.tgz#1eade7acc012034ad84e2396767ead9fa5495821" + +jsonify@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/jsonify/-/jsonify-0.0.0.tgz#2c74b6ee41d93ca51b7b5aaee8f503631d252a73" + +jsonpointer@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" + +jsprim@^1.2.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.0.tgz#a3b87e40298d8c380552d8cc7628a0bb95a22918" + dependencies: + assert-plus "1.0.0" + extsprintf "1.0.2" + json-schema "0.2.3" + verror "1.3.6" + +kind-of@^3.0.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-3.2.2.tgz#31ea21a734bab9bbb0f32466d893aea51e4a3c64" + dependencies: + is-buffer "^1.1.5" + +kind-of@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-4.0.0.tgz#20813df3d712928b207378691a45066fae72dd57" + dependencies: + is-buffer "^1.1.5" + +lazy-cache@^1.0.3: + version "1.0.4" + resolved "https://registry.yarnpkg.com/lazy-cache/-/lazy-cache-1.0.4.tgz#a1d78fc3a50474cb80845d3b3b6e1da49a446e8e" + +lcid@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/lcid/-/lcid-1.0.0.tgz#308accafa0bc483a3867b4b6f2b9506251d1b835" + dependencies: + invert-kv "^1.0.0" + +leven@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/leven/-/leven-2.1.0.tgz#c2e7a9f772094dee9d34202ae8acce4687875580" + +levn@^0.3.0, levn@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee" + dependencies: + prelude-ls "~1.1.2" + type-check "~0.3.2" + +load-json-file@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-1.1.0.tgz#956905708d58b4bab4c2261b04f59f31c99374c0" + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + pinkie-promise "^2.0.0" + strip-bom "^2.0.0" + +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" + +locate-path@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e" + dependencies: + p-locate "^2.0.0" + path-exists "^3.0.0" + +lodash.cond@^4.3.0: + version "4.5.2" + resolved "https://registry.yarnpkg.com/lodash.cond/-/lodash.cond-4.5.2.tgz#f471a1da486be60f6ab955d17115523dd1d255d5" + +lodash@^4.0.0, lodash@^4.14.0, lodash@^4.15.0, lodash@^4.17.4, lodash@^4.2.0, lodash@^4.3.0: + version "4.17.4" + resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.4.tgz#78203a4d1c328ae1d86dca6460e369b57f4055ae" + +longest@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/longest/-/longest-1.0.1.tgz#30a0b2da38f73770e8294a0d22e6625ed77d0097" + +loose-envify@^1.0.0: + version "1.3.1" + resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" + dependencies: + js-tokens "^3.0.0" + +makeerror@1.0.x: + version "1.0.11" + resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.11.tgz#e01a5c9109f2af79660e4e8b9587790184f5a96c" + dependencies: + tmpl "1.0.x" + +map-stream@~0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/map-stream/-/map-stream-0.1.0.tgz#e56aa94c4c8055a16404a0674b78f215f7c8e194" + +memory-fs@^0.4.1: + version "0.4.1" + resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" + dependencies: + errno "^0.1.3" + readable-stream "^2.0.1" + +merge@^1.1.3: + version "1.2.0" + resolved "https://registry.yarnpkg.com/merge/-/merge-1.2.0.tgz#7531e39d4949c281a66b8c5a6e0265e8b05894da" + +micromatch@^2.1.5, micromatch@^2.3.11: + version "2.3.11" + resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-2.3.11.tgz#86677c97d1720b363431d04d0d15293bd38c1565" + dependencies: + arr-diff "^2.0.0" + array-unique "^0.2.1" + braces "^1.8.2" + expand-brackets "^0.1.4" + extglob "^0.3.1" + filename-regex "^2.0.0" + is-extglob "^1.0.0" + is-glob "^2.0.1" + kind-of "^3.0.2" + normalize-path "^2.0.1" + object.omit "^2.0.0" + parse-glob "^3.0.4" + regex-cache "^0.4.2" + +mime-db@~1.27.0: + version "1.27.0" + resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.27.0.tgz#820f572296bbd20ec25ed55e5b5de869e5436eb1" + +mime-types@^2.1.12, mime-types@~2.1.7: + version "2.1.15" + resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.15.tgz#a4ebf5064094569237b8cf70046776d09fc92aed" + dependencies: + mime-db "~1.27.0" + +mimic-fn@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-1.1.0.tgz#e667783d92e89dbd342818b5230b9d62a672ad18" + +minimatch@^3.0.0, minimatch@^3.0.2, minimatch@^3.0.3, minimatch@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.0.4.tgz#5166e286457f03306064be5497e8dbb0c3d32083" + dependencies: + brace-expansion "^1.1.7" + +minimist@0.0.8, minimist@~0.0.1: + version "0.0.8" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-0.0.8.tgz#857fcabfc3397d2625b8228262e86aa7a011b05d" + +minimist@^1.1.1, minimist@^1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.0.tgz#a35008b20f41383eec1fb914f4cd5df79a264284" + +"mkdirp@>=0.5 0", mkdirp@^0.5.1: + version "0.5.1" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.1.tgz#30057438eac6cf7f8c4767f38648d6697d75c903" + dependencies: + minimist "0.0.8" + +ms@0.7.1: + version "0.7.1" + resolved "https://registry.yarnpkg.com/ms/-/ms-0.7.1.tgz#9cd13c03adbff25b65effde7ce864ee952017098" + +ms@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" + +mute-stream@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.7.tgz#3075ce93bc21b8fab43e1bc4da7e8115ed1e7bab" + +nan@^2.3.0: + version "2.6.2" + resolved "https://registry.yarnpkg.com/nan/-/nan-2.6.2.tgz#e4ff34e6c95fdfb5aecc08de6596f43605a7db45" + +natural-compare@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" + +network-byte-order@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/network-byte-order/-/network-byte-order-0.2.0.tgz#6ac11bf44bf610daeddbe90a09a5c817c6e0d2b3" + +node-int64@^0.4.0: + version "0.4.0" + resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b" + +node-notifier@^5.0.2: + version "5.1.2" + resolved "https://registry.yarnpkg.com/node-notifier/-/node-notifier-5.1.2.tgz#2fa9e12605fa10009d44549d6fcd8a63dde0e4ff" + dependencies: + growly "^1.3.0" + semver "^5.3.0" + shellwords "^0.1.0" + which "^1.2.12" + +node-pre-gyp@^0.6.36: + version "0.6.36" + resolved "https://registry.yarnpkg.com/node-pre-gyp/-/node-pre-gyp-0.6.36.tgz#db604112cb74e0d477554e9b505b17abddfab786" + dependencies: + mkdirp "^0.5.1" + nopt "^4.0.1" + npmlog "^4.0.2" + rc "^1.1.7" + request "^2.81.0" + rimraf "^2.6.1" + semver "^5.3.0" + tar "^2.2.1" + tar-pack "^3.4.0" + +nopt@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/nopt/-/nopt-4.0.1.tgz#d0d4685afd5415193c8c7505602d0d17cd64474d" + dependencies: + abbrev "1" + osenv "^0.1.4" + +normalize-package-data@^2.3.2: + version "2.3.8" + resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.3.8.tgz#d819eda2a9dedbd1ffa563ea4071d936782295bb" + dependencies: + hosted-git-info "^2.1.4" + is-builtin-module "^1.0.0" + semver "2 || 3 || 4 || 5" + validate-npm-package-license "^3.0.1" + +normalize-path@^2.0.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-2.1.1.tgz#1ab28b556e198363a8c1a6f7e6fa20137fe6aed9" + dependencies: + remove-trailing-separator "^1.0.1" + +npmlog@^4.0.2: + version "4.1.0" + resolved "https://registry.yarnpkg.com/npmlog/-/npmlog-4.1.0.tgz#dc59bee85f64f00ed424efb2af0783df25d1c0b5" + dependencies: + are-we-there-yet "~1.1.2" + console-control-strings "~1.1.0" + gauge "~2.7.3" + set-blocking "~2.0.0" + +number-is-nan@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/number-is-nan/-/number-is-nan-1.0.1.tgz#097b602b53422a522c1afb8790318336941a011d" + +"nwmatcher@>= 1.3.9 < 2.0.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/nwmatcher/-/nwmatcher-1.4.0.tgz#b4389362170e7ef9798c3c7716d80ebc0106fccf" + +oauth-sign@~0.8.1: + version "0.8.2" + resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" + +object-assign@^4.0.1, object-assign@^4.1.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" + +object.omit@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/object.omit/-/object.omit-2.0.1.tgz#1a9c744829f39dbb858c76ca3579ae2a54ebd1fa" + dependencies: + for-own "^0.1.4" + is-extendable "^0.1.1" + +once@^1.3.0, once@^1.3.3, once@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1" + dependencies: + wrappy "1" + +onetime@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/onetime/-/onetime-2.0.1.tgz#067428230fd67443b2794b22bba528b6867962d4" + dependencies: + mimic-fn "^1.0.0" + +optimist@^0.6.1: + version "0.6.1" + resolved "https://registry.yarnpkg.com/optimist/-/optimist-0.6.1.tgz#da3ea74686fa21a19a111c326e90eb15a0196686" + dependencies: + minimist "~0.0.1" + wordwrap "~0.0.2" + +optionator@^0.8.1, optionator@^0.8.2: + version "0.8.2" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.4" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + wordwrap "~1.0.0" + +os-homedir@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" + +os-locale@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/os-locale/-/os-locale-1.4.0.tgz#20f9f17ae29ed345e8bde583b13d2009803c14d9" + dependencies: + lcid "^1.0.0" + +os-tmpdir@^1.0.0, os-tmpdir@^1.0.1, os-tmpdir@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/os-tmpdir/-/os-tmpdir-1.0.2.tgz#bbe67406c79aa85c5cfec766fe5734555dfa1274" + +osenv@^0.1.4: + version "0.1.4" + resolved "https://registry.yarnpkg.com/osenv/-/osenv-0.1.4.tgz#42fe6d5953df06c8064be6f176c3d05aaaa34644" + dependencies: + os-homedir "^1.0.0" + os-tmpdir "^1.0.0" + +output-file-sync@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/output-file-sync/-/output-file-sync-1.1.2.tgz#d0a33eefe61a205facb90092e826598d5245ce76" + dependencies: + graceful-fs "^4.1.4" + mkdirp "^0.5.1" + object-assign "^4.1.0" + +p-limit@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-1.1.0.tgz#b07ff2d9a5d88bec806035895a2bab66a27988bc" + +p-locate@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-2.0.0.tgz#20a0103b222a70c8fd39cc2e580680f3dde5ec43" + dependencies: + p-limit "^1.1.0" + +p-map@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/p-map/-/p-map-1.1.1.tgz#05f5e4ae97a068371bc2a5cc86bfbdbc19c4ae7a" + +pad@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pad/-/pad-1.1.0.tgz#7a7d185200ebac32f9f12ee756c3a1d087b3190b" + +pako@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/pako/-/pako-1.0.5.tgz#d2205dfe5b9da8af797e7c163db4d1f84e4600bc" + +parse-glob@^3.0.4: + version "3.0.4" + resolved "https://registry.yarnpkg.com/parse-glob/-/parse-glob-3.0.4.tgz#b2c376cfb11f35513badd173ef0bb6e3a388391c" + dependencies: + glob-base "^0.3.0" + is-dotfile "^1.0.0" + is-extglob "^1.0.0" + is-glob "^2.0.0" + +parse-json@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-2.2.0.tgz#f480f40434ef80741f8469099f8dea18f55a4dc9" + dependencies: + error-ex "^1.2.0" + +parse5@^1.5.1: + version "1.5.1" + resolved "https://registry.yarnpkg.com/parse5/-/parse5-1.5.1.tgz#9b7f3b0de32be78dc2401b17573ccaf0f6f59d94" + +path-exists@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-2.1.0.tgz#0feb6c64f0fc518d9a754dd5efb62c7022761f4b" + dependencies: + pinkie-promise "^2.0.0" + +path-exists@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-3.0.0.tgz#ce0ebeaa5f78cb18925ea7d810d7b59b010fd515" + +path-is-absolute@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f" + +path-is-inside@^1.0.1, path-is-inside@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53" + +path-parse@^1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" + +path-type@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-1.1.0.tgz#59c44f7ee491da704da415da5a4070ba4f8fe441" + dependencies: + graceful-fs "^4.1.2" + pify "^2.0.0" + pinkie-promise "^2.0.0" + +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + dependencies: + pify "^2.0.0" + +pause-stream@0.0.11: + version "0.0.11" + resolved "https://registry.yarnpkg.com/pause-stream/-/pause-stream-0.0.11.tgz#fe5a34b0cbce12b5aa6a2b403ee2e73b602f1445" + dependencies: + through "~2.3" + +performance-now@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-0.2.0.tgz#33ef30c5c77d4ea21c5a53869d91b56d8f2555e5" + +pify@^2.0.0, pify@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" + +pinkie-promise@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa" + dependencies: + pinkie "^2.0.0" + +pinkie@^2.0.0: + version "2.0.4" + resolved "https://registry.yarnpkg.com/pinkie/-/pinkie-2.0.4.tgz#72556b80cfa0d48a974e80e77248e80ed4f7f870" + +pkg-dir@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-1.0.0.tgz#7a4b508a8d5bb2d629d447056ff4e9c9314cf3d4" + dependencies: + find-up "^1.0.0" + +pluralize@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/pluralize/-/pluralize-4.0.0.tgz#59b708c1c0190a2f692f1c7618c446b052fd1762" + +prelude-ls@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54" + +preserve@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/preserve/-/preserve-0.2.0.tgz#815ed1f6ebc65926f865b310c0713bcb3315ce4b" + +prettier@^1.4.4: + version "1.4.4" + resolved "https://registry.yarnpkg.com/prettier/-/prettier-1.4.4.tgz#a8d1447b14c9bf67e6d420dcadd10fb9a4fad65a" + +pretty-format@^20.0.3: + version "20.0.3" + resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-20.0.3.tgz#020e350a560a1fe1a98dc3beb6ccffb386de8b14" + dependencies: + ansi-regex "^2.1.1" + ansi-styles "^3.0.0" + +private@^0.1.6: + version "0.1.7" + resolved "https://registry.yarnpkg.com/private/-/private-0.1.7.tgz#68ce5e8a1ef0a23bb570cc28537b5332aba63ef1" + +process-nextick-args@~1.0.6: + version "1.0.7" + resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" + +progress@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.0.tgz#8a1be366bf8fc23db2bd23f10c6fe920b4389d1f" + +prr@~0.0.0: + version "0.0.0" + resolved "https://registry.yarnpkg.com/prr/-/prr-0.0.0.tgz#1a84b85908325501411853d0081ee3fa86e2926a" + +punycode@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" + +qs@~6.4.0: + version "6.4.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.4.0.tgz#13e26d28ad6b0ffaa91312cd3bf708ed351e7233" + +randomatic@^1.1.3: + version "1.1.7" + resolved "https://registry.yarnpkg.com/randomatic/-/randomatic-1.1.7.tgz#c7abe9cc8b87c0baa876b19fde83fd464797e38c" + dependencies: + is-number "^3.0.0" + kind-of "^4.0.0" + +rc@^1.1.7: + version "1.2.1" + resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.1.tgz#2e03e8e42ee450b8cb3dce65be1bf8974e1dfd95" + dependencies: + deep-extend "~0.4.0" + ini "~1.3.0" + minimist "^1.2.0" + strip-json-comments "~2.0.1" + +read-pkg-up@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-1.0.1.tgz#9d63c13276c065918d57f002a57f40a1b643fb02" + dependencies: + find-up "^1.0.0" + read-pkg "^1.0.0" + +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + dependencies: + find-up "^2.0.0" + read-pkg "^2.0.0" + +read-pkg@^1.0.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-1.1.0.tgz#f5ffaa5ecd29cb31c0474bca7d756b6bb29e3f28" + dependencies: + load-json-file "^1.0.0" + normalize-package-data "^2.3.2" + path-type "^1.0.0" + +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + +readable-stream@^2.0.0, readable-stream@^2.0.1, readable-stream@^2.0.2, readable-stream@^2.0.5, readable-stream@^2.0.6, readable-stream@^2.1.4, readable-stream@^2.1.5, readable-stream@^2.2.2: + version "2.2.11" + resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.2.11.tgz#0796b31f8d7688007ff0b93a8088d34aa17c0f72" + dependencies: + core-util-is "~1.0.0" + inherits "~2.0.1" + isarray "~1.0.0" + process-nextick-args "~1.0.6" + safe-buffer "~5.0.1" + string_decoder "~1.0.0" + util-deprecate "~1.0.1" + +readdirp@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-2.1.0.tgz#4ed0ad060df3073300c48440373f72d1cc642d78" + dependencies: + graceful-fs "^4.1.2" + minimatch "^3.0.2" + readable-stream "^2.0.2" + set-immediate-shim "^1.0.1" + +regenerate@^1.2.1: + version "1.3.2" + resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.3.2.tgz#d1941c67bad437e1be76433add5b385f95b19260" + +regenerator-runtime@^0.10.0: + version "0.10.5" + resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658" + +regenerator-transform@0.9.11: + version "0.9.11" + resolved "https://registry.yarnpkg.com/regenerator-transform/-/regenerator-transform-0.9.11.tgz#3a7d067520cb7b7176769eb5ff868691befe1283" + dependencies: + babel-runtime "^6.18.0" + babel-types "^6.19.0" + private "^0.1.6" + +regex-cache@^0.4.2: + version "0.4.3" + resolved "https://registry.yarnpkg.com/regex-cache/-/regex-cache-0.4.3.tgz#9b1a6c35d4d0dfcef5711ae651e8e9d3d7114145" + dependencies: + is-equal-shallow "^0.1.3" + is-primitive "^2.0.0" + +regexpu-core@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/regexpu-core/-/regexpu-core-2.0.0.tgz#49d038837b8dcf8bfa5b9a42139938e6ea2ae240" + dependencies: + regenerate "^1.2.1" + regjsgen "^0.2.0" + regjsparser "^0.1.4" + +regjsgen@^0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.2.0.tgz#6c016adeac554f75823fe37ac05b92d5a4edb1f7" + +regjsparser@^0.1.4: + version "0.1.5" + resolved "https://registry.yarnpkg.com/regjsparser/-/regjsparser-0.1.5.tgz#7ee8f84dc6fa792d3fd0ae228d24bd949ead205c" + dependencies: + jsesc "~0.5.0" + +remove-trailing-separator@^1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/remove-trailing-separator/-/remove-trailing-separator-1.0.2.tgz#69b062d978727ad14dc6b56ba4ab772fd8d70511" + +repeat-element@^1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/repeat-element/-/repeat-element-1.1.2.tgz#ef089a178d1483baae4d93eb98b4f9e4e11d990a" + +repeat-string@^1.5.2: + version "1.6.1" + resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637" + +repeating@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/repeating/-/repeating-2.0.1.tgz#5214c53a926d3552707527fbab415dbc08d06dda" + dependencies: + is-finite "^1.0.0" + +request@^2.79.0, request@^2.81.0: + version "2.81.0" + resolved "https://registry.yarnpkg.com/request/-/request-2.81.0.tgz#c6928946a0e06c5f8d6f8a9333469ffda46298a0" + dependencies: + aws-sign2 "~0.6.0" + aws4 "^1.2.1" + caseless "~0.12.0" + combined-stream "~1.0.5" + extend "~3.0.0" + forever-agent "~0.6.1" + form-data "~2.1.1" + har-validator "~4.2.1" + hawk "~3.1.3" + http-signature "~1.1.0" + is-typedarray "~1.0.0" + isstream "~0.1.2" + json-stringify-safe "~5.0.1" + mime-types "~2.1.7" + oauth-sign "~0.8.1" + performance-now "^0.2.0" + qs "~6.4.0" + safe-buffer "^5.0.1" + stringstream "~0.0.4" + tough-cookie "~2.3.0" + tunnel-agent "^0.6.0" + uuid "^3.0.0" + +require-directory@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" + +require-main-filename@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-1.0.1.tgz#97f717b69d48784f5f526a6c5aa8ffdda055a4d1" + +require-uncached@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/require-uncached/-/require-uncached-1.0.3.tgz#4e0d56d6c9662fd31e43011c4b95aa49955421d3" + dependencies: + caller-path "^0.1.0" + resolve-from "^1.0.0" + +resolve-from@^1.0.0: + version "1.0.1" + resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-1.0.1.tgz#26cbfe935d1aeeeabb29bc3fe5aeb01e93d44226" + +resolve@1.1.7: + version "1.1.7" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" + +resolve@^1.1.6, resolve@^1.3.2: + version "1.3.3" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.3.3.tgz#655907c3469a8680dc2de3a275a8fdd69691f0e5" + dependencies: + path-parse "^1.0.5" + +restore-cursor@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/restore-cursor/-/restore-cursor-2.0.0.tgz#9f7ee287f82fd326d4fd162923d62129eee0dfaf" + dependencies: + onetime "^2.0.0" + signal-exit "^3.0.2" + +right-align@^0.1.1: + version "0.1.3" + resolved "https://registry.yarnpkg.com/right-align/-/right-align-0.1.3.tgz#61339b722fe6a3515689210d24e14c96148613ef" + dependencies: + align-text "^0.1.1" + +rimraf@2, rimraf@^2.2.8, rimraf@^2.5.1, rimraf@^2.6.1: + version "2.6.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.6.1.tgz#c2338ec643df7a1b7fe5c54fa86f57428a55f33d" + dependencies: + glob "^7.0.5" + +run-async@^2.2.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/run-async/-/run-async-2.3.0.tgz#0371ab4ae0bdd720d4166d7dfda64ff7a445a6c0" + dependencies: + is-promise "^2.1.0" + +rx-lite-aggregates@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/rx-lite-aggregates/-/rx-lite-aggregates-4.0.8.tgz#753b87a89a11c95467c4ac1626c4efc4e05c67be" + dependencies: + rx-lite "*" + +rx-lite@*, rx-lite@^4.0.8: + version "4.0.8" + resolved "https://registry.yarnpkg.com/rx-lite/-/rx-lite-4.0.8.tgz#0b1e11af8bc44836f04a6407e92da42467b79444" + +safe-buffer@^5.0.1, safe-buffer@~5.0.1: + version "5.0.1" + resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.0.1.tgz#d263ca54696cd8a306b5ca6551e92de57918fbe7" + +sane@~1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/sane/-/sane-1.6.0.tgz#9610c452307a135d29c1fdfe2547034180c46775" + dependencies: + anymatch "^1.3.0" + exec-sh "^0.2.0" + fb-watchman "^1.8.0" + minimatch "^3.0.2" + minimist "^1.1.1" + walker "~1.0.5" + watch "~0.10.0" + +sax@^1.2.1: + version "1.2.2" + resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.2.tgz#fd8631a23bc7826bef5d871bdb87378c95647828" + +"semver@2 || 3 || 4 || 5", semver@^5.3.0: + version "5.3.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-5.3.0.tgz#9b2ce5d3de02d17c6012ad326aa6b4d0cf54f94f" + +set-blocking@^2.0.0, set-blocking@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" + +set-immediate-shim@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/set-immediate-shim/-/set-immediate-shim-1.0.1.tgz#4b2b1b27eb808a9f8dcc481a58e5e56f599f3f61" + +shellwords@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/shellwords/-/shellwords-0.1.0.tgz#66afd47b6a12932d9071cbfd98a52e785cd0ba14" + +signal-exit@^3.0.0, signal-exit@^3.0.2: + version "3.0.2" + resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.2.tgz#b5fdc08f1287ea1178628e415e25132b73646c6d" + +slash@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/slash/-/slash-1.0.0.tgz#c41f2f6c39fc16d1cd17ad4b5d896114ae470d55" + +slice-ansi@0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/slice-ansi/-/slice-ansi-0.0.4.tgz#edbf8903f66f7ce2f8eafd6ceed65e264c831b35" + +sntp@1.x.x: + version "1.0.9" + resolved "https://registry.yarnpkg.com/sntp/-/sntp-1.0.9.tgz#6541184cc90aeea6c6e7b35e2659082443c66198" + dependencies: + hoek "2.x.x" + +source-map-support@^0.4.2: + version "0.4.15" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.4.15.tgz#03202df65c06d2bd8c7ec2362a193056fef8d3b1" + dependencies: + source-map "^0.5.6" + +source-map@^0.4.4: + version "0.4.4" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.4.4.tgz#eba4f5da9c0dc999de68032d8b4f76173652036b" + dependencies: + amdefine ">=0.0.4" + +source-map@^0.5.0, source-map@^0.5.3, source-map@^0.5.6, source-map@~0.5.1: + version "0.5.6" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.6.tgz#75ce38f52bf0733c5a7f0c118d81334a2bb5f412" + +source-map@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.2.0.tgz#dab73fbcfc2ba819b4de03bd6f6eaa48164b3f9d" + dependencies: + amdefine ">=0.0.4" + +spdx-correct@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-1.0.2.tgz#4b3073d933ff51f3912f03ac5519498a4150db40" + dependencies: + spdx-license-ids "^1.0.2" + +spdx-expression-parse@~1.0.0: + version "1.0.4" + resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-1.0.4.tgz#9bdf2f20e1f40ed447fbe273266191fced51626c" + +spdx-license-ids@^1.0.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-1.2.2.tgz#c9df7a3424594ade6bd11900d596696dc06bac57" + +split@0.3: + version "0.3.3" + resolved "https://registry.yarnpkg.com/split/-/split-0.3.3.tgz#cd0eea5e63a211dfff7eb0f091c4133e2d0dd28f" + dependencies: + through "2" + +sprintf-js@~1.0.2: + version "1.0.3" + resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" + +sshpk@^1.7.0: + version "1.13.1" + resolved "https://registry.yarnpkg.com/sshpk/-/sshpk-1.13.1.tgz#512df6da6287144316dc4c18fe1cf1d940739be3" + dependencies: + asn1 "~0.2.3" + assert-plus "^1.0.0" + dashdash "^1.12.0" + getpass "^0.1.1" + optionalDependencies: + bcrypt-pbkdf "^1.0.0" + ecc-jsbn "~0.1.1" + jsbn "~0.1.0" + tweetnacl "~0.14.0" + +stream-combiner2@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/stream-combiner2/-/stream-combiner2-1.1.1.tgz#fb4d8a1420ea362764e21ad4780397bebcb41cbe" + dependencies: + duplexer2 "~0.1.0" + readable-stream "^2.0.2" + +stream-combiner@~0.0.4: + version "0.0.4" + resolved "https://registry.yarnpkg.com/stream-combiner/-/stream-combiner-0.0.4.tgz#4d5e433c185261dde623ca3f44c586bcf5c4ad14" + dependencies: + duplexer "~0.1.1" + +string-length@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/string-length/-/string-length-1.0.1.tgz#56970fb1c38558e9e70b728bf3de269ac45adfac" + dependencies: + strip-ansi "^3.0.0" + +string-width@^1.0.1, string-width@^1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-1.0.2.tgz#118bdf5b8cdc51a2a7e70d211e07e2b0b9b107d3" + dependencies: + code-point-at "^1.0.0" + is-fullwidth-code-point "^1.0.0" + strip-ansi "^3.0.0" + +string-width@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.0.0.tgz#635c5436cc72a6e0c387ceca278d4e2eec52687e" + dependencies: + is-fullwidth-code-point "^2.0.0" + strip-ansi "^3.0.0" + +string_decoder@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.2.tgz#b29e1f4e1125fa97a10382b8a533737b7491e179" + dependencies: + safe-buffer "~5.0.1" + +stringstream@~0.0.4: + version "0.0.5" + resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878" + +strip-ansi@^3.0.0, strip-ansi@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf" + dependencies: + ansi-regex "^2.0.0" + +strip-bom@3.0.0, strip-bom@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" + +strip-bom@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-2.0.0.tgz#6219a85616520491f35788bdbf1447a99c7e6b0e" + dependencies: + is-utf8 "^0.2.0" + +strip-json-comments@~2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + +supports-color@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-2.0.0.tgz#535d045ce6b6363fa40117084629995e9df324c7" + +supports-color@^3.1.2: + version "3.2.3" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.2.3.tgz#65ac0504b3954171d8a64946b2ae3cbb8a5f54f6" + dependencies: + has-flag "^1.0.0" + +symbol-tree@^3.2.1: + version "3.2.2" + resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.2.tgz#ae27db38f660a7ae2e1c3b7d1bc290819b8519e6" + +table@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/table/-/table-4.0.1.tgz#a8116c133fac2c61f4a420ab6cdf5c4d61f0e435" + dependencies: + ajv "^4.7.0" + ajv-keywords "^1.0.0" + chalk "^1.1.1" + lodash "^4.0.0" + slice-ansi "0.0.4" + string-width "^2.0.0" + +tar-pack@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/tar-pack/-/tar-pack-3.4.0.tgz#23be2d7f671a8339376cbdb0b8fe3fdebf317984" + dependencies: + debug "^2.2.0" + fstream "^1.0.10" + fstream-ignore "^1.0.5" + once "^1.3.3" + readable-stream "^2.1.4" + rimraf "^2.5.1" + tar "^2.2.1" + uid-number "^0.0.6" + +tar@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tar/-/tar-2.2.1.tgz#8e4d2a256c0e2185c6b18ad694aec968b83cb1d1" + dependencies: + block-stream "*" + fstream "^1.0.2" + inherits "2" + +test-exclude@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-4.1.1.tgz#4d84964b0966b0087ecc334a2ce002d3d9341e26" + dependencies: + arrify "^1.0.1" + micromatch "^2.3.11" + object-assign "^4.1.0" + read-pkg-up "^1.0.1" + require-main-filename "^1.0.1" + +text-table@~0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" + +throat@^3.0.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/throat/-/throat-3.2.0.tgz#50cb0670edbc40237b9e347d7e1f88e4620af836" + +through2@^2.0.3: + version "2.0.3" + resolved "https://registry.yarnpkg.com/through2/-/through2-2.0.3.tgz#0004569b37c7c74ba39c43f3ced78d1ad94140be" + dependencies: + readable-stream "^2.1.5" + xtend "~4.0.1" + +through@2, through@^2.3.6, through@~2.3, through@~2.3.1: + version "2.3.8" + resolved "https://registry.yarnpkg.com/through/-/through-2.3.8.tgz#0dd4c9ffaabc357960b1b724115d7e0e86a2e1f5" + +tmp@^0.0.31: + version "0.0.31" + resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.31.tgz#8f38ab9438e17315e5dbd8b3657e8bfb277ae4a7" + dependencies: + os-tmpdir "~1.0.1" + +tmpl@1.0.x: + version "1.0.4" + resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.4.tgz#23640dd7b42d00433911140820e5cf440e521dd1" + +to-fast-properties@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" + +tough-cookie@^2.3.2, tough-cookie@~2.3.0: + version "2.3.2" + resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.2.tgz#f081f76e4c85720e6c37a5faced737150d84072a" + dependencies: + punycode "^1.4.1" + +tr46@~0.0.3: + version "0.0.3" + resolved "https://registry.yarnpkg.com/tr46/-/tr46-0.0.3.tgz#8184fd347dac9cdc185992f3a6622e14b9d9ab6a" + +trim-right@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/trim-right/-/trim-right-1.0.1.tgz#cb2e1203067e0c8de1f614094b9fe45704ea6003" + +tryit@^1.0.1: + version "1.0.3" + resolved "https://registry.yarnpkg.com/tryit/-/tryit-1.0.3.tgz#393be730a9446fd1ead6da59a014308f36c289cb" + +tunnel-agent@^0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/tunnel-agent/-/tunnel-agent-0.6.0.tgz#27a5dea06b36b04a0a9966774b290868f0fc40fd" + dependencies: + safe-buffer "^5.0.1" + +tweetnacl@^0.14.3, tweetnacl@~0.14.0: + version "0.14.5" + resolved "https://registry.yarnpkg.com/tweetnacl/-/tweetnacl-0.14.5.tgz#5ae68177f192d4456269d108afa93ff8743f4f64" + +type-check@~0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72" + dependencies: + prelude-ls "~1.1.2" + +typedarray-to-buffer@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.2.tgz#1017b32d984ff556eba100f501589aba1ace2e04" + dependencies: + is-typedarray "^1.0.0" + +typedarray@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" + +uglify-js@^2.6: + version "2.8.29" + resolved "https://registry.yarnpkg.com/uglify-js/-/uglify-js-2.8.29.tgz#29c5733148057bb4e1f75df35b7a9cb72e6a59dd" + dependencies: + source-map "~0.5.1" + yargs "~3.10.0" + optionalDependencies: + uglify-to-browserify "~1.0.0" + +uglify-to-browserify@~1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/uglify-to-browserify/-/uglify-to-browserify-1.0.2.tgz#6e0924d6bda6b5afe349e39a6d632850a0f882b7" + +uid-number@^0.0.6: + version "0.0.6" + resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" + +user-home@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/user-home/-/user-home-1.1.1.tgz#2b5be23a32b63a7c9deb8d0f28d485724a3df190" + +util-deprecate@~1.0.1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" + +uuid@^3.0.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.1.0.tgz#3dd3d3e790abc24d7b0d3a034ffababe28ebbc04" + +v8flags@^2.0.10: + version "2.1.1" + resolved "https://registry.yarnpkg.com/v8flags/-/v8flags-2.1.1.tgz#aab1a1fa30d45f88dd321148875ac02c0b55e5b4" + dependencies: + user-home "^1.1.1" + +validate-npm-package-license@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.1.tgz#2804babe712ad3379459acfbe24746ab2c303fbc" + dependencies: + spdx-correct "~1.0.0" + spdx-expression-parse "~1.0.0" + +varint@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/varint/-/varint-5.0.0.tgz#d826b89f7490732fabc0c0ed693ed475dcb29ebf" + +verror@1.3.6: + version "1.3.6" + resolved "https://registry.yarnpkg.com/verror/-/verror-1.3.6.tgz#cff5df12946d297d2baaefaa2689e25be01c005c" + dependencies: + extsprintf "1.0.2" + +walker@~1.0.5: + version "1.0.7" + resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.7.tgz#2f7f9b8fd10d677262b18a884e28d19618e028fb" + dependencies: + makeerror "1.0.x" + +watch@~0.10.0: + version "0.10.0" + resolved "https://registry.yarnpkg.com/watch/-/watch-0.10.0.tgz#77798b2da0f9910d595f1ace5b0c2258521f21dc" + +webidl-conversions@^3.0.0: + version "3.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" + +webidl-conversions@^4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-4.0.1.tgz#8015a17ab83e7e1b311638486ace81da6ce206a0" + +whatwg-encoding@^1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.1.tgz#3c6c451a198ee7aec55b1ec61d0920c67801a5f4" + dependencies: + iconv-lite "0.4.13" + +whatwg-url@^4.3.0: + version "4.8.0" + resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-4.8.0.tgz#d2981aa9148c1e00a41c5a6131166ab4683bbcc0" + dependencies: + tr46 "~0.0.3" + webidl-conversions "^3.0.0" + +which-module@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/which-module/-/which-module-1.0.0.tgz#bba63ca861948994ff307736089e3b96026c2a4f" + +which@^1.2.12: + version "1.2.14" + resolved "https://registry.yarnpkg.com/which/-/which-1.2.14.tgz#9a87c4378f03e827cecaf1acdf56c736c01c14e5" + dependencies: + isexe "^2.0.0" + +wide-align@^1.1.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/wide-align/-/wide-align-1.1.2.tgz#571e0f1b0604636ebc0dfc21b0339bbe31341710" + dependencies: + string-width "^1.0.2" + +window-size@0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/window-size/-/window-size-0.1.0.tgz#5438cd2ea93b202efa3a19fe8887aee7c94f9c9d" + +wordwrap@0.0.2: + version "0.0.2" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f" + +wordwrap@~0.0.2: + version "0.0.3" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.3.tgz#a3d5da6cd5c0bc0008d37234bbaf1bed63059107" + +wordwrap@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb" + +worker-farm@^1.3.1: + version "1.3.1" + resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.3.1.tgz#4333112bb49b17aa050b87895ca6b2cacf40e5ff" + dependencies: + errno ">=0.1.1 <0.2.0-0" + xtend ">=4.0.0 <4.1.0-0" + +wrap-ansi@^2.0.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-2.1.0.tgz#d8fc3d284dd05794fe84973caecdd1cf824fdd85" + dependencies: + string-width "^1.0.1" + strip-ansi "^3.0.1" + +wrappy@1: + version "1.0.2" + resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" + +write@^0.2.1: + version "0.2.1" + resolved "https://registry.yarnpkg.com/write/-/write-0.2.1.tgz#5fc03828e264cea3fe91455476f7a3c566cb0757" + dependencies: + mkdirp "^0.5.1" + +xml-name-validator@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-2.0.1.tgz#4d8b8f1eccd3419aa362061becef515e1e559635" + +"xtend@>=4.0.0 <4.1.0-0", xtend@^4.0.0, xtend@~4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.1.tgz#a5c6d532be656e23db820efb943a1f04998d63af" + +y18n@^3.2.1: + version "3.2.1" + resolved "https://registry.yarnpkg.com/y18n/-/y18n-3.2.1.tgz#6d15fba884c08679c0d77e88e7759e811e07fa41" + +yargs-parser@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-5.0.0.tgz#275ecf0d7ffe05c77e64e7c86e4cd94bf0e1228a" + dependencies: + camelcase "^3.0.0" + +yargs@^7.0.2: + version "7.1.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-7.1.0.tgz#6ba318eb16961727f5d284f8ea003e8d6154d0c8" + dependencies: + camelcase "^3.0.0" + cliui "^3.2.0" + decamelize "^1.1.1" + get-caller-file "^1.0.1" + os-locale "^1.4.0" + read-pkg-up "^1.0.1" + require-directory "^2.1.1" + require-main-filename "^1.0.1" + set-blocking "^2.0.0" + string-width "^1.0.2" + which-module "^1.0.0" + y18n "^3.2.1" + yargs-parser "^5.0.0" + +yargs@~3.10.0: + version "3.10.0" + resolved "https://registry.yarnpkg.com/yargs/-/yargs-3.10.0.tgz#f7ee7bd857dd7c1d2d38c0e74efbd681d1431fd1" + dependencies: + camelcase "^1.0.2" + cliui "^2.1.0" + decamelize "^1.0.0" + window-size "0.1.0"