diff --git a/.travis.yml b/.travis.yml index 8852a79..14607b3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,3 +4,7 @@ node_js: - "0.8" - "0.10" - "0.11" + +install: + - "npm install -g npm" + - "npm install" diff --git a/README.md b/README.md index 9e06532..c29f912 100644 --- a/README.md +++ b/README.md @@ -185,6 +185,14 @@ Commit some changes. ### `Repo#checkout(treeish, callback)` `git checkout ` +### `Repo#checkoutFile([files, options, ]callback)` +Checkout some files. + + * `files` - File(s) to checkout. Pass `'.'` or nothing to checkout all files. + * `options` - + - `force` - `Boolean` + * `callback` - Receives `(err)`. + ### `Repo#sync([[remote, ]branch, ]callback)` Sync the current branch with the remote, keeping all local changes intact. @@ -194,6 +202,17 @@ The following steps are carried out: `stash`, `pull`, `push`, `stash pop`. If th * `branch` - `String` (defaults to `master`). * `callback` - Receives `(err)`. +### `Repo#reset([treeish, options, ]callback)` +Checkout files. + + * `treeish` - The git object to reset to. Defaults to HEAD. + * `options` - + - `soft` - `Boolean` + - `mixed` - `Boolean` __default__ + - `hard` - `Boolean` + - `merge` - `Boolean` + - `keep` - `Boolean` + * `callback` - Receives `(err)`. ## Commit ### `Commit#id` diff --git a/package.json b/package.json index e65a9d8..b9fe6f4 100644 --- a/package.json +++ b/package.json @@ -17,7 +17,7 @@ }, "main": "./lib/index", "scripts": { - "test": "mocha --compilers coffee:'./node_modules/coffee-script/lib/coffee-script/coffee-script'", + "test": "mocha --compilers coffee:'./node_modules/coffee-script/lib/coffee-script/register'", "prepublish": "coffee -o lib -c src" }, "repository": { @@ -28,11 +28,11 @@ "underscore": "1.x.x" }, "devDependencies": { - "should": "~2.0.1", - "mocha": "1.x.x", + "should": "~4.0.4", + "mocha": "~1.20.1", "sinon": "~1.7.3", - "coffee-script": "1.6.x", - "rimraf": "2.0.x" + "coffee-script": "~1.7.1", + "fs-extra": "~0.9.1" }, "engines": { "node": "> 0.4.1" diff --git a/src/actor.coffee b/src/actor.coffee index 7f4a420..63fa71e 100644 --- a/src/actor.coffee +++ b/src/actor.coffee @@ -4,13 +4,13 @@ module.exports = class Actor constructor: (@name, @email) -> if @email @hash = crypto.createHash("md5").update(@email, "ascii").digest("hex") - + # Public: Get a string representation of the Actor. toString: -> "#{@name} <#{@email}>" - + # Public: Parse an Actor from a "bla " string. - # + # # Returns Actor. @from_string: (str) -> if /<.+>/.test str diff --git a/src/blob.coffee b/src/blob.coffee index 7965a7e..103143e 100644 --- a/src/blob.coffee +++ b/src/blob.coffee @@ -3,12 +3,12 @@ path = require 'path' module.exports = class Blob constructor: (@repo, attrs) -> {@id, @name, @mode} = attrs - + # Public: Get the blob contents. - # + # # callback - Receives `(err, data)`. - # - # Warning, this only returns files less than 200k, the standard buffer size for + # + # Warning, this only returns files less than 200k, the standard buffer size for # node's exec(). If you need to get bigger files, you should use dataStream() to # get a stream for the file's data # @@ -16,7 +16,7 @@ module.exports = class Blob @repo.git "cat-file", {p: true}, @id , (err, stdout, stderr) -> return callback err, stdout - + # Public: Get the blob contents as a stream # # returns - [dataStream, errstream] diff --git a/src/commit.coffee b/src/commit.coffee index 43902cd..c53774a 100644 --- a/src/commit.coffee +++ b/src/commit.coffee @@ -5,42 +5,42 @@ Tree = require './tree' module.exports = class Commit constructor: (@repo, @id, parents, tree, @author, @authored_date, @committer, @committed_date, @gpgsig, @message) -> # Public: Get the commit's Tree. - # + # # Returns Tree. @tree = _.memoize => (new Tree @repo, tree) - + # Public: Get the Commit's parent Commits. - # + # # Returns an Array of Commits. @parents = _.memoize => _.map parents, (parent) => new Commit @repo, parent - - + + toJSON: -> {@id, @author, @authored_date, @committer, @committed_date, @message} - - + + # Public: Find the matching commits. - # + # # callback - Receives `(err, commits)` - # + # @find_all: (repo, ref, options, callback) -> options = _.extend {pretty: "raw"}, options repo.git "rev-list", options, ref , (err, stdout, stderr) => return callback err if err return callback null, @parse_commits(repo, stdout) - - + + @find: (repo, id, callback) -> options = {pretty: "raw", "max-count": 1} repo.git "rev-list", options, id , (err, stdout, stderr) => return callback err if err return callback null, @parse_commits(repo, stdout)[0] - - + + @find_commits: (repo, ids, callback) -> commits = [] next = (i) -> @@ -53,10 +53,10 @@ module.exports = class Commit else callback null, commits next 0 - - + + # Internal: Parse the commits from `git rev-list` - # + # # Return Commit[] @parse_commits: (repo, text) -> commits = [] @@ -65,17 +65,17 @@ module.exports = class Commit id = _.last lines.shift().split(" ") break if !id tree = _.last lines.shift().split(" ") - + parents = [] while /^parent/.test lines[0] parents.push _.last lines.shift().split(" ") - + author_line = lines.shift() [author, authored_date] = @actor author_line - + committer_line = lines.shift() [committer, committed_date] = @actor committer_line - + gpgsig = [] if /^gpgsig/.test lines[0] gpgsig.push lines.shift().replace /^gpgsig /, '' @@ -86,22 +86,22 @@ module.exports = class Commit # not doing anything with this yet, but it's sometimes there if /^encoding/.test lines[0] encoding = _.last lines.shift().split(" ") - + lines.shift() - + message_lines = [] while /^ {4}/.test lines[0] message_lines.push lines.shift()[4..-1] - + while lines[0]? && !lines[0].length lines.shift() commits.push new Commit(repo, id, parents, tree, author, authored_date, committer, committed_date, gpgsig.join("\n"), message_lines.join("\n")) return commits - - + + # Internal: Parse the actor. - # + # # Returns [String name and email, Date] @actor: (line) -> [m, actor, epoch] = /^.+? (.*) (\d+) .*$/.exec line diff --git a/src/diff.coffee b/src/diff.coffee index 9c9a433..9890530 100644 --- a/src/diff.coffee +++ b/src/diff.coffee @@ -11,37 +11,37 @@ module.exports = class Diff if b_blob isnt null @b_blob = new Blob @repo, {id: b_blob} @b_sha = b_blob - + toJSON: -> {@a_path, @b_path, @a_mode, @b_mode, @new_file , @deleted_file, @diff, @renamed_file, @similarity_index} - + # Public: Parse the Diffs from the command output. - # + # # text - String stdout of a `git diff` command. - # + # # Returns Array of Diff. @parse: (repo, text) -> lines = text.split "\n" diffs = [] - + while lines.length && lines[0] # FIXME shift is O(n), so iterating n over O(n) operation might be O(n^2) [m, a_path, b_path] = ///^diff\s--git\s"?a/(.+?)"?\s"?b/(.+)"?$///.exec lines.shift() - + if /^old mode/.test lines[0] [m, a_mode] = /^old mode (\d+)/.exec lines.shift() [m, b_mode] = /^new mode (\d+)/.exec lines.shift() - + if !lines.length || /^diff --git/.test(lines[0]) diffs.push new Diff(repo, a_path, b_path, null, null, a_mode, b_mode, false, false, null) continue - + sim_index = 0 new_file = false deleted_file = false renamed_file = false - + if /^new file/.test lines[0] [m, b_mode] = /^new file mode (.+)$/.exec lines.shift() a_mode = null @@ -56,17 +56,17 @@ module.exports = class Diff # shift away the 2 `rename from/to ...` lines lines.shift() lines.shift() - + [m, a_blob, b_blob, b_mode] = ///^index\s([0-9A-Fa-f]+)\.\.([0-9A-Fa-f]+)\s?(.+)?$///.exec lines.shift() b_mode = b_mode.trim() if b_mode - + diff_lines = [] while lines[0] && !/^diff/.test(lines[0]) diff_lines.push lines.shift() diff = diff_lines.join "\n" - + diffs.push new Diff(repo, a_path, b_path, a_blob, b_blob, a_mode, b_mode, new_file, deleted_file, diff, renamed_file, sim_index) - + return diffs # Public: Parse the raw diff format from the command output. diff --git a/src/index.coffee b/src/index.coffee index 927b70e..2a61bdf 100644 --- a/src/index.coffee +++ b/src/index.coffee @@ -2,18 +2,18 @@ Repo = require './repo' # Public: Create a Repo from the given path. -# +# # Returns Repo. module.exports = Git = (path, bare=false) -> return new Repo path, bare # Public: Initialize a git repository. -# +# # path - The directory to run `git init .` in. # bare - Create a bare repository when true. # callback - Receives `(err, repo)`. -# +# Git.init = (path, bare, callback) -> [bare, callback] = [callback, bare] if !callback if bare @@ -26,11 +26,11 @@ Git.init = (path, bare, callback) -> return callback err, (new Repo path, bare) # Public: Clone a git repository. -# +# # repository - The repository to clone from. # path - The directory to clone into. # callback - Receives `(err, repo)`. -# +# Git.clone = (repository, path, callback) -> bash = "git clone #{repository} #{path}" exec bash, (err, stdout, stderr) -> diff --git a/src/ref.coffee b/src/ref.coffee index 0b66484..2aa4a2d 100644 --- a/src/ref.coffee +++ b/src/ref.coffee @@ -4,15 +4,15 @@ Commit = require './commit' exports.Ref = class Ref constructor: (@name, @commit) -> {@repo} = @commit - + # Public: Get a String representation of the Ref. toString: -> "#" - + # Internal: Find all refs. - # + # # options - (optional). - # + # # Returns Array of Ref. @find_all: (repo, type, RefClass, callback) -> repo.git.refs type, {}, (err, text) -> @@ -24,7 +24,7 @@ exports.Ref = class Ref [name, id] = ref.split(' ') names.push name ids.push id - + Commit.find_commits repo, ids, (err, commits) -> return callback err if err refs = [] @@ -36,7 +36,7 @@ exports.Ref = class Ref exports.Head = class Head extends Ref @find_all: (repo, callback) -> Ref.find_all repo, "head", Head, callback - + @current: (repo, callback) -> fs.readFile "#{repo.dot_git}/HEAD", (err, data) -> return callback err if err diff --git a/src/repo.coffee b/src/repo.coffee index 16d8541..983a490 100644 --- a/src/repo.coffee +++ b/src/repo.coffee @@ -73,7 +73,7 @@ module.exports = class Repo # # # Skip some (for pagination): # repo.commits "master", 30, 30, (err, commits) -> - # + # # # Do not limit commits amount # repo.commits "master", -1, (err, commits) -> # @@ -93,10 +93,10 @@ module.exports = class Repo Commit.find_all this, start, options, callback - # Internal: Returns current commit id - # + # Internal: Returns current commit id + # # callback - Receives `(err, id)`. - # + # current_commit_id: (callback) -> @git "rev-parse HEAD", {}, [] , (err, stdout, stderr) => @@ -104,10 +104,10 @@ module.exports = class Repo return callback null, _.first stdout.split "\n" - # Public: - # + # Public: + # # callback - Receives `(err, commit)` - # + # current_commit: (callback) -> @current_commit_id (err, commit_id) => return callback err if err @@ -241,7 +241,7 @@ module.exports = class Repo status: (callback) -> return Status(this, callback) - # Public: Show information about files in the index and the + # Public: Show information about files in the index and the # working tree. # # options - An Object of command line arguments to pass to @@ -333,6 +333,42 @@ module.exports = class Repo checkout: (treeish, callback) -> @git "checkout", {}, treeish, callback + # Public: Reset the git repo. + # + # treeish - The {String} to reset to. + # options - The {Object} containing one of the following items: + # :soft - {Boolean) + # :mixed - {Boolean) When no other option given git defaults to 'mixed'. + # :hard - {Boolean) + # :merge - {Boolean) + # :keep - {Boolean) + # callback - The {Function} to callback. + # + reset: (treeish, options, callback) -> + [options, callback] = [callback, options] if !callback + [treeish, callback] = [callback, treeish] if !callback + [treeish, options] = [options, treeish] if typeof treeish is 'object' + treeish ?= 'HEAD' + options ?= {} + + @git "reset", options, treeish, callback + + # Public: Checkout file(s) to the index + # + # files - Array of String paths; or a String path. If you want to + # checkout all files pass '.'.' + # options - Object (optional). + # "force" - Boolean + # callback - Receives `(err)`. + # + checkoutFile: (files, options, callback) -> + [options, callback] = [callback, options] if !callback + [files, callback] = [callback, files] if !callback + [files, options] = [options, files] if typeof files is 'object' + options ?= {} + files ?= '.' + files = [files] if _.isString files + @git "checkout", options, _.flatten(['--', files]), callback # Public: Commit some code. # @@ -357,13 +393,13 @@ module.exports = class Repo # options - Object (optional). # "all" - Boolean # callback - Receives `(err)`. - # + # add: (files, options, callback) -> [options, callback] = [callback, options] if !callback options ?= {} files = [files] if _.isString files @git "add", options, files, callback - + # Public: Remove files from the index. # # files - Array of String paths; or a String path. @@ -411,7 +447,7 @@ module.exports = class Repo return callback null else return callback null - + # Public: Pull the remotes from the master. # # Arguments: ([[remote_name, ]branch_name, ]callback) @@ -432,9 +468,9 @@ module.exports = class Repo @git "pull", {}, [remote, branch], (err, stdout, stderr) => return callback stderr if err return callback null - + # Internal: Parse the list of files from `git ls-files` - # + # # Return Files[] parse_lsFiles: (text,options) -> files = [] diff --git a/src/status.coffee b/src/status.coffee index c4f8953..fe591ed 100644 --- a/src/status.coffee +++ b/src/status.coffee @@ -1,8 +1,8 @@ # Public: Create a Status. -# +# # repo - A Repo. # callback - Receives `(err, status)` -# +# module.exports = S = (repo, callback) -> repo.git "status --porcelain", (err, stdout, stderr) -> status = new Status repo @@ -11,13 +11,13 @@ module.exports = S = (repo, callback) -> S.Status = class Status constructor: (@repo) -> - + # Internal: Parse the status from stdout of a `git status` command. parse: (text) -> @files = {} @clean = text.length == 0 for line in text.split("\n") - if line.length == 0 + if line.length == 0 continue file = line.substr 3 type = line.substr 0,2 diff --git a/src/submodule.coffee b/src/submodule.coffee index bc4cef7..2a13640 100644 --- a/src/submodule.coffee +++ b/src/submodule.coffee @@ -1,59 +1,59 @@ module.exports = class Submodule constructor: (@repo, options) -> {@id, @name, @mode} = options - + # Public: Get the URL of the submodule. - # + # # treeish - String treeish to look up the url within. # callback - Receives `(err, url)`. - # + # url: (treeish, callback) -> [treeish, callback] = [callback, treeish] if !callback treeish ?= "master" - + Submodule.config @repo, treeish, (err, config) => return callback err, config?[@name].url - - + + # Internal: Parse the `.gitmodules` file. - # + # # repo - A Repo. # treeish - String # callback - Receives `(err, config)`, where the config object has # the submodule names as its keys. - # + # # Examples - # + # # The following `.gitmodules` file: - # + # # [submodule "spoon-knife"] # path = spoon-knife # url = git://github.com/octocat/Spoon-Knife.git - # + # # would parse to: - # + # # { "spoon-knife": # { "path": "spoon-knife" # , "url": "git://github.com/octocat/Spoon-Knife.git" # } # } - # + # @config: (repo, treeish, callback) -> repo.tree(treeish).find ".gitmodules", (err, blob) -> return callback err if err blob.data (err, data) -> return callback err if err - + conf = {} lines = data.split "\n" current = null while lines.length line = lines.shift() - + if match = /^\[submodule "(.+)"\]$/.exec line current = match[1] conf[current] = {} else if match = /^\s+([^\s]+)\s+[=]\s+(.+)$/.exec line conf[current][match[1]] = match[2] - + return callback null, conf diff --git a/src/tag.coffee b/src/tag.coffee index 937259a..b57b81a 100644 --- a/src/tag.coffee +++ b/src/tag.coffee @@ -6,32 +6,32 @@ Actor = require './actor' module.exports = class Tag extends Ref @find_all: (repo, callback) -> Ref.find_all repo, "tag", Tag, callback - - + + # Public: Get the tag message. - # + # # Returns String. message: (callback) -> @lazy (err, data) -> return callback err if err return callback null, data.message - + # Public: Get the tag author. - # + # # Returns Actor. tagger: (callback) -> @lazy (err, data) -> return callback err if err return callback null, data.tagger - + # Public: Get the date that the tag was created. - # + # # Returns Date. tag_date: (callback) -> @lazy (err, data) -> return callback err if err return callback null, data.tag_date - + # Internal: Load the tag data. lazy: (callback) -> return callback null, @_lazy_data if @_lazy_data @@ -40,23 +40,23 @@ module.exports = class Tag extends Ref return callback err if err lines = stdout.split "\n" data = {} - + lines.shift() # object 4ae1cc5e6c7bb85b14ecdf221030c71d0654a42e lines.shift() # type commit lines.shift() # tag v0.0.2 - + # bob author_line = lines.shift() [m, author, epoch] = /^.+? (.*) (\d+) .*$/.exec author_line - + data.tagger = Actor.from_string author data.tag_date = new Date epoch - + lines.shift() message = [] while line = lines.shift() message.push line data.message = message.join("\n") - + return callback null, (@_lazy_data = data) diff --git a/src/tree.coffee b/src/tree.coffee index c0c5d5f..3d67221 100644 --- a/src/tree.coffee +++ b/src/tree.coffee @@ -11,13 +11,13 @@ module.exports = class Tree @id = options else {@id, @name, @mode} = options - - + + # Public: Get the children of the tree. - # + # # callback - Receives `(err, children)`, where children is a list # of Trees, Blobs, and Submodules. - # + # contents: (callback) -> return callback null, @_contents if @_contents @repo.git "ls-tree", {}, @id @@ -27,35 +27,35 @@ module.exports = class Tree for line in stdout.split("\n") @_contents.push @content_from_string(line) if line return callback null, @_contents - - + + # Public: Get the child blobs. - # + # # callback - Receives `(err, blobs)`. - # + # blobs: (callback) -> @contents (err, children) -> return callback err if err return callback null, _.filter children, (child) -> child instanceof Blob - - + + # Public: Get the child blobs. - # + # # callback - Receives `(err, trees)`. - # + # trees: (callback) -> @contents (err, children) -> return callback err if err return callback null, _.filter children, (child) -> child instanceof Tree - - + + # Public: Find the named object in this tree's contents. - # + # # callback - Receives `(err, obj)` where obj is Tree, Blob, or null # if not found. - # + # find: (file, callback) -> if /\//.test file [dir, rest] = file.split "/", 2 @@ -70,17 +70,17 @@ module.exports = class Tree if child.name == file return callback null, child return callback null, null - - + + # Internal: Parse a Blob or Tree from the line. - # + # # line - String - # + # # Examples - # + # # tree.content_from_string "100644 blob e4ff69dd8f19d770e9731b4bc424ccb695f0b5ad README.md" # # => # - # + # # Returns Blob, Tree or Submodule. content_from_string: (line) -> [mode, type, id, name] = line.split /[\t ]+/, 4 @@ -95,9 +95,9 @@ module.exports = class Tree new Submodule @repo, {id, name, mode} else throw new Error "Invalid object type: '#{type}'" - + # Public: Get a String representation of the Tree. - # + # # Returns String. toString: -> "#" diff --git a/test/actor.test.coffee b/test/actor.test.coffee index dbaab48..2e5be34 100644 --- a/test/actor.test.coffee +++ b/test/actor.test.coffee @@ -6,38 +6,38 @@ describe "Actor", -> actor = new Actor "bob", "bob@example.com" it "assigns @name", -> actor.name.should.eql "bob" - + it "assigns @email", -> actor.email.should.eql "bob@example.com" - - + + describe "#toString", -> actor = new Actor "bob", "bob@example.com" - + it "is a string representation of the actor", -> actor.toString().should.eql "bob " - - + + describe "#hash", -> actor = new Actor "bob", "bob@example.com" - + it "is the md5 hash of the email", -> actor.hash.should.eql "4b9bb80620f03eb3719e0a061c14283d" - - + + describe ".from_string", -> describe "with a name and email", -> actor = Actor.from_string "bob " it "parses the name", -> actor.name.should.eql "bob" - + it "parses the email", -> actor.email.should.eql "bob@example.com" - + describe "with only a name", -> actor = Actor.from_string "bob" it "parses the name", -> actor.name.should.eql "bob" - + it "does not parse the email", -> should.not.exist actor.email diff --git a/test/blob.test.coffee b/test/blob.test.coffee index 01d5d34..cd32bd9 100644 --- a/test/blob.test.coffee +++ b/test/blob.test.coffee @@ -27,7 +27,7 @@ describe "Blob", -> it "is a string", -> data.should.be.type "string" - data.should.include "Bla" + data.should.containEql "Bla" describe "of a file in a subdir", -> repo = git "#{__dirname}/fixtures/branched" @@ -41,7 +41,7 @@ describe "Blob", -> it "is a string", -> data.should.be.type "string" - data.should.include "!!!" + data.should.containEql "!!!" describe "#dataStream", -> describe "of a file off the root", -> @@ -57,7 +57,7 @@ describe "Blob", -> it "is a string", -> data.should.be.type "string" - data.should.include "Bla" + data.should.containEql "Bla" describe "of a file in a subdir", -> repo = git "#{__dirname}/fixtures/branched" @@ -73,6 +73,6 @@ describe "Blob", -> it "is a string", -> data.should.be.type "string" - data.should.include "!!!" + data.should.containEql "!!!" diff --git a/test/commit.test.coffee b/test/commit.test.coffee index 8ae427b..9b5630a 100644 --- a/test/commit.test.coffee +++ b/test/commit.test.coffee @@ -12,11 +12,11 @@ describe "Commit", -> repo.commits "master", (err, commits) -> tree = commits[0].tree() done err - + it "passes a tree", -> tree.should.be.an.instanceof Tree - - + + describe "#parents", -> repo = fixtures.branched parents = null @@ -26,10 +26,10 @@ describe "Commit", -> parents = commits[0].parents() parent = commits[1] done err - + it "is an Array of Commits", -> parents.should.be.an.instanceof Array parents[0].should.be.an.instanceof Commit - + it "has the parent commit", -> parents[0].id.should.eql parent.id diff --git a/test/diff.test.coffee b/test/diff.test.coffee index 8cbf1fa..eccb9a4 100644 --- a/test/diff.test.coffee +++ b/test/diff.test.coffee @@ -19,38 +19,38 @@ describe "Diff", -> +12 """ diffs = Diff.parse repo, stdout - + it "is an Array of Diffs", -> diffs.should.be.an.instanceof Array diffs[0].should.be.an.instanceof Diff - + it "has one diff", -> diffs.should.have.lengthOf 1 - + describe "the first diff", -> diff = diffs[0] - + it "has the repo", -> diff.repo.should.eql repo - + for blob in ["a_blob", "b_blob"] it "has a #{blob}", -> diff[blob].should.be.an.instanceof Blob - + for path in ["a_path", "b_path"] it "has a #{path}", -> diff[path].should.eql "file.txt" - + it "has a b_mode", -> diff.b_mode.should.eql "100644" - + for change in ["new_file", "renamed_file", "deleted_file"] it "#{change} is false", -> diff[change].should.be.false - + it "has a similarity_index of 0", -> diff.similarity_index.should.eql 0 - + describe ".parse_raw", -> describe "simple editing", -> repo = fixtures.tagged @@ -109,15 +109,15 @@ describe "Diff", -> -!!! """ diffs = Diff.parse repo, stdout - + it "has 2 diffs", -> diffs.should.have.lengthOf 2 - + describe "the second diff", -> diff = diffs[1] it "deletes a file", -> diff.deleted_file.should.be.true - + describe "create a file", -> repo = fixtures.branched stdout = """ @@ -130,8 +130,8 @@ describe "Diff", -> +!!! """ diffs = Diff.parse repo, stdout - + it "creates a file", -> diffs[0].new_file.should.be.true - + diff --git a/test/fixtures/reset/a.coffee b/test/fixtures/reset/a.coffee new file mode 100644 index 0000000..0db65dd --- /dev/null +++ b/test/fixtures/reset/a.coffee @@ -0,0 +1,28 @@ +# Assignment: +number = 42 +opposite = true + +# Conditions: +number = -42 if opposite + +# Functions: +square = (x) -> x * x + +# Arrays: +list = [1, 2, 3, 4, 5] + +# Objects: +math = + root: Math.sqrt + square: square + cube: (x) -> x * square x + +# Splats: +race = (winner, runners...) -> + print winner, runners + +# Existence: +alert "I knew it!" if elvis? + +# Array comprehensions: +cubes = (math.cube num for num in list) diff --git a/test/fixtures/reset/b.coffee b/test/fixtures/reset/b.coffee new file mode 100644 index 0000000..3463c49 --- /dev/null +++ b/test/fixtures/reset/b.coffee @@ -0,0 +1,9 @@ +grade = (student) -> + if student.excellentWork + "A+" + else if student.okayStuff + if student.triedHard then "B" else "B-" + else + "C" + +eldest = if 24 > 21 then "Liz" else "Ike" diff --git a/test/fixtures/reset/d.js b/test/fixtures/reset/d.js new file mode 100644 index 0000000..83357ab --- /dev/null +++ b/test/fixtures/reset/d.js @@ -0,0 +1,44 @@ +var cubes, list, math, num, number, opposite, race, square, + __slice = [].slice; + +number = 42; + +opposite = true; + +if (opposite) { + number = -42; +} + +square = function(x) { + return x * x; +}; + +list = [1, 2, 3, 4, 5]; + +math = { + root: Math.sqrt, + square: square, + cube: function(x) { + return x * square(x); + } +}; + +race = function() { + var runners, winner; + winner = arguments[0], runners = 2 <= arguments.length ? __slice.call(arguments, 1) : []; + return print(winner, runners); +}; + +if (typeof elvis !== "undefined" && elvis !== null) { + alert("I knew it!"); +} + +cubes = (function() { + var _i, _len, _results; + _results = []; + for (_i = 0, _len = list.length; _i < _len; _i++) { + num = list[_i]; + _results.push(math.cube(num)); + } + return _results; +})(); diff --git a/test/fixtures/reset/git.git/HEAD b/test/fixtures/reset/git.git/HEAD new file mode 100644 index 0000000..cb089cd --- /dev/null +++ b/test/fixtures/reset/git.git/HEAD @@ -0,0 +1 @@ +ref: refs/heads/master diff --git a/test/fixtures/reset/git.git/config b/test/fixtures/reset/git.git/config new file mode 100644 index 0000000..515f483 --- /dev/null +++ b/test/fixtures/reset/git.git/config @@ -0,0 +1,5 @@ +[core] + repositoryformatversion = 0 + filemode = true + bare = false + logallrefupdates = true diff --git a/test/fixtures/reset/git.git/index b/test/fixtures/reset/git.git/index new file mode 100644 index 0000000..936ee3b Binary files /dev/null and b/test/fixtures/reset/git.git/index differ diff --git a/test/fixtures/reset/git.git/objects/14/e5a1a3a459e05d486b98df2802ee4b30c33995 b/test/fixtures/reset/git.git/objects/14/e5a1a3a459e05d486b98df2802ee4b30c33995 new file mode 100644 index 0000000..7ca6048 Binary files /dev/null and b/test/fixtures/reset/git.git/objects/14/e5a1a3a459e05d486b98df2802ee4b30c33995 differ diff --git a/test/fixtures/reset/git.git/objects/41/2d321c36a34331125a77493b5dd41f5313a568 b/test/fixtures/reset/git.git/objects/41/2d321c36a34331125a77493b5dd41f5313a568 new file mode 100644 index 0000000..e4969ec Binary files /dev/null and b/test/fixtures/reset/git.git/objects/41/2d321c36a34331125a77493b5dd41f5313a568 differ diff --git a/test/fixtures/reset/git.git/objects/55/80d8a9feeadf1c36a73a2778c62206314aebac b/test/fixtures/reset/git.git/objects/55/80d8a9feeadf1c36a73a2778c62206314aebac new file mode 100644 index 0000000..7d66d78 Binary files /dev/null and b/test/fixtures/reset/git.git/objects/55/80d8a9feeadf1c36a73a2778c62206314aebac differ diff --git a/test/fixtures/reset/git.git/objects/56/02c1d7079f5ff3f49f341722c6d5baf93bb26e b/test/fixtures/reset/git.git/objects/56/02c1d7079f5ff3f49f341722c6d5baf93bb26e new file mode 100644 index 0000000..23c64cd --- /dev/null +++ b/test/fixtures/reset/git.git/objects/56/02c1d7079f5ff3f49f341722c6d5baf93bb26e @@ -0,0 +1,3 @@ +xm�� +�@D��+�����V��(�YX_���,9�l@�z�@ +�ݝ�7SJ(am�xD�;,;�=��B�7@]a git.init newRepositoryDir, (err, _repo) -> repo = _repo done err - it "inits a Repo", -> + it "inits a Repo", -> repo.should.be.an.instanceof Repo bare = repo.bare || false bare.should.be.false diff --git a/test/ref.test.coffee b/test/ref.test.coffee index 02242a3..a56f486 100644 --- a/test/ref.test.coffee +++ b/test/ref.test.coffee @@ -15,15 +15,15 @@ describe "Ref", -> Ref.find_all repo, "remote", Ref, (err, _remotes) -> remotes = _remotes done err - + it "is an Array of Refs", -> remotes.should.be.an.instanceof Array remotes[0].should.be.an.instanceof Ref - + it "the first item is a remote", -> remotes[0].name.should.eql "origin/HEAD" remotes[0].commit.should.be.an.instanceof Commit - + it "the second item is a remote", -> remotes[1].name.should.eql "origin/master" remotes[1].commit.should.be.an.instanceof Commit @@ -37,18 +37,18 @@ describe "Head", -> Head.find_all repo, (err, h) -> heads = h done err - + it "is an Array of Heads", -> heads.should.be.an.instanceof Array heads[0].should.be.an.instanceof Head - + it "contains the branches", -> heads.should.have.lengthOf 2 names = _.map heads, ((b) -> b.name) - names.should.include "master" - names.should.include "something" - - + names.should.containEql "master" + names.should.containEql "something" + + describe ".current", -> repo = fixtures.branched branch = null @@ -56,9 +56,9 @@ describe "Head", -> Head.current repo, (err, b) -> branch = b done err - + it "is a Head", -> branch.should.be.an.instanceof Head - + it "has the correct name", -> branch.name.should.eql "master" diff --git a/test/repo.test.coffee b/test/repo.test.coffee index 98bcea2..c97cf2c 100644 --- a/test/repo.test.coffee +++ b/test/repo.test.coffee @@ -1,8 +1,7 @@ should = require 'should' sinon = require 'sinon' -fs = require 'fs' -rimraf = require 'rimraf' +fs = require 'fs-extra' fixtures = require './fixtures' git = require '../src' Actor = require '../src/actor' @@ -22,10 +21,10 @@ describe "Repo", -> git_dir = __dirname + "/fixtures/junk_add" status = null file = null - + # given a fresh new repo before (done) -> - rimraf git_dir, (err) -> + fs.remove git_dir, (err) -> return done err if err fs.mkdir git_dir, '0755', (err) -> return done err if err @@ -35,7 +34,7 @@ describe "Repo", -> done() after (done) -> - rimraf git_dir, done + fs.remove git_dir, done describe "with only a file", -> file = 'foo.txt' @@ -48,7 +47,7 @@ describe "Repo", -> repo.status (err, _status) -> status = _status done err - + it "was added", -> status.files.should.have.a.property file status.files[file].staged.should.be.true @@ -66,7 +65,7 @@ describe "Repo", -> repo.status (err, _status) -> status = _status done err - + it "was added", -> status.files.should.have.a.property file status.files[file].staged.should.be.true @@ -142,7 +141,7 @@ describe "Repo", -> # given a fresh new repo before (done) -> - rimraf git_dir, (err) -> + fs.remove git_dir, (err) -> return done err if err? fs.mkdir git_dir, '0755', (err) -> return done err if err? @@ -154,7 +153,7 @@ describe "Repo", -> return done err if err? repo.add "#{git_dir}/foo.txt", (err) -> return done err if err? - repo.commit 'message with spaces', + repo.commit 'message with spaces', author: 'Someone ' , (err) -> return done err if err? @@ -163,7 +162,7 @@ describe "Repo", -> done err after (done) -> - rimraf git_dir, done + fs.remove git_dir, done it "has right message", (done) -> commit.message.should.eql 'message with spaces' @@ -224,7 +223,7 @@ describe "Repo", -> done err it "is the latest commit on the tag", -> - commits[0].message.should.include "commit 5" + commits[0].message.should.containEql "commit 5" describe "limit the number of commits", -> repo = fixtures.tagged @@ -246,7 +245,7 @@ describe "Repo", -> done err it "returns 2 commits", -> - commits[0].message.should.include "commit 4" + commits[0].message.should.containEql "commit 4" describe "with or without gpg signature", -> repo = fixtures.gpgsigned @@ -264,18 +263,17 @@ describe "Repo", -> it "contains the correct signature", -> commits[1].gpgsig.should.equal """ - -----BEGIN PGP SIGNATURE----- - Version: GnuPG v2.0.22 (GNU/Linux) - - iQEcBAABAgAGBQJTQw8qAAoJEL0/h9tqDFPiP3UH/RwxUS90+6DEkThcKMmV9H4K - dr+D0H0z2ViMq3AHSmCydv5dWr3bupl2XyaLWWuRCxAJ78xuf98qVRIBfT/FKGeP - fz+GtXkv3naCD12Ay6YiwfxSQhxFiJtRwP5rla2i7hlV3BLFPYCWTtL8OLF4CoRm - 7aF5EuDr1x7emEDyu1rf5E59ttSIySuIw0J1mTjrPCkC6lsowzTJS/vaCxZ3e7fN - iZE6VEWWY/iOxd8foJH/VZ3cfNKjfi8+Fh8t7o9ztjYTQAOZUJTn2CHB7Wkyr0Ar - HNM3v26gPFpb7UkHw0Cq2HWNV/Z7cbQc/BQ4HmrmuBPB6SWNOaBN751BbQKnPcA= - =IusH - -----END PGP SIGNATURE----- - """ + -----BEGIN#{" "}PGP#{" "}SIGNATURE----- + #{" "}Version:#{" "}GnuPG#{" "}v2.0.22#{" "}(GNU/Linux) + #{" "} + #{" "}iQEcBAABAgAGBQJTQw8qAAoJEL0/h9tqDFPiP3UH/RwxUS90+6DEkThcKMmV9H4K + #{" "}dr+D0H0z2ViMq3AHSmCydv5dWr3bupl2XyaLWWuRCxAJ78xuf98qVRIBfT/FKGeP + #{" "}fz+GtXkv3naCD12Ay6YiwfxSQhxFiJtRwP5rla2i7hlV3BLFPYCWTtL8OLF4CoRm + #{" "}7aF5EuDr1x7emEDyu1rf5E59ttSIySuIw0J1mTjrPCkC6lsowzTJS/vaCxZ3e7fN + #{" "}iZE6VEWWY/iOxd8foJH/VZ3cfNKjfi8+Fh8t7o9ztjYTQAOZUJTn2CHB7Wkyr0Ar + #{" "}HNM3v26gPFpb7UkHw0Cq2HWNV/Z7cbQc/BQ4HmrmuBPB6SWNOaBN751BbQKnPcA= + #{" "}=IusH + #{" "}-----END#{" "}PGP#{" "}SIGNATURE-----""" describe "#tree", -> repo = fixtures.branched @@ -286,8 +284,8 @@ describe "Repo", -> it "checks out branch:master", (done) -> repo.tree().blobs (err, blobs) -> blobs[0].data (err, data) -> - data.should.include "Bla" - data.should.not.include "Bla2" + data.should.containEql "Bla" + data.should.not.containEql "Bla2" done err describe "specific branch", -> @@ -297,7 +295,7 @@ describe "Repo", -> it "checks out branch:something", (done) -> repo.tree("something").blobs (err, blobs) -> blobs[0].data (err, data) -> - data.should.include "Bla2" + data.should.containEql "Bla2" done err @@ -399,7 +397,7 @@ describe "Repo", -> git_dir = __dirname + "/fixtures/junk_create_tag" before (done) -> - rimraf git_dir, (err) -> + fs.remove git_dir, (err) -> return done err if err fs.mkdir git_dir, 0o755, (err) -> return done err if err @@ -413,7 +411,7 @@ describe "Repo", -> repo.commit "initial commit", {all: true}, done after (done) -> - rimraf git_dir, done + fs.remove git_dir, done it "creates a tag", (done) -> repo.create_tag "foo", done @@ -487,3 +485,170 @@ describe "Repo", -> should.exist err done() + describe "#reset", -> + repo = null + git_dir = __dirname + "/fixtures/junk_reset" + status = null + file = "bla.txt" + + # given a fresh new repo + beforeEach (done) -> + status = null + fs.remove git_dir, (err) -> + return done err if err + fs.copy "#{__dirname}/fixtures/reset", "#{git_dir}", (err) -> + return done err if err + fs.rename "#{git_dir}/git.git", "#{git_dir}/.git", (err) -> + return done err if err + git.init git_dir, (err) -> + repo = git git_dir + fs.writeFile "#{git_dir}/#{file}", "hello", (err) -> + return done err if err? + repo.add "#{git_dir}/#{file}", (err) -> + done err + + after (done) -> + fs.remove git_dir, (err) -> + done err + + describe "reset without specific treeish (defaults to HEAD)", -> + describe "reset (--mixed)", -> + beforeEach (done) -> + repo.reset -> + repo.status (err, _status) -> + status = _status + done err + + it "removes the file from index, leaves it in working tree", -> + status.files.should.have.a.property file + status.files[file].staged.should.be.false + status.files[file].tracked.should.be.false + status.files[file].should.not.have.a.property 'type' + + describe "reset --soft", -> + beforeEach (done) -> + repo.reset {soft: true}, -> + repo.status (err, _status) -> + status = _status + done err + + it "leaves the added file in the index", -> + status.files.should.have.a.property file + status.files[file].staged.should.be.true + status.files[file].tracked.should.be.true + status.files[file].type.should.eql 'A' + + describe "reset --hard", -> + beforeEach (done) -> + repo.reset {hard: true}, -> + repo.status (err, _status) -> + status = _status + done err + + it "removes the file from index and working tree", -> + status.files.should.not.have.a.property file + + describe "reset to specific treeish", -> + describe "reset (--mixed) HEAD~1", -> + beforeEach (done) -> + repo.reset 'HEAD~1', -> + repo.status (err, _status) -> + status = _status + done err + + it "resets to HEAD~1, changes stay in the working tree", -> + status.files.should.have.a.property file + status.files[file].staged.should.be.false + status.files[file].tracked.should.be.false + status.files[file].should.not.have.a.property 'type' + + status.files.should.have.a.property 'rawr.txt' + status.files['rawr.txt'].staged.should.be.false + status.files['rawr.txt'].tracked.should.be.false + status.files['rawr.txt'].should.not.have.a.property 'type' + + describe "reset --soft HEAD~1", -> + beforeEach (done) -> + repo.reset 'HEAD~1', {soft: true}, -> + repo.status (err, _status) -> + status = _status + done err + + it "resets to HEAD~1, changes stay in the index and working tree", -> + status.files.should.have.a.property file + status.files[file].staged.should.be.true + status.files[file].tracked.should.be.true + status.files[file].type.should.eql 'A' + + status.files.should.have.a.property 'rawr.txt' + status.files['rawr.txt'].staged.should.be.true + status.files['rawr.txt'].tracked.should.be.true + status.files['rawr.txt'].type.should.eql 'AM' + + describe "reset --hard HEAD~1", -> + beforeEach (done) -> + repo.reset 'HEAD~1', {hard: true}, -> + repo.status (err, _status) -> + status = _status + done err + + it "resets to HEAD~1, all changes get discarded completely", -> + status.files.should.not.have.a.property file + status.files.should.not.have.a.property 'rawr.txt' + + describe "#checkoutFile", -> + repo = null + git_dir = __dirname + "/fixtures/junk_checkoutFile" + status = null + file = "bla.txt" + + # given a fresh new repo + beforeEach (done) -> + status = null + fs.remove git_dir, (err) -> + return done err if err + fs.copy "#{__dirname}/fixtures/reset", "#{git_dir}", (err) -> + return done err if err + fs.rename "#{git_dir}/git.git", "#{git_dir}/.git", (err) -> + git.init git_dir, (err) -> + return done err if err + repo = git git_dir + fs.writeFile "#{git_dir}/#{file}", "hello", (err) -> + return done err if err? + repo.add "#{git_dir}/#{file}", (err) -> + done err + + after (done) -> + fs.remove git_dir, (err) -> + done err + + describe "passing no explicit files", -> + beforeEach (done) -> + repo.checkoutFile -> + repo.status (err, _status) -> + status = _status + done err + + it "discards changes in the working tree for all files", -> + status.files.should.have.a.property file + status.files[file].staged.should.be.true + status.files[file].tracked.should.be.true + status.files[file].type.should.eql 'A' + + status.files.should.have.a.property 'rawr.txt' + status.files['rawr.txt'].staged.should.be.true + status.files['rawr.txt'].tracked.should.be.true + status.files['rawr.txt'].type.should.eql 'M' + + describe "passing an explicit file", -> + beforeEach (done) -> + repo.checkoutFile 'rawr.txt', -> + repo.status (err, _status) -> + status = _status + done err + + it "discard changes to the specified file", -> + status.files.should.have.a.property 'rawr.txt' + status.files['rawr.txt'].staged.should.be.true + status.files['rawr.txt'].tracked.should.be.true + status.files['rawr.txt'].type.should.eql 'M' diff --git a/test/tag.test.coffee b/test/tag.test.coffee index 98627be..4ef4aa2 100644 --- a/test/tag.test.coffee +++ b/test/tag.test.coffee @@ -11,19 +11,19 @@ describe "Tag", -> Tag.find_all repo, (err, _tags) -> tags = _tags done err - + it "is an Array of Tags", -> tags.should.be.an.instanceof Array tags[0].should.be.an.instanceof Tag - + pref = "the tag" it "#{pref} has the correct name", -> tags[0].name.should.eql "tag-1" - + it "#{pref} has the correct commit", -> tags[0].commit.id.should.eql "32bbb351de16c3e404b3b7c77601c3d124e1e1a1" - - + + describe "#message", -> repo = fixtures.tagged tags = null @@ -35,10 +35,10 @@ describe "Tag", -> tags[0].message (err, _message) -> message = _message done err - + it "is the correct message", -> - message.should.include "the first tag" - + message.should.containEql "the first tag" + it "has the correct commit", -> tags[0].commit.message.should.eql "commit 5" diff --git a/test/tree.test.coffee b/test/tree.test.coffee index f10555b..153ed25 100644 --- a/test/tree.test.coffee +++ b/test/tree.test.coffee @@ -11,77 +11,77 @@ describe "Tree", -> repo = fixtures.branched tree = repo.tree() contents = null - + before (done) -> tree.contents (err, _contents) -> contents = _contents done err - + it "is an Array", -> contents.should.be.an.instanceof Array - + it "contains a Blob", -> contents[0].should.be.an.instanceof Blob contents[0].name.should.eql "README.md" - + it "contains a Tree", -> contents[1].should.be.an.instanceof Tree contents[1].name.should.eql "some" - + describe "with submodules", -> repo = fixtures.submodule tree = repo.tree() contents = null - + before (done) -> tree.contents (err, _contents) -> contents = _contents done err - + it "contains a Submodule", (done) -> contents[2].should.be.an.instanceof Submodule contents[2].name.should.eql "spoon-knife" contents[2].url (err, url) -> url.should.eql "git://github.com/octocat/Spoon-Knife.git" done err - - + + describe "#blobs", -> repo = fixtures.branched tree = repo.tree() blobs = null - + before (done) -> tree.blobs (err, _blobs) -> blobs = _blobs done err - + it "has only 1 item", -> blobs.should.have.lengthOf 1 - + it "contains a Blob", -> blobs[0].should.be.an.instanceof Blob blobs[0].name.should.eql "README.md" - - + + describe "#trees", -> repo = fixtures.branched tree = repo.tree() trees = null - + before (done) -> tree.trees (err, _trees) -> trees = _trees done err - + it "has only 1 item", -> trees.should.have.lengthOf 1 - + it "contains a Tree", -> trees[0].should.be.an.instanceof Tree trees[0].name.should.eql "some" - - + + describe "#find", -> repo = fixtures.branched tree = repo.tree() @@ -91,29 +91,29 @@ describe "Tree", -> tree.find "README.md", (err, _blob) -> blob = _blob done err - + it "finds the Blob", -> blob.should.be.an.instanceof Blob blob.name.should.eql "README.md" - + describe "find a directory", -> subtree = null before (done) -> tree.find "some", (err, _tree) -> subtree = _tree done err - + it "finds the Tree", -> subtree.should.be.an.instanceof Tree subtree.name.should.eql "some" - + describe "find inside a directory", -> blob = null before (done) -> tree.find "some/hi.txt", (err, _blob) -> blob = _blob done err - + it "finds the Blob", -> blob.should.be.an.instanceof Blob blob.name.should.eql "hi.txt" @@ -124,7 +124,7 @@ describe "Tree", -> tree.find "nonexistant", (err, _tree) -> subtree = _tree done err - + it "is null", -> should.not.exist subtree