diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..bbf1d1fa --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,24 @@ +name: CI + +on: [push] + +jobs: + Build: + runs-on: ubuntu-latest + env: + CC: clang + CXX: clang++ + npm_config_clang: 1 + steps: + - uses: actions/checkout@v2 + - uses: actions/setup-node@v2 + with: + node-version: 14 + - name: Install + run: | + npm run prepare + npm install + - name: Test + run: | + npm run lint + npm run test diff --git a/.npmignore b/.npmignore index 27e4f0ff..338b1395 100644 --- a/.npmignore +++ b/.npmignore @@ -1,6 +1,8 @@ /build /spec /deps/libgit2/build +/deps/libgit2/fuzzers +/deps/libgit2/tests /deps/libgit2/tests-clar *.a *.o diff --git a/.travis.yml b/.travis.yml deleted file mode 100644 index 195a90f8..00000000 --- a/.travis.yml +++ /dev/null @@ -1,7 +0,0 @@ -language: node_js - -notifications: - email: false - -node_js: - - 0.10 diff --git a/Gruntfile.coffee b/Gruntfile.coffee deleted file mode 100644 index 79a107cd..00000000 --- a/Gruntfile.coffee +++ /dev/null @@ -1,59 +0,0 @@ -module.exports = (grunt) -> - grunt.initConfig - pkg: grunt.file.readJSON('package.json') - - coffee: - glob_to_multiple: - expand: true - cwd: 'src' - src: ['*.coffee'] - dest: 'lib' - ext: '.js' - - coffeelint: - options: - no_empty_param_list: - level: 'error' - max_line_length: - level: 'ignore' - - gruntfile: ['Gruntfile.coffee'] - src: ['src/**/*.coffee'] - test: ['spec/**/*.coffee'] - - cpplint: - files: [ - 'src/**/*.cc' - 'src/**/*.h' - ] - reporter: 'spec' - verbosity: 1 - filters: - build: - include: false - - shell: - rebuild: - command: 'npm build .' - options: - stdout: true - stderr: true - failOnError: true - - test: - command: 'node node_modules/jasmine-focused/bin/jasmine-focused --captureExceptions --coffee spec/' - options: - stdout: true - stderr: true - failOnError: true - - grunt.loadNpmTasks('grunt-contrib-coffee') - grunt.loadNpmTasks('grunt-shell') - grunt.loadNpmTasks('grunt-coffeelint') - grunt.loadNpmTasks('node-cpplint') - grunt.registerTask('lint', ['coffeelint', 'cpplint']) - grunt.registerTask('default', ['coffee', 'lint', 'shell:rebuild']) - grunt.registerTask('test', ['default', 'shell:test']) - grunt.registerTask 'clean', -> - grunt.file.delete('lib') if grunt.file.exists('lib') - grunt.file.delete('build') if grunt.file.exists('build') diff --git a/README.md b/README.md index a6958fd5..91a2765d 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,6 @@ -# Git Node module [![Build Status](https://travis-ci.org/atom/git-utils.svg?branch=master)](https://travis-ci.org/atom/git-utils) +##### Atom and all repositories under Atom will be archived on December 15, 2022. Learn more in our [official announcement](https://github.blog/2022-06-08-sunsetting-atom/) + # Git Node module [![CI](https://github.com/atom/git-utils/actions/workflows/ci.yml/badge.svg)](https://github.com/atom/git-utils/actions/workflows/ci.yml) + Helpers for working with Git repositories built natively on top of [libgit2](http://libgit2.github.com). @@ -9,20 +11,22 @@ Helpers for working with Git repositories built natively on top of npm install git-utils ``` -## Building - * Clone the repository with the `--recurse` option to get the libgit2 - submodule +## Development + * Clone the repository + * Run `npm run prepare` to get the submodule * Run `npm install` - * Run `grunt` to compile the native and CoffeeScript code - * Run `grunt test` to run the specs + * Run `npm test` to run the specs ## Docs -### git.open(path) +### git.open(path, [search = true]) Open the repository at the given path. This will return `null` if the repository at the given path does not exist or cannot be opened. +`path` - The path from which to try to open a repository +`search` - Set to false if we shouldn't search up in the directory tree + ```coffeescript git = require 'git-utils' @@ -33,6 +37,11 @@ The opened repository will have a `submodules` property that will be an object of paths mapped to submodule {Repository} objects. The path keys will be relative to the opened repository's working directory. +If search is set to true (the default), all paths up to the filesystem root will +be recursively checked to try and find the root directory of a repository. If a +search is false, traversing not be performed, and a repository will only be +returned if the given path is the root of a repository. + ### Repository.checkoutHead(path) Restore the contents of a path in the working directory and index to the @@ -137,14 +146,31 @@ text. `options` - An optional object with the following keys: - * `ignoreEolWhitespace` - `true` to ignore any whitespace diffs at the end of - lines. + * `ignoreSpaceAtEOL` - `true` to ignore changes in whitespace at the end of lines. + (ignoreEolWhitespace also works.) + * `ignoreSpaceChange` - `true` to ignore changes in amount of whitespace. + This ignores whitespace at line end, and considers all other sequences of + one or more whitespace characters to be equivalent. + * `ignoreAllSpace` - `true` to ignore whitespace when comparing lines. + This ignores differences even if one line has whitespace where the other line has none. * `useIndex` - `true` to compare against the index version instead of the HEAD version. Returns an array of objects that have `oldStart`, `oldLines`, `newStart`, and `newLines` keys pointing to integer values, may be `null` if the diff fails. +### Repository.getLineDiffDetails(path, text, [options]) + +Get the line diff details comparing the HEAD version of the given path and the given +text. + +Takes the same arguments as `getLineDiffs`. + +Returns an array of objects which represent an old or new line in a diff. Every +object has `oldStart`, `oldLines`, `newStart`, `newLines`, `oldLineNumber` and +`newLineNumber` keys pointing to integer values, and a `line` key pointing to the +respective line content. May be `null` if the diff fails. + ### Repository.getMergeBase(commit1, commit2) Get the merge base of two commits. @@ -241,6 +267,22 @@ Get the deleted status of a given path. Returns `true` if the path is deleted, `false` otherwise. +### Repository.isPathStaged(path) + +Get the staged status of a given path. + +`path` - The string repository-relative path. + +Returns `true` if the path is staged in the index, `false` otherwise. + +### Repository.isStatusIgnored(status) + +Check if a status value represents an ignored path. + +`status` - The integer status value. + +Returns `true` if the status is a ignored one, `false` otherwise. + ### Repository.isStatusModified(status) Check if a status value represents a modified path. @@ -265,6 +307,14 @@ Check if a status value represents a deleted path. Returns `true` if the status is a deleted one, `false` otherwise. +### Repository.isStatusStaged(status) + +Check if a status value represents a changed that is staged in the index. + +`status` - The integer status value. + +Returns `true` if the status is a staged one, `false` otherwise. + ### Repository.isSubmodule(path) Check if the path is a submodule in the index. @@ -287,6 +337,20 @@ Relativize the given path to the repository's working directory. Returns a repository-relative path if the given path is prefixed with the repository's working directory path. +### Repository.isWorkingDirectory(path) + +Is the given path the repository's working directory? + +It is better to call this method than comparing a path directly against +the value of `getWorkingDirectory()` since this method handles slash +normalization on Windows, case insensitive filesystems, and symlinked +repositories. + +`path` - The string path to check. + +Returns `true` if the given path is the repository's working directory, +false otherwise. + ### Repository.release() Release the repository and close all file handles it has open. No other methods @@ -299,3 +363,12 @@ Get the repository for the submodule that the path is located in. `path` - The absolute or repository-relative string path. Returns a `Repository` or `null` if the path isn't in a submodule. + +### Repository.add(path) + +Stage the changes in `path` into the repository's index. Clear any conflict state +associated with `path`. + +`path` - A repository-relative string path. + +Raises an `Error` if the path isn't readable or if another exception occurs. diff --git a/binding.gyp b/binding.gyp index a4aa0e42..71fafacd 100644 --- a/binding.gyp +++ b/binding.gyp @@ -12,14 +12,19 @@ 'conditions': [ ['OS=="win"', { 'msvs_disabled_warnings': [ + 4244, # conversion from 'ssize_t' to 'int32_t', possible loss of data 4530, # C++ exception handler used, but unwind semantics are not enabled. 4506, # no definition for inline function 4267, # conversion from 'size_t' to 'int', possible loss of data + 4344, # behavior change ], }, { 'cflags': [ '-Wno-missing-field-initializers', ], + 'cflags_cc!': [ + '-fno-delete-null-pointer-checks', # clang-3.4 doesn't understand this flag and fails. + ], 'xcode_settings': { 'WARNING_CFLAGS': [ '-Wno-missing-field-initializers', @@ -30,9 +35,11 @@ }, { 'target_name': 'libgit2', + 'win_delay_load_hook': 'false', 'type': 'static_library', 'defines': [ 'GIT_THREADS', + 'LIBGIT2_NO_FEATURES_H', # Node's util.h may be accidentally included so use this to guard # against compilation error. 'SRC_UTIL_H_', @@ -42,18 +49,28 @@ 'http_parser', ], 'sources': [ + 'deps/libgit2/src/annotated_commit.c', + 'deps/libgit2/src/annotated_commit.h', + 'deps/libgit2/src/apply.c', + 'deps/libgit2/src/apply.h', + 'deps/libgit2/src/alloc.c', + 'deps/libgit2/src/alloc.h', 'deps/libgit2/src/array.h', 'deps/libgit2/src/attr.c', 'deps/libgit2/src/attr.h', 'deps/libgit2/src/attr_file.c', 'deps/libgit2/src/attr_file.h', - 'deps/libgit2/src/bitvec.h', + 'deps/libgit2/src/attrcache.c', 'deps/libgit2/src/attrcache.h', + 'deps/libgit2/src/bitvec.h', + 'deps/libgit2/src/blame.c', + 'deps/libgit2/src/blame.h', + 'deps/libgit2/src/blame_git.c', + 'deps/libgit2/src/blame_git.h', 'deps/libgit2/src/blob.c', 'deps/libgit2/src/blob.h', 'deps/libgit2/src/branch.c', 'deps/libgit2/src/branch.h', - 'deps/libgit2/src/bswap.h', 'deps/libgit2/src/buf_text.c', 'deps/libgit2/src/buf_text.h', 'deps/libgit2/src/buffer.c', @@ -63,35 +80,42 @@ 'deps/libgit2/src/cc-compat.h', 'deps/libgit2/src/checkout.c', 'deps/libgit2/src/checkout.h', + 'deps/libgit2/src/cherrypick.c', 'deps/libgit2/src/clone.c', + 'deps/libgit2/src/clone.h', 'deps/libgit2/src/commit.c', 'deps/libgit2/src/commit.h', 'deps/libgit2/src/commit_list.c', 'deps/libgit2/src/commit_list.h', 'deps/libgit2/src/common.h', - 'deps/libgit2/src/compress.c', - 'deps/libgit2/src/compress.h', 'deps/libgit2/src/config.c', 'deps/libgit2/src/config.h', 'deps/libgit2/src/config_cache.c', + 'deps/libgit2/src/config_entries.c', + 'deps/libgit2/src/config_entries.h', 'deps/libgit2/src/config_file.c', 'deps/libgit2/src/config_file.h', + 'deps/libgit2/src/config_parse.c', + 'deps/libgit2/src/config_parse.h', 'deps/libgit2/src/crlf.c', 'deps/libgit2/src/date.c', - 'deps/libgit2/src/delta-apply.c', - 'deps/libgit2/src/delta-apply.h', 'deps/libgit2/src/delta.c', 'deps/libgit2/src/delta.h', + 'deps/libgit2/src/describe.c', 'deps/libgit2/src/diff.c', 'deps/libgit2/src/diff.h', 'deps/libgit2/src/diff_driver.c', 'deps/libgit2/src/diff_driver.h', 'deps/libgit2/src/diff_file.c', 'deps/libgit2/src/diff_file.h', - 'deps/libgit2/src/diff_patch.c', - 'deps/libgit2/src/diff_patch.h', + 'deps/libgit2/src/diff_generate.c', + 'deps/libgit2/src/diff_generate.h', + 'deps/libgit2/src/diff_parse.c', + 'deps/libgit2/src/diff_parse.h', 'deps/libgit2/src/diff_print.c', + 'deps/libgit2/src/diff_stats.c', 'deps/libgit2/src/diff_tform.c', + 'deps/libgit2/src/diff_tform.h', 'deps/libgit2/src/diff_xdiff.c', 'deps/libgit2/src/diff_xdiff.h', 'deps/libgit2/src/errors.c', @@ -105,29 +129,35 @@ 'deps/libgit2/src/fileops.h', 'deps/libgit2/src/filter.c', 'deps/libgit2/src/filter.h', - 'deps/libgit2/src/fnmatch.c', - 'deps/libgit2/src/fnmatch.h', + 'deps/libgit2/src/wildmatch.c', + 'deps/libgit2/src/wildmatch.h', 'deps/libgit2/src/global.c', 'deps/libgit2/src/global.h', 'deps/libgit2/src/graph.c', 'deps/libgit2/src/hash.c', 'deps/libgit2/src/hash.h', 'deps/libgit2/src/hashsig.c', - 'deps/libgit2/src/hashsig.h', 'deps/libgit2/src/ident.c', + 'deps/libgit2/src/idxmap.c', + 'deps/libgit2/src/idxmap.h', 'deps/libgit2/src/ignore.c', 'deps/libgit2/src/ignore.h', 'deps/libgit2/src/index.c', 'deps/libgit2/src/index.h', 'deps/libgit2/src/indexer.c', + 'deps/libgit2/src/indexer.h', + 'deps/libgit2/src/integer.h', 'deps/libgit2/src/iterator.c', 'deps/libgit2/src/iterator.h', 'deps/libgit2/src/khash.h', + 'deps/libgit2/src/mailmap.c', + 'deps/libgit2/src/mailmap.h', 'deps/libgit2/src/map.h', 'deps/libgit2/src/merge.c', 'deps/libgit2/src/merge.h', + 'deps/libgit2/src/merge_driver.c', + 'deps/libgit2/src/merge_driver.h', 'deps/libgit2/src/merge_file.c', - 'deps/libgit2/src/merge_file.h', 'deps/libgit2/src/message.c', 'deps/libgit2/src/message.h', 'deps/libgit2/src/mwindow.c', @@ -142,15 +172,28 @@ 'deps/libgit2/src/odb.c', 'deps/libgit2/src/odb.h', 'deps/libgit2/src/odb_loose.c', + 'deps/libgit2/src/odb_mempack.c', 'deps/libgit2/src/odb_pack.c', + 'deps/libgit2/src/offmap.c', 'deps/libgit2/src/offmap.h', 'deps/libgit2/src/oid.c', 'deps/libgit2/src/oid.h', + 'deps/libgit2/src/oidarray.c', + 'deps/libgit2/src/oidarray.h', + 'deps/libgit2/src/oidmap.c', 'deps/libgit2/src/oidmap.h', 'deps/libgit2/src/pack-objects.c', 'deps/libgit2/src/pack-objects.h', 'deps/libgit2/src/pack.c', 'deps/libgit2/src/pack.h', + 'deps/libgit2/src/parse.c', + 'deps/libgit2/src/parse.h', + 'deps/libgit2/src/patch.c', + 'deps/libgit2/src/patch.h', + 'deps/libgit2/src/patch_generate.c', + 'deps/libgit2/src/patch_generate.h', + 'deps/libgit2/src/patch_parse.c', + 'deps/libgit2/src/patch_parse.h', 'deps/libgit2/src/path.c', 'deps/libgit2/src/path.h', 'deps/libgit2/src/pathspec.c', @@ -161,8 +204,11 @@ 'deps/libgit2/src/posix.h', 'deps/libgit2/src/pqueue.c', 'deps/libgit2/src/pqueue.h', + 'deps/libgit2/src/proxy.c', + 'deps/libgit2/src/proxy.h', 'deps/libgit2/src/push.c', 'deps/libgit2/src/push.h', + 'deps/libgit2/src/rebase.c', 'deps/libgit2/src/refdb.c', 'deps/libgit2/src/refdb.h', 'deps/libgit2/src/refdb_fs.c', @@ -179,9 +225,11 @@ 'deps/libgit2/src/repository.c', 'deps/libgit2/src/repository.h', 'deps/libgit2/src/reset.c', + 'deps/libgit2/src/revert.c', 'deps/libgit2/src/revparse.c', 'deps/libgit2/src/revwalk.c', 'deps/libgit2/src/revwalk.h', + 'deps/libgit2/src/settings.c', 'deps/libgit2/src/sha1_lookup.c', 'deps/libgit2/src/sha1_lookup.h', 'deps/libgit2/src/signature.c', @@ -191,36 +239,71 @@ 'deps/libgit2/src/stash.c', 'deps/libgit2/src/status.c', 'deps/libgit2/src/status.h', + 'deps/libgit2/src/allocators/stdalloc.c', + 'deps/libgit2/src/allocators/stdalloc.h', + 'deps/libgit2/src/stream.h', 'deps/libgit2/src/strmap.c', 'deps/libgit2/src/strmap.h', + 'deps/libgit2/src/strnlen.h', 'deps/libgit2/src/submodule.c', 'deps/libgit2/src/submodule.h', + 'deps/libgit2/src/sysdir.c', + 'deps/libgit2/src/sysdir.h', 'deps/libgit2/src/tag.c', 'deps/libgit2/src/tag.h', 'deps/libgit2/src/thread-utils.c', 'deps/libgit2/src/thread-utils.h', 'deps/libgit2/src/trace.c', 'deps/libgit2/src/trace.h', + 'deps/libgit2/src/trailer.c', + 'deps/libgit2/src/transaction.c', + 'deps/libgit2/src/transaction.h', 'deps/libgit2/src/transport.c', 'deps/libgit2/src/tree-cache.c', 'deps/libgit2/src/tree-cache.h', 'deps/libgit2/src/tree.c', 'deps/libgit2/src/tree.h', 'deps/libgit2/src/tsort.c', + 'deps/libgit2/src/userdiff.h', 'deps/libgit2/src/util.c', 'deps/libgit2/src/util.h', + 'deps/libgit2/src/varint.c', + 'deps/libgit2/src/varint.h', 'deps/libgit2/src/vector.c', 'deps/libgit2/src/vector.h', + 'deps/libgit2/src/worktree.c', + 'deps/libgit2/src/worktree.h', + 'deps/libgit2/src/zstream.c', + 'deps/libgit2/src/zstream.h', + 'deps/libgit2/src/streams/registry.c', + 'deps/libgit2/src/streams/registry.h', + 'deps/libgit2/src/streams/mbedtls.c', + 'deps/libgit2/src/streams/mbedtls.h', + 'deps/libgit2/src/streams/openssl.c', + 'deps/libgit2/src/streams/openssl.h', + 'deps/libgit2/src/streams/socket.c', + 'deps/libgit2/src/streams/socket.h', + 'deps/libgit2/src/streams/stransport.c', + 'deps/libgit2/src/streams/stransport.h', + 'deps/libgit2/src/streams/tls.c', + 'deps/libgit2/src/streams/tls.h', + 'deps/libgit2/src/transports/auth.c', + 'deps/libgit2/src/transports/auth.h', + 'deps/libgit2/src/transports/auth_negotiate.c', + 'deps/libgit2/src/transports/auth_negotiate.h', 'deps/libgit2/src/transports/cred.c', + 'deps/libgit2/src/transports/cred.h', 'deps/libgit2/src/transports/cred_helpers.c', 'deps/libgit2/src/transports/git.c', 'deps/libgit2/src/transports/http.c', + 'deps/libgit2/src/transports/http.h', 'deps/libgit2/src/transports/local.c', 'deps/libgit2/src/transports/smart.c', 'deps/libgit2/src/transports/smart.h', 'deps/libgit2/src/transports/smart_pkt.c', 'deps/libgit2/src/transports/smart_protocol.c', 'deps/libgit2/src/transports/ssh.c', + 'deps/libgit2/src/transports/ssh.h', 'deps/libgit2/src/transports/winhttp.c', 'deps/libgit2/src/xdiff/xdiff.h', 'deps/libgit2/src/xdiff/xdiffi.c', @@ -238,23 +321,39 @@ 'deps/libgit2/src/xdiff/xutils.c', 'deps/libgit2/src/xdiff/xutils.h', 'deps/libgit2/src/hash/hash_generic.c', + 'deps/libgit2/src/hash/hash_generic.h', ], 'conditions': [ - ['OS=="linux"', { + ['OS=="mac"', { + 'defines': [ + 'GIT_USE_STAT_MTIMESPEC' + ], + }], + ['OS=="linux" or OS.endswith("bsd")', { 'cflags': [ '-w', ], + 'defines': [ + 'GIT_USE_STAT_MTIM' + ] }], ['OS=="win"', { 'defines': [ 'GIT_WINHTTP', + 'GIT_REGEX_BUILTIN', + ], + 'dependencies': [ + 'pcre', ], + 'link_settings': { + 'libraries': [ + '-lcrypt32.lib', + '-lrpcrt4.lib', + '-lwinhttp.lib', + '-lws2_32.lib', + ], + }, 'msvs_settings': { - 'VCLinkerTool': { - 'AdditionalDependencies': [ - 'ws2_32.lib', - ], - }, # Workaround of a strange bug: # TargetMachine + static_library + x64 = nothing. 'conditions': [ @@ -274,6 +373,7 @@ ], }, 'msvs_disabled_warnings': [ + 4005, # macro redefinition 4244, # conversion from 'ssize_t' to 'int32_t', possible loss of data 4267, # conversion from 'size_t' to 'int', possible loss of data 4090, # different 'volatile' qualifiers @@ -281,26 +381,37 @@ 4013, # 'InterlockedDecrement' undefined; assuming extern returning int ], 'sources': [ + 'deps/libgit2/src/net.c', 'deps/libgit2/src/win32/dir.c', 'deps/libgit2/src/win32/dir.h', 'deps/libgit2/src/win32/error.c', 'deps/libgit2/src/win32/error.h', 'deps/libgit2/src/win32/findfile.c', 'deps/libgit2/src/win32/findfile.h', - 'deps/libgit2/src/win32/git2.rc', 'deps/libgit2/src/win32/map.c', 'deps/libgit2/src/win32/mingw-compat.h', 'deps/libgit2/src/win32/msvc-compat.h', + 'deps/libgit2/src/win32/path_w32.c', + 'deps/libgit2/src/win32/path_w32.h', 'deps/libgit2/src/win32/posix.h', 'deps/libgit2/src/win32/posix_w32.c', 'deps/libgit2/src/win32/precompiled.c', 'deps/libgit2/src/win32/precompiled.h', - 'deps/libgit2/src/win32/pthread.c', - 'deps/libgit2/src/win32/pthread.h', + 'deps/libgit2/src/win32/reparse.h', + 'deps/libgit2/src/win32/thread.c', + 'deps/libgit2/src/win32/thread.h', 'deps/libgit2/src/win32/utf-conv.c', 'deps/libgit2/src/win32/utf-conv.h', 'deps/libgit2/src/win32/version.h', - 'deps/libgit2/deps/regex/regex.c', + 'deps/libgit2/src/win32/w32_buffer.c', + 'deps/libgit2/src/win32/w32_buffer.h', + 'deps/libgit2/src/win32/w32_crtdbg_stacktrace.c', + 'deps/libgit2/src/win32/w32_crtdbg_stacktrace.h', + 'deps/libgit2/src/win32/w32_stack.c', + 'deps/libgit2/src/win32/w32_stack.h', + 'deps/libgit2/src/win32/w32_util.c', + 'deps/libgit2/src/win32/w32_util.h', + 'deps/libgit2/src/win32/win32-compat.h', ], }, { 'libraries': [ @@ -309,16 +420,19 @@ 'sources': [ 'deps/libgit2/src/unix/map.c', 'deps/libgit2/src/unix/posix.h', + 'deps/libgit2/src/unix/pthread.h', 'deps/libgit2/src/unix/realpath.c', ], 'cflags': [ '-Wno-missing-field-initializers', - '-Wno-unused-variable' + '-Wno-unused-variable', + '-Wno-unused-function', ], 'xcode_settings': { 'WARNING_CFLAGS': [ '-Wno-missing-field-initializers', - '-Wno-unused-variable' + '-Wno-unused-variable', + '-Wno-unused-function', ], }, }], @@ -326,7 +440,6 @@ 'include_dirs': [ 'deps/libgit2/include', 'deps/libgit2/src', - 'deps/libgit2/deps/regex', ], 'direct_dependent_settings': { 'include_dirs': [ @@ -336,6 +449,7 @@ }, { 'target_name': 'zlib', + 'win_delay_load_hook': 'false', 'type': 'static_library', 'sources': [ 'deps/libgit2/deps/zlib/adler32.c', @@ -343,6 +457,8 @@ 'deps/libgit2/deps/zlib/crc32.h', 'deps/libgit2/deps/zlib/deflate.c', 'deps/libgit2/deps/zlib/deflate.h', + 'deps/libgit2/deps/zlib/gzguts.h', + 'deps/libgit2/deps/zlib/infback.c', 'deps/libgit2/deps/zlib/inffast.c', 'deps/libgit2/deps/zlib/inffast.h', 'deps/libgit2/deps/zlib/inffixed.h', @@ -364,16 +480,26 @@ ], 'include_dirs': [ 'deps/libgit2/include', - 'deps/libgit2/deps/regex', ], 'direct_dependent_settings': { 'include_dirs': [ 'deps/libgit2/deps/zlib', ], }, + 'conditions': [ + ['OS=="win"', { + 'msvs_disabled_warnings': [ + 4005, # macro redefinition + ], + 'include_dirs': [ + 'deps/libgit2/deps/regex', + ], + }], + ], }, { 'target_name': 'http_parser', + 'win_delay_load_hook': 'false', 'type': 'static_library', 'sources': [ 'deps/libgit2/deps/http-parser/http_parser.c', @@ -393,4 +519,58 @@ ], }, ], + 'conditions': [ + ['OS=="win"', { + 'targets': [ + { + 'target_name': 'pcre', + 'win_delay_load_hook': 'false', + 'type': 'static_library', + 'sources': [ + 'deps/libgit2/deps/pcre/pcre_byte_order.c', + 'deps/libgit2/deps/pcre/pcre_chartables.c', + 'deps/libgit2/deps/pcre/pcre_compile.c', + 'deps/libgit2/deps/pcre/pcre_config.c', + 'deps/libgit2/deps/pcre/pcre_dfa_exec.c', + 'deps/libgit2/deps/pcre/pcre_exec.c', + 'deps/libgit2/deps/pcre/pcre_fullinfo.c', + 'deps/libgit2/deps/pcre/pcre_get.c', + 'deps/libgit2/deps/pcre/pcre_globals.c', + 'deps/libgit2/deps/pcre/pcre_jit_compile.c', + 'deps/libgit2/deps/pcre/pcre_maketables.c', + 'deps/libgit2/deps/pcre/pcre_newline.c', + 'deps/libgit2/deps/pcre/pcre_ord2utf8.c', + 'deps/libgit2/deps/pcre/pcre_refcount.c', + 'deps/libgit2/deps/pcre/pcre_string_utils.c', + 'deps/libgit2/deps/pcre/pcre_study.c', + 'deps/libgit2/deps/pcre/pcre_tables.c', + 'deps/libgit2/deps/pcre/pcre_ucd.c', + 'deps/libgit2/deps/pcre/pcre_valid_utf8.c', + 'deps/libgit2/deps/pcre/pcre_version.c', + 'deps/libgit2/deps/pcre/pcre_xclass.c', + 'deps/libgit2/deps/pcre/pcreposix.c', + ], + 'defines': [ + 'SUPPORT_PCRE8=1', + 'LINK_SIZE=2', + 'PARENS_NEST_LIMIT=250', + 'MATCH_LIMIT=10000000', + 'MATCH_LIMIT_RECURSION="MATCH_LIMIT"', + 'NEWLINE="LF"', + 'NO_RECURSE=1', + 'POSIX_MALLOC_THRESHOLD=10', + 'BSR_ANYCRLF=0', + 'MAX_NAME_SIZE=32', + 'MAX_NAME_COUNT=10000', + ], + 'include_dirs': [], + 'direct_dependent_settings': { + 'include_dirs': [ + 'deps/libgit2/deps/pcre', + ], + }, + }, + ] + }] + ] } diff --git a/deps/libgit2 b/deps/libgit2 index 43cb8b32..fef847ae 160000 --- a/deps/libgit2 +++ b/deps/libgit2 @@ -1 +1 @@ -Subproject commit 43cb8b32428b1b29994874349ec22eb5372e152c +Subproject commit fef847ae57d74e93563bc04222d9da7007fffc4f diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..1cb2463d --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2094 @@ +{ + "name": "git-utils", + "version": "5.7.3", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "acorn": { + "version": "5.7.4", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-5.7.4.tgz", + "integrity": "sha512-1D++VG7BhrtvQpNbBzovKNc1FLGGEE/oGe7b9xJm/RFHMBeUaUGpluV9RLjZa47YFdPcDAenEYuq9pQPcMdLJg==", + "dev": true + }, + "acorn-jsx": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz", + "integrity": "sha1-r9+UiPsezvyDSPb7IvRk4ypYs2s=", + "dev": true, + "requires": { + "acorn": "^3.0.4" + }, + "dependencies": { + "acorn": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz", + "integrity": "sha1-ReN/s56No/JbruP/U2niu18iAXo=", + "dev": true + } + } + }, + "ajv": { + "version": "4.11.8", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-4.11.8.tgz", + "integrity": "sha1-gv+wKynmYq5TvcIK8VlHcGc5xTY=", + "dev": true, + "requires": { + "co": "^4.6.0", + "json-stable-stringify": "^1.0.1" + } + }, + "ajv-keywords": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-1.5.1.tgz", + "integrity": "sha1-MU3QpLM2j609/NxU7eYXG4htrzw=", + "dev": true + }, + "amdefine": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", + "integrity": "sha1-SlKCrBZHKek2Gbz9OtFR+BfOkfU=", + "dev": true + }, + "ansi-escapes": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-1.4.0.tgz", + "integrity": "sha1-06ioOzGapneTZisT52HHkRQiMG4=", + "dev": true + }, + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array.prototype.find": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/array.prototype.find/-/array.prototype.find-2.1.1.tgz", + "integrity": "sha512-mi+MYNJYLTx2eNYy+Yh6raoQacCsNeeMUaspFPh9Y141lFSsWxxB8V9mM2ye+eqiRs917J6/pJ4M9ZPzenWckA==", + "dev": true, + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.4" + } + }, + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=", + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "resolved": "https://registry.npmjs.org/babel-code-frame/-/babel-code-frame-6.26.0.tgz", + "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + } + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", + "dev": true + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha1-Jw8HbFpywC9bZaR9+Uxf46J4iS8=", + "dev": true + }, + "call-bind": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.0.tgz", + "integrity": "sha512-AEXsYIyyDY3MCzbwdhzG3Jx1R0J2wetQyUynn6dYHAO+bg8l1k7jwZtRv4ryryFs7EP+NDlikJlVe59jr0cM2w==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.0" + } + }, + "caller-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/caller-path/-/caller-path-0.1.0.tgz", + "integrity": "sha1-lAhe9jWB7NPaqSREqP6U6CV3dR8=", + "dev": true, + "requires": { + "callsites": "^0.2.0" + } + }, + "callsites": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-0.2.0.tgz", + "integrity": "sha1-r6uWJikQp/M8GaV3WCXGnzTjUMo=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "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" + } + }, + "circular-json": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/circular-json/-/circular-json-0.3.3.tgz", + "integrity": "sha512-UZK3NBx2Mca+b5LsG7bY183pHWt5Y1xts4P3Pz7ENTwGVnJOUWbRb3ocjvX7hx9tq/yTAdclXm9sZ38gNuem4A==", + "dev": true + }, + "cli-cursor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-1.0.2.tgz", + "integrity": "sha1-ZNo/fValRBLll5S9Ytw1KV6PKYc=", + "dev": true, + "requires": { + "restore-cursor": "^1.0.1" + } + }, + "cli-width": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-2.2.1.tgz", + "integrity": "sha512-GRMWDxpOB6Dgk2E5Uo+3eEBvtOOlimMmpbFiKuLFnQzYDavtLFY3K5ona41jgN/WdRZtG7utuVSVTL4HbZHGkw==", + "dev": true + }, + "co": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/co/-/co-4.6.0.tgz", + "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", + "dev": true + }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, + "coffee-script": { + "version": "1.12.7", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.12.7.tgz", + "integrity": "sha512-fLeEhqwymYat/MpTPUjSKHVYYl0ec2mOyALEMLmzr5i1isuG+6jfI2j2d5oBO3VIzgUXgBVIcOT9uH1TFxBckw==", + "dev": true + }, + "coffeestack": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/coffeestack/-/coffeestack-1.2.0.tgz", + "integrity": "sha512-vXT7ZxSZ4lXHh/0A2cODyFqrVIl4Vb0Er5wcS2SrFN4jW8g1qIAmcMsRlRdUKvnvfmKixvENYspAyF/ihWbpyw==", + "dev": true, + "requires": { + "coffee-script": "~1.8.0", + "fs-plus": "^3.1.1", + "source-map": "~0.1.43" + }, + "dependencies": { + "coffee-script": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/coffee-script/-/coffee-script-1.8.0.tgz", + "integrity": "sha1-nJ8dK0pSoADe0Vtll5FwNkgmPB0=", + "dev": true, + "requires": { + "mkdirp": "~0.3.5" + } + }, + "fs-plus": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/fs-plus/-/fs-plus-3.1.1.tgz", + "integrity": "sha512-Se2PJdOWXqos1qVTkvqqjb0CSnfBnwwD+pq+z4ksT+e97mEShod/hrNg0TRCCsXPbJzcIq+NuzQhigunMWMJUA==", + "dev": true, + "requires": { + "async": "^1.5.2", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.2", + "underscore-plus": "1.x" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + } + } + }, + "mkdirp": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", + "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=", + "dev": true + } + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", + "dev": true + }, + "concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "contains-path": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/contains-path/-/contains-path-0.1.0.tgz", + "integrity": "sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo=", + "dev": true + }, + "core-util-is": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", + "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", + "dev": true + }, + "d": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz", + "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==", + "dev": true, + "requires": { + "es5-ext": "^0.10.50", + "type": "^1.0.1" + } + }, + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "debug-log": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/debug-log/-/debug-log-1.0.1.tgz", + "integrity": "sha1-IwdjLUwEOCuN+KMvcLiVBG1SdF8=", + "dev": true + }, + "deep-is": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", + "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", + "dev": true + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "deglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/deglob/-/deglob-2.1.1.tgz", + "integrity": "sha512-2kjwuGGonL7gWE1XU4Fv79+vVzpoQCl0V+boMwWtOQJV2AGDabCwez++nB1Nli/8BabAfZQ/UuHPlp6AymKdWw==", + "dev": true, + "requires": { + "find-root": "^1.0.0", + "glob": "^7.0.5", + "ignore": "^3.0.9", + "pkg-config": "^1.1.0", + "run-parallel": "^1.1.2", + "uniq": "^1.0.1" + } + }, + "doctrine": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "error-ex": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", + "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "es-abstract": { + "version": "1.17.7", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.17.7.tgz", + "integrity": "sha512-VBl/gnfcJ7OercKA9MVaegWsBHFjV492syMudcnQZvt/Dw8ezpcOHYZXa/J96O8vx+g4x65YKhxOwDUh63aS5g==", + "dev": true, + "requires": { + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1", + "is-callable": "^1.2.2", + "is-regex": "^1.1.1", + "object-inspect": "^1.8.0", + "object-keys": "^1.1.1", + "object.assign": "^4.1.1", + "string.prototype.trimend": "^1.0.1", + "string.prototype.trimstart": "^1.0.1" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dev": true, + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "es5-ext": { + "version": "0.10.53", + "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.53.tgz", + "integrity": "sha512-Xs2Stw6NiNHWypzRTY1MtaG/uJlwCk8kH81920ma8mvN8Xq1gsfhZvpkImLQArw8AHnv8MT2I45J3c0R8slE+Q==", + "dev": true, + "requires": { + "es6-iterator": "~2.0.3", + "es6-symbol": "~3.1.3", + "next-tick": "~1.0.0" + } + }, + "es6-iterator": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz", + "integrity": "sha1-p96IkUGgWpSwhUQDstCg+/qY87c=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.35", + "es6-symbol": "^3.1.1" + } + }, + "es6-map": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-map/-/es6-map-0.1.5.tgz", + "integrity": "sha1-kTbgUD3MBqMBaQ8LsU/042TpSfA=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-set": "~0.1.5", + "es6-symbol": "~3.1.1", + "event-emitter": "~0.3.5" + } + }, + "es6-set": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/es6-set/-/es6-set-0.1.5.tgz", + "integrity": "sha1-0rPsXU2ADO2BjbU40ol02wpzzLE=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14", + "es6-iterator": "~2.0.1", + "es6-symbol": "3.1.1", + "event-emitter": "~0.3.5" + }, + "dependencies": { + "es6-symbol": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.1.tgz", + "integrity": "sha1-vwDvT9q2uhtG7Le2KbTH7VcVzHc=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + } + } + }, + "es6-symbol": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.3.tgz", + "integrity": "sha512-NJ6Yn3FuDinBaBRWl/q5X/s4koRHBrgKAu+yGI6JCBeiu3qrcbJhwT2GeR/EXVfylRk8dpQVJoLEFhK+Mu31NA==", + "dev": true, + "requires": { + "d": "^1.0.1", + "ext": "^1.1.2" + } + }, + "es6-weak-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz", + "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "^0.10.46", + "es6-iterator": "^2.0.3", + "es6-symbol": "^3.1.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "escope": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/escope/-/escope-3.6.0.tgz", + "integrity": "sha1-4Bl16BJ4GhY6ba392AOY3GTIicM=", + "dev": true, + "requires": { + "es6-map": "^0.1.3", + "es6-weak-map": "^2.0.1", + "esrecurse": "^4.1.0", + "estraverse": "^4.1.1" + } + }, + "eslint": { + "version": "3.19.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-3.19.0.tgz", + "integrity": "sha1-yPxiAcf0DdCJQbh8CFdnOGpnmsw=", + "dev": true, + "requires": { + "babel-code-frame": "^6.16.0", + "chalk": "^1.1.3", + "concat-stream": "^1.5.2", + "debug": "^2.1.1", + "doctrine": "^2.0.0", + "escope": "^3.6.0", + "espree": "^3.4.0", + "esquery": "^1.0.0", + "estraverse": "^4.2.0", + "esutils": "^2.0.2", + "file-entry-cache": "^2.0.0", + "glob": "^7.0.3", + "globals": "^9.14.0", + "ignore": "^3.2.0", + "imurmurhash": "^0.1.4", + "inquirer": "^0.12.0", + "is-my-json-valid": "^2.10.0", + "is-resolvable": "^1.0.0", + "js-yaml": "^3.5.1", + "json-stable-stringify": "^1.0.0", + "levn": "^0.3.0", + "lodash": "^4.0.0", + "mkdirp": "^0.5.0", + "natural-compare": "^1.4.0", + "optionator": "^0.8.2", + "path-is-inside": "^1.0.1", + "pluralize": "^1.2.1", + "progress": "^1.1.8", + "require-uncached": "^1.0.2", + "shelljs": "^0.7.5", + "strip-bom": "^3.0.0", + "strip-json-comments": "~2.0.1", + "table": "^3.7.8", + "text-table": "~0.2.0", + "user-home": "^2.0.0" + } + }, + "eslint-config-standard": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/eslint-config-standard/-/eslint-config-standard-10.2.1.tgz", + "integrity": "sha1-wGHk0GbzedwXzVYsZOgZtN1FRZE=", + "dev": true + }, + "eslint-config-standard-jsx": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/eslint-config-standard-jsx/-/eslint-config-standard-jsx-4.0.2.tgz", + "integrity": "sha512-F8fRh2WFnTek7dZH9ZaE0PCBwdVGkwVWZmizla/DDNOmg7Tx6B/IlK5+oYpiX29jpu73LszeJj5i1axEZv6VMw==", + "dev": true + }, + "eslint-import-resolver-node": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.2.3.tgz", + "integrity": "sha1-Wt2BBujJKNssuiMrzZ76hG49oWw=", + "dev": true, + "requires": { + "debug": "^2.2.0", + "object-assign": "^4.0.1", + "resolve": "^1.1.6" + } + }, + "eslint-module-utils": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.6.0.tgz", + "integrity": "sha512-6j9xxegbqe8/kZY8cYpcp0xhbK0EgJlg3g9mib3/miLaExuuwc3n5UEfSnU6hWMbT0FAYVvDbL9RrRgpUeQIvA==", + "dev": true, + "requires": { + "debug": "^2.6.9", + "pkg-dir": "^2.0.0" + } + }, + "eslint-plugin-import": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.2.0.tgz", + "integrity": "sha1-crowb60wXWfEgWNIpGmaQimsi04=", + "dev": true, + "requires": { + "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", + "pkg-up": "^1.0.0" + }, + "dependencies": { + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + } + } + }, + "eslint-plugin-node": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-4.2.3.tgz", + "integrity": "sha512-vIUQPuwbVYdz/CYnlTLsJrRy7iXHQjdEe5wz0XhhdTym3IInM/zZLlPf9nZ2mThsH0QcsieCOWs2vOeCy/22LQ==", + "dev": true, + "requires": { + "ignore": "^3.0.11", + "minimatch": "^3.0.2", + "object-assign": "^4.0.1", + "resolve": "^1.1.7", + "semver": "5.3.0" + } + }, + "eslint-plugin-promise": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-promise/-/eslint-plugin-promise-3.5.0.tgz", + "integrity": "sha1-ePu2/+BHIBYnVp6FpsU3OvKmj8o=", + "dev": true + }, + "eslint-plugin-react": { + "version": "6.10.3", + "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-6.10.3.tgz", + "integrity": "sha1-xUNb6wZ3ThLH2y9qut3L+QDNP3g=", + "dev": true, + "requires": { + "array.prototype.find": "^2.0.1", + "doctrine": "^1.2.2", + "has": "^1.0.1", + "jsx-ast-utils": "^1.3.4", + "object.assign": "^4.0.4" + }, + "dependencies": { + "doctrine": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-1.5.0.tgz", + "integrity": "sha1-N53Ocw9hZvds76TmcHoVmwLFpvo=", + "dev": true, + "requires": { + "esutils": "^2.0.2", + "isarray": "^1.0.0" + } + } + } + }, + "eslint-plugin-standard": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-standard/-/eslint-plugin-standard-3.0.1.tgz", + "integrity": "sha1-NNDJFbRe3G8BA5PH7vOCOwhWXPI=", + "dev": true + }, + "espree": { + "version": "3.5.4", + "resolved": "https://registry.npmjs.org/espree/-/espree-3.5.4.tgz", + "integrity": "sha512-yAcIQxtmMiB/jL32dzEp2enBeidsB7xWPLNiw3IIkpVds1P+h7qF9YwJq1yUNzp2OKXgAprs4F61ih66UsoD1A==", + "dev": true, + "requires": { + "acorn": "^5.5.0", + "acorn-jsx": "^3.0.0" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esquery": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.3.1.tgz", + "integrity": "sha512-olpvt9QG0vniUBZspVRN6lwB7hOZoTRtT+jzR+tS4ffYx2mzbw+z0XCOk44aaLYKApNX5nMm+E+P6o25ip/DHQ==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "event-emitter": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz", + "integrity": "sha1-34xp7vFkeSPHFXuc6DhAYQsCzDk=", + "dev": true, + "requires": { + "d": "1", + "es5-ext": "~0.10.14" + } + }, + "exit-hook": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/exit-hook/-/exit-hook-1.1.1.tgz", + "integrity": "sha1-8FyiM7SMBdVP/wd2XfhQfpXAL/g=", + "dev": true + }, + "ext": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/ext/-/ext-1.4.0.tgz", + "integrity": "sha512-Key5NIsUxdqKg3vIsdw9dSuXpPCQ297y6wBjL30edxwPgt2E44WcWBZey/ZvUc6sERLTxKdyCu4gZFmUbk1Q7A==", + "dev": true, + "requires": { + "type": "^2.0.0" + }, + "dependencies": { + "type": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type/-/type-2.1.0.tgz", + "integrity": "sha512-G9absDWvhAWCV2gmF1zKud3OyC61nZDwWvBL2DApaVFogI07CprggiQAOOjvp2NRjYWFzPyu7vwtDrQFq8jeSA==", + "dev": true + } + } + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", + "dev": true + }, + "figures": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/figures/-/figures-1.7.0.tgz", + "integrity": "sha1-y+Hjr/zxzUS4DK3+0o3Hk6lwHS4=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.5", + "object-assign": "^4.1.0" + } + }, + "file-entry-cache": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-2.0.0.tgz", + "integrity": "sha1-w5KZDD5oR4PYOLjISkXYoEhFg2E=", + "dev": true, + "requires": { + "flat-cache": "^1.2.1", + "object-assign": "^4.0.1" + } + }, + "fileset": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/fileset/-/fileset-0.1.8.tgz", + "integrity": "sha1-UGuRqTluqn4y+0KoQHfHoMc2t0E=", + "dev": true, + "requires": { + "glob": "3.x", + "minimatch": "0.x" + }, + "dependencies": { + "glob": { + "version": "3.2.11", + "resolved": "https://registry.npmjs.org/glob/-/glob-3.2.11.tgz", + "integrity": "sha1-Spc/Y1uRkPcV0QmH1cAP0oFevj0=", + "dev": true, + "requires": { + "inherits": "2", + "minimatch": "0.3" + }, + "dependencies": { + "minimatch": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.3.0.tgz", + "integrity": "sha1-J12O2qxPG7MyZHIInnlJyDlGmd0=", + "dev": true, + "requires": { + "lru-cache": "2", + "sigmund": "~1.0.0" + } + } + } + }, + "minimatch": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.4.0.tgz", + "integrity": "sha1-vSx9Bg0sjI/Xzefx8u0tWycP2xs=", + "dev": true, + "requires": { + "lru-cache": "2", + "sigmund": "~1.0.0" + } + } + } + }, + "find-root": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz", + "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==", + "dev": true + }, + "find-up": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-2.1.0.tgz", + "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "flat-cache": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-1.3.4.tgz", + "integrity": "sha512-VwyB3Lkgacfik2vhqR4uv2rvebqmDvFu4jlN/C1RzWoJEo8I7z4Q404oiqYCkq41mni8EzQnm95emU9seckwtg==", + "dev": true, + "requires": { + "circular-json": "^0.3.1", + "graceful-fs": "^4.1.2", + "rimraf": "~2.6.2", + "write": "^0.2.1" + } + }, + "fs-plus": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/fs-plus/-/fs-plus-3.0.1.tgz", + "integrity": "sha1-VMFpxA4ohKZtNSeA0Y3TH5HToQ0=", + "requires": { + "async": "^1.5.2", + "mkdirp": "^0.5.1", + "rimraf": "^2.5.2", + "underscore-plus": "1.x" + }, + "dependencies": { + "async": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", + "integrity": "sha1-7GphrlZIDAw8skHJVhjiCJL5Zyo=" + }, + "balanced-match": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", + "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" + }, + "brace-expansion": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.8.tgz", + "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=" + }, + "glob": { + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", + "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", + "requires": { + "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" + } + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "requires": { + "minimist": "^1.2.5" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" + }, + "rimraf": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", + "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", + "requires": { + "glob": "^7.0.5" + } + }, + "underscore": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.6.0.tgz", + "integrity": "sha1-izixDKze9jM3uLJOT/htRa6lKag=" + }, + "underscore-plus": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/underscore-plus/-/underscore-plus-1.6.6.tgz", + "integrity": "sha1-ZezeG9xEGjXYnmUP1w3PE65Dmn0=", + "requires": { + "underscore": "~1.6.0" + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" + } + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", + "dev": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "gaze": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/gaze/-/gaze-0.3.4.tgz", + "integrity": "sha1-X5S92gr+U7xxCWm81vKCVI1gwnk=", + "dev": true, + "requires": { + "fileset": "~0.1.5", + "minimatch": "~0.2.9" + }, + "dependencies": { + "minimatch": { + "version": "0.2.14", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", + "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", + "dev": true, + "requires": { + "lru-cache": "2", + "sigmund": "~1.0.0" + } + } + } + }, + "generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "dev": true, + "requires": { + "is-property": "^1.0.2" + } + }, + "generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha1-nA4cQDCM6AT0eDYYuTf6iPmdUNA=", + "dev": true, + "requires": { + "is-property": "^1.0.0" + } + }, + "get-intrinsic": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.0.2.tgz", + "integrity": "sha512-aeX0vrFm21ILl3+JpFFRNe9aUvp6VFZb2/CTbgLb8j75kOhvoNYjt9d8KA/tJG4gSo8nzEDedRl0h7vDmBYRVg==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "get-stdin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", + "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=", + "dev": true + }, + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "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": { + "version": "9.18.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-9.18.0.tgz", + "integrity": "sha512-S0nG3CLEQiY/ILxqtztTWH/3iRRdyBLw6KMDxnKMchrtbj2OFmehVh0WUCfW3DUrIgx/qFrJPICrq4Z4sTR9UQ==", + "dev": true + }, + "graceful-fs": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz", + "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-symbols": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.1.tgz", + "integrity": "sha512-PLcsoqu++dmEIZB+6totNFKq/7Do+Z0u4oT0zKOJNl3lYK6vGwwu2hjHs+68OEZbTjiUE9bgOABXbP/GvrS0Kg==", + "dev": true + }, + "ignore": { + "version": "3.3.10", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-3.3.10.tgz", + "integrity": "sha512-Pgs951kaMm5GXP7MOvxERINe3gsaVjUWFm+UZPSq9xYriQAksyhg0csnS0KXSNRD5NmNdapXEpjxG49+AKh/ug==", + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha1-khi5srkoojixPcT7a21XbyMUU+o=", + "dev": true + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "inquirer": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-0.12.0.tgz", + "integrity": "sha1-HvK/1jUE3wvHV4X/+MLEHfEvB34=", + "dev": true, + "requires": { + "ansi-escapes": "^1.1.0", + "ansi-regex": "^2.0.0", + "chalk": "^1.0.0", + "cli-cursor": "^1.0.1", + "cli-width": "^2.0.0", + "figures": "^1.3.5", + "lodash": "^4.3.0", + "readline2": "^1.0.1", + "run-async": "^0.1.0", + "rx-lite": "^3.1.2", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.0", + "through": "^2.3.6" + } + }, + "interpret": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", + "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", + "dev": true + }, + "is-arrayish": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", + "integrity": "sha1-d8mYQFJ6qOyxqLppe4BkWnqSap0=", + "dev": true + }, + "is-callable": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.2.tgz", + "integrity": "sha512-dnMqspv5nU3LoewK2N/y7KLtxtakvTuaCsU9FU50/QDmdbHNy/4/JuRtMHqRU22o3q+W89YQndQEeCVwK+3qrA==", + "dev": true + }, + "is-core-module": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.2.0.tgz", + "integrity": "sha512-XRAfAdyyY5F5cOXn7hYQDqh2Xmii+DEfIcQGxK/uNwMHhIkPWO0g8msXcbzLe+MpGoR951MlqM/2iIlU4vKDdQ==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-date-object": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", + "integrity": "sha512-USlDT524woQ08aoZFzh3/Z6ch9Y/EWXEHQ/AaRN0SkKq4t2Jw2R2339tSXmwuVoY7LLlBCbOIlx2myP/L5zk0g==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-my-ip-valid": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.0.tgz", + "integrity": "sha512-gmh/eWXROncUzRnIa1Ubrt5b8ep/MGSnfAUI3aRp+sqTCs1tv1Isl8d8F6JmkN3dXKc3ehZMrtiPN9eL03NuaQ==", + "dev": true + }, + "is-my-json-valid": { + "version": "2.20.5", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.20.5.tgz", + "integrity": "sha512-VTPuvvGQtxvCeghwspQu1rBgjYUT6FGxPlvFKbYuFtgc4ADsX3U5ihZOYN0qyU6u+d4X9xXb0IT5O6QpXKt87A==", + "dev": true, + "requires": { + "generate-function": "^2.0.0", + "generate-object-property": "^1.1.0", + "is-my-ip-valid": "^1.0.0", + "jsonpointer": "^4.0.0", + "xtend": "^4.0.0" + } + }, + "is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha1-V/4cTkhHTt1lsJkR8msc1Ald2oQ=", + "dev": true + }, + "is-regex": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.1.tgz", + "integrity": "sha512-1+QkEcxiLlB7VEyFtyBg94e08OAsvq7FUBgApTq/w2ymCLyKJgDPsybBENVtA7XCQEgEXxKPonG+mvYRxh/LIg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "is-resolvable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz", + "integrity": "sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==", + "dev": true + }, + "is-symbol": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.3.tgz", + "integrity": "sha512-OwijhaRSgqvhm/0ZdAcXNZt9lYdKFpcRDT5ULUuYXPoT794UNOdU+gpT6Rzo7b4V2HUl/op6GqY894AZwv9faQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.1" + } + }, + "isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=", + "dev": true + }, + "jasmine-focused": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/jasmine-focused/-/jasmine-focused-1.0.7.tgz", + "integrity": "sha1-uDx1fIAOaOHW78GjoaE/85/23NI=", + "dev": true, + "requires": { + "jasmine-node": "git+https://github.com/kevinsawicki/jasmine-node.git#81af4f953a2b7dfb5bde8331c05362a4b464c5ef", + "underscore-plus": "1.x", + "walkdir": "0.0.7" + } + }, + "jasmine-node": { + "version": "git+https://github.com/kevinsawicki/jasmine-node.git#81af4f953a2b7dfb5bde8331c05362a4b464c5ef", + "from": "git+https://github.com/kevinsawicki/jasmine-node.git#81af4f953a2b7dfb5bde8331c05362a4b464c5ef", + "dev": true, + "requires": { + "coffee-script": ">=1.0.1", + "coffeestack": ">=1 <2", + "gaze": "~0.3.2", + "jasmine-reporters": ">=0.2.0", + "mkdirp": "~0.3.5", + "requirejs": ">=0.27.1", + "underscore": ">= 1.3.1", + "walkdir": ">= 0.0.1" + }, + "dependencies": { + "mkdirp": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", + "integrity": "sha1-3j5fiWHIjHh+4TaN+EmsRBPsqNc=", + "dev": true + } + } + }, + "jasmine-reporters": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/jasmine-reporters/-/jasmine-reporters-2.3.2.tgz", + "integrity": "sha512-u/7AT9SkuZsUfFBLLzbErohTGNsEUCKaQbsVYnLFW1gEuL2DzmBL4n8v90uZsqIqlWvWUgian8J6yOt5Fyk/+A==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1", + "xmldom": "^0.1.22" + } + }, + "js-tokens": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-3.0.2.tgz", + "integrity": "sha1-mGbfOVECEw449/mWvOtlRDIJwls=", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "json-stable-stringify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify/-/json-stable-stringify-1.0.1.tgz", + "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", + "dev": true, + "requires": { + "jsonify": "~0.0.0" + } + }, + "jsonify": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/jsonify/-/jsonify-0.0.0.tgz", + "integrity": "sha1-LHS27kHZPKUbe1qu6PUDYx0lKnM=", + "dev": true + }, + "jsonpointer": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-4.1.0.tgz", + "integrity": "sha512-CXcRvMyTlnR53xMcKnuMzfCA5i/nfblTnnr74CZb6C4vG39eu6w51t7nKmU5MfLfbTgGItliNyjO/ciNPDqClg==", + "dev": true + }, + "jsx-ast-utils": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-1.4.1.tgz", + "integrity": "sha1-OGchPo3Xm/Ho8jAMDPwe+xgsDfE=", + "dev": true + }, + "levn": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", + "integrity": "sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2" + } + }, + "load-json-file": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-4.0.0.tgz", + "integrity": "sha1-L19Fq5HjMhYjT9U62rZo607AmTs=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^4.0.0", + "pify": "^3.0.0", + "strip-bom": "^3.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-2.0.0.tgz", + "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.20", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.20.tgz", + "integrity": "sha512-PlhdFcillOINfeV7Ni6oF1TAEayyZBoZ8bcshTHqOYJYlrqzRK5hagpagky5o4HfCzzd1TRkXPMFq6cKk9rGmA==", + "dev": true + }, + "lodash.cond": { + "version": "4.5.2", + "resolved": "https://registry.npmjs.org/lodash.cond/-/lodash.cond-4.5.2.tgz", + "integrity": "sha1-9HGh2khr5g9quVXRcRVSPdHSVdU=", + "dev": true + }, + "lru-cache": { + "version": "2.7.3", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-2.7.3.tgz", + "integrity": "sha1-bUUk6LlV+V1PW1iFHOId1y+06VI=", + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==" + }, + "mkdirp": { + "version": "0.5.5", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz", + "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=", + "dev": true + }, + "mute-stream": { + "version": "0.0.5", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-0.0.5.tgz", + "integrity": "sha1-j7+rsKmKJT0xhDMfno3rc3L6xsA=", + "dev": true + }, + "nan": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==" + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", + "dev": true + }, + "next-tick": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.0.0.tgz", + "integrity": "sha1-yobR/ogoFpsBICCOPchCS524NCw=", + "dev": true + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, + "object-inspect": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.9.0.tgz", + "integrity": "sha512-i3Bp9iTqwhaLZBxGkRfo5ZbE07BQRT7MGu8+nNgwW9ItGp1TzCTw2DLEoWwjClxBjOFI/hWljTAmYGCEwmtnOw==", + "dev": true + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "dev": true, + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "p-limit": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-1.3.0.tgz", + "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-2.0.0.tgz", + "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-1.0.0.tgz", + "integrity": "sha1-y8ec26+P1CKOE/Yh8rGiN8GyB7M=", + "dev": true + }, + "parse-json": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-4.0.0.tgz", + "integrity": "sha1-vjX1Qlvh9/bHRxhPmKeIy5lHfuA=", + "dev": true, + "requires": { + "error-ex": "^1.3.1", + "json-parse-better-errors": "^1.0.1" + } + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=", + "dev": true + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", + "dev": true + }, + "path-is-inside": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/path-is-inside/-/path-is-inside-1.0.2.tgz", + "integrity": "sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=", + "dev": true + }, + "path-parse": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.6.tgz", + "integrity": "sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw==", + "dev": true + }, + "pify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", + "integrity": "sha1-5aSs0sEB/fPZpNB/DbxNtJ3SgXY=", + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/pinkie/-/pinkie-2.0.4.tgz", + "integrity": "sha1-clVrgM+g1IqXToDnckjoDtT3+HA=", + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pinkie-promise/-/pinkie-promise-2.0.1.tgz", + "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "pkg-conf": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/pkg-conf/-/pkg-conf-2.1.0.tgz", + "integrity": "sha1-ISZRTKbyq/69FoWW3xi6V4Z/AFg=", + "dev": true, + "requires": { + "find-up": "^2.0.0", + "load-json-file": "^4.0.0" + } + }, + "pkg-config": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pkg-config/-/pkg-config-1.1.1.tgz", + "integrity": "sha1-VX7yLXPaPIg3EHdmxS6tq94pj+Q=", + "dev": true, + "requires": { + "debug-log": "^1.0.0", + "find-root": "^1.0.0", + "xtend": "^4.0.1" + } + }, + "pkg-dir": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-2.0.0.tgz", + "integrity": "sha1-9tXREJ4Z1j7fQo4L1X4Sd3YVM0s=", + "dev": true, + "requires": { + "find-up": "^2.1.0" + } + }, + "pkg-up": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/pkg-up/-/pkg-up-1.0.0.tgz", + "integrity": "sha1-Pgj7RhUlxEIWJKM7n35tCvWwWiY=", + "dev": true, + "requires": { + "find-up": "^1.0.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-1.1.2.tgz", + "integrity": "sha1-ay6YIrGizgpgq2TWEOzK1TyyTQ8=", + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "path-exists": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-2.1.0.tgz", + "integrity": "sha1-D+tsZPD8UY2adU3V77YscCJ2H0s=", + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + } + } + }, + "pluralize": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/pluralize/-/pluralize-1.2.1.tgz", + "integrity": "sha1-0aIUg/0iu0HlihL6NCGCMUCJfEU=", + "dev": true + }, + "prelude-ls": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", + "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", + "dev": true + }, + "process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "dev": true + }, + "progress": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/progress/-/progress-1.1.8.tgz", + "integrity": "sha1-4mDHj2Fhzdmw5WzD4Khd4Xx6V74=", + "dev": true + }, + "readable-stream": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.7.tgz", + "integrity": "sha512-Ebho8K4jIbHAxnuxi7o42OrZgF/ZTNcsZj6nRKyUmkhLFq8CHItp/fy6hQZuZmP/n3yZ9VBUbp4zz/mX8hmYPw==", + "dev": true, + "requires": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "readline2": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/readline2/-/readline2-1.0.1.tgz", + "integrity": "sha1-QQWWCP/BVHV7cV2ZidGZ/783LjU=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "mute-stream": "0.0.5" + } + }, + "rechoir": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", + "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", + "dev": true, + "requires": { + "resolve": "^1.1.6" + } + }, + "require-uncached": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/require-uncached/-/require-uncached-1.0.3.tgz", + "integrity": "sha1-Tg1W1slmL9MeQwEcS5WqSZVUIdM=", + "dev": true, + "requires": { + "caller-path": "^0.1.0", + "resolve-from": "^1.0.0" + } + }, + "requirejs": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/requirejs/-/requirejs-2.3.6.tgz", + "integrity": "sha512-ipEzlWQe6RK3jkzikgCupiTbTvm4S0/CAU5GlgptkN5SO6F3u0UD0K18wy6ErDqiCyP4J4YYe1HuAShvsxePLg==", + "dev": true + }, + "resolve": { + "version": "1.19.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.19.0.tgz", + "integrity": "sha512-rArEXAgsBG4UgRGcynxWIWKFvh/XZCcS8UJdHhwy91zwAvCZIbcs+vAbflgBnNjYMs/i/i+/Ux6IZhML1yPvxg==", + "dev": true, + "requires": { + "is-core-module": "^2.1.0", + "path-parse": "^1.0.6" + } + }, + "resolve-from": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-1.0.1.tgz", + "integrity": "sha1-Jsv+k10a7uq7Kbw/5a6wHpPUQiY=", + "dev": true + }, + "restore-cursor": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-1.0.1.tgz", + "integrity": "sha1-NGYfRohjJ/7SmRR5FSJS35LapUE=", + "dev": true, + "requires": { + "exit-hook": "^1.0.0", + "onetime": "^1.0.0" + } + }, + "rimraf": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.3.tgz", + "integrity": "sha512-mwqeW5XsA2qAejG46gYdENaxXjx9onRNCfn7L0duuP4hCuTIi/QO7PDK07KJfp1d+izWPrzEJDcSqBa0OZQriA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "run-async": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-0.1.0.tgz", + "integrity": "sha1-yK1KXhEGYeQCp9IbUw4AnyX444k=", + "dev": true, + "requires": { + "once": "^1.3.0" + } + }, + "run-parallel": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.10.tgz", + "integrity": "sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==", + "dev": true + }, + "rx-lite": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/rx-lite/-/rx-lite-3.1.2.tgz", + "integrity": "sha1-Gc5QLKVyZl87ZHsQk5+X/RYV8QI=", + "dev": true + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "semver": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.3.0.tgz", + "integrity": "sha1-myzl094C0XxgEq0yaqa00M9U+U8=", + "dev": true + }, + "shelljs": { + "version": "0.7.8", + "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.7.8.tgz", + "integrity": "sha1-3svPh0sNHl+3LhSxZKloMEjprLM=", + "dev": true, + "requires": { + "glob": "^7.0.0", + "interpret": "^1.0.0", + "rechoir": "^0.6.2" + } + }, + "sigmund": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sigmund/-/sigmund-1.0.1.tgz", + "integrity": "sha1-P/IfGYytIXX587eBhT/ZTQ0ZtZA=", + "dev": true + }, + "slice-ansi": { + "version": "0.0.4", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", + "dev": true + }, + "source-map": { + "version": "0.1.43", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.1.43.tgz", + "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=", + "dev": true + }, + "standard": { + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/standard/-/standard-10.0.3.tgz", + "integrity": "sha512-JURZ+85ExKLQULckDFijdX5WHzN6RC7fgiZNSV4jFQVo+3tPoQGHyBrGekye/yf0aOfb4210EM5qPNlc2cRh4w==", + "dev": true, + "requires": { + "eslint": "~3.19.0", + "eslint-config-standard": "10.2.1", + "eslint-config-standard-jsx": "4.0.2", + "eslint-plugin-import": "~2.2.0", + "eslint-plugin-node": "~4.2.2", + "eslint-plugin-promise": "~3.5.0", + "eslint-plugin-react": "~6.10.0", + "eslint-plugin-standard": "~3.0.1", + "standard-engine": "~7.0.0" + } + }, + "standard-engine": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/standard-engine/-/standard-engine-7.0.0.tgz", + "integrity": "sha1-67d7nI/CyBZf+jU72Rug3/Qa9pA=", + "dev": true, + "requires": { + "deglob": "^2.1.0", + "get-stdin": "^5.0.1", + "minimist": "^1.1.0", + "pkg-conf": "^2.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "string.prototype.trimend": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.3.tgz", + "integrity": "sha512-ayH0pB+uf0U28CtjlLvL7NaohvR1amUvVZk+y3DYb0Ey2PUV5zPkkKy9+U1ndVEIXO8hNg18eIv9Jntbii+dKw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + }, + "string.prototype.trimstart": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.3.tgz", + "integrity": "sha512-oBIBUy5lea5tt0ovtOFiEQaBkoBBkyJhZXzJYrSmDo5IUUqbOPvVezuRs/agBIdZ2p2Eo1FD6bD9USyBLfl3xg==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3" + } + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", + "integrity": "sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM=", + "dev": true + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + }, + "table": { + "version": "3.8.3", + "resolved": "https://registry.npmjs.org/table/-/table-3.8.3.tgz", + "integrity": "sha1-K7xULw/amGGnVdOUf+/Ys/UThV8=", + "dev": true, + "requires": { + "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" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "temp": { + "version": "0.9.4", + "resolved": "https://registry.npmjs.org/temp/-/temp-0.9.4.tgz", + "integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==", + "dev": true, + "requires": { + "mkdirp": "^0.5.1", + "rimraf": "~2.6.2" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, + "through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", + "dev": true + }, + "type": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/type/-/type-1.2.0.tgz", + "integrity": "sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==", + "dev": true + }, + "type-check": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", + "integrity": "sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=", + "dev": true, + "requires": { + "prelude-ls": "~1.1.2" + } + }, + "typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=", + "dev": true + }, + "underscore": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.5.2.tgz", + "integrity": "sha1-EzXF5PXm0zu7SwBrqMhqAPVW3gg=", + "dev": true + }, + "underscore-plus": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/underscore-plus/-/underscore-plus-1.7.0.tgz", + "integrity": "sha512-A3BEzkeicFLnr+U/Q3EyWwJAQPbA19mtZZ4h+lLq3ttm9kn8WC4R3YpuJZEXmWdLjYP47Zc8aLZm9kwdv+zzvA==", + "dev": true, + "requires": { + "underscore": "^1.9.1" + }, + "dependencies": { + "underscore": { + "version": "1.12.0", + "resolved": "https://registry.npmjs.org/underscore/-/underscore-1.12.0.tgz", + "integrity": "sha512-21rQzss/XPMjolTiIezSu3JAjgagXKROtNrYFEOWK109qY1Uv2tVjPTZ1ci2HgvQDA16gHYSthQIJfB+XId/rQ==", + "dev": true + } + } + }, + "uniq": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz", + "integrity": "sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=", + "dev": true + }, + "user-home": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/user-home/-/user-home-2.0.0.tgz", + "integrity": "sha1-nHC/2Babwdy/SGBODwS4tJzenp8=", + "dev": true, + "requires": { + "os-homedir": "^1.0.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "walkdir": { + "version": "0.0.7", + "resolved": "https://registry.npmjs.org/walkdir/-/walkdir-0.0.7.tgz", + "integrity": "sha1-BNoCcKh6d4VAFzzb8KLbSZqNnik=", + "dev": true + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "dev": true + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", + "dev": true + }, + "wrench": { + "version": "1.4.4", + "resolved": "https://registry.npmjs.org/wrench/-/wrench-1.4.4.tgz", + "integrity": "sha1-f1I+/bcbAQDnfc6DTAZSPL49VOA=", + "dev": true + }, + "write": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/write/-/write-0.2.1.tgz", + "integrity": "sha1-X8A4KOJkzqP+kUVUdvejxWbLB1c=", + "dev": true, + "requires": { + "mkdirp": "^0.5.1" + } + }, + "xmldom": { + "version": "0.1.31", + "resolved": "https://registry.npmjs.org/xmldom/-/xmldom-0.1.31.tgz", + "integrity": "sha512-yS2uJflVQs6n+CyjHoaBmVSqIDevTAWrzMmjG1Gc7h1qQ7uVozNhEPJAwZXWyGQ/Gafo3fCwrcaokezLPupVyQ==", + "dev": true + }, + "xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "dev": true + } + } +} diff --git a/package.json b/package.json index 69a8a8ea..2d6a9e1e 100644 --- a/package.json +++ b/package.json @@ -1,13 +1,8 @@ { "name": "git-utils", "description": "A package for using Git repositories", - "version": "1.2.2", - "licenses": [ - { - "type": "MIT", - "url": "http://github.com/atom/git-utils/raw/master/LICENSE.md" - } - ], + "version": "5.7.3", + "license": "MIT", "author": { "name": "Kevin Sawicki", "email": "kevin@github.com" @@ -32,26 +27,21 @@ "dvcs", "vcs" ], - "main": "./lib/git.js", + "main": "./src/git.js", "devDependencies": { - "grunt": "~0.4.0", - "grunt-contrib-coffee": "~0.9.0", - "grunt-shell": "~0.2.1", - "grunt-cli": "~0.1.6", - "wrench": "~1.4.4", - "temp": "~0.5.0", - "underscore": "~1.5.2", "jasmine-focused": "^1.0.4", - "node-cpplint": "~0.1.5", - "grunt-coffeelint": "0.0.6" + "standard": "^10.0.3", + "temp": "~0.9.4", + "underscore": "~1.5.2", + "wrench": "~1.4.4" }, "dependencies": { - "nan": "0.8.0", - "bindings": "~1.0.0", - "fs-plus": "^2.1.0" + "fs-plus": "^3.0.0", + "nan": "^2.14.2" }, "scripts": { - "prepublish": "grunt clean coffee lint", - "test": "grunt test" + "lint": "standard src spec", + "test": "jasmine-focused --captureExceptions spec", + "prepare": "git submodule update --init --recursive" } } diff --git a/spec/async-spec-helper-functions.js b/spec/async-spec-helper-functions.js new file mode 100644 index 00000000..850ba8e5 --- /dev/null +++ b/spec/async-spec-helper-functions.js @@ -0,0 +1,52 @@ +exports.beforeEach = function beforeEach (fn) { + global.beforeEach(function () { + const result = fn() + if (result instanceof Promise) { + waitsForPromise(result, this) + } + }) +} + +exports.afterEach = function afterEach (fn) { + global.afterEach(function () { + const result = fn() + if (result instanceof Promise) { + waitsForPromise(result, this) + } + }) +} + +;['it', 'fit', 'ffit', 'fffit'].forEach((name) => { + exports[name] = function (description, fn) { + if (fn === undefined) { + global[name](description) + return + } + + global[name](description, function () { + const result = fn() + if (result instanceof Promise) { + waitsForPromise(result, this) + } + }) + } +}) + +function waitsForPromise (promise, spec) { + let done = false + let error = null + + global.waitsFor('test promise to resolve', () => { + return done + }) + + promise.then( + () => { + done = true + }, + (e) => { + spec.fail(e) + done = true + } + ) +} diff --git a/spec/fixtures/file.txt b/spec/fixtures/file.txt index ff6e6b1a..e281c6b8 100644 --- a/spec/fixtures/file.txt +++ b/spec/fixtures/file.txt @@ -1,3 +1,3 @@ first -second -third + second + third diff --git a/spec/fixtures/ignored-workspace/.gitignore b/spec/fixtures/ignored-workspace/.gitignore new file mode 100644 index 00000000..e69de29b diff --git a/spec/fixtures/ignored-workspace/subdir/.gitignore b/spec/fixtures/ignored-workspace/subdir/.gitignore new file mode 100644 index 00000000..80034a11 --- /dev/null +++ b/spec/fixtures/ignored-workspace/subdir/.gitignore @@ -0,0 +1 @@ +subdir diff --git a/spec/fixtures/ignored.git/info/exclude b/spec/fixtures/ignored.git/info/exclude index eaa5fa87..4c2695aa 100644 --- a/spec/fixtures/ignored.git/info/exclude +++ b/spec/fixtures/ignored.git/info/exclude @@ -1 +1,2 @@ a.txt +**.foo diff --git a/spec/fixtures/no-commits.git/HEAD b/spec/fixtures/no-commits.git/HEAD new file mode 100644 index 00000000..cb089cd8 --- /dev/null +++ b/spec/fixtures/no-commits.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/spec/fixtures/no-commits.git/config b/spec/fixtures/no-commits.git/config new file mode 100644 index 00000000..6c9406b7 --- /dev/null +++ b/spec/fixtures/no-commits.git/config @@ -0,0 +1,7 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true diff --git a/spec/fixtures/no-commits.git/description b/spec/fixtures/no-commits.git/description new file mode 100644 index 00000000..498b267a --- /dev/null +++ b/spec/fixtures/no-commits.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/spec/fixtures/no-commits.git/hooks/applypatch-msg.sample b/spec/fixtures/no-commits.git/hooks/applypatch-msg.sample new file mode 100755 index 00000000..a5d7b84a --- /dev/null +++ b/spec/fixtures/no-commits.git/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +commitmsg="$(git rev-parse --git-path hooks/commit-msg)" +test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} +: diff --git a/spec/fixtures/no-commits.git/hooks/commit-msg.sample b/spec/fixtures/no-commits.git/hooks/commit-msg.sample new file mode 100755 index 00000000..b58d1184 --- /dev/null +++ b/spec/fixtures/no-commits.git/hooks/commit-msg.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to check the commit log message. +# Called by "git commit" with one argument, the name of the file +# that has the commit message. The hook should exit with non-zero +# status after issuing an appropriate message if it wants to stop the +# commit. The hook is allowed to edit the commit message file. +# +# To enable this hook, rename this file to "commit-msg". + +# Uncomment the below to add a Signed-off-by line to the message. +# Doing this in a hook is a bad idea in general, but the prepare-commit-msg +# hook is more suited to it. +# +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} diff --git a/spec/fixtures/no-commits.git/hooks/post-update.sample b/spec/fixtures/no-commits.git/hooks/post-update.sample new file mode 100755 index 00000000..ec17ec19 --- /dev/null +++ b/spec/fixtures/no-commits.git/hooks/post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/spec/fixtures/no-commits.git/hooks/pre-applypatch.sample b/spec/fixtures/no-commits.git/hooks/pre-applypatch.sample new file mode 100755 index 00000000..4142082b --- /dev/null +++ b/spec/fixtures/no-commits.git/hooks/pre-applypatch.sample @@ -0,0 +1,14 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed +# by applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-applypatch". + +. git-sh-setup +precommit="$(git rev-parse --git-path hooks/pre-commit)" +test -x "$precommit" && exec "$precommit" ${1+"$@"} +: diff --git a/spec/fixtures/no-commits.git/hooks/pre-commit.sample b/spec/fixtures/no-commits.git/hooks/pre-commit.sample new file mode 100755 index 00000000..68d62d54 --- /dev/null +++ b/spec/fixtures/no-commits.git/hooks/pre-commit.sample @@ -0,0 +1,49 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +if git rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 +fi + +# If you want to allow non-ASCII filenames set this variable to true. +allownonascii=$(git config --bool hooks.allownonascii) + +# Redirect output to stderr. +exec 1>&2 + +# Cross platform projects tend to avoid non-ASCII filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test $(git diff --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 +then + cat <<\EOF +Error: Attempt to add a non-ASCII file name. + +This can cause problems if you want to work with people on other platforms. + +To be portable it is advisable to rename the file. + +If you know what you are doing you can disable this check using: + + git config hooks.allownonascii true +EOF + exit 1 +fi + +# If there are whitespace errors, print the offending file names and fail. +exec git diff-index --check --cached $against -- diff --git a/spec/fixtures/no-commits.git/hooks/pre-push.sample b/spec/fixtures/no-commits.git/hooks/pre-push.sample new file mode 100755 index 00000000..6187dbf4 --- /dev/null +++ b/spec/fixtures/no-commits.git/hooks/pre-push.sample @@ -0,0 +1,53 @@ +#!/bin/sh + +# An example hook script to verify what is about to be pushed. Called by "git +# push" after it has checked the remote status, but before anything has been +# pushed. If this script exits with a non-zero status nothing will be pushed. +# +# This hook is called with the following parameters: +# +# $1 -- Name of the remote to which the push is being done +# $2 -- URL to which the push is being done +# +# If pushing without using a named remote those arguments will be equal. +# +# Information about the commits which are being pushed is supplied as lines to +# the standard input in the form: +# +# +# +# This sample shows how to prevent push of commits where the log message starts +# with "WIP" (work in progress). + +remote="$1" +url="$2" + +z40=0000000000000000000000000000000000000000 + +while read local_ref local_sha remote_ref remote_sha +do + if [ "$local_sha" = $z40 ] + then + # Handle delete + : + else + if [ "$remote_sha" = $z40 ] + then + # New branch, examine all commits + range="$local_sha" + else + # Update to existing branch, examine new commits + range="$remote_sha..$local_sha" + fi + + # Check for WIP commit + commit=`git rev-list -n 1 --grep '^WIP' "$range"` + if [ -n "$commit" ] + then + echo >&2 "Found WIP commit in $local_ref, not pushing" + exit 1 + fi + fi +done + +exit 0 diff --git a/spec/fixtures/no-commits.git/hooks/pre-rebase.sample b/spec/fixtures/no-commits.git/hooks/pre-rebase.sample new file mode 100755 index 00000000..9773ed4c --- /dev/null +++ b/spec/fixtures/no-commits.git/hooks/pre-rebase.sample @@ -0,0 +1,169 @@ +#!/bin/sh +# +# Copyright (c) 2006, 2008 Junio C Hamano +# +# The "pre-rebase" hook is run just before "git rebase" starts doing +# its job, and can prevent the command from running by exiting with +# non-zero status. +# +# The hook is called with the following parameters: +# +# $1 -- the upstream the series was forked from. +# $2 -- the branch being rebased (or empty when rebasing the current branch). +# +# This sample shows how to prevent topic branches that are already +# merged to 'next' branch from getting rebased, because allowing it +# would result in rebasing already published history. + +publish=next +basebranch="$1" +if test "$#" = 2 +then + topic="refs/heads/$2" +else + topic=`git symbolic-ref HEAD` || + exit 0 ;# we do not interrupt rebasing detached HEAD +fi + +case "$topic" in +refs/heads/??/*) + ;; +*) + exit 0 ;# we do not interrupt others. + ;; +esac + +# Now we are dealing with a topic branch being rebased +# on top of master. Is it OK to rebase it? + +# Does the topic really exist? +git show-ref -q "$topic" || { + echo >&2 "No such branch $topic" + exit 1 +} + +# Is topic fully merged to master? +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` +if test -z "$not_in_master" +then + echo >&2 "$topic is fully merged to master; better remove it." + exit 1 ;# we could allow it, but there is no point. +fi + +# Is topic ever merged to next? If so you should not be rebasing it. +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` +if test "$only_next_1" = "$only_next_2" +then + not_in_topic=`git rev-list "^$topic" master` + if test -z "$not_in_topic" + then + echo >&2 "$topic is already up-to-date with master" + exit 1 ;# we could allow it, but there is no point. + else + exit 0 + fi +else + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + /usr/bin/perl -e ' + my $topic = $ARGV[0]; + my $msg = "* $topic has commits already merged to public branch:\n"; + my (%not_in_next) = map { + /^([0-9a-f]+) /; + ($1 => 1); + } split(/\n/, $ARGV[1]); + for my $elem (map { + /^([0-9a-f]+) (.*)$/; + [$1 => $2]; + } split(/\n/, $ARGV[2])) { + if (!exists $not_in_next{$elem->[0]}) { + if ($msg) { + print STDERR $msg; + undef $msg; + } + print STDERR " $elem->[1]\n"; + } + } + ' "$topic" "$not_in_next" "$not_in_master" + exit 1 +fi + +exit 0 + +################################################################ + +This sample hook safeguards topic branches that have been +published from being rewound. + +The workflow assumed here is: + + * Once a topic branch forks from "master", "master" is never + merged into it again (either directly or indirectly). + + * Once a topic branch is fully cooked and merged into "master", + it is deleted. If you need to build on top of it to correct + earlier mistakes, a new topic branch is created by forking at + the tip of the "master". This is not strictly necessary, but + it makes it easier to keep your history simple. + + * Whenever you need to test or publish your changes to topic + branches, merge them into "next" branch. + +The script, being an example, hardcodes the publish branch name +to be "next", but it is trivial to make it configurable via +$GIT_DIR/config mechanism. + +With this workflow, you would want to know: + +(1) ... if a topic branch has ever been merged to "next". Young + topic branches can have stupid mistakes you would rather + clean up before publishing, and things that have not been + merged into other branches can be easily rebased without + affecting other people. But once it is published, you would + not want to rewind it. + +(2) ... if a topic branch has been fully merged to "master". + Then you can delete it. More importantly, you should not + build on top of it -- other people may already want to + change things related to the topic as patches against your + "master", so if you need further changes, it is better to + fork the topic (perhaps with the same name) afresh from the + tip of "master". + +Let's look at this example: + + o---o---o---o---o---o---o---o---o---o "next" + / / / / + / a---a---b A / / + / / / / + / / c---c---c---c B / + / / / \ / + / / / b---b C \ / + / / / / \ / + ---o---o---o---o---o---o---o---o---o---o---o "master" + + +A, B and C are topic branches. + + * A has one fix since it was merged up to "next". + + * B has finished. It has been fully merged up to "master" and "next", + and is ready to be deleted. + + * C has not merged to "next" at all. + +We would want to allow C to be rebased, refuse A, and encourage +B to be deleted. + +To compute (1): + + git rev-list ^master ^topic next + git rev-list ^master next + + if these match, topic has not merged in next at all. + +To compute (2): + + git rev-list master..topic + + if this is empty, it is fully merged to "master". diff --git a/spec/fixtures/no-commits.git/hooks/prepare-commit-msg.sample b/spec/fixtures/no-commits.git/hooks/prepare-commit-msg.sample new file mode 100755 index 00000000..f093a02e --- /dev/null +++ b/spec/fixtures/no-commits.git/hooks/prepare-commit-msg.sample @@ -0,0 +1,36 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by "git commit" with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, rename this file to "prepare-commit-msg". + +# This hook includes three examples. The first comments out the +# "Conflicts:" part of a merge commit. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +case "$2,$3" in + merge,) + /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; + +# ,|template,) +# /usr/bin/perl -i.bak -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$1" ;; + + *) ;; +esac + +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" diff --git a/spec/fixtures/no-commits.git/hooks/update.sample b/spec/fixtures/no-commits.git/hooks/update.sample new file mode 100755 index 00000000..80ba9413 --- /dev/null +++ b/spec/fixtures/no-commits.git/hooks/update.sample @@ -0,0 +1,128 @@ +#!/bin/sh +# +# An example hook script to block unannotated tags from entering. +# Called by "git receive-pack" with arguments: refname sha1-old sha1-new +# +# To enable this hook, rename this file to "update". +# +# Config +# ------ +# hooks.allowunannotated +# This boolean sets whether unannotated tags will be allowed into the +# repository. By default they won't be. +# hooks.allowdeletetag +# This boolean sets whether deleting tags will be allowed in the +# repository. By default they won't be. +# hooks.allowmodifytag +# This boolean sets whether a tag may be modified after creation. By default +# it won't be. +# hooks.allowdeletebranch +# This boolean sets whether deleting branches will be allowed in the +# repository. By default they won't be. +# hooks.denycreatebranch +# This boolean sets whether remotely creating branches will be denied +# in the repository. By default this is allowed. +# + +# --- Command line +refname="$1" +oldrev="$2" +newrev="$3" + +# --- Safety check +if [ -z "$GIT_DIR" ]; then + echo "Don't run this script from the command line." >&2 + echo " (if you want, you could supply GIT_DIR then run" >&2 + echo " $0 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +# --- Config +allowunannotated=$(git config --bool hooks.allowunannotated) +allowdeletebranch=$(git config --bool hooks.allowdeletebranch) +denycreatebranch=$(git config --bool hooks.denycreatebranch) +allowdeletetag=$(git config --bool hooks.allowdeletetag) +allowmodifytag=$(git config --bool hooks.allowmodifytag) + +# check for no description +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +case "$projectdesc" in +"Unnamed repository"* | "") + echo "*** Project description file hasn't been set" >&2 + exit 1 + ;; +esac + +# --- Check types +# if $newrev is 0000...0000, it's a commit to delete a ref. +zero="0000000000000000000000000000000000000000" +if [ "$newrev" = "$zero" ]; then + newrev_type=delete +else + newrev_type=$(git cat-file -t $newrev) +fi + +case "$refname","$newrev_type" in + refs/tags/*,commit) + # un-annotated tag + short_refname=${refname##refs/tags/} + if [ "$allowunannotated" != "true" ]; then + echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 + exit 1 + fi + ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/tags/*,tag) + # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi + ;; + refs/heads/*,commit) + # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/remotes/*,commit) + # tracking branch + ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + *) + # Anything else (is there anything else?) + echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 + exit 1 + ;; +esac + +# --- Finished +exit 0 diff --git a/spec/fixtures/no-commits.git/info/exclude b/spec/fixtures/no-commits.git/info/exclude new file mode 100644 index 00000000..a5196d1b --- /dev/null +++ b/spec/fixtures/no-commits.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/spec/fixtures/no-commits.git/objects/info/.gitkeep b/spec/fixtures/no-commits.git/objects/info/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/spec/fixtures/no-commits.git/objects/pack/.gitkeep b/spec/fixtures/no-commits.git/objects/pack/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/spec/fixtures/no-commits.git/refs/heads/.gitkeep b/spec/fixtures/no-commits.git/refs/heads/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/spec/fixtures/no-commits.git/refs/tags/.gitkeep b/spec/fixtures/no-commits.git/refs/tags/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/spec/fixtures/subdir.git/COMMIT_EDITMSG b/spec/fixtures/subdir.git/COMMIT_EDITMSG new file mode 100644 index 00000000..9d4e7727 --- /dev/null +++ b/spec/fixtures/subdir.git/COMMIT_EDITMSG @@ -0,0 +1,10 @@ +first +# Please enter the commit message for your changes. Lines starting +# with '#' will be ignored, and an empty message aborts the commit. +# On branch master +# +# Initial commit +# +# Changes to be committed: +# new file: dir/a.txt +# diff --git a/spec/fixtures/subdir.git/HEAD b/spec/fixtures/subdir.git/HEAD new file mode 100644 index 00000000..cb089cd8 --- /dev/null +++ b/spec/fixtures/subdir.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/spec/fixtures/subdir.git/config b/spec/fixtures/subdir.git/config new file mode 100644 index 00000000..6c9406b7 --- /dev/null +++ b/spec/fixtures/subdir.git/config @@ -0,0 +1,7 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true + ignorecase = true + precomposeunicode = true diff --git a/spec/fixtures/subdir.git/description b/spec/fixtures/subdir.git/description new file mode 100644 index 00000000..498b267a --- /dev/null +++ b/spec/fixtures/subdir.git/description @@ -0,0 +1 @@ +Unnamed repository; edit this file 'description' to name the repository. diff --git a/spec/fixtures/subdir.git/hooks/applypatch-msg.sample b/spec/fixtures/subdir.git/hooks/applypatch-msg.sample new file mode 100755 index 00000000..a5d7b84a --- /dev/null +++ b/spec/fixtures/subdir.git/hooks/applypatch-msg.sample @@ -0,0 +1,15 @@ +#!/bin/sh +# +# An example hook script to check the commit log message taken by +# applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. The hook is +# allowed to edit the commit message file. +# +# To enable this hook, rename this file to "applypatch-msg". + +. git-sh-setup +commitmsg="$(git rev-parse --git-path hooks/commit-msg)" +test -x "$commitmsg" && exec "$commitmsg" ${1+"$@"} +: diff --git a/spec/fixtures/subdir.git/hooks/commit-msg.sample b/spec/fixtures/subdir.git/hooks/commit-msg.sample new file mode 100755 index 00000000..b58d1184 --- /dev/null +++ b/spec/fixtures/subdir.git/hooks/commit-msg.sample @@ -0,0 +1,24 @@ +#!/bin/sh +# +# An example hook script to check the commit log message. +# Called by "git commit" with one argument, the name of the file +# that has the commit message. The hook should exit with non-zero +# status after issuing an appropriate message if it wants to stop the +# commit. The hook is allowed to edit the commit message file. +# +# To enable this hook, rename this file to "commit-msg". + +# Uncomment the below to add a Signed-off-by line to the message. +# Doing this in a hook is a bad idea in general, but the prepare-commit-msg +# hook is more suited to it. +# +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" + +# This example catches duplicate Signed-off-by lines. + +test "" = "$(grep '^Signed-off-by: ' "$1" | + sort | uniq -c | sed -e '/^[ ]*1[ ]/d')" || { + echo >&2 Duplicate Signed-off-by lines. + exit 1 +} diff --git a/spec/fixtures/subdir.git/hooks/post-update.sample b/spec/fixtures/subdir.git/hooks/post-update.sample new file mode 100755 index 00000000..ec17ec19 --- /dev/null +++ b/spec/fixtures/subdir.git/hooks/post-update.sample @@ -0,0 +1,8 @@ +#!/bin/sh +# +# An example hook script to prepare a packed repository for use over +# dumb transports. +# +# To enable this hook, rename this file to "post-update". + +exec git update-server-info diff --git a/spec/fixtures/subdir.git/hooks/pre-applypatch.sample b/spec/fixtures/subdir.git/hooks/pre-applypatch.sample new file mode 100755 index 00000000..4142082b --- /dev/null +++ b/spec/fixtures/subdir.git/hooks/pre-applypatch.sample @@ -0,0 +1,14 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed +# by applypatch from an e-mail message. +# +# The hook should exit with non-zero status after issuing an +# appropriate message if it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-applypatch". + +. git-sh-setup +precommit="$(git rev-parse --git-path hooks/pre-commit)" +test -x "$precommit" && exec "$precommit" ${1+"$@"} +: diff --git a/spec/fixtures/subdir.git/hooks/pre-commit.sample b/spec/fixtures/subdir.git/hooks/pre-commit.sample new file mode 100755 index 00000000..68d62d54 --- /dev/null +++ b/spec/fixtures/subdir.git/hooks/pre-commit.sample @@ -0,0 +1,49 @@ +#!/bin/sh +# +# An example hook script to verify what is about to be committed. +# Called by "git commit" with no arguments. The hook should +# exit with non-zero status after issuing an appropriate message if +# it wants to stop the commit. +# +# To enable this hook, rename this file to "pre-commit". + +if git rev-parse --verify HEAD >/dev/null 2>&1 +then + against=HEAD +else + # Initial commit: diff against an empty tree object + against=4b825dc642cb6eb9a060e54bf8d69288fbee4904 +fi + +# If you want to allow non-ASCII filenames set this variable to true. +allownonascii=$(git config --bool hooks.allownonascii) + +# Redirect output to stderr. +exec 1>&2 + +# Cross platform projects tend to avoid non-ASCII filenames; prevent +# them from being added to the repository. We exploit the fact that the +# printable range starts at the space character and ends with tilde. +if [ "$allownonascii" != "true" ] && + # Note that the use of brackets around a tr range is ok here, (it's + # even required, for portability to Solaris 10's /usr/bin/tr), since + # the square bracket bytes happen to fall in the designated range. + test $(git diff --cached --name-only --diff-filter=A -z $against | + LC_ALL=C tr -d '[ -~]\0' | wc -c) != 0 +then + cat <<\EOF +Error: Attempt to add a non-ASCII file name. + +This can cause problems if you want to work with people on other platforms. + +To be portable it is advisable to rename the file. + +If you know what you are doing you can disable this check using: + + git config hooks.allownonascii true +EOF + exit 1 +fi + +# If there are whitespace errors, print the offending file names and fail. +exec git diff-index --check --cached $against -- diff --git a/spec/fixtures/subdir.git/hooks/pre-push.sample b/spec/fixtures/subdir.git/hooks/pre-push.sample new file mode 100755 index 00000000..6187dbf4 --- /dev/null +++ b/spec/fixtures/subdir.git/hooks/pre-push.sample @@ -0,0 +1,53 @@ +#!/bin/sh + +# An example hook script to verify what is about to be pushed. Called by "git +# push" after it has checked the remote status, but before anything has been +# pushed. If this script exits with a non-zero status nothing will be pushed. +# +# This hook is called with the following parameters: +# +# $1 -- Name of the remote to which the push is being done +# $2 -- URL to which the push is being done +# +# If pushing without using a named remote those arguments will be equal. +# +# Information about the commits which are being pushed is supplied as lines to +# the standard input in the form: +# +# +# +# This sample shows how to prevent push of commits where the log message starts +# with "WIP" (work in progress). + +remote="$1" +url="$2" + +z40=0000000000000000000000000000000000000000 + +while read local_ref local_sha remote_ref remote_sha +do + if [ "$local_sha" = $z40 ] + then + # Handle delete + : + else + if [ "$remote_sha" = $z40 ] + then + # New branch, examine all commits + range="$local_sha" + else + # Update to existing branch, examine new commits + range="$remote_sha..$local_sha" + fi + + # Check for WIP commit + commit=`git rev-list -n 1 --grep '^WIP' "$range"` + if [ -n "$commit" ] + then + echo >&2 "Found WIP commit in $local_ref, not pushing" + exit 1 + fi + fi +done + +exit 0 diff --git a/spec/fixtures/subdir.git/hooks/pre-rebase.sample b/spec/fixtures/subdir.git/hooks/pre-rebase.sample new file mode 100755 index 00000000..9773ed4c --- /dev/null +++ b/spec/fixtures/subdir.git/hooks/pre-rebase.sample @@ -0,0 +1,169 @@ +#!/bin/sh +# +# Copyright (c) 2006, 2008 Junio C Hamano +# +# The "pre-rebase" hook is run just before "git rebase" starts doing +# its job, and can prevent the command from running by exiting with +# non-zero status. +# +# The hook is called with the following parameters: +# +# $1 -- the upstream the series was forked from. +# $2 -- the branch being rebased (or empty when rebasing the current branch). +# +# This sample shows how to prevent topic branches that are already +# merged to 'next' branch from getting rebased, because allowing it +# would result in rebasing already published history. + +publish=next +basebranch="$1" +if test "$#" = 2 +then + topic="refs/heads/$2" +else + topic=`git symbolic-ref HEAD` || + exit 0 ;# we do not interrupt rebasing detached HEAD +fi + +case "$topic" in +refs/heads/??/*) + ;; +*) + exit 0 ;# we do not interrupt others. + ;; +esac + +# Now we are dealing with a topic branch being rebased +# on top of master. Is it OK to rebase it? + +# Does the topic really exist? +git show-ref -q "$topic" || { + echo >&2 "No such branch $topic" + exit 1 +} + +# Is topic fully merged to master? +not_in_master=`git rev-list --pretty=oneline ^master "$topic"` +if test -z "$not_in_master" +then + echo >&2 "$topic is fully merged to master; better remove it." + exit 1 ;# we could allow it, but there is no point. +fi + +# Is topic ever merged to next? If so you should not be rebasing it. +only_next_1=`git rev-list ^master "^$topic" ${publish} | sort` +only_next_2=`git rev-list ^master ${publish} | sort` +if test "$only_next_1" = "$only_next_2" +then + not_in_topic=`git rev-list "^$topic" master` + if test -z "$not_in_topic" + then + echo >&2 "$topic is already up-to-date with master" + exit 1 ;# we could allow it, but there is no point. + else + exit 0 + fi +else + not_in_next=`git rev-list --pretty=oneline ^${publish} "$topic"` + /usr/bin/perl -e ' + my $topic = $ARGV[0]; + my $msg = "* $topic has commits already merged to public branch:\n"; + my (%not_in_next) = map { + /^([0-9a-f]+) /; + ($1 => 1); + } split(/\n/, $ARGV[1]); + for my $elem (map { + /^([0-9a-f]+) (.*)$/; + [$1 => $2]; + } split(/\n/, $ARGV[2])) { + if (!exists $not_in_next{$elem->[0]}) { + if ($msg) { + print STDERR $msg; + undef $msg; + } + print STDERR " $elem->[1]\n"; + } + } + ' "$topic" "$not_in_next" "$not_in_master" + exit 1 +fi + +exit 0 + +################################################################ + +This sample hook safeguards topic branches that have been +published from being rewound. + +The workflow assumed here is: + + * Once a topic branch forks from "master", "master" is never + merged into it again (either directly or indirectly). + + * Once a topic branch is fully cooked and merged into "master", + it is deleted. If you need to build on top of it to correct + earlier mistakes, a new topic branch is created by forking at + the tip of the "master". This is not strictly necessary, but + it makes it easier to keep your history simple. + + * Whenever you need to test or publish your changes to topic + branches, merge them into "next" branch. + +The script, being an example, hardcodes the publish branch name +to be "next", but it is trivial to make it configurable via +$GIT_DIR/config mechanism. + +With this workflow, you would want to know: + +(1) ... if a topic branch has ever been merged to "next". Young + topic branches can have stupid mistakes you would rather + clean up before publishing, and things that have not been + merged into other branches can be easily rebased without + affecting other people. But once it is published, you would + not want to rewind it. + +(2) ... if a topic branch has been fully merged to "master". + Then you can delete it. More importantly, you should not + build on top of it -- other people may already want to + change things related to the topic as patches against your + "master", so if you need further changes, it is better to + fork the topic (perhaps with the same name) afresh from the + tip of "master". + +Let's look at this example: + + o---o---o---o---o---o---o---o---o---o "next" + / / / / + / a---a---b A / / + / / / / + / / c---c---c---c B / + / / / \ / + / / / b---b C \ / + / / / / \ / + ---o---o---o---o---o---o---o---o---o---o---o "master" + + +A, B and C are topic branches. + + * A has one fix since it was merged up to "next". + + * B has finished. It has been fully merged up to "master" and "next", + and is ready to be deleted. + + * C has not merged to "next" at all. + +We would want to allow C to be rebased, refuse A, and encourage +B to be deleted. + +To compute (1): + + git rev-list ^master ^topic next + git rev-list ^master next + + if these match, topic has not merged in next at all. + +To compute (2): + + git rev-list master..topic + + if this is empty, it is fully merged to "master". diff --git a/spec/fixtures/subdir.git/hooks/prepare-commit-msg.sample b/spec/fixtures/subdir.git/hooks/prepare-commit-msg.sample new file mode 100755 index 00000000..f093a02e --- /dev/null +++ b/spec/fixtures/subdir.git/hooks/prepare-commit-msg.sample @@ -0,0 +1,36 @@ +#!/bin/sh +# +# An example hook script to prepare the commit log message. +# Called by "git commit" with the name of the file that has the +# commit message, followed by the description of the commit +# message's source. The hook's purpose is to edit the commit +# message file. If the hook fails with a non-zero status, +# the commit is aborted. +# +# To enable this hook, rename this file to "prepare-commit-msg". + +# This hook includes three examples. The first comments out the +# "Conflicts:" part of a merge commit. +# +# The second includes the output of "git diff --name-status -r" +# into the message, just before the "git status" output. It is +# commented because it doesn't cope with --amend or with squashed +# commits. +# +# The third example adds a Signed-off-by line to the message, that can +# still be edited. This is rarely a good idea. + +case "$2,$3" in + merge,) + /usr/bin/perl -i.bak -ne 's/^/# /, s/^# #/#/ if /^Conflicts/ .. /#/; print' "$1" ;; + +# ,|template,) +# /usr/bin/perl -i.bak -pe ' +# print "\n" . `git diff --cached --name-status -r` +# if /^#/ && $first++ == 0' "$1" ;; + + *) ;; +esac + +# SOB=$(git var GIT_AUTHOR_IDENT | sed -n 's/^\(.*>\).*$/Signed-off-by: \1/p') +# grep -qs "^$SOB" "$1" || echo "$SOB" >> "$1" diff --git a/spec/fixtures/subdir.git/hooks/update.sample b/spec/fixtures/subdir.git/hooks/update.sample new file mode 100755 index 00000000..d8475837 --- /dev/null +++ b/spec/fixtures/subdir.git/hooks/update.sample @@ -0,0 +1,128 @@ +#!/bin/sh +# +# An example hook script to blocks unannotated tags from entering. +# Called by "git receive-pack" with arguments: refname sha1-old sha1-new +# +# To enable this hook, rename this file to "update". +# +# Config +# ------ +# hooks.allowunannotated +# This boolean sets whether unannotated tags will be allowed into the +# repository. By default they won't be. +# hooks.allowdeletetag +# This boolean sets whether deleting tags will be allowed in the +# repository. By default they won't be. +# hooks.allowmodifytag +# This boolean sets whether a tag may be modified after creation. By default +# it won't be. +# hooks.allowdeletebranch +# This boolean sets whether deleting branches will be allowed in the +# repository. By default they won't be. +# hooks.denycreatebranch +# This boolean sets whether remotely creating branches will be denied +# in the repository. By default this is allowed. +# + +# --- Command line +refname="$1" +oldrev="$2" +newrev="$3" + +# --- Safety check +if [ -z "$GIT_DIR" ]; then + echo "Don't run this script from the command line." >&2 + echo " (if you want, you could supply GIT_DIR then run" >&2 + echo " $0 )" >&2 + exit 1 +fi + +if [ -z "$refname" -o -z "$oldrev" -o -z "$newrev" ]; then + echo "usage: $0 " >&2 + exit 1 +fi + +# --- Config +allowunannotated=$(git config --bool hooks.allowunannotated) +allowdeletebranch=$(git config --bool hooks.allowdeletebranch) +denycreatebranch=$(git config --bool hooks.denycreatebranch) +allowdeletetag=$(git config --bool hooks.allowdeletetag) +allowmodifytag=$(git config --bool hooks.allowmodifytag) + +# check for no description +projectdesc=$(sed -e '1q' "$GIT_DIR/description") +case "$projectdesc" in +"Unnamed repository"* | "") + echo "*** Project description file hasn't been set" >&2 + exit 1 + ;; +esac + +# --- Check types +# if $newrev is 0000...0000, it's a commit to delete a ref. +zero="0000000000000000000000000000000000000000" +if [ "$newrev" = "$zero" ]; then + newrev_type=delete +else + newrev_type=$(git cat-file -t $newrev) +fi + +case "$refname","$newrev_type" in + refs/tags/*,commit) + # un-annotated tag + short_refname=${refname##refs/tags/} + if [ "$allowunannotated" != "true" ]; then + echo "*** The un-annotated tag, $short_refname, is not allowed in this repository" >&2 + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 + exit 1 + fi + ;; + refs/tags/*,delete) + # delete tag + if [ "$allowdeletetag" != "true" ]; then + echo "*** Deleting a tag is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/tags/*,tag) + # annotated tag + if [ "$allowmodifytag" != "true" ] && git rev-parse $refname > /dev/null 2>&1 + then + echo "*** Tag '$refname' already exists." >&2 + echo "*** Modifying a tag is not allowed in this repository." >&2 + exit 1 + fi + ;; + refs/heads/*,commit) + # branch + if [ "$oldrev" = "$zero" -a "$denycreatebranch" = "true" ]; then + echo "*** Creating a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/heads/*,delete) + # delete branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + refs/remotes/*,commit) + # tracking branch + ;; + refs/remotes/*,delete) + # delete tracking branch + if [ "$allowdeletebranch" != "true" ]; then + echo "*** Deleting a tracking branch is not allowed in this repository" >&2 + exit 1 + fi + ;; + *) + # Anything else (is there anything else?) + echo "*** Update hook: unknown type of update to ref $refname of type $newrev_type" >&2 + exit 1 + ;; +esac + +# --- Finished +exit 0 diff --git a/spec/fixtures/subdir.git/index b/spec/fixtures/subdir.git/index new file mode 100644 index 00000000..7307df29 Binary files /dev/null and b/spec/fixtures/subdir.git/index differ diff --git a/spec/fixtures/subdir.git/info/exclude b/spec/fixtures/subdir.git/info/exclude new file mode 100644 index 00000000..a5196d1b --- /dev/null +++ b/spec/fixtures/subdir.git/info/exclude @@ -0,0 +1,6 @@ +# git ls-files --others --exclude-from=.git/info/exclude +# Lines that start with '#' are comments. +# For a project mostly in C, the following would be a good set of +# exclude patterns (uncomment them if you want to use them): +# *.[oa] +# *~ diff --git a/spec/fixtures/subdir.git/logs/HEAD b/spec/fixtures/subdir.git/logs/HEAD new file mode 100644 index 00000000..014e0c37 --- /dev/null +++ b/spec/fixtures/subdir.git/logs/HEAD @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 0161b71e6fec1c93588328e95a1b35d29dc11535 joshaber 1455122054 -0500 commit (initial): first diff --git a/spec/fixtures/subdir.git/logs/refs/heads/master b/spec/fixtures/subdir.git/logs/refs/heads/master new file mode 100644 index 00000000..014e0c37 --- /dev/null +++ b/spec/fixtures/subdir.git/logs/refs/heads/master @@ -0,0 +1 @@ +0000000000000000000000000000000000000000 0161b71e6fec1c93588328e95a1b35d29dc11535 joshaber 1455122054 -0500 commit (initial): first diff --git a/spec/fixtures/subdir.git/objects/01/61b71e6fec1c93588328e95a1b35d29dc11535 b/spec/fixtures/subdir.git/objects/01/61b71e6fec1c93588328e95a1b35d29dc11535 new file mode 100644 index 00000000..e9b46fa6 --- /dev/null +++ b/spec/fixtures/subdir.git/objects/01/61b71e6fec1c93588328e95a1b35d29dc11535 @@ -0,0 +1,2 @@ +xA +à E»ös'™I"„Ы¨i,Á˜û×MöÝ=>ïóbÉ95À‰n­Š€1ð¸¸@.ª(‹ „UëãKUi6þl{©ð)ÇîƒTX/z¾³OßG,y$fËwËÖš¾ö\ëþ¥ÿy4šêÑÌí<6 \ No newline at end of file diff --git a/spec/fixtures/subdir.git/objects/65/a457425a679cbe9adf0d2741785d3ceabb44a7 b/spec/fixtures/subdir.git/objects/65/a457425a679cbe9adf0d2741785d3ceabb44a7 new file mode 100644 index 00000000..ba1f06fc Binary files /dev/null and b/spec/fixtures/subdir.git/objects/65/a457425a679cbe9adf0d2741785d3ceabb44a7 differ diff --git a/spec/fixtures/subdir.git/objects/93/1b5389b49cf064be5ee1eb81ff95f7ccdfff47 b/spec/fixtures/subdir.git/objects/93/1b5389b49cf064be5ee1eb81ff95f7ccdfff47 new file mode 100644 index 00000000..3506513e Binary files /dev/null and b/spec/fixtures/subdir.git/objects/93/1b5389b49cf064be5ee1eb81ff95f7ccdfff47 differ diff --git a/spec/fixtures/subdir.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 b/spec/fixtures/subdir.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 new file mode 100644 index 00000000..71122389 Binary files /dev/null and b/spec/fixtures/subdir.git/objects/e6/9de29bb2d1d6434b8b29ae775ad8c2e48c5391 differ diff --git a/spec/fixtures/subdir.git/refs/heads/master b/spec/fixtures/subdir.git/refs/heads/master new file mode 100644 index 00000000..4e9b28da --- /dev/null +++ b/spec/fixtures/subdir.git/refs/heads/master @@ -0,0 +1 @@ +0161b71e6fec1c93588328e95a1b35d29dc11535 diff --git a/spec/fixtures/whitespace.git/config b/spec/fixtures/whitespace.git/config index bb4d11c1..6c9406b7 100644 --- a/spec/fixtures/whitespace.git/config +++ b/spec/fixtures/whitespace.git/config @@ -4,4 +4,4 @@ bare = false logallrefupdates = true ignorecase = true - precomposeunicode = false + precomposeunicode = true diff --git a/spec/fixtures/whitespace.git/index b/spec/fixtures/whitespace.git/index index fa597b62..86dc7309 100644 Binary files a/spec/fixtures/whitespace.git/index and b/spec/fixtures/whitespace.git/index differ diff --git a/spec/fixtures/whitespace.git/objects/32/7dcd4d73d1d555ed7c3339809aadc584b16e11 b/spec/fixtures/whitespace.git/objects/32/7dcd4d73d1d555ed7c3339809aadc584b16e11 deleted file mode 100644 index b619c1a5..00000000 Binary files a/spec/fixtures/whitespace.git/objects/32/7dcd4d73d1d555ed7c3339809aadc584b16e11 and /dev/null differ diff --git a/spec/fixtures/whitespace.git/objects/5f/6a1c992cfafadd97053fae380aef21a964621a b/spec/fixtures/whitespace.git/objects/5f/6a1c992cfafadd97053fae380aef21a964621a new file mode 100644 index 00000000..73620c13 Binary files /dev/null and b/spec/fixtures/whitespace.git/objects/5f/6a1c992cfafadd97053fae380aef21a964621a differ diff --git a/spec/fixtures/whitespace.git/objects/9e/9f22baaf485ce95df8de089024e5fa13bc7fb4 b/spec/fixtures/whitespace.git/objects/9e/9f22baaf485ce95df8de089024e5fa13bc7fb4 new file mode 100644 index 00000000..563d51fc Binary files /dev/null and b/spec/fixtures/whitespace.git/objects/9e/9f22baaf485ce95df8de089024e5fa13bc7fb4 differ diff --git a/spec/fixtures/whitespace.git/objects/de/14a8e29deb59e93d6daa82b1c55413581017a1 b/spec/fixtures/whitespace.git/objects/de/14a8e29deb59e93d6daa82b1c55413581017a1 deleted file mode 100644 index 5e08be11..00000000 Binary files a/spec/fixtures/whitespace.git/objects/de/14a8e29deb59e93d6daa82b1c55413581017a1 and /dev/null differ diff --git a/spec/fixtures/whitespace.git/objects/e2/81c6b8b1c5b44193540a0dc1ada9957d07677c b/spec/fixtures/whitespace.git/objects/e2/81c6b8b1c5b44193540a0dc1ada9957d07677c new file mode 100644 index 00000000..078a7661 Binary files /dev/null and b/spec/fixtures/whitespace.git/objects/e2/81c6b8b1c5b44193540a0dc1ada9957d07677c differ diff --git a/spec/fixtures/whitespace.git/objects/ff/6e6b1a505523bd4c9af36bd9d70d136872b225 b/spec/fixtures/whitespace.git/objects/ff/6e6b1a505523bd4c9af36bd9d70d136872b225 deleted file mode 100644 index f8b20186..00000000 Binary files a/spec/fixtures/whitespace.git/objects/ff/6e6b1a505523bd4c9af36bd9d70d136872b225 and /dev/null differ diff --git a/spec/fixtures/whitespace.git/refs/heads/master b/spec/fixtures/whitespace.git/refs/heads/master index 633fcad8..8bf7db85 100644 --- a/spec/fixtures/whitespace.git/refs/heads/master +++ b/spec/fixtures/whitespace.git/refs/heads/master @@ -1 +1 @@ -de14a8e29deb59e93d6daa82b1c55413581017a1 +5f6a1c992cfafadd97053fae380aef21a964621a diff --git a/spec/git-spec.coffee b/spec/git-spec.coffee deleted file mode 100644 index 4f7551bd..00000000 --- a/spec/git-spec.coffee +++ /dev/null @@ -1,553 +0,0 @@ -git = require '../lib/git' -path = require 'path' -fs = require 'fs' -{exec} = require 'child_process' -wrench = require 'wrench' -temp = require 'temp' -_ = require 'underscore' - -describe "git", -> - repo = null - - afterEach -> - repo?.release() - - describe ".open(path)", -> - describe "when the path is a repository", -> - it "returns a repository", -> - expect(git.open(__dirname)).not.toBeNull() - - describe "when the path isn't a repository", -> - it "returns null", -> - expect(git.open('/tmp/path/does/not/exist')).toBeNull() - - describe ".getPath()", -> - it "returns the path to the .git directory", -> - repositoryPath = git.open(__dirname).getPath() - currentGitPath = path.join(path.dirname(__dirname), '.git/') - currentGitPath = currentGitPath.replace(/\\/g, '/') if process.platform is 'win32' - expect(repositoryPath).toBe currentGitPath - - describe ".getWorkingDirectory()", -> - it "returns the path to the working directory", -> - workingDirectory = git.open(__dirname).getWorkingDirectory() - cwd = path.dirname(__dirname) - cwd = cwd.replace(/\\/g, '/') if process.platform is 'win32' - expect(workingDirectory).toBe cwd - - describe ".getHead()", -> - describe "when a branch is checked out", -> - it "returns the branch's full path", -> - repo = git.open(path.join(__dirname, 'fixtures/master.git')) - expect(repo.getHead()).toBe 'refs/heads/master' - - describe "when the HEAD is detached", -> - it "return the SHA-1 that is checked out", -> - repo = git.open(path.join(__dirname, 'fixtures/detached.git')) - expect(repo.getHead()).toBe '50719ab369dcbbc2fb3b7a0167c52accbd0eb40e' - - describe ".getShortHead()", -> - describe "when a branch is checked out", -> - it "returns the branch's name", -> - repo = git.open(path.join(__dirname, 'fixtures/master.git')) - expect(repo.getShortHead()).toBe 'master' - - describe "when the HEAD is detached", -> - it "return the abbreviated SHA-1 that is checked out", -> - repo = git.open(path.join(__dirname, 'fixtures/detached.git')) - expect(repo.getShortHead()).toBe '50719ab' - - describe ".isIgnored(path)", -> - describe "when the path is undefined", -> - it "return false", -> - repo = git.open(path.join(__dirname, 'fixtures/ignored.git')) - expect(repo.isIgnored()).toBe false - - describe "when the path is ignored", -> - it "returns true", -> - repo = git.open(path.join(__dirname, 'fixtures/ignored.git')) - expect(repo.isIgnored('a.txt')).toBe true - - describe "when the path is not ignored", -> - it "return false", -> - repo = git.open(path.join(__dirname, 'fixtures/ignored.git')) - expect(repo.isIgnored('b.txt')).toBe false - - describe ".isSubmodule(path)", -> - describe "when the path is undefined", -> - it "return false", -> - repo = git.open(path.join(__dirname, 'fixtures/submodule.git')) - expect(repo.isSubmodule()).toBe false - - describe "when the path is a submodule", -> - it "returns true", -> - repo = git.open(path.join(__dirname, 'fixtures/submodule.git')) - expect(repo.isSubmodule('a')).toBe true - - describe "when the path is not a submodule", -> - it "return false", -> - repo = git.open(path.join(__dirname, 'fixtures/submodule.git')) - expect(repo.isSubmodule('b')).toBe false - - describe ".getConfigValue(key)", -> - it "returns the value for the key", -> - repo = git.open(path.join(__dirname, 'fixtures/master.git')) - expect(repo.getConfigValue("core.repositoryformatversion")).toBe '0' - expect(repo.getConfigValue("core.ignorecase")).toBe 'true' - expect(repo.getConfigValue("not.section")).toBe null - - describe ".setConfigValue(key, value)", -> - beforeEach -> - repoDirectory = temp.mkdirSync('node-git-repo-') - wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) - repo = git.open(repoDirectory) - - it "sets the key to the value in the config", -> - expect(repo.setConfigValue()).toBe false - expect(repo.setConfigValue('1')).toBe false - expect(repo.setConfigValue('a.b', 'test')).toBe true - expect(repo.getConfigValue('a.b')).toBe 'test' - expect(repo.setConfigValue('a.b.c', 'foo')).toBe true - expect(repo.getConfigValue('a.b.c')).toBe 'foo' - - describe '.isPathModified(path)', -> - beforeEach -> - repoDirectory = temp.mkdirSync('node-git-repo-') - wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) - repo = git.open(repoDirectory) - - describe 'when a path is deleted', -> - it 'returns true', -> - expect(repo.isPathModified('a.txt')).toBe true - - describe 'when a path is modified', -> - it 'returns true', -> - fs.writeFileSync(path.join(repo.getWorkingDirectory(), 'a.txt'), 'changing a.txt', 'utf8') - expect(repo.isPathModified('a.txt')).toBe true - - describe 'when a path is new', -> - it 'returns false', -> - fs.writeFileSync(path.join(repo.getWorkingDirectory(), 'new.txt'), 'new', 'utf8') - expect(repo.isPathModified('new.txt')).toBe false - - describe '.isPathDeleted(path)', -> - beforeEach -> - repoDirectory = temp.mkdirSync('node-git-repo-') - wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) - repo = git.open(repoDirectory) - - describe 'when a path is deleted', -> - it 'returns true', -> - expect(repo.isPathDeleted('a.txt')).toBe true - - describe 'when a path is modified', -> - it 'returns false', -> - fs.writeFileSync(path.join(repo.getWorkingDirectory(), 'a.txt'), 'changing a.txt', 'utf8') - expect(repo.isPathDeleted('a.txt')).toBe false - - describe 'when a path is new', -> - it 'returns false', -> - fs.writeFileSync(path.join(repo.getWorkingDirectory(), 'new.txt'), 'new', 'utf8') - expect(repo.isPathDeleted('new.txt')).toBe false - - describe '.isPathNew(path)', -> - beforeEach -> - repoDirectory = temp.mkdirSync('node-git-repo-') - wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) - repo = git.open(repoDirectory) - - describe 'when a path is deleted', -> - it 'returns false', -> - expect(repo.isPathNew('a.txt')).toBe false - - describe 'when a path is modified', -> - it 'returns false', -> - fs.writeFileSync(path.join(repo.getWorkingDirectory(), 'a.txt'), 'changing a.txt', 'utf8') - expect(repo.isPathNew('a.txt')).toBe false - - describe 'when a path is new', -> - it 'returns true', -> - fs.writeFileSync(path.join(repo.getWorkingDirectory(), 'new.txt'), 'new', 'utf8') - expect(repo.isPathNew('new.txt')).toBe true - - describe '.getUpstreamBranch()', -> - describe 'when no upstream branch exists', -> - it 'returns null', -> - repo = git.open(path.join(__dirname, 'fixtures/master.git')) - expect(repo.getUpstreamBranch()).toBe null - - describe 'when an upstream branch exists', -> - it 'returns the full path to the branch', -> - repo = git.open(path.join(__dirname, 'fixtures/upstream.git')) - expect(repo.getUpstreamBranch()).toBe 'refs/remotes/origin/master' - - describe '.checkoutReference(reference, [create])', -> - repoDirectory = null - - beforeEach -> - repoDirectory = temp.mkdirSync('node-git-repo-') - wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/references.git'), path.join(repoDirectory, '.git')) - repo = git.open(repoDirectory) - expect(repo.getHead()).toBe 'refs/heads/master' - - describe 'when a local reference exists', -> - it 'checks a branch out if passed a short reference', -> - expect(repo.checkoutReference('getHeadOriginal')).toBe true - expect(repo.getHead()).toBe 'refs/heads/getHeadOriginal' - - it 'checks a branch out if passed a long reference', -> - expect(repo.checkoutReference('refs/heads/getHeadOriginal')).toBe true - expect(repo.getHead()).toBe 'refs/heads/getHeadOriginal' - - # in this test, we need to fake a commit and try to switch to a new branch - it 'does not check a branch out if the dirty tree interferes', -> - fs.writeFileSync(path.join(repo.getWorkingDirectory(), 'README.md'), 'great words', 'utf8') - gitCommandHandler = jasmine.createSpy('gitCommandHandler') - exec "cd #{repoDirectory} && git add . && git commit -m 'update README'", gitCommandHandler - - waitsFor -> - gitCommandHandler.callCount is 1 - - runs -> - expect(repo.checkoutReference('refs/heads/getHeadOriginal')).toBe true - fs.writeFileSync(path.join(repo.getWorkingDirectory(), 'README.md'), 'more words', 'utf8') - expect(repo.checkoutReference('refs/heads/master')).toBe false - expect(repo.getHead()).toBe 'refs/heads/getHeadOriginal' - - it 'does check a branch out if the dirty tree does not interfere', -> - fs.writeFileSync(path.join(repo.getWorkingDirectory(), 'new_file.md'), 'a new file', 'utf8') - expect(repo.checkoutReference('refs/heads/getHeadOriginal')).toBe true - - describe 'when a local reference doesn\'t exist', -> - it 'does nothing if branch creation was not specified', -> - expect(repo.checkoutReference('refs/heads/whoop-whoop')).toBe false - - it 'creates the new branch (if asked to)', -> - expect(repo.checkoutReference('refs/heads/whoop-whoop', true)).toBe true - expect(repo.getHead()).toBe 'refs/heads/whoop-whoop' - - it 'does nothing if the new branch is malformed (even if asked to)', -> - expect(repo.checkoutReference('refs/heads/inv@{id', true)).toBe false - expect(repo.getHead()).toBe 'refs/heads/master' - - describe 'when a short reference is passed', -> - it 'does nothing if branch creation was not specified', -> - expect(repo.checkoutReference('bananas')).toBe false - - it 'creates the new branch (if asked to)', -> - expect(repo.checkoutReference('bananas', true)).toBe true - expect(repo.getHead()).toBe 'refs/heads/bananas' - - describe '.checkoutHead(path)', -> - beforeEach -> - repoDirectory = temp.mkdirSync('node-git-repo-') - wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) - repo = git.open(repoDirectory) - - describe 'when the path exists', -> - it 'replaces the file contents with the HEAD revision and returns true', -> - filePath = path.join(repo.getWorkingDirectory(), 'a.txt') - fs.writeFileSync(filePath, 'changing a.txt', 'utf8') - expect(repo.checkoutHead('a.txt')).toBe true - lineEnding = if process.platform is 'win32' then '\r\n' else '\n' - expect(fs.readFileSync(filePath, 'utf8')).toBe "first line#{lineEnding}" - - describe 'when the path is undefined', -> - it 'returns false', -> - expect(repo.checkoutHead()).toBe false - - describe '.getReferences()', -> - it 'returns a list of all the references', -> - referencesObj = - heads: [ 'refs/heads/diff-lines', 'refs/heads/getHeadOriginal', 'refs/heads/master' ] - remotes: [ 'refs/remotes/origin/getHeadOriginal', 'refs/remotes/origin/HEAD', 'refs/remotes/origin/master', 'refs/remotes/upstream/HEAD', 'refs/remotes/upstream/master' ] - tags: [ 'refs/tags/v1.0', 'refs/tags/v2.0' ] - - repo = git.open(path.join(__dirname, 'fixtures/references.git')) - expect(repo.getReferences()).toEqual referencesObj - - describe '.getReferenceTarget(branch)', -> - it 'returns the SHA-1 for a reference', -> - repo = git.open(path.join(__dirname, 'fixtures/master.git')) - expect(repo.getReferenceTarget('HEAD2')).toBe null - expect(repo.getReferenceTarget('HEAD')).toBe 'b2c96bdffe1a8f239c2d450863e4a6caa6dcb655' - expect(repo.getReferenceTarget('refs/heads/master')).toBe 'b2c96bdffe1a8f239c2d450863e4a6caa6dcb655' - - describe '.getDiffStats(path)', -> - beforeEach -> - repoDirectory = temp.mkdirSync('node-git-repo-') - wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) - repo = git.open(repoDirectory) - - describe 'when the path is deleted', -> - it 'returns of number of lines deleted', -> - expect(repo.getDiffStats('a.txt')).toEqual {added: 0, deleted: 1} - describe 'when the path is modified', -> - it 'returns the number of lines added and deleted', -> - filePath = path.join(repo.getWorkingDirectory(), 'a.txt') - fs.writeFileSync(filePath, 'changing\na.txt', 'utf8') - expect(repo.getDiffStats('a.txt')).toEqual {added: 2, deleted: 1} - - describe 'when the path is new', -> - it 'returns that no lines were added or deleted', -> - filePath = path.join(repo.getWorkingDirectory(), 'b.txt') - fs.writeFileSync(filePath, 'changing\nb.txt\nwith lines', 'utf8') - expect(repo.getDiffStats('b.txt')).toEqual {added: 0, deleted: 0} - - describe 'when the repository has no HEAD', -> - it 'returns that no lines were added and deleted', -> - repoDirectory = temp.mkdirSync('node-git-repo-') - wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) - repo = git.open(repoDirectory) - fs.unlinkSync(path.join(repoDirectory, '.git/HEAD')) - expect(repo.getDiffStats('b.txt')).toEqual {added: 0, deleted: 0} - - describe '.getHeadBlob(path)', -> - beforeEach -> - repoDirectory = temp.mkdirSync('node-git-repo-') - wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) - repo = git.open(repoDirectory) - - describe 'when the path is modified', -> - it 'returns the HEAD blob contents', -> - filePath = path.join(repo.getWorkingDirectory(), 'a.txt') - fs.writeFileSync(filePath, 'changing\na.txt', 'utf8') - expect(repo.getHeadBlob('a.txt')).toBe 'first line\n' - - describe 'when the path is not modified', -> - it 'returns the HEAD blob contents', -> - expect(repo.getHeadBlob('a.txt')).toBe 'first line\n' - - describe 'when the path does not exist', -> - it 'returns null', -> - expect(repo.getHeadBlob('i-do-not-exist.txt')).toBeNull() - - describe '.getIndexBlob(path)', -> - [repo, repoDirectory] = [] - - beforeEach -> - repoDirectory = temp.mkdirSync('node-git-repo-') - wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) - repo = git.open(repoDirectory) - - describe 'when the path is staged', -> - it 'returns the index blob contents', -> - expect(repo.getIndexBlob('a.txt')).toBe 'first line\n' - - filePath = path.join(repo.getWorkingDirectory(), 'a.txt') - fs.writeFileSync(filePath, 'changing\na.txt', 'utf8') - expect(repo.getIndexBlob('a.txt')).toBe 'first line\n' - - gitCommandHandler = jasmine.createSpy('gitCommandHandler') - exec "cd #{repoDirectory} && git add a.txt", gitCommandHandler - - waitsFor -> - gitCommandHandler.callCount is 1 - - runs -> - expect(repo.getIndexBlob('a.txt')).toBe 'changing\na.txt' - - describe 'when the path is not staged', -> - it 'returns the index blob contents', -> - expect(repo.getIndexBlob('a.txt')).toBe 'first line\n' - - filePath = path.join(repo.getWorkingDirectory(), 'a.txt') - fs.writeFileSync(filePath, 'changing\na.txt', 'utf8') - expect(repo.getIndexBlob('a.txt')).toBe 'first line\n' - - describe 'when the path does not exist', -> - it 'returns null', -> - expect(repo.getIndexBlob('i-do-not-exist.txt')).toBeNull() - - describe '.getStatus([path])', -> - beforeEach -> - repoDirectory = temp.mkdirSync('node-git-repo-') - wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) - repo = git.open(repoDirectory) - - newFilePath = path.join(repo.getWorkingDirectory(), 'b.txt') - fs.writeFileSync(newFilePath, '', 'utf8') - - fs.mkdirSync(path.join(repo.getWorkingDirectory(), '.git/info')) - ignoreFile = path.join(repo.getWorkingDirectory(), '.git/info/exclude') - fs.writeFileSync(ignoreFile, 'c.txt', 'utf8') - ignoredFilePath = path.join(repo.getWorkingDirectory(), 'c.txt') - fs.writeFileSync(ignoredFilePath, '', 'utf8') - - describe 'when no path is specified', -> - it 'returns the status of all modified paths', -> - statuses = repo.getStatus() - expect(_.keys(statuses).length).toBe 2 - expect(statuses['a.txt']).toBe 1 << 9 - expect(statuses['b.txt']).toBe 1 << 7 - - describe 'when a path is specified', -> - it 'returns the status of the given path', -> - expect(repo.getStatus('a.txt')).toBe 1 << 9 - - describe '.getAheadBehindCount()', -> - beforeEach -> - repoDirectory = temp.mkdirSync('node-git-repo-') - wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/ahead-behind.git'), path.join(repoDirectory, '.git')) - repo = git.open(repoDirectory) - - it 'returns the number of commits ahead of and behind the upstream branch', -> - counts = repo.getAheadBehindCount() - expect(counts).toEqual {ahead: 3, behind: 2} - - counts = repo.getAheadBehindCount('refs/heads/master') - expect(counts).toEqual {ahead: 3, behind: 2} - - counts = repo.getAheadBehindCount('master') - expect(counts).toEqual {ahead: 3, behind: 2} - - counts = repo.getAheadBehindCount('refs/heads/masterblaster') - expect(counts).toEqual {ahead: 0, behind: 0} - - counts = repo.getAheadBehindCount('') - expect(counts).toEqual {ahead: 0, behind: 0} - - describe '.getLineDiffs(path, text, options)', -> - it 'returns all hunks that differ', -> - repo = git.open(path.join(__dirname, 'fixtures/master.git')) - - diffs = repo.getLineDiffs('a.txt', 'first line is different') - expect(diffs.length).toBe 1 - expect(diffs[0].oldStart).toBe 1 - expect(diffs[0].oldLines).toBe 1 - expect(diffs[0].newStart).toBe 1 - expect(diffs[0].newLines).toBe 1 - - diffs = repo.getLineDiffs('a.txt', 'first line\nsecond line') - expect(diffs.length).toBe 1 - expect(diffs[0].oldStart).toBe 1 - expect(diffs[0].oldLines).toBe 0 - expect(diffs[0].newStart).toBe 2 - expect(diffs[0].newLines).toBe 1 - - diffs = repo.getLineDiffs('a.txt', '') - expect(diffs.length).toBe 1 - expect(diffs[0].oldStart).toBe 1 - expect(diffs[0].oldLines).toBe 1 - expect(diffs[0].newStart).toBe 0 - expect(diffs[0].newLines).toBe 0 - - it "returns null for paths that don't exist", -> - repo = git.open(path.join(__dirname, 'fixtures/master.git')) - - diffs = repo.getLineDiffs('i-dont-exists.txt', 'content') - expect(diffs).toBeNull() - - describe "ignoreEolWhitespace option", -> - it "ignores eol of line whitespace changes", -> - repo = git.open(path.join(__dirname, 'fixtures/whitespace.git')) - - diffs = repo.getLineDiffs('file.txt', 'first\r\nsecond\r\nthird\r\n', ignoreEolWhitespace: false) - expect(diffs.length).toBe 1 - - diffs = repo.getLineDiffs('file.txt', 'first\r\nsecond\r\nthird\r\n', ignoreEolWhitespace: true) - expect(diffs.length).toBe 0 - - describe "useIndex options", -> - it "uses the index version instead of the HEAD version for diffs", -> - repoDirectory = temp.mkdirSync('node-git-repo-') - wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) - repo = git.open(repoDirectory) - - diffs = repo.getLineDiffs('a.txt', 'first line is different', useIndex: true) - expect(diffs.length).toBe 1 - - filePath = path.join(repo.getWorkingDirectory(), 'a.txt') - fs.writeFileSync(filePath, 'first line is different', 'utf8') - - gitCommandHandler = jasmine.createSpy('gitCommandHandler') - exec "cd #{repoDirectory} && git add a.txt", gitCommandHandler - - waitsFor -> - gitCommandHandler.callCount is 1 - - runs -> - diffs = repo.getLineDiffs('a.txt', 'first line is different', useIndex: true) - expect(diffs.length).toBe 0 - - diffs = repo.getLineDiffs('a.txt', 'first line is different', useIndex: false) - expect(diffs.length).toBe 1 - - describe '.relativize(path)', -> - it 'relativizes the given path to the working directory of the repository', -> - repo = git.open(__dirname) - workingDirectory = repo.getWorkingDirectory() - - expect(repo.relativize(path.join(workingDirectory, 'a.txt'))).toBe 'a.txt' - expect(repo.relativize(path.join(workingDirectory, 'a/b/c.txt'))).toBe 'a/b/c.txt' - expect(repo.relativize('a.txt')).toBe 'a.txt' - expect(repo.relativize('/not/in/working/dir')).toBe '/not/in/working/dir' - expect(repo.relativize(null)).toBe null - expect(repo.relativize()).toBeUndefined() - expect(repo.relativize('')).toBe '' - - describe 'when the opened path is a symlink', -> - it 'relativizes against both the linked path and the real path', -> - # Creating symbol link on Windows requires administrator permission so - # we just skip this test. - return if process.platform is 'win32' - - repoDirectory = fs.realpathSync(temp.mkdirSync('node-git-repo-')) - linkDirectory = path.join(fs.realpathSync(temp.mkdirSync('node-git-repo-')), 'link') - wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) - fs.symlinkSync(repoDirectory, linkDirectory) - - repo = git.open(linkDirectory) - expect(repo.relativize(path.join(repoDirectory, 'test1'))).toBe 'test1' - expect(repo.relativize(path.join(linkDirectory, 'test2'))).toBe 'test2' - expect(repo.relativize(path.join(linkDirectory, 'test2/test3'))).toBe 'test2/test3' - expect(repo.relativize('test2/test3')).toBe 'test2/test3' - - it "handles case insensitive filesystems", -> - repoDirectory = temp.mkdirSync('lower-case-repo-') - wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) - repo = git.open(repoDirectory) - repo.caseInsensitiveFs = true - workingDirectory = repo.getWorkingDirectory() - - expect(repo.relativize(path.join(workingDirectory.toUpperCase(), 'a.txt'))).toBe 'a.txt' - expect(repo.relativize(path.join(workingDirectory.toUpperCase(), 'a/b/c.txt'))).toBe 'a/b/c.txt' - - linkDirectory = path.join(fs.realpathSync(temp.mkdirSync('lower-case-symlink')), 'link') - wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) - fs.symlinkSync(repoDirectory, linkDirectory) - - repo = git.open(linkDirectory) - repo.caseInsensitiveFs = true - expect(repo.relativize(path.join(linkDirectory.toUpperCase(), 'test2'))).toBe 'test2' - expect(repo.relativize(path.join(linkDirectory.toUpperCase(), 'test2/test3'))).toBe 'test2/test3' - - describe ".submoduleForPath(path)", -> - beforeEach -> - repoDirectory = temp.mkdirSync('node-git-repo-') - submoduleDirectory = temp.mkdirSync('node-git-repo-') - wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures', 'master.git'), path.join(repoDirectory, '.git')) - wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures', 'master.git'), path.join(submoduleDirectory, '.git')) - - gitCommandHandler = jasmine.createSpy('gitCommandHandler') - exec "cd #{repoDirectory} && git submodule add #{submoduleDirectory} sub", gitCommandHandler - - waitsFor -> - gitCommandHandler.callCount is 1 - - runs -> - repo = git.open(repoDirectory) - - it "returns the repository for the path", -> - expect(repo.submoduleForPath()).toBe null - expect(repo.submoduleForPath(null)).toBe null - expect(repo.submoduleForPath('')).toBe null - expect(repo.submoduleForPath('sub1')).toBe null - - submoduleRepoPath = path.join(repo.getPath(), 'modules', 'sub/') - submoduleRepoPath = submoduleRepoPath.replace(/\\/g, '/') if process.platform is 'win32' - - expect(repo.submoduleForPath('sub').getPath()).toBe submoduleRepoPath - expect(repo.submoduleForPath('sub/').getPath()).toBe submoduleRepoPath - expect(repo.submoduleForPath('sub/a').getPath()).toBe submoduleRepoPath - expect(repo.submoduleForPath('sub/a/b/c/d').getPath()).toBe submoduleRepoPath diff --git a/spec/git-spec.js b/spec/git-spec.js new file mode 100644 index 00000000..ac51d274 --- /dev/null +++ b/spec/git-spec.js @@ -0,0 +1,1149 @@ +const git = require('../src/git') +const path = require('path') +const fs = require('fs-plus') +const {exec} = require('child_process') +const wrench = require('wrench') +const temp = require('temp') +const _ = require('underscore') +const {it, fit, beforeEach} = require('./async-spec-helper-functions') + +describe('git', () => { + let repo + + afterEach(() => { + if (repo) repo.release() + }) + + describe('.open(path)', () => { + describe('when the path is a repository', () => { + it('returns a repository', () => { + expect(git.open(__dirname)).not.toBeNull() + }) + }) + + describe("when the path isn't a repository", () => { + it('returns null', () => { + expect(git.open('/tmp/path/does/not/exist')).toBeNull() + }) + }) + + describe('when limiting upwards traversal', () => { + it('returns null', () => { + const repositorySubdirectoryPath = path.join(path.dirname(__dirname), 'spec', 'fixtures') + expect(git.open(repositorySubdirectoryPath, false)).toBeNull() + }) + }) + }) + + describe('.getPath()', () => { + it('returns the path to the .git directory', () => { + const repositoryPath = git.open(__dirname).getPath() + let currentGitPath = path.join(path.dirname(__dirname), '.git/') + if (process.platform === 'win32') { currentGitPath = currentGitPath.replace(/\\/g, '/') } + expect(repositoryPath).toBe(currentGitPath) + }) + }) + + describe('.getWorkingDirectory()', () => { + it('returns the path to the working directory', () => { + const workingDirectory = git.open(__dirname).getWorkingDirectory() + let cwd = path.dirname(__dirname) + if (process.platform === 'win32') { cwd = cwd.replace(/\\/g, '/') } + expect(workingDirectory).toBe(cwd) + }) + }) + + describe('.getHead()', () => { + describe('when a branch is checked out', () => { + it("returns the branch's full path", () => { + repo = git.open(path.join(__dirname, 'fixtures/master.git')) + expect(repo.getHead()).toBe('refs/heads/master') + }) + + it("resolves with the branch's full path", async () => { + repo = git.open(path.join(__dirname, 'fixtures/master.git')) + expect(await repo.getHeadAsync()).toBe('refs/heads/master') + }) + }) + + describe('when the HEAD is detached', () => { + it('return the SHA-1 that is checked out', () => { + repo = git.open(path.join(__dirname, 'fixtures/detached.git')) + expect(repo.getHead()).toBe('50719ab369dcbbc2fb3b7a0167c52accbd0eb40e') + }) + + it('resolves with the SHA-1 that is checked out', async () => { + repo = git.open(path.join(__dirname, 'fixtures/detached.git')) + expect(await repo.getHeadAsync()).toBe('50719ab369dcbbc2fb3b7a0167c52accbd0eb40e') + }) + }) + + describe('when repo has no commits', () => { + it('returns null', () => { + repo = git.open(path.join(__dirname, 'fixtures/no-commits.git')) + expect(repo.getHead()).toBe(null) + }) + + it('resolves with null', async () => { + repo = git.open(path.join(__dirname, 'fixtures/no-commits.git')) + expect(await repo.getHeadAsync()).toBe(null) + }) + }) + }) + + describe('.getShortHead()', () => { + describe('when a branch is checked out', () => { + it("returns the branch's name", () => { + repo = git.open(path.join(__dirname, 'fixtures/master.git')) + expect(repo.getShortHead()).toBe('master') + }) + }) + + describe('when the HEAD is detached', () => { + it('return the abbreviated SHA-1 that is checked out', () => { + repo = git.open(path.join(__dirname, 'fixtures/detached.git')) + expect(repo.getShortHead()).toBe('50719ab') + }) + }) + }) + + describe('.isIgnored(path)', () => { + let ignoreRepoRoot, ignoreRepoDir + + beforeEach(() => { + // this setup should be done in beforeAll, but jasmine 1.x doesn't have it. + ignoreRepoRoot = temp.mkdirSync('ignore-dir') + ignoreRepoDir = path.join(ignoreRepoRoot, 'ignored') + wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/ignored-workspace/'), ignoreRepoDir) + wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/ignored.git'), path.join(ignoreRepoDir, '.git')) + }) + + afterEach(() => wrench.rmdirSyncRecursive(ignoreRepoRoot)) + + describe('when the path is undefined', () => { + it('return false', () => { + repo = git.open(ignoreRepoDir) + expect(repo.isIgnored()).toBe(false) + }) + }) + + describe('when the path is ignored', () => { + it('returns true', () => { + repo = git.open(ignoreRepoDir) + expect(repo.isIgnored('a.txt')).toBe(true) + expect(repo.isIgnored('subdir/subdir')).toBe(true) + expect(repo.isIgnored('a.foo')).toBe(true) + expect(repo.isIgnored('subdir/a.foo')).toBe(true) + }) + }) + + describe('when the path is not ignored', () => { + it('return false', () => { + repo = git.open(ignoreRepoDir) + expect(repo.isIgnored('b.txt')).toBe(false) + expect(repo.isIgnored('subdir')).toBe(false) + expect(repo.isIgnored('subdir/yak.txt')).toBe(false) + }) + }) + }) + + describe('.isSubmodule(path)', () => { + describe('when the path is undefined', () => { + it('return false', () => { + repo = git.open(path.join(__dirname, 'fixtures/submodule.git')) + expect(repo.isSubmodule()).toBe(false) + }) + }) + + describe('when the path is a submodule', () => { + it('returns true', () => { + repo = git.open(path.join(__dirname, 'fixtures/submodule.git')) + expect(repo.isSubmodule('a')).toBe(true) + }) + }) + + describe('when the path is not a submodule', () => { + it('return false', () => { + repo = git.open(path.join(__dirname, 'fixtures/submodule.git')) + expect(repo.isSubmodule('b')).toBe(false) + }) + }) + }) + + describe('.getConfigValue(key)', () => { + it('returns the value for the key', () => { + repo = git.open(path.join(__dirname, 'fixtures/master.git')) + expect(repo.getConfigValue('core.repositoryformatversion')).toBe('0') + expect(repo.getConfigValue('core.ignorecase')).toBe('true') + expect(repo.getConfigValue('not.section')).toBe(null) + }) + }) + + describe('.setConfigValue(key, value)', () => { + beforeEach(() => { + const repoDirectory = temp.mkdirSync('node-git-repo-') + wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) + repo = git.open(repoDirectory) + }) + + it('sets the key to the value in the config', () => { + expect(repo.setConfigValue()).toBe(false) + expect(repo.setConfigValue('1')).toBe(false) + expect(repo.setConfigValue('a.b', 'test')).toBe(true) + expect(repo.getConfigValue('a.b')).toBe('test') + expect(repo.setConfigValue('a.b.c', 'foo')).toBe(true) + expect(repo.getConfigValue('a.b.c')).toBe('foo') + }) + }) + + describe('.isPathModified(path)', () => { + beforeEach(() => { + const repoDirectory = temp.mkdirSync('node-git-repo-') + wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) + repo = git.open(repoDirectory) + }) + + describe('when a path is deleted', () => { + it('returns true', () => expect(repo.isPathModified('a.txt')).toBe(true)) + }) + + describe('when a path is modified', () => { + it('returns true', () => { + fs.writeFileSync(path.join(repo.getWorkingDirectory(), 'a.txt'), 'changing a.txt', 'utf8') + expect(repo.isPathModified('a.txt')).toBe(true) + }) + }) + + describe('when a path is new', () => { + it('returns false', () => { + fs.writeFileSync(path.join(repo.getWorkingDirectory(), 'new.txt'), 'new', 'utf8') + expect(repo.isPathModified('new.txt')).toBe(false) + }) + }) + }) + + describe('.isPathDeleted(path)', () => { + beforeEach(() => { + const repoDirectory = temp.mkdirSync('node-git-repo-') + wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) + repo = git.open(repoDirectory) + }) + + describe('when a path is deleted', () => { + it('returns true', () => expect(repo.isPathDeleted('a.txt')).toBe(true)) + }) + + describe('when a path is modified', () => { + it('returns false', () => { + fs.writeFileSync(path.join(repo.getWorkingDirectory(), 'a.txt'), 'changing a.txt', 'utf8') + expect(repo.isPathDeleted('a.txt')).toBe(false) + }) + }) + + describe('when a path is new', () => { + it('returns false', () => { + fs.writeFileSync(path.join(repo.getWorkingDirectory(), 'new.txt'), 'new', 'utf8') + expect(repo.isPathDeleted('new.txt')).toBe(false) + }) + }) + }) + + describe('.isPathNew(path)', () => { + beforeEach(() => { + const repoDirectory = temp.mkdirSync('node-git-repo-') + wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) + repo = git.open(repoDirectory) + }) + + describe('when a path is deleted', () => { + it('returns false', () => expect(repo.isPathNew('a.txt')).toBe(false)) + }) + + describe('when a path is modified', () => { + it('returns false', () => { + fs.writeFileSync(path.join(repo.getWorkingDirectory(), 'a.txt'), 'changing a.txt', 'utf8') + expect(repo.isPathNew('a.txt')).toBe(false) + }) + }) + + describe('when a path is new', () => { + it('returns true', () => { + fs.writeFileSync(path.join(repo.getWorkingDirectory(), 'new.txt'), 'new', 'utf8') + expect(repo.isPathNew('new.txt')).toBe(true) + }) + }) + }) + + describe('isPathStaged(path)', () => { + let repoDirectory + + beforeEach(() => { + repoDirectory = temp.mkdirSync('node-git-repo-') + wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) + repo = git.open(repoDirectory) + }) + + describe('when a path is new and staged', () => { + it('returns true ', () => { + fs.writeFileSync(path.join(repo.getWorkingDirectory(), 'new.txt'), 'new', 'utf8') + expect(repo.isPathStaged('new.txt')).toBe(false) + repo.add('new.txt') + expect(repo.isPathStaged('new.txt')).toBe(true) + }) + }) + + describe('when a path is modified and staged', () => { + it('returns true ', () => { + fs.writeFileSync(path.join(repo.getWorkingDirectory(), 'a.txt'), 'changing a.txt', 'utf8') + expect(repo.isPathStaged('a.txt')).toBe(false) + repo.add('a.txt') + expect(repo.isPathStaged('a.txt')).toBe(true) + }) + }) + + describe('when a path is deleted and staged', () => { + it('returns true ', () => { + expect(repo.isPathStaged('a.txt')).toBe(false) + const gitCommandHandler = jasmine.createSpy('gitCommandHandler') + execCommands([`cd ${repoDirectory}`, 'git rm -f a.txt'], gitCommandHandler) + + waitsFor(() => gitCommandHandler.callCount === 1) + + runs(() => expect(repo.isPathStaged('a.txt')).toBe(true)) + }) + }) + }) + + describe('.isStatusIgnored(status)', () => { + it('returns true when the status is ignored, false otherwise', () => { + const repoDirectory = temp.mkdirSync('node-git-repo-') + wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/ignored.git'), path.join(repoDirectory, '.git')) + repo = git.open(repoDirectory) + const ignoreFile = path.join(repo.getWorkingDirectory(), '.git/info/exclude') + fs.writeFileSync(ignoreFile, 'c.txt') + fs.writeFileSync(path.join(repoDirectory, 'c.txt'), '') + + expect(repo.isStatusIgnored(repo.getStatus('c.txt'))).toBe(true) + expect(repo.isStatusIgnored(repo.getStatus('b.txt'))).toBe(false) + expect(repo.isStatusIgnored()).toBe(false) + }) + }) + + describe('.getUpstreamBranch()', () => { + describe('when no upstream branch exists', () => { + it('returns null', () => { + repo = git.open(path.join(__dirname, 'fixtures/master.git')) + expect(repo.getUpstreamBranch()).toBe(null) + }) + }) + + describe('when an upstream branch exists', () => { + it('returns the full path to the branch', () => { + repo = git.open(path.join(__dirname, 'fixtures/upstream.git')) + expect(repo.getUpstreamBranch()).toBe('refs/remotes/origin/master') + }) + }) + }) + + describe('.checkoutReference(reference, [create])', () => { + let repoDirectory = null + + beforeEach(() => { + repoDirectory = temp.mkdirSync('node-git-repo-') + wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/references.git'), path.join(repoDirectory, '.git')) + repo = git.open(repoDirectory) + expect(repo.getHead()).toBe('refs/heads/master') + }) + + describe('when a local reference exists', () => { + it('checks a branch out if passed a short reference', () => { + expect(repo.checkoutReference('getHeadOriginal')).toBe(true) + expect(repo.getHead()).toBe('refs/heads/getHeadOriginal') + }) + + it('checks a branch out if passed a long reference', () => { + expect(repo.checkoutReference('refs/heads/getHeadOriginal')).toBe(true) + expect(repo.getHead()).toBe('refs/heads/getHeadOriginal') + }) + + // in this test, we need to fake a commit and try to switch to a new branch + it('does not check a branch out if the dirty tree interferes', () => { + fs.writeFileSync(path.join(repo.getWorkingDirectory(), 'README.md'), 'great words', 'utf8') + const gitCommandHandler = jasmine.createSpy('gitCommandHandler') + execCommands([`cd ${repoDirectory}`, 'git add .', "git commit -m 'comitting'"], gitCommandHandler) + + waitsFor(() => gitCommandHandler.callCount === 1) + + runs(() => { + expect(repo.checkoutReference('refs/heads/getHeadOriginal')).toBe(true) + fs.writeFileSync(path.join(repo.getWorkingDirectory(), 'README.md'), 'more words', 'utf8') + expect(repo.checkoutReference('refs/heads/master')).toBe(false) + expect(repo.getHead()).toBe('refs/heads/getHeadOriginal') + }) + }) + + it('does check a branch out if the dirty tree does not interfere', () => { + fs.writeFileSync(path.join(repo.getWorkingDirectory(), 'new_file.md'), 'a new file', 'utf8') + expect(repo.checkoutReference('refs/heads/getHeadOriginal')).toBe(true) + }) + }) + + describe('when a local reference doesn\'t exist', () => { + it('does nothing if branch creation was not specified', () => expect(repo.checkoutReference('refs/heads/whoop-whoop')).toBe(false)) + + it('creates the new branch (if asked to)', () => { + expect(repo.checkoutReference('refs/heads/whoop-whoop', true)).toBe(true) + expect(repo.getHead()).toBe('refs/heads/whoop-whoop') + }) + + it('does nothing if the new branch is malformed (even if asked to)', () => { + expect(repo.checkoutReference('refs/heads/inv@{id', true)).toBe(false) + expect(repo.getHead()).toBe('refs/heads/master') + }) + + describe('when a short reference is passed', () => { + it('does nothing if branch creation was not specified', () => expect(repo.checkoutReference('bananas')).toBe(false)) + + it('creates the new branch (if asked to)', () => { + expect(repo.checkoutReference('bananas', true)).toBe(true) + expect(repo.getHead()).toBe('refs/heads/bananas') + }) + }) + }) + }) + + describe('.checkoutHead(path)', () => { + beforeEach(() => { + const repoDirectory = temp.mkdirSync('node-git-repo-') + wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) + repo = git.open(repoDirectory) + }) + + describe('when the path exists', () => { + it('replaces the file contents with the HEAD revision and returns true', () => { + const filePath = path.join(repo.getWorkingDirectory(), 'a.txt') + fs.writeFileSync(filePath, 'changing a.txt', 'utf8') + expect(repo.checkoutHead('a.txt')).toBe(true) + const lineEnding = process.platform === 'win32' ? '\r\n' : '\n' + expect(fs.readFileSync(filePath, 'utf8')).toBe(`first line${lineEnding}`) + }) + }) + + describe('when the path is undefined', () => { + it('returns false', () => expect(repo.checkoutHead()).toBe(false)) + }) + }) + + describe('.getReferences()', () => { + it('returns a list of all the references', () => { + const referencesObj = { + heads: [ 'refs/heads/diff-lines', 'refs/heads/getHeadOriginal', 'refs/heads/master' ], + remotes: [ 'refs/remotes/origin/getHeadOriginal', 'refs/remotes/origin/HEAD', 'refs/remotes/origin/master', 'refs/remotes/upstream/HEAD', 'refs/remotes/upstream/master' ], + tags: [ 'refs/tags/v1.0', 'refs/tags/v2.0' ] + } + + repo = git.open(path.join(__dirname, 'fixtures/references.git')) + expect(repo.getReferences()).toEqual(referencesObj) + }) + }) + + describe('.getReferenceTarget(branch)', () => { + it('returns the SHA-1 for a reference', () => { + repo = git.open(path.join(__dirname, 'fixtures/master.git')) + expect(repo.getReferenceTarget('HEAD2')).toBe(null) + expect(repo.getReferenceTarget('HEAD')).toBe('b2c96bdffe1a8f239c2d450863e4a6caa6dcb655') + expect(repo.getReferenceTarget('refs/heads/master')).toBe('b2c96bdffe1a8f239c2d450863e4a6caa6dcb655') + }) + }) + + describe('.getDiffStats(path)', () => { + beforeEach(() => { + const repoDirectory = temp.mkdirSync('node-git-repo-') + wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) + repo = git.open(repoDirectory) + }) + + describe('when the path is deleted', () => { + it('returns of number of lines deleted', () => { + expect(repo.getDiffStats('a.txt')).toEqual({added: 0, deleted: 1}) + }) + }) + + describe('when the path is modified', () => { + it('returns the number of lines added and deleted', () => { + const filePath = path.join(repo.getWorkingDirectory(), 'a.txt') + fs.writeFileSync(filePath, 'changing\na.txt', 'utf8') + expect(repo.getDiffStats('a.txt')).toEqual({added: 2, deleted: 1}) + }) + }) + + describe('when the path is new', () => { + it('returns that no lines were added or deleted', () => { + const filePath = path.join(repo.getWorkingDirectory(), 'b.txt') + fs.writeFileSync(filePath, 'changing\nb.txt\nwith lines', 'utf8') + expect(repo.getDiffStats('b.txt')).toEqual({added: 0, deleted: 0}) + }) + }) + + describe('when the repository has no HEAD', () => { + it('returns that no lines were added and deleted', () => { + const repoDirectory = temp.mkdirSync('node-git-repo-') + wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) + repo = git.open(repoDirectory) + fs.unlinkSync(path.join(repoDirectory, '.git/HEAD')) + expect(repo.getDiffStats('b.txt')).toEqual({added: 0, deleted: 0}) + }) + }) + }) + + describe('.getHeadBlob(path)', () => { + beforeEach(() => { + const repoDirectory = temp.mkdirSync('node-git-repo-') + wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) + repo = git.open(repoDirectory) + }) + + describe('when the path is modified', () => { + it('returns the HEAD blob contents', () => { + const filePath = path.join(repo.getWorkingDirectory(), 'a.txt') + fs.writeFileSync(filePath, 'changing\na.txt', 'utf8') + expect(repo.getHeadBlob('a.txt')).toBe('first line\n') + }) + }) + + describe('when the path is not modified', () => { + it('returns the HEAD blob contents', () => expect(repo.getHeadBlob('a.txt')).toBe('first line\n')) + }) + + describe('when the path does not exist', () => { + it('returns null', () => expect(repo.getHeadBlob('i-do-not-exist.txt')).toBeNull()) + }) + }) + + describe('.getIndexBlob(path)', () => { + let repoDirectory + + beforeEach(() => { + repoDirectory = temp.mkdirSync('node-git-repo-') + wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) + repo = git.open(repoDirectory) + }) + + describe('when the path is staged', () => { + it('returns the index blob contents', () => { + expect(repo.getIndexBlob('a.txt')).toBe('first line\n') + + const filePath = path.join(repo.getWorkingDirectory(), 'a.txt') + fs.writeFileSync(filePath, 'changing\na.txt', 'utf8') + expect(repo.getIndexBlob('a.txt')).toBe('first line\n') + + const gitCommandHandler = jasmine.createSpy('gitCommandHandler') + execCommands([`cd ${repoDirectory}`, 'git add a.txt'], gitCommandHandler) + + waitsFor(() => gitCommandHandler.callCount === 1) + + runs(() => expect(repo.getIndexBlob('a.txt')).toBe('changing\na.txt')) + }) + }) + + describe('when the path is not staged', () => { + it('returns the index blob contents', () => { + expect(repo.getIndexBlob('a.txt')).toBe('first line\n') + + const filePath = path.join(repo.getWorkingDirectory(), 'a.txt') + fs.writeFileSync(filePath, 'changing\na.txt', 'utf8') + expect(repo.getIndexBlob('a.txt')).toBe('first line\n') + }) + }) + + describe('when the path does not exist', () => { + it('returns null', () => expect(repo.getIndexBlob('i-do-not-exist.txt')).toBeNull()) + }) + }) + + describe('.getStatus([path])', () => { + beforeEach(() => { + const repoDirectory = temp.mkdirSync('node-git-repo-') + wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) + repo = git.open(repoDirectory) + + const newFilePath = path.join(repo.getWorkingDirectory(), 'b.txt') + fs.writeFileSync(newFilePath, '', 'utf8') + + const ignoreFile = path.join(repo.getWorkingDirectory(), '.git/info/exclude') + fs.writeFileSync(ignoreFile, 'c.txt', 'utf8') + const ignoredFilePath = path.join(repo.getWorkingDirectory(), 'c.txt') + fs.writeFileSync(ignoredFilePath, '', 'utf8') + }) + + describe('when no path is specified', () => { + it('returns the status of all modified paths', () => { + const statuses = repo.getStatus() + expect(statuses).toEqual({ + 'a.txt': 1 << 9, + 'b.txt': 1 << 7 + }) + }) + + it('resolves with the status of all modified paths', async () => { + const statuses = await repo.getStatusAsync() + expect(statuses).toEqual({ + 'a.txt': 1 << 9, + 'b.txt': 1 << 7 + }) + }) + }) + + describe('when a path is specified', () => { + it('returns the status of the given path', () => { + expect(repo.getStatus('a.txt')).toBe(1 << 9) + }) + }) + }) + + describe('.getStatusForPaths([paths])', () => { + let repoDirectory, filePath + + beforeEach(() => { + repoDirectory = temp.mkdirSync('node-git-repo-') + wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/subdir.git'), path.join(repoDirectory, '.git')) + repo = git.open(repoDirectory) + + const dir = path.join(repo.getWorkingDirectory(), 'dir') + filePath = path.join(dir, 'a.txt') + fs.writeFileSync(filePath, 'hey there', 'utf8') + }) + + describe('when a path is specified', () => { + it('returns the status of only that path', () => { + const statuses = repo.getStatusForPaths(['dir/**']) + expect(_.keys(statuses).length).toBe(1) + + const status = statuses['dir/a.txt'] + expect(repo.isStatusModified(status)).toBe(true) + expect(repo.isStatusNew(status)).toBe(false) + }) + + it('resolves with the status of only that path', async () => { + const statuses = await repo.getStatusForPathsAsync(['dir/**']) + expect(_.keys(statuses).length).toBe(1) + + const status = statuses['dir/a.txt'] + expect(repo.isStatusModified(status)).toBe(true) + expect(repo.isStatusNew(status)).toBe(false) + }) + + it('returns the status of only that path2', () => { + fs.writeFileSync(filePath, '', 'utf8') + + const statuses = repo.getStatusForPaths(['dir/**']) + expect(_.keys(statuses).length).toBe(0) + }) + }) + + describe('when no path is specified', () => { + it('returns an empty object', () => { + const statuses = repo.getStatusForPaths() + expect(_.keys(statuses).length).toBe(0) + }) + }) + + describe('when an empty array is specified', () => { + it('returns an empty object', () => { + const statuses = repo.getStatusForPaths([]) + expect(_.keys(statuses).length).toBe(0) + }) + }) + + describe('when an empty string is specified', () => { + it('returns an empty object', () => { + const statuses = repo.getStatusForPaths(['']) + expect(_.keys(statuses).length).toBe(1) + }) + }) + }) + + describe('.getAheadBehindCount()', () => { + beforeEach(() => { + const repoDirectory = temp.mkdirSync('node-git-repo-') + wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/ahead-behind.git'), path.join(repoDirectory, '.git')) + repo = git.open(repoDirectory) + }) + + it('returns the number of commits ahead of and behind the upstream branch', () => { + let counts = repo.getAheadBehindCount() + expect(counts).toEqual({ahead: 3, behind: 2}) + + counts = repo.getAheadBehindCount('refs/heads/master') + expect(counts).toEqual({ahead: 3, behind: 2}) + + counts = repo.getAheadBehindCount('master') + expect(counts).toEqual({ahead: 3, behind: 2}) + + counts = repo.getAheadBehindCount('refs/heads/masterblaster') + expect(counts).toEqual({ahead: 0, behind: 0}) + + counts = repo.getAheadBehindCount('') + expect(counts).toEqual({ahead: 0, behind: 0}) + }) + + it('resolves with the number of commits ahead of and behind the upstream branch', async () => { + let counts = await repo.getAheadBehindCountAsync() + expect(counts).toEqual({ahead: 3, behind: 2}) + + counts = await repo.getAheadBehindCountAsync('refs/heads/master') + expect(counts).toEqual({ahead: 3, behind: 2}) + + counts = await repo.getAheadBehindCountAsync('master') + expect(counts).toEqual({ahead: 3, behind: 2}) + + counts = await repo.getAheadBehindCountAsync('refs/heads/masterblaster') + expect(counts).toEqual({ahead: 0, behind: 0}) + + counts = await repo.getAheadBehindCountAsync('') + expect(counts).toEqual({ahead: 0, behind: 0}) + }) + }) + + describe('.getLineDiffs(path, text, options)', () => { + it('returns all hunks that differ', () => { + repo = git.open(path.join(__dirname, 'fixtures/master.git')) + + let diffs = repo.getLineDiffs('a.txt', 'first line is different') + expect(diffs.length).toBe(1) + expect(diffs[0].oldStart).toBe(1) + expect(diffs[0].oldLines).toBe(1) + expect(diffs[0].newStart).toBe(1) + expect(diffs[0].newLines).toBe(1) + + diffs = repo.getLineDiffs('a.txt', 'first line\nsecond line') + expect(diffs.length).toBe(1) + expect(diffs[0].oldStart).toBe(1) + expect(diffs[0].oldLines).toBe(0) + expect(diffs[0].newStart).toBe(2) + expect(diffs[0].newLines).toBe(1) + + diffs = repo.getLineDiffs('a.txt', '') + expect(diffs.length).toBe(1) + expect(diffs[0].oldStart).toBe(1) + expect(diffs[0].oldLines).toBe(1) + expect(diffs[0].newStart).toBe(0) + expect(diffs[0].newLines).toBe(0) + }) + + it("returns null for paths that don't exist", () => { + repo = git.open(path.join(__dirname, 'fixtures/master.git')) + + const diffs = repo.getLineDiffs('i-dont-exists.txt', 'content') + expect(diffs).toBeNull() + }) + + describe('ignoreSpaceAtEOL option', () => { + it('ignores eol of line whitespace changes', () => { + repo = git.open(path.join(__dirname, 'fixtures/whitespace.git')) + + let diffs = repo.getLineDiffs('file.txt', 'first\r\n second\r\n\tthird\r\n', {ignoreEolWhitespace: false}) + expect(diffs.length).toBe(1) + + diffs = repo.getLineDiffs('file.txt', 'first\r\n second\r\n\tthird\r\n', {ignoreEolWhitespace: true}) + expect(diffs.length).toBe(0) + + diffs = repo.getLineDiffs('file.txt', 'first\r\n second\r\n\tthird\r\n', {ignoreSpaceAtEOL: false}) + expect(diffs.length).toBe(1) + + diffs = repo.getLineDiffs('file.txt', 'first\r\n second\r\n\tthird\r\n', {ignoreSpaceAtEOL: true}) + expect(diffs.length).toBe(0) + + diffs = repo.getLineDiffs('file.txt', 'first\r\n \tsecond\r\n \tthird\r\n', {ignoreSpaceAtEOL: false}) + expect(diffs.length).toBe(1) + + diffs = repo.getLineDiffs('file.txt', 'first\r\n \tsecond\r\n \tthird\r\n', {ignoreSpaceAtEOL: true}) + expect(diffs.length).toBe(1) + + diffs = repo.getLineDiffs('file.txt', ' \tfirst\r\n \tsecond\r\n \tthird\r\n', {ignoreSpaceAtEOL: false}) + expect(diffs.length).toBe(1) + + diffs = repo.getLineDiffs('file.txt', ' \tfirst\r\n \tsecond\r\n \tthird\r\n', {ignoreSpaceAtEOL: true}) + expect(diffs.length).toBe(1) + }) + }) + + describe('ignoreSpaceChange option', () => { + it('ignores whitespace changes', () => { + repo = git.open(path.join(__dirname, 'fixtures/whitespace.git')) + + let diffs = repo.getLineDiffs('file.txt', 'first\r\n second\r\n\tthird\r\n', {ignoreSpaceChange: false}) + expect(diffs.length).toBe(1) + + diffs = repo.getLineDiffs('file.txt', 'first\r\n second\r\n\tthird\r\n', {ignoreSpaceChange: true}) + expect(diffs.length).toBe(0) + + diffs = repo.getLineDiffs('file.txt', 'first\r\n \tsecond\r\n \tthird\r\n', {ignoreSpaceChange: false}) + expect(diffs.length).toBe(1) + + diffs = repo.getLineDiffs('file.txt', 'first\r\n \tsecond\r\n \tthird\r\n', {ignoreSpaceChange: true}) + expect(diffs.length).toBe(0) + + diffs = repo.getLineDiffs('file.txt', ' \tfirst\r\n \tsecond\r\n \tthird\r\n', {ignoreSpaceChange: false}) + expect(diffs.length).toBe(1) + + diffs = repo.getLineDiffs('file.txt', ' \tfirst\r\n \tsecond\r\n \tthird\r\n', {ignoreSpaceChange: true}) + expect(diffs.length).toBe(1) + }) + }) + + describe('ignoreAllSpace option', () => { + it('ignores whitespace', () => { + repo = git.open(path.join(__dirname, 'fixtures/whitespace.git')) + + let diffs = repo.getLineDiffs('file.txt', 'first\r\n second\r\n\tthird\r\n', {ignoreAllSpace: false}) + expect(diffs.length).toBe(1) + + diffs = repo.getLineDiffs('file.txt', 'first\r\n second\r\n\tthird\r\n', {ignoreAllSpace: true}) + expect(diffs.length).toBe(0) + + diffs = repo.getLineDiffs('file.txt', 'first\r\n \tsecond\r\n \tthird\r\n', {ignoreAllSpace: false}) + expect(diffs.length).toBe(1) + + diffs = repo.getLineDiffs('file.txt', 'first\r\n \tsecond\r\n \tthird\r\n', {ignoreAllSpace: true}) + expect(diffs.length).toBe(0) + + diffs = repo.getLineDiffs('file.txt', ' \tfirst\r\n \tsecond\r\n \tthird\r\n', {ignoreAllSpace: false}) + expect(diffs.length).toBe(1) + + diffs = repo.getLineDiffs('file.txt', ' \tfirst\r\n \tsecond\r\n \tthird\r\n', {ignoreAllSpace: true}) + expect(diffs.length).toBe(0) + }) + }) + + describe('useIndex options', () => { + it('uses the index version instead of the HEAD version for diffs', () => { + const repoDirectory = temp.mkdirSync('node-git-repo-') + wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) + repo = git.open(repoDirectory) + + let diffs = repo.getLineDiffs('a.txt', 'first line is different', {useIndex: true}) + expect(diffs.length).toBe(1) + + const filePath = path.join(repo.getWorkingDirectory(), 'a.txt') + fs.writeFileSync(filePath, 'first line is different', 'utf8') + + const gitCommandHandler = jasmine.createSpy('gitCommandHandler') + execCommands([`cd ${repoDirectory}`, 'git add a.txt'], gitCommandHandler) + + waitsFor(() => gitCommandHandler.callCount === 1) + + runs(() => { + diffs = repo.getLineDiffs('a.txt', 'first line is different', {useIndex: true}) + expect(diffs.length).toBe(0) + + diffs = repo.getLineDiffs('a.txt', 'first line is different', {useIndex: false}) + expect(diffs.length).toBe(1) + }) + }) + }) + }) + + describe('.getLineDiffDetails(path, text, options)', () => { + it('returns all relevant lines in a diff', () => { + repo = git.open(path.join(__dirname, 'fixtures/master.git')) + + const isOldLine = diff => (diff.oldLineNumber >= 0) && (diff.newLineNumber === -1) + + const isNewLine = diff => (diff.oldLineNumber === -1) && (diff.newLineNumber >= 0) + + let diffs = repo.getLineDiffDetails('a.txt', 'first line is different') + expect(diffs.length).toBe(3) + expect(isOldLine(diffs[0])).toBe(true) + expect(diffs[0].line).toEqual('first line\n') + expect(isNewLine(diffs[1])).toBe(true) + expect(diffs[1].line).toEqual('first line is different') + + diffs = repo.getLineDiffDetails('a.txt', 'first line\nsecond line') + expect(diffs.length).toBe(2) + expect(isNewLine(diffs[0])).toBe(true) + expect(diffs[0].line).toEqual('second line') + + diffs = repo.getLineDiffDetails('a.txt', '') + expect(diffs.length).toBe(1) + expect(isOldLine(diffs[0])).toBe(true) + expect(diffs[0].line).toEqual('first line\n') + }) + + it("returns null for paths that don't exist", () => { + repo = git.open(path.join(__dirname, 'fixtures/master.git')) + + const diffs = repo.getLineDiffDetails('i-dont-exists.txt', 'content') + expect(diffs).toBeNull() + }) + + describe('ignoreSpaceAtEOL option', () => { + it('ignores eol of line whitespace changes', () => { + repo = git.open(path.join(__dirname, 'fixtures/whitespace.git')) + + let diffs = repo.getLineDiffDetails('file.txt', 'first\r\n second\r\n\tthird\r\n', {ignoreEolWhitespace: false}) + expect(diffs.length).toBe(6) + + diffs = repo.getLineDiffDetails('file.txt', 'first\r\n second\r\n\tthird\r\n', {ignoreEolWhitespace: true}) + expect(diffs.length).toBe(0) + + diffs = repo.getLineDiffDetails('file.txt', 'first\r\n second\r\n\tthird\r\n', {ignoreSpaceAtEOL: false}) + expect(diffs.length).toBe(6) + + diffs = repo.getLineDiffDetails('file.txt', 'first\r\n second\r\n\tthird\r\n', {ignoreSpaceAtEOL: true}) + expect(diffs.length).toBe(0) + + diffs = repo.getLineDiffDetails('file.txt', 'first\r\n \tsecond\r\n \tthird\r\n', {ignoreSpaceAtEOL: false}) + expect(diffs.length).toBe(6) + + diffs = repo.getLineDiffDetails('file.txt', 'first\r\n \tsecond\r\n \tthird\r\n', {ignoreSpaceAtEOL: true}) + expect(diffs.length).toBe(4) + + diffs = repo.getLineDiffDetails('file.txt', ' \tfirst\r\n \tsecond\r\n \tthird\r\n', {ignoreSpaceAtEOL: false}) + expect(diffs.length).toBe(6) + + diffs = repo.getLineDiffDetails('file.txt', ' \tfirst\r\n \tsecond\r\n \tthird\r\n', {ignoreSpaceAtEOL: true}) + expect(diffs.length).toBe(6) + }) + }) + + describe('ignoreSpaceChange option', () => { + it('ignores whitespace changes', () => { + repo = git.open(path.join(__dirname, 'fixtures/whitespace.git')) + + let diffs = repo.getLineDiffDetails('file.txt', 'first\r\n second\r\n\tthird\r\n', {ignoreSpaceChange: false}) + expect(diffs.length).toBe(6) + + diffs = repo.getLineDiffDetails('file.txt', 'first\r\n second\r\n\tthird\r\n', {ignoreSpaceChange: true}) + expect(diffs.length).toBe(0) + + diffs = repo.getLineDiffDetails('file.txt', 'first\r\n \tsecond\r\n \tthird\r\n', {ignoreSpaceChange: false}) + expect(diffs.length).toBe(6) + + diffs = repo.getLineDiffDetails('file.txt', 'first\r\n \tsecond\r\n \tthird\r\n', {ignoreSpaceChange: true}) + expect(diffs.length).toBe(0) + + diffs = repo.getLineDiffDetails('file.txt', ' \tfirst\r\n \tsecond\r\n \tthird\r\n', {ignoreSpaceChange: false}) + expect(diffs.length).toBe(6) + + diffs = repo.getLineDiffDetails('file.txt', ' \tfirst\r\n \tsecond\r\n \tthird\r\n', {ignoreSpaceChange: true}) + expect(diffs.length).toBe(2) + }) + }) + + describe('ignoreAllSpace option', () => { + it('ignores whitespace', () => { + repo = git.open(path.join(__dirname, 'fixtures/whitespace.git')) + + let diffs = repo.getLineDiffDetails('file.txt', 'first\r\n second\r\n\tthird\r\n', {ignoreAllSpace: false}) + expect(diffs.length).toBe(6) + + diffs = repo.getLineDiffDetails('file.txt', 'first\r\n second\r\n\tthird\r\n', {ignoreAllSpace: true}) + expect(diffs.length).toBe(0) + + diffs = repo.getLineDiffDetails('file.txt', 'first\r\n \tsecond\r\n \tthird\r\n', {ignoreAllSpace: false}) + expect(diffs.length).toBe(6) + + diffs = repo.getLineDiffDetails('file.txt', 'first\r\n \tsecond\r\n \tthird\r\n', {ignoreAllSpace: true}) + expect(diffs.length).toBe(0) + + diffs = repo.getLineDiffDetails('file.txt', ' \tfirst\r\n \tsecond\r\n \tthird\r\n', {ignoreAllSpace: false}) + expect(diffs.length).toBe(6) + + diffs = repo.getLineDiffDetails('file.txt', ' \tfirst\r\n \tsecond\r\n \tthird\r\n', {ignoreAllSpace: true}) + expect(diffs.length).toBe(0) + }) + }) + + describe('useIndex options', () => { + it('uses the index version instead of the HEAD version for diffs', () => { + const repoDirectory = temp.mkdirSync('node-git-repo-') + wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) + repo = git.open(repoDirectory) + + let diffs = repo.getLineDiffDetails('a.txt', 'first line is different', {useIndex: true}) + expect(diffs.length).toBe(3) + + const filePath = path.join(repo.getWorkingDirectory(), 'a.txt') + fs.writeFileSync(filePath, 'first line is different', 'utf8') + + const gitCommandHandler = jasmine.createSpy('gitCommandHandler') + execCommands([`cd ${repoDirectory}`, 'git add a.txt'], gitCommandHandler) + + waitsFor(() => gitCommandHandler.callCount === 1) + + runs(() => { + diffs = repo.getLineDiffDetails('a.txt', 'first line is different', {useIndex: true}) + expect(diffs.length).toBe(0) + + diffs = repo.getLineDiffDetails('a.txt', 'first line is different', {useIndex: false}) + expect(diffs.length).toBe(3) + }) + }) + }) + }) + + describe('.relativize(path)', () => { + it('relativizes the given path to the working directory of the repository', () => { + repo = git.open(__dirname) + const workingDirectory = repo.getWorkingDirectory() + + expect(repo.relativize(path.join(workingDirectory, 'a.txt'))).toBe('a.txt') + expect(repo.relativize(path.join(workingDirectory, 'a/b/c.txt'))).toBe('a/b/c.txt') + expect(repo.relativize('a.txt')).toBe('a.txt') + expect(repo.relativize('/not/in/working/dir')).toBe('/not/in/working/dir') + expect(repo.relativize(null)).toBe(null) + expect(repo.relativize()).toBeUndefined() + expect(repo.relativize('')).toBe('') + expect(repo.relativize(workingDirectory)).toBe('') + }) + + describe('when the opened path is a symlink', () => { + it('relativizes against both the linked path and the real path', () => { + // Creating symbol link on Windows requires administrator permission so + // we just skip this test. + if (process.platform === 'win32') { return } + + const repoDirectory = fs.realpathSync(temp.mkdirSync('node-git-repo-')) + const linkDirectory = path.join(fs.realpathSync(temp.mkdirSync('node-git-repo-')), 'link') + wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) + fs.symlinkSync(repoDirectory, linkDirectory) + + repo = git.open(linkDirectory) + expect(repo.relativize(path.join(repoDirectory, 'test1'))).toBe('test1') + expect(repo.relativize(path.join(linkDirectory, 'test2'))).toBe('test2') + expect(repo.relativize(path.join(linkDirectory, 'test2/test3'))).toBe('test2/test3') + expect(repo.relativize('test2/test3')).toBe('test2/test3') + }) + }) + + it('handles case insensitive filesystems', () => { + const repoDirectory = temp.mkdirSync('lower-case-repo-') + wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) + repo = git.open(repoDirectory) + repo.caseInsensitiveFs = true + const workingDirectory = repo.getWorkingDirectory() + + expect(repo.relativize(path.join(workingDirectory.toUpperCase(), 'a.txt'))).toBe('a.txt') + expect(repo.relativize(path.join(workingDirectory.toUpperCase(), 'a/b/c.txt'))).toBe('a/b/c.txt') + + const linkDirectory = path.join(fs.realpathSync(temp.mkdirSync('lower-case-symlink')), 'link') + wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) + fs.symlinkSync(repoDirectory, linkDirectory) + + repo = git.open(linkDirectory) + repo.caseInsensitiveFs = true + expect(repo.relativize(path.join(linkDirectory.toUpperCase(), 'test2'))).toBe('test2') + expect(repo.relativize(path.join(linkDirectory.toUpperCase(), 'test2/test3'))).toBe('test2/test3') + }) + }) + + describe('.isWorkingDirectory(path)', () => { + it("returns whether the given path is the repository's working directory", () => { + repo = git.open(__dirname) + let workingDirectory = repo.getWorkingDirectory() + + expect(repo.isWorkingDirectory(workingDirectory)).toBe(true) + expect(repo.isWorkingDirectory()).toBe(false) + expect(repo.isWorkingDirectory(null)).toBe(false) + expect(repo.isWorkingDirectory('')).toBe(false) + expect(repo.isWorkingDirectory('test')).toBe(false) + + const repoDirectory = temp.mkdirSync('lower-case-repo-') + wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) + repo = git.open(repoDirectory) + repo.caseInsensitiveFs = true + workingDirectory = repo.getWorkingDirectory() + + expect(repo.isWorkingDirectory(workingDirectory.toUpperCase())).toBe(true) + }) + }) + + describe('.submoduleForPath(path)', () => { + beforeEach(() => { + const repoDirectory = temp.mkdirSync('node-git-repo-') + const submoduleDirectory = temp.mkdirSync('node-git-repo-') + wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures', 'master.git'), path.join(repoDirectory, '.git')) + wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures', 'master.git'), path.join(submoduleDirectory, '.git')) + + const gitCommandHandler = jasmine.createSpy('gitCommandHandler') + execCommands([`cd ${repoDirectory}`, `git submodule add ${submoduleDirectory} sub`], gitCommandHandler) + + waitsFor(() => gitCommandHandler.callCount === 1) + + runs(() => repo = git.open(repoDirectory)) + }) + + it('returns the repository for the path', () => { + expect(repo.submoduleForPath()).toBe(null) + expect(repo.submoduleForPath(null)).toBe(null) + expect(repo.submoduleForPath('')).toBe(null) + expect(repo.submoduleForPath('sub1')).toBe(null) + + let submoduleRepoPath = path.join(repo.getPath(), 'modules', 'sub/') + if (process.platform === 'win32') { submoduleRepoPath = submoduleRepoPath.replace(/\\/g, '/') } + + expect(repo.submoduleForPath('sub').getPath()).toBe(submoduleRepoPath) + expect(repo.submoduleForPath('sub/').getPath()).toBe(submoduleRepoPath) + expect(repo.submoduleForPath('sub/a').getPath()).toBe(submoduleRepoPath) + expect(repo.submoduleForPath('sub/a/b/c/d').getPath()).toBe(submoduleRepoPath) + }) + }) + + describe('.add(path)', () => { + beforeEach(() => { + const repoDirectory = temp.mkdirSync('node-git-repo-') + wrench.copyDirSyncRecursive(path.join(__dirname, 'fixtures/master.git'), path.join(repoDirectory, '.git')) + repo = git.open(repoDirectory) + + const filePath = path.join(repoDirectory, 'toadd.txt') + fs.writeFileSync(filePath, 'changes to stage', 'utf8') + }) + + it('introduces the current state of the file to the index', () => { + expect(repo.getStatus('toadd.txt')).toBe(1 << 7) + repo.add('toadd.txt') + expect(repo.getStatus('toadd.txt')).toBe(1 << 0) + }) + + it("throws an error if the file doesn't exist", () => expect(() => repo.add('missing.txt')).toThrow()) + }) + + it('can handle multiple simultaneous async calls', async () => { + repoDirectory = temp.mkdirSync('node-git-repo-') + wrench.copyDirSyncRecursive( + path.join(__dirname, 'fixtures/subdir.git'), + path.join(repoDirectory, '.git') + ) + repo = git.open(repoDirectory) + + const operations = [ + () => repo.getAheadBehindCountAsync(), + () => repo.getHeadAsync(), + () => repo.getStatusAsync(), + () => repo.getStatusAsync(['*']), + ] + + for (let i = 0; i < 20; i++) { + const promises = [] + for (let j = 0, count = random(100); j < count; j++) { + promises.push(operations[random(operations.length)]()) + } + await Promise.all(promises) + } + + function random (max) { + return Math.floor(Math.random() * max) + } + }) +}) + +function execCommands (commands, callback) { + let command + if (process.platform === 'win32') { + command = commands.join(' & ') + } else { + command = commands.join(' && ') + } + exec(command, callback) +} diff --git a/src/git.coffee b/src/git.coffee deleted file mode 100644 index 21acbdb6..00000000 --- a/src/git.coffee +++ /dev/null @@ -1,191 +0,0 @@ -path = require 'path' -fs = require 'fs-plus' -{Repository} = require('bindings')('git.node') - -statusIndexNew = 1 << 0 -statusIndexModified = 1 << 1 -statusIndexDeleted = 1 << 2 -statusIndexRenamed = 1 << 3 -statusIndexTypeChange = 1 << 4 -statusWorkingDirNew = 1 << 7 -statusWorkingDirModified = 1 << 8 -statusWorkingDirDelete = 1 << 9 -statusWorkingDirTypeChange = 1 << 10 -statusIgnore = 1 << 14 - -modifiedStatusFlags = statusWorkingDirModified | statusIndexModified | - statusWorkingDirDelete | statusIndexDeleted | - statusWorkingDirTypeChange | statusIndexTypeChange - -newStatusFlags = statusWorkingDirNew | statusIndexNew - -deletedStatusFlags = statusWorkingDirDelete | statusIndexDeleted - -Repository::release = -> - submoduleRepo?.release() for submodulePath, submoduleRepo of @submodules - @_release() - -Repository::getWorkingDirectory = -> - @workingDirectory ?= @_getWorkingDirectory()?.replace(/\/$/, '') - -Repository::getShortHead = -> - head = @getHead() - return head unless head? - return head.substring(11) if head.indexOf('refs/heads/') is 0 - return head.substring(10) if head.indexOf('refs/tags/') is 0 - return head.substring(13) if head.indexOf('refs/remotes/') is 0 - return head.substring(0, 7) if head.match(/[a-fA-F0-9]{40}/) - return head - -Repository::isStatusModified = (status=0) -> - (status & modifiedStatusFlags) > 0 - -Repository::isPathModified = (path) -> - @isStatusModified(@getStatus(path)) - -Repository::isStatusNew = (status=0) -> - (status & newStatusFlags) > 0 - -Repository::isPathNew = (path) -> - @isStatusNew(@getStatus(path)) - -Repository::isStatusDeleted = (status=0) -> - (status & deletedStatusFlags) > 0 - -Repository::isPathDeleted = (path) -> - @isStatusDeleted(@getStatus(path)) - -Repository::getUpstreamBranch = (branch) -> - branch ?= @getHead() - return null unless branch?.length > 11 - return null unless branch.indexOf('refs/heads/') is 0 - - shortBranch = branch.substring(11) - - branchMerge = @getConfigValue("branch.#{shortBranch}.merge") - return null unless branchMerge?.length > 11 - return null unless branchMerge.indexOf('refs/heads/') is 0 - - branchRemote = @getConfigValue("branch.#{shortBranch}.remote") - return null unless branchRemote?.length > 0 - - "refs/remotes/#{branchRemote}/#{branchMerge.substring(11)}" - -Repository::getAheadBehindCount = (branch='HEAD')-> - if branch isnt 'HEAD' and branch.indexOf('refs/heads/') isnt 0 - branch = "refs/heads/#{branch}" - - counts = - ahead: 0 - behind: 0 - headCommit = @getReferenceTarget(branch) - return counts unless headCommit?.length > 0 - - upstream = @getUpstreamBranch() - return counts unless upstream?.length > 0 - upstreamCommit = @getReferenceTarget(upstream) - return counts unless upstreamCommit?.length > 0 - - mergeBase = @getMergeBase(headCommit, upstreamCommit) - return counts unless mergeBase?.length > 0 - - counts.ahead = @getCommitCount(headCommit, mergeBase) - counts.behind = @getCommitCount(upstreamCommit, mergeBase) - counts - -Repository::checkoutReference = (branch, create)-> - if branch.indexOf('refs/heads/') isnt 0 - branch = "refs/heads/#{branch}" - - @checkoutRef(branch, create) - -Repository::relativize = (path) -> - return path unless path - - if process.platform is 'win32' - path = path.replace(/\\/g, '/') - else - return path unless path[0] is '/' - - if @caseInsensitiveFs - lowerCasePath = path.toLowerCase() - - workingDirectory = @getWorkingDirectory() - if workingDirectory - workingDirectory = workingDirectory.toLowerCase() - if lowerCasePath.indexOf("#{workingDirectory}/") is 0 - return path.substring(workingDirectory.length + 1) - - if @openedWorkingDirectory - workingDirectory = @openedWorkingDirectory.toLowerCase() - if lowerCasePath.indexOf("#{workingDirectory}/") is 0 - return path.substring(workingDirectory.length + 1) - else - workingDirectory = @getWorkingDirectory() - if workingDirectory and path.indexOf("#{workingDirectory}/") is 0 - return path.substring(workingDirectory.length + 1) - - if @openedWorkingDirectory and path.indexOf("#{@openedWorkingDirectory}/") is 0 - return path.substring(@openedWorkingDirectory.length + 1) - - path - -Repository::submoduleForPath = (path) -> - path = @relativize(path) - return null unless path - - for submodulePath, submoduleRepo of @submodules - if path is submodulePath - return submoduleRepo - else if path.indexOf("#{submodulePath}/") is 0 - # Handle submodules inside of submodules - path = path.substring(submodulePath.length + 1) - return submoduleRepo.submoduleForPath(path) ? submoduleRepo - - null - -realpath = (unrealPath) -> - try - fs.realpathSync(unrealPath) - catch e - unrealPath - -isRootPath = (repositoryPath) -> - if process.platform is 'win32' - /^[a-zA-Z]+:[\\\/]$/.test(repositoryPath) - else - repositoryPath is path.sep - -openRepository = (repositoryPath) -> - symlink = realpath(repositoryPath) isnt repositoryPath - - repositoryPath = repositoryPath.replace(/\\/g, '/') if process.platform is 'win32' - repository = new Repository(repositoryPath) - if repository.exists() - repository.caseInsensitiveFs = fs.isCaseInsensitive() - if symlink - workingDirectory = repository.getWorkingDirectory() - while not isRootPath(repositoryPath) - if realpath(repositoryPath) is workingDirectory - repository.openedWorkingDirectory = repositoryPath - break - repositoryPath = path.resolve(repositoryPath, '..') - repository - else - null - -openSubmodules = (repository) -> - repository.submodules = {} - for relativePath in repository.getSubmodulePaths() when relativePath - submodulePath = path.join(repository.getWorkingDirectory(), relativePath) - if submoduleRepo = openRepository(submodulePath) - if submoduleRepo.getPath() is repository.getPath() - submoduleRepo.release() - else - openSubmodules(submoduleRepo) - repository.submodules[relativePath] = submoduleRepo - -exports.open = (repositoryPath) -> - repository = openRepository(repositoryPath) - openSubmodules(repository) if repository? - repository diff --git a/src/git.js b/src/git.js new file mode 100644 index 00000000..217e6038 --- /dev/null +++ b/src/git.js @@ -0,0 +1,356 @@ +const path = require('path') +const fs = require('fs-plus') +const {Repository} = require('../build/Release/git.node') + +const statusIndexNew = 1 << 0 +const statusIndexModified = 1 << 1 +const statusIndexDeleted = 1 << 2 +const statusIndexRenamed = 1 << 3 +const statusIndexTypeChange = 1 << 4 +const statusWorkingDirNew = 1 << 7 +const statusWorkingDirModified = 1 << 8 +const statusWorkingDirDelete = 1 << 9 +const statusWorkingDirTypeChange = 1 << 10 +const statusIgnored = 1 << 14 + +const modifiedStatusFlags = + statusWorkingDirModified | + statusIndexModified | + statusWorkingDirDelete | + statusIndexDeleted | + statusWorkingDirTypeChange | + statusIndexTypeChange + +const newStatusFlags = statusWorkingDirNew | statusIndexNew + +const deletedStatusFlags = statusWorkingDirDelete | statusIndexDeleted + +const indexStatusFlags = + statusIndexNew | + statusIndexModified | + statusIndexDeleted | + statusIndexRenamed | + statusIndexTypeChange + +Repository.prototype.release = function () { + for (let submodulePath in this.submodules) { + const submoduleRepo = this.submodules[submodulePath] + if (submoduleRepo) submoduleRepo.release() + } + return this._release() +} + +Repository.prototype.getWorkingDirectory = function () { + if (!this.workingDirectory) { + this.workingDirectory = this._getWorkingDirectory() + if (this.workingDirectory) this.workingDirectory = this.workingDirectory.replace(/\/$/, '') + } + return this.workingDirectory +} + +Repository.prototype.getShortHead = function () { + const head = this.getHead() + if (head == null) return head + if (head.startsWith('refs/heads/')) return head.substring(11) + if (head.startsWith('refs/tags/')) return head.substring(10) + if (head.startsWith('refs/remotes/')) return head.substring(13) + if (head.match(/[a-fA-F0-9]{40}/)) return head.substring(0, 7) + return head +} + +Repository.prototype.isStatusModified = function (status = 0) { + return (status & modifiedStatusFlags) > 0 +} + +Repository.prototype.isPathModified = function (path) { + return this.isStatusModified(this.getStatus(path)) +} + +Repository.prototype.isStatusNew = function (status = 0) { + return (status & newStatusFlags) > 0 +} + +Repository.prototype.isPathNew = function (path) { + return this.isStatusNew(this.getStatus(path)) +} + +Repository.prototype.isStatusDeleted = function (status = 0) { + return (status & deletedStatusFlags) > 0 +} + +Repository.prototype.isPathDeleted = function (path) { + return this.isStatusDeleted(this.getStatus(path)) +} + +Repository.prototype.isPathStaged = function (path) { + return this.isStatusStaged(this.getStatus(path)) +} + +Repository.prototype.isStatusIgnored = function (status = 0) { + return (status & statusIgnored) > 0 +} + +Repository.prototype.isStatusStaged = function (status = 0) { + return (status & indexStatusFlags) > 0 +} + +Repository.prototype.getUpstreamBranch = function (branch) { + if (branch == null) branch = this.getHead() + if (!branch || !branch.startsWith('refs/heads/')) return null + const shortBranch = branch.substring(11) + + const branchMerge = this.getConfigValue(`branch.${shortBranch}.merge`) + if (!branchMerge || !branchMerge.startsWith('refs/heads/')) return null + const shortBranchMerge = branchMerge.substring(11) + + const branchRemote = this.getConfigValue(`branch.${shortBranch}.remote`) + if (!branch || branch.length === 0) return null + + return `refs/remotes/${branchRemote}/${shortBranchMerge}` +} + +Repository.prototype.getAheadBehindCount = function (branch = 'HEAD') { + if (branch !== 'HEAD' && !branch.startsWith('refs/heads/')) { + branch = `refs/heads/${branch}` + } + + const headCommit = this.getReferenceTarget(branch) + if (!headCommit || headCommit.length === 0) return {ahead: 0, behind: 0} + + const upstream = this.getUpstreamBranch() + if (!upstream || upstream.length === 0) return {ahead: 0, behind: 0} + + const upstreamCommit = this.getReferenceTarget(upstream) + if (!upstreamCommit || upstreamCommit.length === 0) return {ahead: 0, behind: 0} + + return this.compareCommits(headCommit, upstreamCommit) +} + +Repository.prototype.getAheadBehindCountAsync = function (branch = 'HEAD') { + if (branch !== 'HEAD' && !branch.startsWith('refs/heads/')) { + branch = `refs/heads/${branch}` + } + + const headCommit = this.getReferenceTarget(branch) + if (!headCommit || headCommit.length === 0) return {ahead: 0, behind: 0} + + const upstream = this.getUpstreamBranch() + if (!upstream || upstream.length === 0) return {ahead: 0, behind: 0} + + const upstreamCommit = this.getReferenceTarget(upstream) + if (!upstreamCommit || upstreamCommit.length === 0) return {ahead: 0, behind: 0} + + return performAsyncWork(this, done => this.compareCommitsAsync( + done, + headCommit, + upstreamCommit + )) +} + +Repository.prototype.checkoutReference = function (branch, create) { + if (branch.indexOf('refs/heads/') !== 0) branch = `refs/heads/${branch}` + return this.checkoutRef(branch, create) +} + +Repository.prototype.relativize = function (path) { + let workingDirectory + if (!path) return path + + if (process.platform === 'win32') { + path = path.replace(/\\/g, '/') + } else { + if (path[0] !== '/') return path + } + + if (this.caseInsensitiveFs) { + const lowerCasePath = path.toLowerCase() + + workingDirectory = this.getWorkingDirectory() + if (workingDirectory) { + workingDirectory = workingDirectory.toLowerCase() + if (lowerCasePath.startsWith(`${workingDirectory}/`)) { + return path.substring(workingDirectory.length + 1) + } else if (lowerCasePath === workingDirectory) { + return '' + } + } + + if (this.openedWorkingDirectory) { + workingDirectory = this.openedWorkingDirectory.toLowerCase() + if (lowerCasePath.startsWith(`${workingDirectory}/`)) { + return path.substring(workingDirectory.length + 1) + } else if (lowerCasePath === workingDirectory) { + return '' + } + } + } else { + workingDirectory = this.getWorkingDirectory() + if (workingDirectory) { + if (path.startsWith(`${workingDirectory}/`)) { + return path.substring(workingDirectory.length + 1) + } else if (path === workingDirectory) { + return '' + } + } + + if (this.openedWorkingDirectory) { + if (path.startsWith(`${this.openedWorkingDirectory}/`)) { + return path.substring(this.openedWorkingDirectory.length + 1) + } else if (path === this.openedWorkingDirectory) { + return '' + } + } + } + + return path +} + +Repository.prototype.submoduleForPath = function (path) { + path = this.relativize(path) + if (!path) return null + + for (let submodulePath in this.submodules) { + const submoduleRepo = this.submodules[submodulePath] + if (path === submodulePath) { + return submoduleRepo + } else if (path.startsWith(`${submodulePath}/`)) { + path = path.substring(submodulePath.length + 1) + return submoduleRepo.submoduleForPath(path) || submoduleRepo + } + } + + return null +} + +Repository.prototype.isWorkingDirectory = function (path) { + if (!path) return false + + if (process.platform === 'win32') { + path = path.replace(/\\/g, '/') + } else { + if (path[0] !== '/') return false + } + + if (this.caseInsensitiveFs) { + const lowerCasePath = path.toLowerCase() + const workingDirectory = this.getWorkingDirectory() + if (workingDirectory && workingDirectory.toLowerCase() === lowerCasePath) return true + if (this.openedWorkingDirectory && this.openedWorkingDirectory.toLowerCase() === lowerCasePath) return true + } else { + return path === this.getWorkingDirectory() || path === this.openedWorkingDirectory + } + + return false +} + +const {getHeadAsync, getStatus, getStatusAsync, getStatusForPath} = Repository.prototype +delete Repository.prototype.getStatusForPath + +Repository.prototype.getStatusForPaths = function (paths) { + if (paths && paths.length > 0) { + return getStatus.call(this, paths) + } else { + return {} + } +} + +Repository.prototype.getStatus = function (path) { + if (typeof path === 'string') { + return getStatusForPath.call(this, path) + } else { + return getStatus.call(this) + } +} + +Repository.prototype.getHeadAsync = function () { + return performAsyncWork(this, done => getHeadAsync.call(this, done)) +} + +Repository.prototype.getStatusAsync = function () { + return performAsyncWork(this, done => getStatusAsync.call(this, done)) +} + +Repository.prototype.getStatusForPathsAsync = function (paths) { + return performAsyncWork(this, done => getStatusAsync.call(this, done, paths)) +} + +function performAsyncWork (repo, fn) { + fn = promisify(fn) + + if (repo._lastAsyncPromise) { + repo._lastAsyncPromise = repo._lastAsyncPromise.then(fn, fn) + } else { + repo._lastAsyncPromise = fn() + } + return repo._lastAsyncPromise +} + +function promisify (fn) { + return () => new Promise((resolve, reject) => + fn((error, result) => error ? reject(error) : resolve(result)) + ) +} + +function realpath (unrealPath) { + try { + return fs.realpathSync(unrealPath) + } catch (e) { + return unrealPath + } +} + +function isRootPath (repositoryPath) { + if (process.platform === 'win32') { + return /^[a-zA-Z]+:[\\/]$/.test(repositoryPath) + } else { + return repositoryPath === path.sep + } +} + +function openRepository (repositoryPath, search) { + const symlink = realpath(repositoryPath) !== repositoryPath + + if (process.platform === 'win32') repositoryPath = repositoryPath.replace(/\\/g, '/') + const repository = new Repository(repositoryPath, search) + if (repository.exists()) { + repository.caseInsensitiveFs = fs.isCaseInsensitive() + if (symlink) { + const workingDirectory = repository.getWorkingDirectory() + while (!isRootPath(repositoryPath)) { + if (realpath(repositoryPath) === workingDirectory) { + repository.openedWorkingDirectory = repositoryPath + break + } + repositoryPath = path.resolve(repositoryPath, '..') + } + } + return repository + } else { + return null + } +} + +function openSubmodules (repository) { + repository.submodules = {} + + for (let relativePath of repository.getSubmodulePaths()) { + if (relativePath) { + const submodulePath = path.join(repository.getWorkingDirectory(), relativePath) + const submoduleRepo = openRepository(submodulePath, false) + if (submoduleRepo) { + if (submoduleRepo.getPath() === repository.getPath()) { + submoduleRepo.release() + } else { + openSubmodules(submoduleRepo) + repository.submodules[relativePath] = submoduleRepo + } + } + } + } +} + +exports.open = function (repositoryPath, search = true) { + const repository = openRepository(repositoryPath, search) + if (repository) openSubmodules(repository) + return repository +} diff --git a/src/repository.cc b/src/repository.cc index aa49907c..fb995f05 100644 --- a/src/repository.cc +++ b/src/repository.cc @@ -20,61 +20,133 @@ // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. #include "repository.h" - #include - #include #include -void Repository::Init(Handle target) { - NanScope(); +void Repository::Init(Local target) { + Nan::HandleScope scope; + git_libgit2_init(); - git_threads_init(); - - Local newTemplate = FunctionTemplate::New( + Local newTemplate = Nan::New( Repository::New); - newTemplate->SetClassName(NanSymbol("Repository")); + newTemplate->SetClassName(Nan::New("Repository").ToLocalChecked()); newTemplate->InstanceTemplate()->SetInternalFieldCount(1); Local proto = newTemplate->PrototypeTemplate(); - NODE_SET_METHOD(proto, "getPath", Repository::GetPath); - NODE_SET_METHOD(proto, "_getWorkingDirectory", + Nan::SetMethod(proto, "getPath", Repository::GetPath); + Nan::SetMethod(proto, "_getWorkingDirectory", Repository::GetWorkingDirectory); - NODE_SET_METHOD(proto, "exists", Repository::Exists); - NODE_SET_METHOD(proto, "getSubmodulePaths", Repository::GetSubmodulePaths); - NODE_SET_METHOD(proto, "getHead", Repository::GetHead); - NODE_SET_METHOD(proto, "refreshIndex", Repository::RefreshIndex); - NODE_SET_METHOD(proto, "isIgnored", Repository::IsIgnored); - NODE_SET_METHOD(proto, "isSubmodule", Repository::IsSubmodule); - NODE_SET_METHOD(proto, "getConfigValue", Repository::GetConfigValue); - NODE_SET_METHOD(proto, "setConfigValue", Repository::SetConfigValue); - NODE_SET_METHOD(proto, "getStatus", Repository::GetStatus); - NODE_SET_METHOD(proto, "checkoutHead", Repository::CheckoutHead); - NODE_SET_METHOD(proto, "getReferenceTarget", Repository::GetReferenceTarget); - NODE_SET_METHOD(proto, "getDiffStats", Repository::GetDiffStats); - NODE_SET_METHOD(proto, "getIndexBlob", Repository::GetIndexBlob); - NODE_SET_METHOD(proto, "getHeadBlob", Repository::GetHeadBlob); - NODE_SET_METHOD(proto, "getCommitCount", Repository::GetCommitCount); - NODE_SET_METHOD(proto, "getMergeBase", Repository::GetMergeBase); - NODE_SET_METHOD(proto, "_release", Repository::Release); - NODE_SET_METHOD(proto, "getLineDiffs", Repository::GetLineDiffs); - NODE_SET_METHOD(proto, "getReferences", Repository::GetReferences); - NODE_SET_METHOD(proto, "checkoutRef", Repository::CheckoutReference); - - target->Set(NanSymbol("Repository"), newTemplate->GetFunction()); + Nan::SetMethod(proto, "exists", Repository::Exists); + Nan::SetMethod(proto, "getSubmodulePaths", Repository::GetSubmodulePaths); + Nan::SetMethod(proto, "getHead", Repository::GetHead); + Nan::SetMethod(proto, "getHeadAsync", Repository::GetHeadAsync); + Nan::SetMethod(proto, "refreshIndex", Repository::RefreshIndex); + Nan::SetMethod(proto, "isIgnored", Repository::IsIgnored); + Nan::SetMethod(proto, "isSubmodule", Repository::IsSubmodule); + Nan::SetMethod(proto, "getConfigValue", Repository::GetConfigValue); + Nan::SetMethod(proto, "setConfigValue", Repository::SetConfigValue); + Nan::SetMethod(proto, "getStatus", Repository::GetStatus); + Nan::SetMethod(proto, "getStatusForPath", Repository::GetStatusForPath); + Nan::SetMethod(proto, "getStatusAsync", Repository::GetStatusAsync); + Nan::SetMethod(proto, "checkoutHead", Repository::CheckoutHead); + Nan::SetMethod(proto, "getReferenceTarget", Repository::GetReferenceTarget); + Nan::SetMethod(proto, "getDiffStats", Repository::GetDiffStats); + Nan::SetMethod(proto, "getIndexBlob", Repository::GetIndexBlob); + Nan::SetMethod(proto, "getHeadBlob", Repository::GetHeadBlob); + Nan::SetMethod(proto, "compareCommits", Repository::CompareCommits); + Nan::SetMethod(proto, "compareCommitsAsync", Repository::CompareCommitsAsync); + Nan::SetMethod(proto, "_release", Repository::Release); + Nan::SetMethod(proto, "getLineDiffs", Repository::GetLineDiffs); + Nan::SetMethod(proto, "getLineDiffDetails", Repository::GetLineDiffDetails); + Nan::SetMethod(proto, "getReferences", Repository::GetReferences); + Nan::SetMethod(proto, "checkoutRef", Repository::CheckoutReference); + Nan::SetMethod(proto, "add", Repository::Add); + + Nan::Set(target, + Nan::New("Repository").ToLocalChecked(), + Nan::GetFunction(newTemplate).ToLocalChecked()); } NODE_MODULE(git, Repository::Init) NAN_METHOD(Repository::New) { - NanScope(); - Repository* repository = new Repository(Local::Cast(args[0])); - repository->Wrap(args.This()); - NanReturnUndefined(); + Nan::HandleScope scope; + Repository* repository = new Repository( + Local::Cast(info[0]), Local::Cast(info[1])); + repository->Wrap(info.This()); + info.GetReturnValue().SetUndefined(); +} + +git_repository* Repository::GetRepository(Nan::NAN_METHOD_ARGS_TYPE args) { + return Nan::ObjectWrap::Unwrap(args.This())->repository; +} + +git_repository* Repository::GetAsyncRepository(Nan::NAN_METHOD_ARGS_TYPE args) { + return Nan::ObjectWrap::Unwrap(args.This())->async_repository; } -git_repository* Repository::GetRepository(_NAN_METHOD_ARGS_TYPE args) { - return node::ObjectWrap::Unwrap(args.This())->repository; +int Repository::GetBlob(Nan::NAN_METHOD_ARGS_TYPE args, + git_repository* repo, git_blob*& blob) { + std::string path(*Nan::Utf8String(args[0])); + + int useIndex = false; + if (args.Length() >= 3) { + Local optionsArg(Local::Cast(args[2])); + if (Nan::To(Nan::Get(optionsArg, Nan::New("useIndex").ToLocalChecked()).ToLocalChecked()).FromJust()) + useIndex = true; + } + + if (useIndex) { + git_index* index; + if (git_repository_index(&index, repo) != GIT_OK) + return -1; + + git_index_read(index, 0); + const git_index_entry* entry = git_index_get_bypath(index, path.data(), 0); + if (entry == NULL) { + git_index_free(index); + return -1; + } + + const git_oid* blobSha = &entry->id; + if (blobSha != NULL && git_blob_lookup(&blob, repo, blobSha) != GIT_OK) + blob = NULL; + } else { + git_reference* head; + if (git_repository_head(&head, repo) != GIT_OK) + return -1; + + const git_oid* sha = git_reference_target(head); + git_commit* commit; + int commitStatus = git_commit_lookup(&commit, repo, sha); + git_reference_free(head); + if (commitStatus != GIT_OK) + return -1; + + git_tree* tree; + int treeStatus = git_commit_tree(&tree, commit); + git_commit_free(commit); + if (treeStatus != GIT_OK) + return -1; + + git_tree_entry* treeEntry; + if (git_tree_entry_bypath(&treeEntry, tree, path.c_str()) != GIT_OK) { + git_tree_free(tree); + return -1; + } + + const git_oid* blobSha = git_tree_entry_id(treeEntry); + if (blobSha != NULL && git_blob_lookup(&blob, repo, blobSha) != GIT_OK) + blob = NULL; + git_tree_entry_free(treeEntry); + git_tree_free(tree); + } + + if (blob == NULL) + return -1; + + return 0; } // C++ equivalent to GIT_DIFF_OPTIONS_INIT, we can not use it directly because @@ -87,184 +159,299 @@ git_diff_options Repository::CreateDefaultGitDiffOptions() { } NAN_METHOD(Repository::Exists) { - NanScope(); - NanReturnValue(Boolean::New(GetRepository(args) != NULL)); + Nan::HandleScope scope; + + info.GetReturnValue().Set(Nan::New(GetRepository(info) != NULL)); } NAN_METHOD(Repository::GetPath) { - NanScope(); - git_repository* repository = GetRepository(args); + Nan::HandleScope scope; + git_repository* repository = GetRepository(info); const char* path = git_repository_path(repository); - NanReturnValue(NanSymbol(path)); + + info.GetReturnValue().Set(Nan::New(path).ToLocalChecked()); } NAN_METHOD(Repository::GetWorkingDirectory) { - NanScope(); - git_repository* repository = GetRepository(args); + Nan::HandleScope scope; + git_repository* repository = GetRepository(info); const char* path = git_repository_workdir(repository); - NanReturnValue(NanSymbol(path)); + info.GetReturnValue().Set(Nan::New(path).ToLocalChecked()); } NAN_METHOD(Repository::GetSubmodulePaths) { - NanScope(); - git_repository* repository = GetRepository(args); + Nan::HandleScope scope; + git_repository* repository = GetRepository(info); std::vector paths; git_submodule_foreach(repository, SubmoduleCallback, &paths); - Local v8Paths = Array::New(paths.size()); + Local v8Paths = Nan::New(paths.size()); for (size_t i = 0; i < paths.size(); i++) - v8Paths->Set(i, NanSymbol(paths[i].data())); - NanReturnValue(v8Paths); + Nan::Set(v8Paths, i, Nan::New(paths[i].data()).ToLocalChecked()); + info.GetReturnValue().Set(v8Paths); } -NAN_METHOD(Repository::GetHead) { - NanScope(); - git_repository* repository = GetRepository(args); - git_reference* head; - if (git_repository_head(&head, repository) != GIT_OK) - NanReturnNull(); +class HeadWorker { + git_repository *repository; + std::string result; - if (git_repository_head_detached(repository) == 1) { - const git_oid* sha = git_reference_target(head); - if (sha != NULL) { - char oid[GIT_OID_HEXSZ + 1]; - git_oid_tostr(oid, GIT_OID_HEXSZ + 1, sha); - git_reference_free(head); - NanReturnValue(NanSymbol(oid)); + public: + void Execute() { + git_reference *head; + if (git_repository_head(&head, repository) != GIT_OK) return; + + if (git_repository_head_detached(repository) == 1) { + const git_oid *oid = git_reference_target(head); + if (oid) { + result.resize(GIT_OID_HEXSZ); + git_oid_tostr(&result[0], GIT_OID_HEXSZ + 1, oid); + } + } else { + result = git_reference_name(head); } + + git_reference_free(head); } - Local referenceName = NanSymbol(git_reference_name(head)); - git_reference_free(head); - NanReturnValue(referenceName); + std::pair, Local> Finish() { + if (result.empty()) { + return {Nan::Null(), Nan::Null()}; + } else { + return {Nan::Null(), Nan::New(result).ToLocalChecked()}; + } + } + + HeadWorker(git_repository *repository) : repository(repository) {} +}; + +NAN_METHOD(Repository::GetHead) { + HeadWorker worker(GetRepository(info)); + worker.Execute(); + info.GetReturnValue().Set(worker.Finish().second); +} + +NAN_METHOD(Repository::GetHeadAsync) { + class HeadAsyncWorker : public Nan::AsyncWorker { + HeadWorker worker; + + public: + void Execute () { worker.Execute(); } + + void HandleOKCallback() { + auto result = worker.Finish(); + Local argv[] = {result.first, result.second}; + callback->Call(2, argv); + } + + HeadAsyncWorker(Nan::Callback *callback, git_repository *repository) : Nan::AsyncWorker(callback), worker(repository) {} + }; + + auto callback = new Nan::Callback(Local::Cast(info[0])); + Nan::AsyncQueueWorker(new HeadAsyncWorker(callback, GetAsyncRepository(info))); } NAN_METHOD(Repository::RefreshIndex) { - NanScope(); - git_repository* repository = GetRepository(args); + Nan::HandleScope scope; + git_repository* repository = GetRepository(info); git_index* index; if (git_repository_index(&index, repository) == GIT_OK) { git_index_read(index, 0); git_index_free(index); } - NanReturnUndefined(); + info.GetReturnValue().SetUndefined(); } NAN_METHOD(Repository::IsIgnored) { - NanScope(); - if (args.Length() < 1) - NanReturnValue(Boolean::New(false)); + Nan::HandleScope scope; + if (info.Length() < 1) + return info.GetReturnValue().Set(Nan::New(false)); - git_repository* repository = GetRepository(args); - std::string path(*String::Utf8Value(args[0])); + git_repository* repository = GetRepository(info); + std::string path(*Nan::Utf8String(info[0])); int ignored; if (git_ignore_path_is_ignored(&ignored, repository, path.c_str()) == GIT_OK) - NanReturnValue(Boolean::New(ignored == 1)); + return info.GetReturnValue().Set(Nan::New(ignored == 1)); else - NanReturnValue(Boolean::New(false)); + return info.GetReturnValue().Set(Nan::New(false)); } NAN_METHOD(Repository::IsSubmodule) { - NanScope(); - if (args.Length() < 1) - NanReturnValue(Boolean::New(false)); + Nan::HandleScope scope; + if (info.Length() < 1) + return info.GetReturnValue().Set(Nan::New(false)); git_index* index; - git_repository* repository = GetRepository(args); + git_repository* repository = GetRepository(info); if (git_repository_index(&index, repository) == GIT_OK) { - std::string path(*String::Utf8Value(args[0])); + std::string path(*Nan::Utf8String(info[0])); const git_index_entry* entry = git_index_get_bypath(index, path.c_str(), 0); - Handle isSubmodule = Boolean::New( + Local isSubmodule = Nan::New( entry != NULL && (entry->mode & S_IFMT) == GIT_FILEMODE_COMMIT); git_index_free(index); - NanReturnValue(isSubmodule); + return info.GetReturnValue().Set(isSubmodule); } else { - NanReturnValue(Boolean::New(false)); + return info.GetReturnValue().Set(Nan::New(false)); } } NAN_METHOD(Repository::GetConfigValue) { - NanScope(); - if (args.Length() < 1) - NanReturnNull(); + Nan::HandleScope scope; + if (info.Length() < 1) + return info.GetReturnValue().Set(Nan::Null()); git_config* config; - git_repository* repository = GetRepository(args); - if (git_repository_config(&config, repository) != GIT_OK) - NanReturnNull(); + git_repository* repository = GetRepository(info); + if (git_repository_config_snapshot(&config, repository) != GIT_OK) + return info.GetReturnValue().Set(Nan::Null()); - std::string configKey(*String::Utf8Value(args[0])); + std::string configKey(*Nan::Utf8String(info[0])); const char* configValue; if (git_config_get_string( &configValue, config, configKey.c_str()) == GIT_OK) { - Handle configValueHandle = NanSymbol(configValue); git_config_free(config); - NanReturnValue(configValueHandle); + return info.GetReturnValue().Set(Nan::New(configValue) + .ToLocalChecked()); } else { git_config_free(config); - NanReturnNull(); + return info.GetReturnValue().Set(Nan::Null()); } } NAN_METHOD(Repository::SetConfigValue) { - NanScope(); - if (args.Length() != 2) - NanReturnValue(Boolean::New(false)); + Nan::HandleScope scope; + if (info.Length() != 2) + return info.GetReturnValue().Set(Nan::New(false)); git_config* config; - git_repository* repository = GetRepository(args); + git_repository* repository = GetRepository(info); if (git_repository_config(&config, repository) != GIT_OK) - NanReturnValue(Boolean::New(false)); + return info.GetReturnValue().Set(Nan::New(false)); - std::string configKey(*String::Utf8Value(args[0])); - std::string configValue(*String::Utf8Value(args[1])); + std::string configKey(*Nan::Utf8String(info[0])); + std::string configValue(*Nan::Utf8String(info[1])); int errorCode = git_config_set_string( config, configKey.c_str(), configValue.c_str()); git_config_free(config); - NanReturnValue(Boolean::New(errorCode == GIT_OK)); + return info.GetReturnValue().Set(Nan::New(errorCode == GIT_OK)); } +static int StatusCallback(const char* path, unsigned int status, void* payload) { + auto statuses = static_cast *>(payload); + statuses->insert(std::make_pair(std::string(path), status)); + return GIT_OK; +} -NAN_METHOD(Repository::GetStatus) { - NanScope(); - if (args.Length() < 1) { - Local result = Object::New(); - std::map statuses; +class StatusWorker { + git_repository *repository; + std::map statuses; + char **paths; + unsigned path_count; + int code; + + public: + void Execute() { git_status_options options = GIT_STATUS_OPTIONS_INIT; - options.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | - GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; - if (git_status_foreach_ext(GetRepository(args), - &options, - StatusCallback, - &statuses) == GIT_OK) { - std::map::iterator iter = statuses.begin(); - for (; iter != statuses.end(); ++iter) - result->Set(NanSymbol(iter->first.c_str()), - Number::New(iter->second)); + options.flags = GIT_STATUS_OPT_INCLUDE_UNTRACKED | GIT_STATUS_OPT_RECURSE_UNTRACKED_DIRS; + if (paths) { + options.pathspec.count = path_count; + options.pathspec.strings = paths; } - NanReturnValue(result); + code = git_status_foreach_ext(repository, &options, StatusCallback, &statuses); + if (paths) { + git_strarray_free(&options.pathspec); + } + } + + std::pair, Local> Finish() { + if (code == GIT_OK) { + Local result = Nan::New(); + for (auto iter = statuses.begin(), end = statuses.end(); iter != end; ++iter) { + Nan::Set( + result, + Nan::New(iter->first.c_str()).ToLocalChecked(), + Nan::New(iter->second) + ); + } + return {Nan::Null(), result}; + } else { + return {Nan::Error("Git status failed"), Nan::Null()}; + } + } + + StatusWorker(git_repository *repository, Local path_filter) : repository{repository} { + if (path_filter->IsArray()) { + Local js_paths = Local::Cast(path_filter); + path_count = js_paths->Length(); + paths = reinterpret_cast(malloc(path_count * sizeof(char *))); + for (unsigned i = 0; i < path_count; i++) { + Nan::Utf8String js_path(Nan::Get(js_paths, i).ToLocalChecked()); + paths[i] = reinterpret_cast(malloc(js_path.length() + 1)); + strcpy(paths[i], *js_path); + } + } else { + paths = NULL; + path_count = 0; + } + } +}; + +NAN_METHOD(Repository::GetStatusAsync) { + class StatusAsyncWorker : public Nan::AsyncWorker { + StatusWorker worker; + + public: + void Execute() { + worker.Execute(); + } + + void HandleOKCallback() { + auto result = worker.Finish(); + Local argv[] = {result.first, result.second}; + callback->Call(2, argv); + } + + StatusAsyncWorker(Nan::Callback *callback, git_repository *repository, Local path_filter) + : Nan::AsyncWorker(callback), worker(repository, path_filter) {} + }; + + auto callback = new Nan::Callback(Local::Cast(info[0])); + Local path_filter = info.Length() > 1 ? info[1] : Local::Cast(Nan::Null()); + Nan::AsyncQueueWorker(new StatusAsyncWorker(callback, GetAsyncRepository(info), path_filter)); +} + +NAN_METHOD(Repository::GetStatus) { + Local path_filter = info.Length() > 0 ? info[0] : Local::Cast(Nan::Null()); + StatusWorker worker(GetRepository(info), path_filter); + worker.Execute(); + auto result = worker.Finish(); + if (result.first->IsNull()) { + info.GetReturnValue().Set(worker.Finish().second); } else { - git_repository* repository = GetRepository(args); - std::string path(*String::Utf8Value(args[0])); - unsigned int status = 0; - if (git_status_file(&status, repository, path.c_str()) == GIT_OK) - NanReturnValue(Number::New(status)); - else - NanReturnValue(Number::New(0)); + info.GetReturnValue().Set(Nan::New()); } } +NAN_METHOD(Repository::GetStatusForPath) { + git_repository* repository = GetRepository(info); + Nan::Utf8String path(info[0]); + unsigned int status = 0; + if (git_status_file(&status, repository, *path) == GIT_OK) + return info.GetReturnValue().Set(Nan::New(status)); + else + return info.GetReturnValue().Set(Nan::New(0)); +} + NAN_METHOD(Repository::CheckoutHead) { - NanScope(); - if (args.Length() < 1) - NanReturnValue(Boolean::New(false)); + Nan::HandleScope scope; + if (info.Length() < 1) + return info.GetReturnValue().Set(Nan::New(false)); - String::Utf8Value utf8Path(args[0]); + Nan::Utf8String utf8Path(info[0]); char* path = *utf8Path; - git_checkout_opts options = GIT_CHECKOUT_OPTS_INIT; + git_checkout_options options = GIT_CHECKOUT_OPTIONS_INIT; options.checkout_strategy = GIT_CHECKOUT_FORCE | GIT_CHECKOUT_DISABLE_PATHSPEC_MATCH; git_strarray paths; @@ -272,58 +459,63 @@ NAN_METHOD(Repository::CheckoutHead) { paths.strings = &path; options.paths = paths; - int result = git_checkout_head(GetRepository(args), &options); - NanReturnValue(Boolean::New(result == GIT_OK)); + int result = git_checkout_head(GetRepository(info), &options); + return info.GetReturnValue().Set(Nan::New(result == GIT_OK)); } NAN_METHOD(Repository::GetReferenceTarget) { - NanScope(); - if (args.Length() < 1) - NanReturnNull(); + Nan::HandleScope scope; + if (info.Length() < 1) + return info.GetReturnValue().Set(Nan::Null()); - std::string refName(*String::Utf8Value(args[0])); + std::string refName(*Nan::Utf8String(info[0])); git_oid sha; if (git_reference_name_to_id( - &sha, GetRepository(args), refName.c_str()) == GIT_OK) { + &sha, GetRepository(info), refName.c_str()) == GIT_OK) { char oid[GIT_OID_HEXSZ + 1]; git_oid_tostr(oid, GIT_OID_HEXSZ + 1, &sha); - NanReturnValue(NanSymbol(oid)); + return info.GetReturnValue().Set(Nan::New(oid, -1) + .ToLocalChecked()); } else { - NanReturnNull(); + return info.GetReturnValue().Set(Nan::Null()); } } NAN_METHOD(Repository::GetDiffStats) { - NanScope(); + Nan::HandleScope scope; int added = 0; int deleted = 0; - Local result = Object::New(); - result->Set(NanSymbol("added"), Number::New(added)); - result->Set(NanSymbol("deleted"), Number::New(deleted)); - - if (args.Length() < 1) - NanReturnValue(result); - - git_repository* repository = GetRepository(args); + Local result = Nan::New(); + Nan::Set(result, + Nan::New("added").ToLocalChecked(), + Nan::New(added)); + Nan::Set(result, + Nan::New("deleted").ToLocalChecked(), + Nan::New(deleted)); + + if (info.Length() < 1) + return info.GetReturnValue().Set(result); + + git_repository* repository = GetRepository(info); git_reference* head; if (git_repository_head(&head, repository) != GIT_OK) - NanReturnValue(result); + return info.GetReturnValue().Set(result); const git_oid* sha = git_reference_target(head); git_commit* commit; int commitStatus = git_commit_lookup(&commit, repository, sha); git_reference_free(head); if (commitStatus != GIT_OK) - NanReturnValue(result); + return info.GetReturnValue().Set(result); git_tree* tree; int treeStatus = git_commit_tree(&tree, commit); git_commit_free(commit); if (treeStatus != GIT_OK) - NanReturnValue(result); + return info.GetReturnValue().Set(result); - String::Utf8Value utf8Path(args[0]); + Nan::Utf8String utf8Path(info[0]); char* path = *utf8Path; git_diff_options options = CreateDefaultGitDiffOptions(); @@ -338,19 +530,19 @@ NAN_METHOD(Repository::GetDiffStats) { int diffStatus = git_diff_tree_to_workdir(&diffs, repository, tree, &options); git_tree_free(tree); if (diffStatus != GIT_OK) - NanReturnValue(result); + return info.GetReturnValue().Set(result); int deltas = git_diff_num_deltas(diffs); if (deltas != 1) { git_diff_free(diffs); - NanReturnValue(result); + return info.GetReturnValue().Set(result); } git_patch* patch; int patchStatus = git_patch_from_diff(&patch, diffs, 0); git_diff_free(diffs); if (patchStatus != GIT_OK) - NanReturnValue(result); + return info.GetReturnValue().Set(result); int hunks = git_patch_num_hunks(patch); for (int i = 0; i < hunks; i++) { @@ -371,40 +563,45 @@ NAN_METHOD(Repository::GetDiffStats) { } git_patch_free(patch); - result->Set(NanSymbol("added"), Number::New(added)); - result->Set(NanSymbol("deleted"), Number::New(deleted)); - NanReturnValue(result); + Nan::Set(result, + Nan::New("added").ToLocalChecked(), + Nan::New(added)); + Nan::Set(result, + Nan::New("deleted").ToLocalChecked(), + Nan::New(deleted)); + + return info.GetReturnValue().Set(result); } NAN_METHOD(Repository::GetHeadBlob) { - NanScope(); - if (args.Length() < 1) - NanReturnNull(); + Nan::HandleScope scope; + if (info.Length() < 1) + return info.GetReturnValue().Set(Nan::Null()); - std::string path(*String::Utf8Value(args[0])); + std::string path(*Nan::Utf8String(info[0])); - git_repository* repo = GetRepository(args); + git_repository* repo = GetRepository(info); git_reference* head; if (git_repository_head(&head, repo) != GIT_OK) - NanReturnNull(); + return info.GetReturnValue().Set(Nan::Null()); const git_oid* sha = git_reference_target(head); git_commit* commit; int commitStatus = git_commit_lookup(&commit, repo, sha); git_reference_free(head); if (commitStatus != GIT_OK) - NanReturnNull(); + return info.GetReturnValue().Set(Nan::Null()); git_tree* tree; int treeStatus = git_commit_tree(&tree, commit); git_commit_free(commit); if (treeStatus != GIT_OK) - NanReturnNull(); + return info.GetReturnValue().Set(Nan::Null()); git_tree_entry* treeEntry; if (git_tree_entry_bypath(&treeEntry, tree, path.c_str()) != GIT_OK) { git_tree_free(tree); - NanReturnNull(); + return info.GetReturnValue().Set(Nan::Null()); } git_blob* blob = NULL; @@ -414,53 +611,45 @@ NAN_METHOD(Repository::GetHeadBlob) { git_tree_entry_free(treeEntry); git_tree_free(tree); if (blob == NULL) - NanReturnNull(); + return info.GetReturnValue().Set(Nan::Null()); const char* content = static_cast(git_blob_rawcontent(blob)); - Handle value = NanSymbol(content); + Local value = Nan::New(content).ToLocalChecked(); git_blob_free(blob); - NanReturnValue(value); + return info.GetReturnValue().Set(value); } NAN_METHOD(Repository::GetIndexBlob) { - NanScope(); - if (args.Length() < 1) - NanReturnNull(); + Nan::HandleScope scope; + if (info.Length() < 1) + return info.GetReturnValue().Set(Nan::Null()); - std::string path(*String::Utf8Value(args[0])); + std::string path(*Nan::Utf8String(info[0])); - git_repository* repo = GetRepository(args); + git_repository* repo = GetRepository(info); git_index* index; if (git_repository_index(&index, repo) != GIT_OK) - NanReturnNull(); + return info.GetReturnValue().Set(Nan::Null()); git_index_read(index, 0); const git_index_entry* entry = git_index_get_bypath(index, path.data(), 0); if (entry == NULL) { git_index_free(index); - NanReturnNull(); + return info.GetReturnValue().Set(Nan::Null()); } git_blob* blob = NULL; - const git_oid* blobSha = &entry->oid; + const git_oid* blobSha = &entry->id; if (blobSha != NULL && git_blob_lookup(&blob, repo, blobSha) != GIT_OK) blob = NULL; git_index_free(index); if (blob == NULL) - NanReturnNull(); + return info.GetReturnValue().Set(Nan::Null()); const char* content = static_cast(git_blob_rawcontent(blob)); - Handle value = NanSymbol(content); + Local value = Nan::New(content).ToLocalChecked(); git_blob_free(blob); - NanReturnValue(value); -} - -int Repository::StatusCallback( - const char* path, unsigned int status, void* payload) { - std::map* statuses = - static_cast*>(payload); - statuses->insert(std::make_pair(std::string(path), status)); - return GIT_OK; + return info.GetReturnValue().Set(value); } int Repository::SubmoduleCallback( @@ -474,68 +663,104 @@ int Repository::SubmoduleCallback( } NAN_METHOD(Repository::Release) { - NanScope(); - Repository* repo = node::ObjectWrap::Unwrap(args.This()); + Nan::HandleScope scope; + Repository* repo = Nan::ObjectWrap::Unwrap(info.This()); if (repo->repository != NULL) { git_repository_free(repo->repository); repo->repository = NULL; } - NanReturnUndefined(); + info.GetReturnValue().SetUndefined(); +} + +unsigned GetCommitCount(git_repository *repository, git_oid *left_oid, git_oid *right_oid) { + git_revwalk *revwalk; + if (git_revwalk_new(&revwalk, repository) != GIT_OK) return 0; + + git_revwalk_push(revwalk, left_oid); + git_revwalk_hide(revwalk, right_oid); + + unsigned result = 0; + git_oid current_commit; + while (git_revwalk_next(¤t_commit, revwalk) == GIT_OK) result++; + git_revwalk_free(revwalk); + + return result; } -NAN_METHOD(Repository::GetCommitCount) { - NanScope(); - if (args.Length() < 2) - NanReturnValue(Number::New(0)); +class CompareCommitsWorker { + git_repository *repository; + std::string left_id; + std::string right_id; + unsigned ahead_count; + unsigned behind_count; + + public: + void Execute() { + git_oid left_oid; + if (git_oid_fromstr(&left_oid, left_id.c_str()) != GIT_OK) return; + + git_oid right_oid; + if (git_oid_fromstr(&right_oid, right_id.c_str()) != GIT_OK) return; + + git_oid merge_base; + if (git_merge_base(&merge_base, repository, &left_oid, &right_oid) != GIT_OK) return; + + ahead_count = GetCommitCount(repository, &left_oid, &merge_base); + behind_count = GetCommitCount(repository, &right_oid, &merge_base); + } - std::string fromCommitId(*String::Utf8Value(args[0])); - git_oid fromCommit; - if (git_oid_fromstr(&fromCommit, fromCommitId.c_str()) != GIT_OK) - NanReturnValue(Number::New(0)); + std::pair, Local> Finish() { + Local result = Nan::New(); + Nan::Set(result, Nan::New("ahead").ToLocalChecked(), Nan::New(ahead_count)); + Nan::Set(result, Nan::New("behind").ToLocalChecked(), Nan::New(behind_count)); + return {Nan::Null(), result}; + } - std::string toCommitId(*String::Utf8Value(args[1])); - git_oid toCommit; - if (git_oid_fromstr(&toCommit, toCommitId.c_str()) != GIT_OK) - NanReturnValue(Number::New(0)); + CompareCommitsWorker(git_repository *repository, Local js_left_id, + Local js_right_id) : repository(repository) { + left_id = *Nan::Utf8String(js_left_id); + right_id = *Nan::Utf8String(js_right_id); + } +}; - git_revwalk* revWalk; - if (git_revwalk_new(&revWalk, GetRepository(args)) != GIT_OK) - NanReturnValue(Number::New(0)); +NAN_METHOD(Repository::CompareCommits) { + if (info.Length() < 2) { + info.GetReturnValue().Set(Nan::Null()); + return; + } - git_revwalk_push(revWalk, &fromCommit); - git_revwalk_hide(revWalk, &toCommit); - git_oid currentCommit; - int count = 0; - while (git_revwalk_next(¤tCommit, revWalk) == GIT_OK) - count++; - git_revwalk_free(revWalk); - NanReturnValue(Number::New(count)); + CompareCommitsWorker worker(GetRepository(info), info[0], info[1]); + worker.Execute(); + info.GetReturnValue().Set(worker.Finish().second); } -NAN_METHOD(Repository::GetMergeBase) { - NanScope(); - if (args.Length() < 2) - NanReturnNull(); +NAN_METHOD(Repository::CompareCommitsAsync) { + class CompareCommitsAsyncWorker : public Nan::AsyncWorker { + CompareCommitsWorker worker; - std::string commitOneId(*String::Utf8Value(args[0])); - git_oid commitOne; - if (git_oid_fromstr(&commitOne, commitOneId.c_str()) != GIT_OK) - NanReturnNull(); + public: + void Execute() { + worker.Execute(); + } - std::string commitTwoId(*String::Utf8Value(args[1])); - git_oid commitTwo; - if (git_oid_fromstr(&commitTwo, commitTwoId.c_str()) != GIT_OK) - NanReturnNull(); + void HandleOKCallback() { + auto result = worker.Finish(); + Local argv[] = {result.first, result.second}; + callback->Call(2, argv); + } + + CompareCommitsAsyncWorker(Nan::Callback *callback, git_repository *repository, + Local js_left_id, Local js_right_id) + : Nan::AsyncWorker(callback), worker(repository, js_left_id, js_right_id) {} + }; - git_oid mergeBase; - if (git_merge_base( - &mergeBase, GetRepository(args), &commitOne, &commitTwo) == GIT_OK) { - char mergeBaseId[GIT_OID_HEXSZ + 1]; - git_oid_tostr(mergeBaseId, GIT_OID_HEXSZ + 1, &mergeBase); - NanReturnValue(NanSymbol(mergeBaseId)); + if (info.Length() < 2) { + info.GetReturnValue().Set(Nan::Null()); + return; } - NanReturnNull(); + auto callback = new Nan::Callback(Local::Cast(info[0])); + Nan::AsyncQueueWorker(new CompareCommitsAsyncWorker(callback, GetAsyncRepository(info), info[1], info[2])); } int Repository::DiffHunkCallback(const git_diff_delta* delta, @@ -548,121 +773,184 @@ int Repository::DiffHunkCallback(const git_diff_delta* delta, } NAN_METHOD(Repository::GetLineDiffs) { - NanScope(); - if (args.Length() < 2) - NanReturnNull(); - - std::string path(*String::Utf8Value(args[0])); - std::string text(*String::Utf8Value(args[1])); + Nan::HandleScope scope; + if (info.Length() < 2) + return info.GetReturnValue().Set(Nan::Null()); - git_repository* repo = GetRepository(args); + std::string text(*Nan::Utf8String(info[1])); - int useIndex = false; - if (args.Length() >= 3) { - Local optionsArg(Local::Cast(args[2])); - if (optionsArg->Get(NanSymbol("useIndex"))->BooleanValue()) - useIndex = true; - } + git_repository* repo = GetRepository(info); git_blob* blob = NULL; - if (useIndex) { - git_index* index; - if (git_repository_index(&index, repo) != GIT_OK) - NanReturnNull(); - git_index_read(index, 0); - const git_index_entry* entry = git_index_get_bypath(index, path.data(), 0); - if (entry == NULL) { - git_index_free(index); - NanReturnNull(); - } + int getBlobResult = GetBlob(info, repo, blob); - const git_oid* blobSha = &entry->oid; - if (blobSha != NULL && git_blob_lookup(&blob, repo, blobSha) != GIT_OK) - blob = NULL; - } else { - git_reference* head; - if (git_repository_head(&head, repo) != GIT_OK) - NanReturnNull(); + if (getBlobResult != 0) + return info.GetReturnValue().Set(Nan::Null()); - const git_oid* sha = git_reference_target(head); - git_commit* commit; - int commitStatus = git_commit_lookup(&commit, repo, sha); - git_reference_free(head); - if (commitStatus != GIT_OK) - NanReturnNull(); + std::vector ranges; + git_diff_options options = CreateDefaultGitDiffOptions(); - git_tree* tree; - int treeStatus = git_commit_tree(&tree, commit); - git_commit_free(commit); - if (treeStatus != GIT_OK) - NanReturnNull(); + if (info.Length() >= 3) { + Local optionsArg(Local::Cast(info[2])); + // Set GIT_DIFF_IGNORE_WHITESPACE when ignoreWhitespace: true + if (Nan::To(Nan::Get(optionsArg, Nan::New("ignoreAllSpace").ToLocalChecked()).ToLocalChecked()).FromJust()) + options.flags = GIT_DIFF_IGNORE_WHITESPACE; + // Set GIT_DIFF_IGNORE_WHITESPACE_CHANGE when ignoreWhitespaceChange: true + else if (Nan::To(Nan::Get(optionsArg, Nan::New("ignoreSpaceChange").ToLocalChecked()).ToLocalChecked()).FromJust()) + options.flags = GIT_DIFF_IGNORE_WHITESPACE_CHANGE; + // Set GIT_DIFF_IGNORE_WHITESPACE_EOL when ignoreEolWhitespace: true + else if (Nan::To(Nan::Get(optionsArg, Nan::New("ignoreSpaceAtEOL").ToLocalChecked()).ToLocalChecked()).FromJust() + || Nan::To(Nan::Get(optionsArg, Nan::New("ignoreEolWhitespace").ToLocalChecked()).ToLocalChecked()).FromJust()) + options.flags = GIT_DIFF_IGNORE_WHITESPACE_EOL; + // Set GIT_DIFF_NORMAL when none of the above are defined + else + options.flags = GIT_DIFF_NORMAL; + } - git_tree_entry* treeEntry; - if (git_tree_entry_bypath(&treeEntry, tree, path.c_str()) != GIT_OK) { - git_tree_free(tree); - NanReturnNull(); + options.context_lines = 0; + if (git_diff_blob_to_buffer(blob, NULL, text.data(), text.length(), NULL, + &options, NULL, NULL, DiffHunkCallback, NULL, + &ranges) == GIT_OK) { + Local v8Ranges = Nan::New(ranges.size()); + for (size_t i = 0; i < ranges.size(); i++) { + Local v8Range = Nan::New(); + Nan::Set(v8Range, + Nan::New("oldStart").ToLocalChecked(), + Nan::New(ranges[i].old_start)); + Nan::Set(v8Range, + Nan::New("oldLines").ToLocalChecked(), + Nan::New(ranges[i].old_lines)); + Nan::Set(v8Range, + Nan::New("newStart").ToLocalChecked(), + Nan::New(ranges[i].new_start)); + Nan::Set(v8Range, + Nan::New("newLines").ToLocalChecked(), + Nan::New(ranges[i].new_lines)); + Nan::Set(v8Ranges, i, v8Range); } - - const git_oid* blobSha = git_tree_entry_id(treeEntry); - if (blobSha != NULL && git_blob_lookup(&blob, repo, blobSha) != GIT_OK) - blob = NULL; - git_tree_entry_free(treeEntry); - git_tree_free(tree); + git_blob_free(blob); + return info.GetReturnValue().Set(v8Ranges); + } else { + git_blob_free(blob); + return info.GetReturnValue().Set(Nan::Null()); } +} - if (blob == NULL) - NanReturnNull(); +struct LineDiff { + git_diff_hunk hunk; + git_diff_line line; +}; - std::vector ranges; +int Repository::DiffLineCallback(const git_diff_delta* delta, + const git_diff_hunk* range, + const git_diff_line* line, + void* payload) { + LineDiff lineDiff; + lineDiff.hunk = *range; + lineDiff.line = *line; + std::vector * lineDiffs = + static_cast*>(payload); + lineDiffs->push_back(lineDiff); + return GIT_OK; +} + +NAN_METHOD(Repository::GetLineDiffDetails) { + Nan::HandleScope scope; + if (info.Length() < 2) + return info.GetReturnValue().Set(Nan::Null()); + + std::string text(*Nan::Utf8String(info[1])); + + git_repository* repo = GetRepository(info); + + git_blob* blob = NULL; + + int getBlobResult = GetBlob(info, repo, blob); + + if (getBlobResult != 0) + return info.GetReturnValue().Set(Nan::Null()); + + std::vector lineDiffs; git_diff_options options = CreateDefaultGitDiffOptions(); - // Set GIT_DIFF_IGNORE_WHITESPACE_EOL when ignoreEolWhitespace: true - if (args.Length() >= 3) { - Local optionsArg(Local::Cast(args[2])); - if (optionsArg->Get(NanSymbol("ignoreEolWhitespace"))->BooleanValue()) + if (info.Length() >= 3) { + Local optionsArg(Local::Cast(info[2])); + // Set GIT_DIFF_IGNORE_WHITESPACE when ignoreWhitespace: true + if (Nan::To(Nan::Get(optionsArg, Nan::New("ignoreAllSpace").ToLocalChecked()).ToLocalChecked()).FromJust()) + options.flags = GIT_DIFF_IGNORE_WHITESPACE; + // Set GIT_DIFF_IGNORE_WHITESPACE_CHANGE when ignoreWhitespaceChange: true + else if (Nan::To(Nan::Get(optionsArg, Nan::New("ignoreSpaceChange").ToLocalChecked()).ToLocalChecked()).FromJust()) + options.flags = GIT_DIFF_IGNORE_WHITESPACE_CHANGE; + // Set GIT_DIFF_IGNORE_WHITESPACE_EOL when ignoreEolWhitespace: true + else if (Nan::To(Nan::Get(optionsArg, Nan::New("ignoreSpaceAtEOL").ToLocalChecked()).ToLocalChecked()).FromJust() + || Nan::To(Nan::Get(optionsArg, Nan::New("ignoreEolWhitespace").ToLocalChecked()).ToLocalChecked()).FromJust()) options.flags = GIT_DIFF_IGNORE_WHITESPACE_EOL; + // Set GIT_DIFF_NORMAL when none of the above are defined + else + options.flags = GIT_DIFF_NORMAL; } options.context_lines = 0; if (git_diff_blob_to_buffer(blob, NULL, text.data(), text.length(), NULL, - &options, NULL, DiffHunkCallback, NULL, - &ranges) == GIT_OK) { - Local v8Ranges = Array::New(ranges.size()); - for (size_t i = 0; i < ranges.size(); i++) { - Local v8Range = Object::New(); - v8Range->Set(NanSymbol("oldStart"), Number::New(ranges[i].old_start)); - v8Range->Set(NanSymbol("oldLines"), Number::New(ranges[i].old_lines)); - v8Range->Set(NanSymbol("newStart"), Number::New(ranges[i].new_start)); - v8Range->Set(NanSymbol("newLines"), Number::New(ranges[i].new_lines)); - v8Ranges->Set(i, v8Range); + &options, NULL, NULL, NULL, DiffLineCallback, + &lineDiffs) == GIT_OK) { + Local v8Ranges = Nan::New(lineDiffs.size()); + for (size_t i = 0; i < lineDiffs.size(); i++) { + Local v8Range = Nan::New(); + + Nan::Set(v8Range, + Nan::New("oldLineNumber").ToLocalChecked(), + Nan::New(lineDiffs[i].line.old_lineno)); + Nan::Set(v8Range, + Nan::New("newLineNumber").ToLocalChecked(), + Nan::New(lineDiffs[i].line.new_lineno)); + Nan::Set(v8Range, + Nan::New("oldStart").ToLocalChecked(), + Nan::New(lineDiffs[i].hunk.old_start)); + Nan::Set(v8Range, + Nan::New("newStart").ToLocalChecked(), + Nan::New(lineDiffs[i].hunk.new_start)); + Nan::Set(v8Range, + Nan::New("oldLines").ToLocalChecked(), + Nan::New(lineDiffs[i].hunk.old_lines)); + Nan::Set(v8Range, + Nan::New("newLines").ToLocalChecked(), + Nan::New(lineDiffs[i].hunk.new_lines)); + Nan::Set(v8Range, + Nan::New("line").ToLocalChecked(), + Nan::New(lineDiffs[i].line.content, + lineDiffs[i].line.content_len) + .ToLocalChecked()); + + Nan::Set(v8Ranges, i, v8Range); } git_blob_free(blob); - NanReturnValue(v8Ranges); + return info.GetReturnValue().Set(v8Ranges); } else { git_blob_free(blob); - NanReturnNull(); + return info.GetReturnValue().Set(Nan::Null()); } } -Handle Repository::ConvertStringVectorToV8Array( +Local Repository::ConvertStringVectorToV8Array( const std::vector& vector) { size_t i = 0, size = vector.size(); - Local array = Array::New(size); + Local array = Nan::New(size); for (i = 0; i < size; i++) - array->Set(i, NanSymbol(vector[i].c_str())); + Nan::Set(array, i, Nan::New(vector[i].c_str()).ToLocalChecked()); return array; } NAN_METHOD(Repository::GetReferences) { - NanScope(); + Nan::HandleScope scope; - Local references = Object::New(); + Local references = Nan::New(); std::vector heads, remotes, tags; git_strarray strarray; - git_reference_list(&strarray, GetRepository(args)); + git_reference_list(&strarray, GetRepository(info)); for (unsigned int i = 0; i < strarray.count; i++) if (strncmp(strarray.strings[i], "refs/heads/", 11) == 0) @@ -674,17 +962,23 @@ NAN_METHOD(Repository::GetReferences) { git_strarray_free(&strarray); - references->Set(NanSymbol("heads"), ConvertStringVectorToV8Array(heads)); - references->Set(NanSymbol("remotes"), ConvertStringVectorToV8Array(remotes)); - references->Set(NanSymbol("tags"), ConvertStringVectorToV8Array(tags)); - - NanReturnValue(references); + Nan::Set(references, + Nan::New("heads").ToLocalChecked(), + ConvertStringVectorToV8Array(heads)); + Nan::Set(references, + Nan::New("remotes").ToLocalChecked(), + ConvertStringVectorToV8Array(remotes)); + Nan::Set(references, + Nan::New("tags").ToLocalChecked(), + ConvertStringVectorToV8Array(tags)); + + info.GetReturnValue().Set(references); } int branch_checkout(git_repository* repo, const char* refName) { git_reference* ref = NULL; git_object* git_obj = NULL; - git_checkout_opts opts = GIT_CHECKOUT_OPTS_INIT; + git_checkout_options opts = GIT_CHECKOUT_OPTIONS_INIT; opts.checkout_strategy = GIT_CHECKOUT_SAFE; int success = -1; @@ -702,28 +996,28 @@ int branch_checkout(git_repository* repo, const char* refName) { } NAN_METHOD(Repository::CheckoutReference) { - NanScope(); + Nan::HandleScope scope; - if (args.Length() < 1) - NanReturnValue(Boolean::New(false)); + if (info.Length() < 1) + return info.GetReturnValue().Set(Nan::New(false)); bool shouldCreateNewRef; - if (args.Length() > 1 && args[1]->BooleanValue()) + if (info.Length() > 1 && Nan::To(info[1]).FromJust()) shouldCreateNewRef = true; else shouldCreateNewRef = false; - std::string strRefName(*String::Utf8Value(args[0])); + std::string strRefName(*Nan::Utf8String(info[0])); const char* refName = strRefName.c_str(); - git_repository* repo = GetRepository(args); + git_repository* repo = GetRepository(info); if (branch_checkout(repo, refName) == GIT_OK) { - NanReturnValue(Boolean::New(true)); + return info.GetReturnValue().Set(Nan::New(true)); } else if (shouldCreateNewRef) { git_reference* head; if (git_repository_head(&head, repo) != GIT_OK) - NanReturnValue(Boolean::New(false)); + return info.GetReturnValue().Set(Nan::New(false)); const git_oid* sha = git_reference_target(head); git_commit* commit; @@ -731,7 +1025,7 @@ NAN_METHOD(Repository::CheckoutReference) { git_reference_free(head); if (commitStatus != GIT_OK) - NanReturnValue(Boolean::New(false)); + return info.GetReturnValue().Set(Nan::New(false)); git_reference* branch; // N.B.: git_branch_create needs a name like 'xxx', not 'refs/heads/xxx' @@ -743,24 +1037,80 @@ NAN_METHOD(Repository::CheckoutReference) { git_commit_free(commit); if (branchCreateStatus != GIT_OK) - NanReturnValue(Boolean::New(false)); + return info.GetReturnValue().Set(Nan::New(false)); git_reference_free(branch); if (branch_checkout(repo, refName) == GIT_OK) - NanReturnValue(Boolean::New(true)); + return info.GetReturnValue().Set(Nan::New(true)); } - NanReturnValue(Boolean::New(false)); + return info.GetReturnValue().Set(Nan::New(false)); } -Repository::Repository(Handle path) { - NanScope(); +NAN_METHOD(Repository::Add) { + Nan::HandleScope scope; + + git_repository* repository = GetRepository(info); + std::string path(*Nan::Utf8String(info[0])); - std::string repositoryPath(*String::Utf8Value(path)); - if (git_repository_open_ext( - &repository, repositoryPath.c_str(), 0, NULL) != GIT_OK) + git_index* index; + if (git_repository_index(&index, repository) != GIT_OK) { + const git_error* e = giterr_last(); + if (e != NULL) + return Nan::ThrowError(e->message); + else + return Nan::ThrowError("Unknown error opening index"); + } + // Modify the in-memory index. + if (git_index_add_bypath(index, path.c_str()) != GIT_OK) { + git_index_free(index); + const git_error* e = giterr_last(); + if (e != NULL) + return Nan::ThrowError(e->message); + else + return Nan::ThrowError("Unknown error adding path to index"); + } + // Write this change in the index back to disk, so it is persistent + if (git_index_write(index) != GIT_OK) { + git_index_free(index); + const git_error* e = giterr_last(); + if (e != NULL) + return Nan::ThrowError(e->message); + else + return Nan::ThrowError("Unknown error adding path to index"); + } + git_index_free(index); + info.GetReturnValue().Set(Nan::New(true)); +} + +Repository::Repository(Local path, Local search) { + Nan::HandleScope scope; + + int flags = 0; + if (!Nan::To(search).FromJust()) { + flags |= GIT_REPOSITORY_OPEN_NO_SEARCH; + } + + Nan::Utf8String repository_path(path); + int result = git_repository_open_ext(&repository, *repository_path, flags, NULL); + if (result != GIT_OK) { + repository = NULL; + async_repository = NULL; + return; + } + + result = git_repository_open_ext( + &async_repository, + git_repository_path(repository), + GIT_REPOSITORY_OPEN_NO_SEARCH, + NULL + ); + if (result != GIT_OK) { repository = NULL; + async_repository = NULL; + return; + } } Repository::~Repository() { @@ -768,4 +1118,8 @@ Repository::~Repository() { git_repository_free(repository); repository = NULL; } + if (async_repository != NULL) { + git_repository_free(async_repository); + async_repository = NULL; + } } diff --git a/src/repository.h b/src/repository.h index eae1ce6e..cddc4465 100644 --- a/src/repository.h +++ b/src/repository.h @@ -29,9 +29,9 @@ #include "nan.h" using namespace v8; // NOLINT -class Repository : public node::ObjectWrap { +class Repository : public Nan::ObjectWrap { public: - static void Init(Handle target); + static void Init(Local target); private: static NAN_METHOD(New); @@ -40,44 +40,55 @@ class Repository : public node::ObjectWrap { static NAN_METHOD(GetSubmodulePaths); static NAN_METHOD(Exists); static NAN_METHOD(GetHead); + static NAN_METHOD(GetHeadAsync); static NAN_METHOD(RefreshIndex); static NAN_METHOD(IsIgnored); static NAN_METHOD(IsSubmodule); static NAN_METHOD(GetConfigValue); static NAN_METHOD(SetConfigValue); static NAN_METHOD(GetStatus); + static NAN_METHOD(GetStatusAsync); + static NAN_METHOD(GetStatusForPath); static NAN_METHOD(CheckoutHead); static NAN_METHOD(GetReferenceTarget); static NAN_METHOD(GetDiffStats); static NAN_METHOD(GetIndexBlob); static NAN_METHOD(GetHeadBlob); - static NAN_METHOD(GetCommitCount); - static NAN_METHOD(GetMergeBase); + static NAN_METHOD(CompareCommits); + static NAN_METHOD(CompareCommitsAsync); static NAN_METHOD(Release); static NAN_METHOD(GetLineDiffs); + static NAN_METHOD(GetLineDiffDetails); static NAN_METHOD(GetReferences); static NAN_METHOD(CheckoutReference); + static NAN_METHOD(Add); - static int StatusCallback(const char *path, unsigned int status, - void *payload); static int DiffHunkCallback(const git_diff_delta *delta, const git_diff_hunk *hunk, void *payload); + static int DiffLineCallback(const git_diff_delta *delta, + const git_diff_hunk *hunk, + const git_diff_line *line, + void *payload); static int SubmoduleCallback(git_submodule *submodule, const char *name, void *payload); - static Handle ConvertStringVectorToV8Array( + static Local ConvertStringVectorToV8Array( const std::vector& vector); + static git_repository* GetRepository(Nan::NAN_METHOD_ARGS_TYPE args); + static git_repository* GetAsyncRepository(Nan::NAN_METHOD_ARGS_TYPE args); - static git_repository* GetRepository(_NAN_METHOD_ARGS_TYPE args); + static int GetBlob(Nan::NAN_METHOD_ARGS_TYPE args, + git_repository* repo, git_blob*& blob); static git_diff_options CreateDefaultGitDiffOptions(); - explicit Repository(Handle path); + explicit Repository(Local path, Local search); ~Repository(); git_repository* repository; + git_repository* async_repository; }; #endif // SRC_REPOSITORY_H_