[Pkg-javascript-commits] [grunt] 01/08: New upstream version 1.0.1

Sruthi Chandran srud-guest at moszumanska.debian.org
Fri Nov 11 08:50:00 UTC 2016


This is an automated email from the git hooks/post-receive script.

srud-guest pushed a commit to branch master
in repository grunt.

commit 200235835f9277b4db6755ad17006ad6877ba236
Author: Sruthi <srud at disroot.org>
Date:   Fri Nov 11 13:35:55 2016 +0530

    New upstream version 1.0.1
---
 .gitattributes                                     |   2 +
 .jscsrc                                            |   5 +
 .jshintrc                                          |  14 +
 .travis.yml                                        |  15 +-
 AUTHORS                                            |   1 +
 CHANGELOG                                          | 100 +++++-
 Gruntfile.js                                       |  75 ++---
 LICENSE                                            |  35 ++
 LICENSE-MIT                                        |  22 --
 README.md                                          |  11 +-
 appveyor.yml                                       |  29 ++
 bin/grunt                                          |   3 +
 internal-tasks/bump.js                             | 143 +++++++++
 internal-tasks/subgrunt.js                         |  25 ++
 lib/grunt.js                                       |  28 +-
 lib/grunt/cli.js                                   |  79 +----
 lib/grunt/config.js                                |  15 +-
 lib/grunt/event.js                                 |   9 -
 lib/grunt/fail.js                                  |  19 +-
 lib/grunt/file.js                                  |  82 +++--
 lib/grunt/help.js                                  |  10 -
 lib/grunt/log.js                                   | 352 ---------------------
 lib/grunt/option.js                                |   9 -
 lib/grunt/task.js                                  |  85 ++---
 lib/grunt/template.js                              |  13 +-
 lib/grunt/util.js                                  | 189 -----------
 lib/util/exit.js                                   |  26 --
 lib/util/namespace.js                              |  63 ----
 lib/util/task.js                                   |  46 ++-
 package.json                                       |  79 ++---
 test/fixtures/Gruntfile-cli.js                     |  16 +
 test/fixtures/Gruntfile-print-text.js              |   8 -
 test/fixtures/exec.cmd                             |   1 -
 test/fixtures/exec.sh                              |   2 -
 test/fixtures/expand/css/baz.css                   |   1 +
 test/fixtures/expand/css/qux.css                   |   1 +
 test/fixtures/expand/deep/deep.txt                 |   1 +
 test/fixtures/expand/deep/deeper/deeper.txt        |   1 +
 .../expand/deep/deeper/deepest/deepest.txt         |   1 +
 test/fixtures/expand/js/bar.js                     |   1 +
 test/fixtures/expand/js/foo.js                     |   1 +
 .../node_modules/grunt-foo-plugin/package.json     |   6 +
 .../node_modules/grunt-foo-plugin/tasks/foo.js     |   7 +
 test/fixtures/load-npm-tasks/package.json          |   7 +
 test/fixtures/spawn.js                             |   5 +-
 test/grunt/cli_test.js                             |  53 ++++
 test/grunt/config_test.js                          |  27 +-
 test/grunt/event_test.js                           |   2 +-
 test/grunt/file_test.js                            | 169 ++++++++--
 test/grunt/log_test.js                             | 224 -------------
 test/grunt/option_test.js                          |   8 +-
 test/grunt/template_test.js                        |   2 +-
 test/grunt/util_test.js                            | 301 ------------------
 test/gruntfile/load-npm-tasks.js                   |  42 +++
 test/gruntfile/multi-task-files.js                 | 118 ++++++-
 test/util/namespace_test.js                        |  51 ---
 test/util/task_test.js                             |  20 +-
 57 files changed, 1042 insertions(+), 1618 deletions(-)

diff --git a/.gitattributes b/.gitattributes
new file mode 100644
index 0000000..222ba62
--- /dev/null
+++ b/.gitattributes
@@ -0,0 +1,2 @@
+* text=auto
+/test/fixtures/*.txt text eol=lf
diff --git a/.jscsrc b/.jscsrc
new file mode 100644
index 0000000..4a6720e
--- /dev/null
+++ b/.jscsrc
@@ -0,0 +1,5 @@
+{
+  "preset": "grunt",
+  // Nullified until the files in test/ can be cleaned
+  "maximumLineLength": null
+}
\ No newline at end of file
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 0000000..e3f057e
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,14 @@
+{
+  "boss": true,
+  "curly": true,
+  "eqeqeq": true,
+  "eqnull": true,
+  "immed": true,
+  "latedef": "nofunc",
+  "newcap": true,
+  "noarg": true,
+  "node": true,
+  "sub": true,
+  "undef": true,
+  "unused": true
+}
diff --git a/.travis.yml b/.travis.yml
index b30fcb7..c7b8f8c 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -1,5 +1,16 @@
+sudo: false
 language: node_js
 node_js:
-  - 0.8
+  - "0.10"
+  - "0.12"
+  - "4.0"
+  - "4.1"
+  - "4.2"
+  - "5"
+  - "iojs"
+before_install:
+  - npm install -g npm
 before_script:
-  - npm install -g grunt-cli
+  - npm uninstall grunt # https://github.com/npm/npm/issues/3958
+matrix:
+  fast_finish: true
diff --git a/AUTHORS b/AUTHORS
index d9cace8..0a17765 100644
--- a/AUTHORS
+++ b/AUTHORS
@@ -2,3 +2,4 @@
 Kyle Robinson Young (http://dontkry.com/)
 Tyler Kellen (http://goingslowly.com)
 Sindre Sorhus (http://sindresorhus.com)
+Vlad Filippov (http://vladfilippov.com/)
diff --git a/CHANGELOG b/CHANGELOG
index 81e937b..935c708 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,14 +1,110 @@
+v1.0.1
+  date: 2016-04-05
+  changes:
+    - minor fix for npm issues when installing grunt and grunt-cli at the same time. Pull #1500.
+v1.0.0
+  date: 2016-04-04
+  changes:
+    - full list of changes is on http://gruntjs.com, please also see changes from 1.0.0-rc1.
+    - if you have a Grunt plugin that includes `grunt` in the `peerDependencies`,
+      we recommend tagging with `"grunt": "">= 0.4.0"` and publishing a new version on npm.
+    - Prevent async callback from being called multiple times. Pull #1464.
+    - Update copyright to jQuery Foundation and remove redundant headers. Fixes #1478.
+    - Update glob to 7.0.x. Fixes #1467.
+    - Removing duplicate BOM strip code. Pull #1482.
+    - Update legacy log and util to 1.0.0.
+    - Update to latest cli ~1.2.0.
+    - Use grunt-known-options for shared options between Grunt and grunt-cli.
+v1.0.0-rc1
+  date: 2016-02-11
+  changes:
+    - full list of changes is on http://gruntjs.com
+    - if you have a Grunt plugin that includes `grunt` in the `peerDependencies`,
+      we recommend tagging with `"grunt": "">= 0.4.0"`
+    - `coffee-script` is upgraded to `~1.10.0` which could incur breaking changes
+      when using the language with plugins and Gruntfiles.
+    - `nopt` is upgraded to `~3.0.6` which has fixed many issues, including passing
+      multiple arguments and dealing with numbers as options. Be aware previously
+      `--foo bar` used to pass the value `'bar'` to the option `foo`. It will now
+      set the option `foo` to `true` and run the task `bar`.
+    -`glob` is upgraded to `~6.0.4` and `minimatch` is upgraded to `~3.0.0`. Results
+      are now sorted by default with `grunt.file.expandMapping()`. Pass the
+      `nosort: true` option if you don't want the results to be sorted.
+    - `lodash` was upgraded to `~4.3.0`. Many changes have occurred. Some of which
+      that directly effect Grunt are `grunt.util._.template()` returns a compile
+      function and `grunt.util._.flatten` no longer flattens deeply.
+      `grunt.util._` is deprecated and we highly encourage you to
+      `npm install lodash` and `var _ = require('lodash')` to use `lodash`.
+      Please see the lodash changelog for a full list of changes: https://github.com/lodash/lodash/wiki/Changelog
+    - `iconv-lite` is upgraded to `~0.4.13` and strips the BOM by default.
+    - `js-yaml` is upgraded to `~3.5.2` and may affect `grunt.file.readYAML`.
+      We encourage you to please `npm install js-yaml` and use
+      `var YAML = require('js-yaml')` directly in case of future deprecations.
+    - A file `mode` option can be passed into
+      [grunt.file.write()](http://gruntjs.com/api/grunt.file#grunt.file.write).
+    - `Done, without errors.` was changed to `Done.` to avoid failing by mistake
+      on the word `errors`.
+v0.4.5:
+  date: 2014-05-12
+  changes:
+    - Updated rimraf to 2.2.8. Closes gh-1134.
+    - Moved grunt.log into separate grunt-legacy-log module.
+    - Updated grunt-legacy-util to 0.2.0. Closes gh-971, gh-1129, gh-1118.
+    - Added grunt.task.exists method to check if a task exists. Closes gh-1131.
+    - Added grunt.config.merge method to deep merge config data. See gh-1039.
+    - Fixed symlink issues with 'file.isPathCwd' and 'file.doesPathContain'. Closes gh-1112.
+    - Config and util.recurse no longer mangle Buffer instances. See gh-971.
+    - Config and util.recurse now enumerate inherited object properties. See gh-1129.
+    - Config and util.recurse now throw useful circular reference error. See gh-1118.
+    - Warn instead of error when no new tasks found via '.loadTasks' method. Closes gh-1059.
+    - Added Windows CI testing. Closes gh-1110.
+    - Removed "CONTRIBUTING.md" from .npmignore. Closes gh-1093.
+v0.4.4:
+  date: 2014-03-12
+  changes:
+    - Only signal completion of tasks async if grunt.task.start is invoked with `{asyncDone:true}`.
+v0.4.3:
+  date: 2014-03-07
+  changes:
+    - When devving Grunt, do "npm install && npm uninstall grunt" (isaacs/npm#3958)
+    - Grunt is now tested on Node.js 0.11
+    - Extracted internal "util" lib to "grunt-legacy-util" lib
+    - task.normalizeMultiTaskFiles now flattens nested "files" arrays. Closes gh-1034.
+    - Better error in renameTask if task doesn't exist. Closes gh-1058.
+    - Update rimraf to latest version. Closes gh-1043.
+    - Empty string "ext" should strip extension. Closes gh-1087.
+    - Add expandMapping .extDot option. Can be 'first' or 'last' but defaults to 'first'. Closes gh-979.
+    - Add default array for util.spawn optional args. Closes gh-1064.
+    - util.spawn "grunt" option now uses proper Node, passes Node exec options. Closes gh-980, gh-981, gh-877.
+    - Make all tasks asynchronous to reduce call stack. Closes gh-1026.
+    - Fix <%= grunt.task.current.target %> in Multitask files. Closes gh-994.
+    - Generalize cli tests, see gh-983, gh-991.
+    - --debug option can optionally be Boolean. Closes Gh-983, gh-991.
+v0.4.2:
+  date: 2013-11-21
+  changes:
+    - Extract internal "namespace" lib to external "getobject" lib.
+    - '"Grunt collections" are now deprecated, use peerDependencies. See "grunt-contrib" 0.8.0 for details.'
+    - Fix stdout / stderr issues on Windows. Closes gh-940, gh-921, gh-744, gh-792, gh-644, gh-708.
+    - Fix pipe-redirecting on Windows. Closes gh-510.
+    - Fixed this.options() in renamed basic tasks. Closes gh-855.
+    - Update underscore.string dependency to follow semver. Closes gh-886.
+    - Output task options in verbose mode. Closes gh-749.
+    - Add file.preserveBOM property. Closes gh-806, gh-937.
+    - Test that file methods warn. Closes gh-909.
+    - Fixed a few spelling errors in code comments. Closes gh-849.
+    - Updated watch, jshint and nodeunit deps. Closes gh-914.
 v0.4.1:
   date: 2013-03-13
   changes:
     - Fix path.join thrown errors with expandMapping rename. Closes gh-725.
     - Update copyright date to 2013. Closes gh-660.
     - Remove some side effects from manually requiring Grunt. Closes gh-605.
-    - grunt.log: add formatting support and implicitly cast msg to a string. Closes gh-703.
+    - "grunt.log: add formatting support and implicitly cast msg to a string. Closes gh-703."
     - Update js-yaml to version 2. Closes gh-683.
     - The grunt.util.spawn method now falls back to stdout when the `grunt` option is set. Closes gh-691.
     - Making --verbose "Files:" warnings less scary. Closes gh-657.
-    - Fixing typo: the grunt.fatal method now defaults to FATAL_ERROR. Closes gh-656, gh-707.
+    - "Fixing typo: the grunt.fatal method now defaults to FATAL_ERROR. Closes gh-656, gh-707."
     - Removed a duplicate line. Closes gh-702.
     - Gruntfile name should no longer be case sensitive. Closes gh-685.
     - The grunt.file.delete method warns and returns false if file doesn't exist. Closes gh-635, gh-714.
diff --git a/Gruntfile.js b/Gruntfile.js
index 5704ab6..0631dfd 100644
--- a/Gruntfile.js
+++ b/Gruntfile.js
@@ -1,12 +1,3 @@
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2013 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
-
 'use strict';
 
 module.exports = function(grunt) {
@@ -14,32 +5,35 @@ module.exports = function(grunt) {
   // Project configuration.
   grunt.initConfig({
     nodeunit: {
-      all: ['test/{grunt,tasks,util}/**/*.js']
+      all: ['test/{grunt,tasks,util}/**/*.js'],
+      tap: {
+        src: '<%= nodeunit.all %>',
+        options: {
+          reporter: 'tap',
+          reporterOutput: 'tests.tap'
+        }
+      }
     },
     jshint: {
-      gruntfile: ['Gruntfile.js'],
+      gruntfile_tasks: ['Gruntfile.js', 'internal-tasks/*.js'],
       libs_n_tests: ['lib/**/*.js', '<%= nodeunit.all %>'],
       subgrunt: ['<%= subgrunt.all %>'],
       options: {
-        curly: true,
-        eqeqeq: true,
-        immed: true,
-        latedef: true,
-        newcap: true,
-        noarg: true,
-        sub: true,
-        undef: true,
-        unused: true,
-        boss: true,
-        eqnull: true,
-        node: true,
-        es5: true
+        jshintrc: '.jshintrc'
       }
     },
+    jscs: {
+      src: [
+        'lib/**/*.js',
+        'internal-tasks/**/*.js',
+        'test/**/*.js',
+        '!test/fixtures/**/*.js'
+      ]
+    },
     watch: {
-      gruntfile: {
-        files: ['<%= jshint.gruntfile %>'],
-        tasks: ['jshint:gruntfile']
+      gruntfile_tasks: {
+        files: ['<%= jshint.gruntfile_tasks %>'],
+        tasks: ['jshint:gruntfile_tasks']
       },
       libs_n_tests: {
         files: ['<%= jshint.libs_n_tests %>'],
@@ -57,32 +51,19 @@ module.exports = function(grunt) {
 
   // These plugins provide necessary tasks.
   grunt.loadNpmTasks('grunt-contrib-jshint');
+  grunt.loadNpmTasks('grunt-jscs');
   grunt.loadNpmTasks('grunt-contrib-nodeunit');
   grunt.loadNpmTasks('grunt-contrib-watch');
 
+  // Some internal tasks. Maybe someday these will be released.
+  grunt.loadTasks('internal-tasks');
+
   // "npm test" runs these tasks
-  grunt.registerTask('test', ['jshint', 'nodeunit', 'subgrunt']);
+  grunt.registerTask('test', '', function(reporter) {
+    grunt.task.run(['jshint', 'jscs', 'nodeunit:' + (reporter || 'all'), 'subgrunt']);
+  });
 
   // Default task.
   grunt.registerTask('default', ['test']);
 
-  // Run sub-grunt files, because right now, testing tasks is a pain.
-  grunt.registerMultiTask('subgrunt', 'Run a sub-gruntfile.', function() {
-    var path = require('path');
-    grunt.util.async.forEachSeries(this.filesSrc, function(gruntfile, next) {
-      grunt.util.spawn({
-        grunt: true,
-        args: ['--gruntfile', path.resolve(gruntfile)],
-      }, function(error, result) {
-        if (error) {
-          grunt.log.error(result.stdout).writeln();
-          next(new Error('Error running sub-gruntfile "' + gruntfile + '".'));
-        } else {
-          grunt.verbose.ok(result.stdout);
-          next();
-        }
-      });
-    }, this.async());
-  });
-
 };
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..dcf8a0c
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,35 @@
+Copyright jQuery Foundation and other contributors, https://jquery.org/
+
+This software consists of voluntary contributions made by many
+individuals. For exact contribution history, see the revision history
+available at https://github.com/gruntjs/grunt .
+
+The following license applies to all parts of this software except as
+documented below:
+
+====
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), to deal in the Software without restriction, including
+without limitation the rights to use, copy, modify, merge, publish,
+distribute, sublicense, and/or sell copies of the Software, and to
+permit persons to whom the Software is furnished to do so, subject to
+the following conditions:
+
+The above copyright notice and this permission notice shall be
+included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+====
+
+All files located in the node_modules directory are externally maintained
+libraries used by this software which have their own licenses; we recommend
+you read them, as their terms may differ from the terms above.
\ No newline at end of file
diff --git a/LICENSE-MIT b/LICENSE-MIT
deleted file mode 100644
index bb2aad6..0000000
--- a/LICENSE-MIT
+++ /dev/null
@@ -1,22 +0,0 @@
-Copyright (c) 2013 "Cowboy" Ben Alman
-
-Permission is hereby granted, free of charge, to any person
-obtaining a copy of this software and associated documentation
-files (the "Software"), to deal in the Software without
-restriction, including without limitation the rights to use,
-copy, modify, merge, publish, distribute, sublicense, and/or sell
-copies of the Software, and to permit persons to whom the
-Software is furnished to do so, subject to the following
-conditions:
-
-The above copyright notice and this permission notice shall be
-included in all copies or substantial portions of the Software.
-
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
-EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
-OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
-NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
-HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
-WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
-FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
-OTHER DEALINGS IN THE SOFTWARE.
diff --git a/README.md b/README.md
index e0d0f4a..dcd6f65 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,11 @@
-# Grunt: The JavaScript Task Runner [![Build Status](https://secure.travis-ci.org/gruntjs/grunt.png?branch=master)](http://travis-ci.org/gruntjs/grunt)
+# Grunt: The JavaScript Task Runner
+
+[![Build Status: Linux](https://travis-ci.org/gruntjs/grunt.svg?branch=master)](https://travis-ci.org/gruntjs/grunt)
+[![Build Status: Windows](https://ci.appveyor.com/api/projects/status/32r7s2skrgm9ubva/branch/master?svg=true)](https://ci.appveyor.com/project/gruntjs/grunt/branch/master)
+[![Built with Grunt](https://cdn.gruntjs.com/builtwith.svg)](http://gruntjs.com/)
+
+<img align="right" height="260" src="http://gruntjs.com/img/grunt-logo-no-wordmark.svg">
+
 
 ### Documentation
 
@@ -7,7 +14,7 @@ Visit the [gruntjs.com](http://gruntjs.com/) website for all the things.
 ### Support / Contributing
 Before you make an issue, please read our [Contributing](http://gruntjs.com/contributing) guide.
 
-You can find the grunt team in [#grunt on irc.freenode.net](irc://irc.freenode.net/#grunt).
+You can find the grunt team in [#grunt on irc.freenode.net](http://webchat.freenode.net/?channels=grunt).
 
 ### Release History
 See the [CHANGELOG](CHANGELOG).
diff --git a/appveyor.yml b/appveyor.yml
new file mode 100644
index 0000000..9661d6e
--- /dev/null
+++ b/appveyor.yml
@@ -0,0 +1,29 @@
+# Fix line endings on Windows
+init:
+  - git config --global core.autocrlf true
+# What combinations to test
+environment:
+  matrix:
+    - nodejs_version: "0.10"
+    - nodejs_version: "0.12"
+    - nodejs_version: "4"
+    - nodejs_version: "5"
+platform:
+  - x86
+  - x64
+install:
+  - ps: Install-Product node $env:nodejs_version
+  - npm install -g npm
+  - npm install
+test_script:
+  # Output useful info for debugging.
+  - node --version && npm --version
+  # We test multiple Windows shells because of prior stdout buffering issues
+  # filed against Grunt. https://github.com/joyent/node/issues/3584
+  - ps: "npm test # PowerShell" # Pass comment to PS for easier debugging
+  - cmd: npm test
+build: off
+matrix:
+  fast_finish: true
+cache:
+  - node_modules -> package.json                                        # local npm modules
diff --git a/bin/grunt b/bin/grunt
new file mode 100755
index 0000000..9ffa444
--- /dev/null
+++ b/bin/grunt
@@ -0,0 +1,3 @@
+#!/usr/bin/env node
+
+require('grunt-cli/bin/grunt');
diff --git a/internal-tasks/bump.js b/internal-tasks/bump.js
new file mode 100644
index 0000000..75a414a
--- /dev/null
+++ b/internal-tasks/bump.js
@@ -0,0 +1,143 @@
+'use strict';
+
+var semver = require('semver');
+var shell = require('shelljs');
+
+module.exports = function(grunt) {
+
+  grunt.registerTask('bump', 'Bump the version property of a JSON file.', function() {
+    // Validate specified semver increment modes.
+    var valids = ['major', 'minor', 'patch', 'prerelease'];
+    var modes = [];
+    this.args.forEach(function(mode) {
+      var matches = [];
+      valids.forEach(function(valid) {
+        if (valid.indexOf(mode) === 0) { matches.push(valid); }
+      });
+      if (matches.length === 0) {
+        grunt.log.error('Error: mode "' + mode + '" does not match any known modes.');
+      } else if (matches.length > 1) {
+        grunt.log.error('Error: mode "' + mode + '" is ambiguous (possibly: ' + matches.join(', ') + ').');
+      } else {
+        modes.push(matches[0]);
+      }
+    });
+    if (this.errorCount === 0 && modes.length === 0) {
+      grunt.log.error('Error: no modes specified.');
+    }
+    if (this.errorCount > 0) {
+      grunt.log.error('Valid modes are: ' + valids.join(', ') + '.');
+      throw new Error('Use valid modes (or unambiguous mode abbreviations).');
+    }
+    // Options.
+    var options = this.options({
+      filepaths: ['package.json'],
+      syncVersions: false,
+      commit: true,
+      commitMessage: 'Bumping version to {%= version %}.',
+      tag: true,
+      tagName: 'v{%= version %}',
+      tagMessage: 'Version {%= version %}',
+      tagPrerelease: false,
+    });
+    // Normalize filepaths to array.
+    var filepaths = Array.isArray(options.filepaths) ? options.filepaths : [options.filepaths];
+    // Process JSON files, in-order.
+    var versions = {};
+    filepaths.forEach(function(filepath) {
+      var o = grunt.file.readJSON(filepath);
+      var origVersion = o.version;
+      // If syncVersions is enabled, only grab version from the first file,
+      // guaranteeing new versions will always be in sync.
+      var firstVersion = Object.keys(versions)[0];
+      if (options.syncVersions && firstVersion) {
+        o.version = firstVersion;
+      }
+      modes.forEach(function(mode) {
+        var orig = o.version;
+        var s = semver.parse(o.version);
+        s.inc(mode);
+        o.version = String(s);
+        // Workaround for https://github.com/isaacs/node-semver/issues/50
+        if (/-/.test(orig) && mode === 'patch') {
+          o.version = o.version.replace(/\d+$/, function(n) { return n - 1; });
+        }
+        // If prerelease on an un-prerelease version, bump patch version first
+        if (!/-/.test(orig) && mode === 'prerelease') {
+          s.inc('patch');
+          s.inc('prerelease');
+          o.version = String(s);
+        }
+      });
+      if (versions[origVersion]) {
+        versions[origVersion].filepaths.push(filepath);
+      } else {
+        versions[origVersion] = {version: o.version, filepaths: [filepath]};
+      }
+      // Actually *do* something.
+      grunt.log.write('Bumping version in ' + filepath + ' from ' + origVersion + ' to ' + o.version + '...');
+      grunt.file.write(filepath, JSON.stringify(o, null, 2));
+      grunt.log.ok();
+    });
+    // Commit changed files?
+    if (options.commit) {
+      Object.keys(versions).forEach(function(origVersion) {
+        var o = versions[origVersion];
+        commit(o.filepaths, processTemplate(options.commitMessage, {
+          version: o.version,
+          origVersion: origVersion
+        }));
+      });
+    }
+    // We're only going to create one tag. And it's going to be the new
+    // version of the first bumped file. Because, sanity.
+    var newVersion = versions[Object.keys(versions)[0]].version;
+    if (options.tag) {
+      if (options.tagPrerelease || modes.indexOf('prerelease') === -1) {
+        tag(
+          processTemplate(options.tagName, {version: newVersion}),
+          processTemplate(options.tagMessage, {version: newVersion})
+        );
+      } else {
+        grunt.log.writeln('Not tagging (prerelease version).');
+      }
+    }
+    if (this.errorCount > 0) {
+      grunt.warn('There were errors.');
+    }
+  });
+
+  // Using custom delimiters keeps templates from being auto-processed.
+  grunt.template.addDelimiters('bump', '{%', '%}');
+
+  function processTemplate(message, data) {
+    return grunt.template.process(message, {
+      delimiters: 'bump',
+      data: data,
+    });
+  }
+
+  // Kinda borrowed from https://github.com/geddski/grunt-release
+  function commit(filepaths, message) {
+    grunt.log.writeln('Committing ' + filepaths.join(', ') + ' with message: ' + message);
+    run("git commit -m '" + message + "' '" + filepaths.join("' '") + "'");
+  }
+
+  function tag(name, message) {
+    grunt.log.writeln('Tagging ' + name + ' with message: ' + message);
+    run("git tag '" + name + "' -m '" + message + "'");
+  }
+
+  function run(cmd) {
+    if (grunt.option('no-write')) {
+      grunt.verbose.writeln('Not actually running: ' + cmd);
+    } else {
+      grunt.verbose.writeln('Running: ' + cmd);
+      var result = shell.exec(cmd, {silent: true});
+      if (result.code !== 0) {
+        grunt.log.error('Error (' + result.code + ') ' + result.output);
+      }
+    }
+  }
+
+};
diff --git a/internal-tasks/subgrunt.js b/internal-tasks/subgrunt.js
new file mode 100644
index 0000000..dba8d83
--- /dev/null
+++ b/internal-tasks/subgrunt.js
@@ -0,0 +1,25 @@
+'use strict';
+
+module.exports = function(grunt) {
+
+  // Run sub-grunt files, because right now, testing tasks is a pain.
+  grunt.registerMultiTask('subgrunt', 'Run a sub-gruntfile.', function() {
+    var path = require('path');
+    grunt.util.async.forEachSeries(this.filesSrc, function(gruntfile, next) {
+      grunt.log.write('Loading ' + gruntfile + '...');
+      grunt.util.spawn({
+        grunt: true,
+        args: ['--gruntfile', path.resolve(gruntfile)],
+      }, function(error, result) {
+        if (error) {
+          grunt.log.error().error(result.stdout).writeln();
+          next(new Error('Error running sub-gruntfile "' + gruntfile + '".'));
+        } else {
+          grunt.log.ok().verbose.ok(result.stdout);
+          next();
+        }
+      });
+    }, this.async());
+  });
+
+};
diff --git a/lib/grunt.js b/lib/grunt.js
index 7afaa17..072ca24 100644
--- a/lib/grunt.js
+++ b/lib/grunt.js
@@ -1,19 +1,10 @@
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2013 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
-
 'use strict';
 
 // Nodejs libs.
 var path = require('path');
 
 // This allows grunt to require() .coffee files.
-require('coffee-script');
+require('coffee-script/register');
 
 // The module to be exported.
 var grunt = module.exports = {};
@@ -22,7 +13,15 @@ var grunt = module.exports = {};
 function gRequire(name) {
   return grunt[name] = require('./grunt/' + name);
 }
-var util = gRequire('util');
+
+var util = require('grunt-legacy-util');
+grunt.util = util;
+grunt.util.task = require('./util/task');
+
+var Log = require('grunt-legacy-log').Log;
+var log = new Log({grunt: grunt});
+grunt.log = log;
+
 gRequire('template');
 gRequire('event');
 var fail = gRequire('fail');
@@ -30,7 +29,6 @@ gRequire('file');
 var option = gRequire('option');
 var config = gRequire('config');
 var task = gRequire('task');
-var log = gRequire('log');
 var help = gRequire('help');
 gRequire('cli');
 var verbose = grunt.verbose = log.verbose;
@@ -110,7 +108,7 @@ grunt.tasks = function(tasks, options, done) {
   tasks = task.parseArgs([tasksSpecified ? tasks : 'default']);
 
   // Initialize tasks.
-  task.init(tasks);
+  task.init(tasks, options);
 
   verbose.writeln();
   if (!tasksSpecified) {
@@ -152,5 +150,7 @@ grunt.tasks = function(tasks, options, done) {
   // Execute all tasks, in order. Passing each task individually in a forEach
   // allows the error callback to execute multiple times.
   tasks.forEach(function(name) { task.run(name); });
-  task.start();
+  // Run tasks async internally to reduce call-stack, per:
+  // https://github.com/gruntjs/grunt/pull/1026
+  task.start({asyncDone: true});
 };
diff --git a/lib/grunt/cli.js b/lib/grunt/cli.js
index a86da8f..1252f54 100644
--- a/lib/grunt/cli.js
+++ b/lib/grunt/cli.js
@@ -1,27 +1,16 @@
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2013 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
-
 'use strict';
 
 var grunt = require('../grunt');
 
-// Nodejs libs.
-var path = require('path');
-
 // External libs.
 var nopt = require('nopt');
+var gruntOptions = require('grunt-known-options');
 
 // This is only executed when run via command line.
 var cli = module.exports = function(options, done) {
   // CLI-parsed options override any passed-in "default" options.
   if (options) {
-    // For each defult option...
+    // For each default option...
     Object.keys(options).forEach(function(key) {
       if (!(key in cli.options)) {
         // If this option doesn't exist in the parsed cli.options, add it in.
@@ -39,69 +28,7 @@ var cli = module.exports = function(options, done) {
 };
 
 // Default options.
-var optlist = cli.optlist = {
-  help: {
-    short: 'h',
-    info: 'Display this help text.',
-    type: Boolean
-  },
-  base: {
-    info: 'Specify an alternate base path. By default, all file paths are relative to the Gruntfile. (grunt.file.setBase) *',
-    type: path
-  },
-  color: {
-    info: 'Disable colored output.',
-    type: Boolean,
-    negate: true
-  },
-  gruntfile: {
-    info: 'Specify an alternate Gruntfile. By default, grunt looks in the current or parent directories for the nearest Gruntfile.js or Gruntfile.coffee file.',
-    type: path
-  },
-  debug: {
-    short: 'd',
-    info: 'Enable debugging mode for tasks that support it.',
-    type: Number
-  },
-  stack: {
-    info: 'Print a stack trace when exiting with a warning or fatal error.',
-    type: Boolean
-  },
-  force: {
-    short: 'f',
-    info: 'A way to force your way past warnings. Want a suggestion? Don\'t use this option, fix your code.',
-    type: Boolean
-  },
-  tasks: {
-    info: 'Additional directory paths to scan for task and "extra" files. (grunt.loadTasks) *',
-    type: Array
-  },
-  npm: {
-    info: 'Npm-installed grunt plugins to scan for task and "extra" files. (grunt.loadNpmTasks) *',
-    type: Array
-  },
-  write: {
-    info: 'Disable writing files (dry run).',
-    type: Boolean,
-    negate: true
-  },
-  verbose: {
-    short: 'v',
-    info: 'Verbose mode. A lot more information output.',
-    type: Boolean
-  },
-  version: {
-    short: 'V',
-    info: 'Print the grunt version. Combine with --verbose for more info.',
-    type: Boolean
-  },
-  // Even though shell auto-completion is now handled by grunt-cli, leave this
-  // option here for display in the --help screen.
-  completion: {
-    info: 'Output shell auto-completion rules. See the grunt-cli documentation for more information.',
-    type: String
-  },
-};
+var optlist = cli.optlist = gruntOptions;
 
 // Parse `optlist` into a form that nopt can handle.
 var aliases = {};
diff --git a/lib/grunt/config.js b/lib/grunt/config.js
index 75619a1..ef2bf80 100644
--- a/lib/grunt/config.js
+++ b/lib/grunt/config.js
@@ -1,12 +1,3 @@
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2013 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
-
 'use strict';
 
 var grunt = require('../grunt');
@@ -81,6 +72,12 @@ config.set = function(prop, value) {
   return grunt.util.namespace.set(config.data, config.getPropString(prop), value);
 };
 
+// Deep merge config data.
+config.merge = function(obj) {
+  grunt.util._.merge(config.data, obj);
+  return config.data;
+};
+
 // Initialize config data.
 config.init = function(obj) {
   grunt.verbose.write('Initializing config...').ok();
diff --git a/lib/grunt/event.js b/lib/grunt/event.js
index 9e5ba0b..7ec1027 100644
--- a/lib/grunt/event.js
+++ b/lib/grunt/event.js
@@ -1,12 +1,3 @@
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2013 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
-
 'use strict';
 
 // External lib.
diff --git a/lib/grunt/fail.js b/lib/grunt/fail.js
index 7b8048d..631e249 100644
--- a/lib/grunt/fail.js
+++ b/lib/grunt/fail.js
@@ -1,12 +1,3 @@
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2013 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
-
 'use strict';
 
 var grunt = require('../grunt');
@@ -50,18 +41,18 @@ function dumpStack(e) {
   }
 }
 
-// A fatal error occured. Abort immediately.
+// A fatal error occurred. Abort immediately.
 fail.fatal = function(e, errcode) {
   writeln(e, 'fatal');
   dumpStack(e);
-  process.exit(typeof errcode === 'number' ? errcode : fail.code.FATAL_ERROR);
+  grunt.util.exit(typeof errcode === 'number' ? errcode : fail.code.FATAL_ERROR);
 };
 
 // Keep track of error and warning counts.
 fail.errorcount = 0;
 fail.warncount = 0;
 
-// A warning ocurred. Abort immediately unless -f or --force was used.
+// A warning occurred. Abort immediately unless -f or --force was used.
 fail.warn = function(e, errcode) {
   var message = typeof e === 'string' ? e : e.message;
   fail.warncount++;
@@ -70,7 +61,7 @@ fail.warn = function(e, errcode) {
   if (!grunt.option('force')) {
     dumpStack(e);
     grunt.log.writeln().fail('Aborted due to warnings.');
-    process.exit(typeof errcode === 'number' ? errcode : fail.code.WARNING);
+    grunt.util.exit(typeof errcode === 'number' ? errcode : fail.code.WARNING);
   }
 };
 
@@ -79,6 +70,6 @@ fail.report = function() {
   if (fail.warncount > 0) {
     grunt.log.writeln().fail('Done, but with warnings.');
   } else {
-    grunt.log.writeln().success('Done, without errors.');
+    grunt.log.writeln().success('Done.');
   }
 };
diff --git a/lib/grunt/file.js b/lib/grunt/file.js
index a84d703..303e0ab 100644
--- a/lib/grunt/file.js
+++ b/lib/grunt/file.js
@@ -1,12 +1,3 @@
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2013 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
-
 'use strict';
 
 var grunt = require('../grunt');
@@ -25,6 +16,7 @@ file.findup = require('findup-sync');
 var YAML = require('js-yaml');
 var rimraf = require('rimraf');
 var iconv = require('iconv-lite');
+var pathIsAbsolute = require('path-is-absolute');
 
 // Windows?
 var win32 = process.platform === 'win32';
@@ -50,7 +42,7 @@ var processPatterns = function(patterns, fn) {
   // Filepaths to return.
   var result = [];
   // Iterate over flattened patterns array.
-  grunt.util._.flatten(patterns).forEach(function(pattern) {
+  grunt.util._.flattenDeep(patterns).forEach(function(pattern) {
     // If the first character is ! it should be omitted
     var exclusion = pattern.indexOf('!') === 0;
     // If the pattern is an exclusion, remove the !
@@ -122,7 +114,7 @@ file.expand = function() {
           // If the file is of the right type and exists, this should work.
           return fs.statSync(filepath)[options.filter]();
         }
-      } catch(e) {
+      } catch (e) {
         // Otherwise, it's probably not the right type.
         return false;
       }
@@ -133,9 +125,17 @@ file.expand = function() {
 
 var pathSeparatorRe = /[\/\\]/g;
 
+// The "ext" option refers to either everything after the first dot (default)
+// or everything after the last dot.
+var extDotRe = {
+  first: /(\.[^\/]*)?$/,
+  last: /(\.[^\/\.]*)?$/,
+};
+
 // Build a multi task "files" object dynamically.
 file.expandMapping = function(patterns, destBase, options) {
   options = grunt.util._.defaults({}, options, {
+    extDot: 'first',
     rename: function(destBase, destPath) {
       return path.join(destBase || '', destPath);
     }
@@ -150,8 +150,8 @@ file.expandMapping = function(patterns, destBase, options) {
       destPath = path.basename(destPath);
     }
     // Change the extension?
-    if (options.ext) {
-      destPath = destPath.replace(/(\.[^\/]*)?$/, options.ext);
+    if ('ext' in options) {
+      destPath = destPath.replace(extDotRe[options.extDot], options.ext);
     }
     // Generate destination filename.
     var dest = options.rename(destBase, destPath, options);
@@ -190,7 +190,7 @@ file.mkdir = function(dirpath, mode) {
     if (!file.exists(subpath)) {
       try {
         fs.mkdirSync(subpath, mode);
-      } catch(e) {
+      } catch (e) {
         throw grunt.util.error('Unable to create directory "' + subpath + '" (Error code: ' + e.code + ').', e);
       }
     }
@@ -213,6 +213,8 @@ file.recurse = function recurse(rootdir, callback, subdir) {
 
 // The default file encoding to use.
 file.defaultEncoding = 'utf8';
+// Whether to preserve the BOM on file.read rather than strip it.
+file.preserveBOM = false;
 
 // Read a file, return its contents.
 file.read = function(filepath, options) {
@@ -224,15 +226,11 @@ file.read = function(filepath, options) {
     // If encoding is not explicitly null, convert from encoded buffer to a
     // string. If no encoding was specified, use the default.
     if (options.encoding !== null) {
-      contents = iconv.decode(contents, options.encoding || file.defaultEncoding);
-      // Strip any BOM that might exist.
-      if (contents.charCodeAt(0) === 0xFEFF) {
-        contents = contents.substring(1);
-      }
+      contents = iconv.decode(contents, options.encoding || file.defaultEncoding, {stripBOM: !file.preserveBOM});
     }
     grunt.verbose.ok();
     return contents;
-  } catch(e) {
+  } catch (e) {
     grunt.verbose.error();
     throw grunt.util.error('Unable to read "' + filepath + '" file (Error code: ' + e.code + ').', e);
   }
@@ -247,7 +245,7 @@ file.readJSON = function(filepath, options) {
     result = JSON.parse(src);
     grunt.verbose.ok();
     return result;
-  } catch(e) {
+  } catch (e) {
     grunt.verbose.error();
     throw grunt.util.error('Unable to parse "' + filepath + '" file (' + e.message + ').', e);
   }
@@ -262,7 +260,7 @@ file.readYAML = function(filepath, options) {
     result = YAML.load(src);
     grunt.verbose.ok();
     return result;
-  } catch(e) {
+  } catch (e) {
     grunt.verbose.error();
     throw grunt.util.error('Unable to parse "' + filepath + '" file (' + e.problem + ').', e);
   }
@@ -283,18 +281,36 @@ file.write = function(filepath, contents, options) {
     }
     // Actually write file.
     if (!nowrite) {
-      fs.writeFileSync(filepath, contents);
+      fs.writeFileSync(filepath, contents, 'mode' in options ? {mode: options.mode} : {});
     }
     grunt.verbose.ok();
     return true;
-  } catch(e) {
+  } catch (e) {
     grunt.verbose.error();
     throw grunt.util.error('Unable to write "' + filepath + '" file (Error code: ' + e.code + ').', e);
   }
 };
 
 // Read a file, optionally processing its content, then write the output.
-file.copy = function(srcpath, destpath, options) {
+// Or read a directory, recursively creating directories, reading files,
+// processing content, writing output.
+file.copy = function copy(srcpath, destpath, options) {
+  if (file.isDir(srcpath)) {
+    // Copy a directory, recursively.
+    // Explicitly create new dest directory.
+    file.mkdir(destpath);
+    // Iterate over all sub-files/dirs, recursing.
+    fs.readdirSync(srcpath).forEach(function(filepath) {
+      copy(path.join(srcpath, filepath), path.join(destpath, filepath), options);
+    });
+  } else {
+    // Copy a single file.
+    file._copy(srcpath, destpath, options);
+  }
+};
+
+// Read a file, optionally processing its content, then write the output.
+file._copy = function(srcpath, destpath, options) {
   if (!options) { options = {}; }
   // If a process function was specified, and noProcess isn't true or doesn't
   // match the srcpath, process the file's source.
@@ -308,9 +324,9 @@ file.copy = function(srcpath, destpath, options) {
   if (process) {
     grunt.verbose.write('Processing source...');
     try {
-      contents = options.process(contents, srcpath);
+      contents = options.process(contents, srcpath, destpath);
       grunt.verbose.ok();
-    } catch(e) {
+    } catch (e) {
       grunt.verbose.error();
       throw grunt.util.error('Error while processing "' + srcpath + '" file.', e);
     }
@@ -360,7 +376,7 @@ file.delete = function(filepath, options) {
     }
     grunt.verbose.ok();
     return true;
-  } catch(e) {
+  } catch (e) {
     grunt.verbose.error();
     throw grunt.util.error('Unable to delete "' + filepath + '" file (' + e.message + ').', e);
   }
@@ -393,7 +409,7 @@ file.isFile = function() {
 // Is a given file path absolute?
 file.isPathAbsolute = function() {
   var filepath = path.join.apply(path, arguments);
-  return path.resolve(filepath) === filepath.replace(/[\/\\]+$/, '');
+  return pathIsAbsolute(filepath);
 };
 
 // Do all the specified paths refer to the same path?
@@ -421,8 +437,8 @@ file.doesPathContain = function(ancestor) {
 file.isPathCwd = function() {
   var filepath = path.join.apply(path, arguments);
   try {
-    return file.arePathsEquivalent(process.cwd(), fs.realpathSync(filepath));
-  } catch(e) {
+    return file.arePathsEquivalent(fs.realpathSync(process.cwd()), fs.realpathSync(filepath));
+  } catch (e) {
     return false;
   }
 };
@@ -431,8 +447,8 @@ file.isPathCwd = function() {
 file.isPathInCwd = function() {
   var filepath = path.join.apply(path, arguments);
   try {
-    return file.doesPathContain(process.cwd(), fs.realpathSync(filepath));
-  } catch(e) {
+    return file.doesPathContain(fs.realpathSync(process.cwd()), fs.realpathSync(filepath));
+  } catch (e) {
     return false;
   }
 };
diff --git a/lib/grunt/help.js b/lib/grunt/help.js
index 3e610fb..c01f727 100644
--- a/lib/grunt/help.js
+++ b/lib/grunt/help.js
@@ -1,12 +1,3 @@
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2013 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
-
 'use strict';
 
 var grunt = require('../grunt');
@@ -49,7 +40,6 @@ exports.display = function() {
   exports.queue.forEach(function(name) { exports[name](); });
 };
 
-
 // Header.
 exports.header = function() {
   grunt.log.writeln('Grunt: The JavaScript Task Runner (v' + grunt.version + ')');
diff --git a/lib/grunt/log.js b/lib/grunt/log.js
deleted file mode 100644
index 41dd534..0000000
--- a/lib/grunt/log.js
+++ /dev/null
@@ -1,352 +0,0 @@
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2013 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
-
-'use strict';
-
-var grunt = require('../grunt');
-
-// Nodejs libs.
-var util = require('util');
-
-// The module to be exported.
-var log = module.exports = {};
-
-// External lib. Requiring this here modifies the String prototype!
-var colors = require('colors');
-
-// Disable colors if --no-colors was passed.
-log.initColors = function() {
-  var util = grunt.util;
-  if (grunt.option('no-color')) {
-    // String color getters should just return the string.
-    colors.mode = 'none';
-    // Strip colors from strings passed to console.log.
-    util.hooker.hook(console, 'log', function() {
-      var args = util.toArray(arguments);
-      return util.hooker.filter(this, args.map(function(arg) {
-        return util.kindOf(arg) === 'string' ? colors.stripColors(arg) : arg;
-      }));
-    });
-  }
-};
-
-// Temporarily suppress output.
-var suppressOutput;
-
-// Allow external muting of output.
-log.muted = false;
-
-// True once anything has actually been logged.
-var hasLogged;
-
-// Parse certain markup in strings to be logged.
-function markup(str) {
-  str = str || '';
-  // Make _foo_ underline.
-  str = str.replace(/(\s|^)_(\S|\S[\s\S]+?\S)_(?=[\s,.!?]|$)/g, '$1' + '$2'.underline);
-  // Make *foo* bold.
-  str = str.replace(/(\s|^)\*(\S|\S[\s\S]+?\S)\*(?=[\s,.!?]|$)/g, '$1' + '$2'.bold);
-  return str;
-}
-
-// Similar to util.format in the standard library, however it'll always
-// cast the first argument to a string and treat it as the format string.
-function format(args) {
-  // Args is a argument array so copy it in order to avoid wonky behavior.
-  args = [].slice.call(args, 0);
-  if (args.length > 0) {
-    args[0] = String(args[0]);
-  }
-  return util.format.apply(util, args);
-}
-
-function write(msg) {
-  msg = msg || '';
-  // Actually write output.
-  if (!log.muted && !suppressOutput) {
-    hasLogged = true;
-    // Users should probably use the colors-provided methods, but if they
-    // don't, this should strip extraneous color codes.
-    if (grunt.option('no-color')) { msg = colors.stripColors(msg); }
-    // Actually write to stdout.
-    process.stdout.write(markup(msg));
-  }
-}
-
-function writeln(msg) {
-  // Write blank line if no msg is passed in.
-  msg = msg || '';
-  write(msg + '\n');
-}
-
-// Write output.
-log.write = function() {
-  write(format(arguments));
-  return log;
-};
-
-// Write a line of output.
-log.writeln = function() {
-  writeln(format(arguments));
-  return log;
-};
-
-log.warn = function() {
-  var msg = format(arguments);
-  if (arguments.length > 0) {
-    writeln('>> '.red + grunt.util._.trim(msg).replace(/\n/g, '\n>> '.red));
-  } else {
-    writeln('ERROR'.red);
-  }
-  return log;
-};
-log.error = function() {
-  grunt.fail.errorcount++;
-  log.warn.apply(log, arguments);
-  return log;
-};
-log.ok = function() {
-  var msg = format(arguments);
-  if (arguments.length > 0) {
-    writeln('>> '.green + grunt.util._.trim(msg).replace(/\n/g, '\n>> '.green));
-  } else {
-    writeln('OK'.green);
-  }
-  return log;
-};
-log.errorlns = function() {
-  var msg = format(arguments);
-  log.error(log.wraptext(77, msg));
-  return log;
-};
-log.oklns = function() {
-  var msg = format(arguments);
-  log.ok(log.wraptext(77, msg));
-  return log;
-};
-log.success = function() {
-  var msg = format(arguments);
-  writeln(msg.green);
-  return log;
-};
-log.fail = function() {
-  var msg = format(arguments);
-  writeln(msg.red);
-  return log;
-};
-log.header = function() {
-  var msg = format(arguments);
-  // Skip line before header, but not if header is the very first line output.
-  if (hasLogged) { writeln(); }
-  writeln(msg.underline);
-  return log;
-};
-log.subhead = function() {
-  var msg = format(arguments);
-  // Skip line before subhead, but not if subhead is the very first line output.
-  if (hasLogged) { writeln(); }
-  writeln(msg.bold);
-  return log;
-};
-// For debugging.
-log.debug = function() {
-  var msg = format(arguments);
-  if (grunt.option('debug')) {
-    writeln('[D] ' + msg.magenta);
-  }
-  return log;
-};
-
-// Write a line of a table.
-log.writetableln = function(widths, texts) {
-  writeln(log.table(widths, texts));
-  return log;
-};
-
-// Wrap a long line of text to 80 columns.
-log.writelns = function() {
-  var msg = format(arguments);
-  writeln(log.wraptext(80, msg));
-  return log;
-};
-
-// Display flags in verbose mode.
-log.writeflags = function(obj, prefix) {
-  var wordlist;
-  if (Array.isArray(obj)) {
-    wordlist = log.wordlist(obj);
-  } else if (typeof obj === 'object' && obj) {
-    wordlist = log.wordlist(Object.keys(obj).map(function(key) {
-      var val = obj[key];
-      return key + (val === true ? '' : '=' + JSON.stringify(val));
-    }));
-  }
-  writeln((prefix || 'Flags') + ': ' + (wordlist || '(none)'.cyan));
-  return log;
-};
-
-// Create explicit "verbose" and "notverbose" functions, one for each already-
-// defined log function, that do the same thing but ONLY if -v or --verbose is
-// specified (or not specified).
-log.verbose = {};
-log.notverbose = {};
-
-// Iterate over all exported functions.
-Object.keys(log).filter(function(key) {
-  return typeof log[key] === 'function';
-}).forEach(function(key) {
-  // Like any other log function, but suppresses output if the "verbose" option
-  // IS NOT set.
-  log.verbose[key] = function() {
-    suppressOutput = !grunt.option('verbose');
-    log[key].apply(log, arguments);
-    suppressOutput = false;
-    return log.verbose;
-  };
-  // Like any other log function, but suppresses output if the "verbose" option
-  // IS set.
-  log.notverbose[key] = function() {
-    suppressOutput = grunt.option('verbose');
-    log[key].apply(log, arguments);
-    suppressOutput = false;
-    return log.notverbose;
-  };
-});
-
-// A way to switch between verbose and notverbose modes. For example, this will
-// write 'foo' if verbose logging is enabled, otherwise write 'bar':
-// verbose.write('foo').or.write('bar');
-log.verbose.or = log.notverbose;
-log.notverbose.or = log.verbose;
-
-// Static methods.
-
-// Pretty-format a word list.
-log.wordlist = function(arr, options) {
-  options = grunt.util._.defaults(options || {}, {
-    separator: ', ',
-    color: 'cyan'
-  });
-  return arr.map(function(item) {
-    return options.color ? String(item)[options.color] : item;
-  }).join(options.separator);
-};
-
-// Return a string, uncolored (suitable for testing .length, etc).
-log.uncolor = function(str) {
-  return str.replace(/\x1B\[\d+m/g, '');
-};
-
-// Word-wrap text to a given width, permitting ANSI color codes.
-log.wraptext = function(width, text) {
-  // notes to self:
-  // grab 1st character or ansi code from string
-  // if ansi code, add to array and save for later, strip from front of string
-  // if character, add to array and increment counter, strip from front of string
-  // if width + 1 is reached and current character isn't space:
-  //  slice off everything after last space in array and prepend it to string
-  //  etc
-
-  // This result array will be joined on \n.
-  var result = [];
-  var matches, color, tmp;
-  var captured = [];
-  var charlen = 0;
-
-  while (matches = text.match(/(?:(\x1B\[\d+m)|\n|(.))([\s\S]*)/)) {
-    // Updated text to be everything not matched.
-    text = matches[3];
-
-    // Matched a color code?
-    if (matches[1]) {
-      // Save last captured color code for later use.
-      color = matches[1];
-      // Capture color code.
-      captured.push(matches[1]);
-      continue;
-
-    // Matched a non-newline character?
-    } else if (matches[2]) {
-      // If this is the first character and a previous color code was set, push
-      // that onto the captured array first.
-      if (charlen === 0 && color) { captured.push(color); }
-      // Push the matched character.
-      captured.push(matches[2]);
-      // Increment the current charlen.
-      charlen++;
-      // If not yet at the width limit or a space was matched, continue.
-      if (charlen <= width || matches[2] === ' ') { continue; }
-      // The current charlen exceeds the width and a space wasn't matched.
-      // "Roll everything back" until the last space character.
-      tmp = captured.lastIndexOf(' ');
-      text = captured.slice(tmp === -1 ? tmp : tmp + 1).join('') + text;
-      captured = captured.slice(0, tmp);
-    }
-
-    // The limit has been reached. Push captured string onto result array.
-    result.push(captured.join(''));
-
-    // Reset captured array and charlen.
-    captured = [];
-    charlen = 0;
-  }
-
-  result.push(captured.join(''));
-  return result.join('\n');
-};
-
-// todo: write unit tests
-//
-// function logs(text) {
-//   [4, 6, 10, 15, 20, 25, 30, 40].forEach(function(n) {
-//     log(n, text);
-//   });
-// }
-//
-// function log(n, text) {
-//   console.log(Array(n + 1).join('-'));
-//   console.log(wrap(n, text));
-// }
-//
-// var text = 'this is '.red + 'a simple'.yellow.inverse + ' test of'.green + ' ' + 'some wrapped'.blue + ' text over '.inverse.magenta + 'many lines'.red;
-// logs(text);
-//
-// var text = 'foolish '.red.inverse + 'monkeys'.yellow + ' eating'.green + ' ' + 'delicious'.inverse.blue + ' bananas '.magenta + 'forever'.red;
-// logs(text);
-//
-// var text = 'foolish monkeys eating delicious bananas forever'.rainbow;
-// logs(text);
-
-// Format output into columns, wrapping words as-necessary.
-log.table = function(widths, texts) {
-  var rows = [];
-  widths.forEach(function(width, i) {
-    var lines = log.wraptext(width, texts[i]).split('\n');
-    lines.forEach(function(line, j) {
-      var row = rows[j];
-      if (!row) { row = rows[j] = []; }
-      row[i] = line;
-    });
-  });
-
-  var lines = [];
-  rows.forEach(function(row) {
-    var txt = '';
-    var column;
-    for (var i = 0; i < row.length; i++) {
-      column = row[i] || '';
-      txt += column;
-      var diff = widths[i] - log.uncolor(column).length;
-      if (diff > 0) { txt += grunt.util.repeat(diff, ' '); }
-    }
-    lines.push(txt);
-  });
-
-  return lines.join('\n');
-};
diff --git a/lib/grunt/option.js b/lib/grunt/option.js
index 84b6763..88f8766 100644
--- a/lib/grunt/option.js
+++ b/lib/grunt/option.js
@@ -1,12 +1,3 @@
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2013 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
-
 'use strict';
 
 // The actual option data.
diff --git a/lib/grunt/task.js b/lib/grunt/task.js
index dcfe9e3..f31ff2e 100644
--- a/lib/grunt/task.js
+++ b/lib/grunt/task.js
@@ -1,12 +1,3 @@
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2013 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
-
 'use strict';
 
 var grunt = require('../grunt');
@@ -45,6 +36,8 @@ task.registerTask = function(name) {
   // Override task function.
   var _fn = thisTask.fn;
   thisTask.fn = function(arg) {
+    // Guaranteed to always be the actual task name.
+    var name = thisTask.name;
     // Initialize the errorcount for this task.
     errorcount = grunt.fail.errorcount;
     // Return the number of errors logged during this task.
@@ -58,13 +51,15 @@ task.registerTask = function(name) {
     this.requires = task.requires.bind(task);
     // Expose config.requires on `this`.
     this.requiresConfig = grunt.config.requires;
-    // Return an options object with the specified defaults overriden by task-
+    // Return an options object with the specified defaults overwritten by task-
     // specific overrides, via the "options" property.
     this.options = function() {
       var args = [{}].concat(grunt.util.toArray(arguments)).concat([
         grunt.config([name, 'options'])
       ]);
-      return grunt.util._.extend.apply(null, args);
+      var options = grunt.util._.extend.apply(null, args);
+      grunt.verbose.writeflags(options, 'Options');
+      return options;
     };
     // If this task was an alias or a multi task called without a target,
     // only log if in verbose mode.
@@ -103,7 +98,7 @@ task.normalizeMultiTaskFiles = function(data, target) {
         files.push({src: data.files[prop], dest: grunt.config.process(prop)});
       }
     } else if (Array.isArray(data.files)) {
-      data.files.forEach(function(obj) {
+      grunt.util._.flattenDeep(data.files).forEach(function(obj) {
         var prop;
         if ('src' in obj || 'dest' in obj) {
           files.push(obj);
@@ -228,7 +223,7 @@ task.registerMultiTask = function(name, info, fn) {
     }
     // Fail if any required config properties have been omitted.
     this.requiresConfig([name, target]);
-    // Return an options object with the specified defaults overriden by task-
+    // Return an options object with the specified defaults overwritten by task-
     // and/or target-specific overrides, via the "options" property.
     this.options = function() {
       var targetObj = grunt.config([name, target]);
@@ -236,8 +231,15 @@ task.registerMultiTask = function(name, info, fn) {
         grunt.config([name, 'options']),
         grunt.util.kindOf(targetObj) === 'object' ? targetObj.options : {}
       ]);
-      return grunt.util._.extend.apply(null, args);
+      var options = grunt.util._.extend.apply(null, args);
+      grunt.verbose.writeflags(options, 'Options');
+      return options;
     };
+    // Expose the current target.
+    this.target = target;
+    // Recreate flags object so that the target isn't set as a flag.
+    this.flags = {};
+    this.args.forEach(function(arg) { this.flags[arg] = true; }, this);
     // Expose data on `this` (as well as task.current).
     this.data = grunt.config([name, target]);
     // Expose normalized files object.
@@ -246,14 +248,9 @@ task.registerMultiTask = function(name, info, fn) {
     Object.defineProperty(this, 'filesSrc', {
       enumerable: true,
       get: function() {
-        return grunt.util._(this.files).chain().pluck('src').flatten().uniq().value();
+        return grunt.util._(this.files).chain().map('src').flatten().uniq().value();
       }.bind(this)
     });
-    // Expose the current target.
-    this.target = target;
-    // Recreate flags object so that the target isn't set as a flag.
-    this.flags = {};
-    this.args.forEach(function(arg) { this.flags[arg] = true; }, this);
     // Call original task function, passing in the target and any other args.
     return fn.apply(this, this.args);
   });
@@ -271,24 +268,33 @@ task.registerInitTask = function(name, info, fn) {
 
 // Override built-in renameTask to use the registry.
 task.renameTask = function(oldname, newname) {
-  // Add and remove task.
-  registry.untasks.push(oldname);
-  registry.tasks.push(newname);
-  // Actually rename task.
-  return parent.renameTask.apply(task, arguments);
+  var result;
+  try {
+    // Actually rename task.
+    result = parent.renameTask.apply(task, arguments);
+    // Add and remove task.
+    registry.untasks.push(oldname);
+    registry.tasks.push(newname);
+    // Return result.
+    return result;
+  } catch (e) {
+    grunt.log.error(e.message);
+  }
 };
 
 // If a property wasn't passed, run all task targets in turn.
 task.runAllTargets = function(taskname, args) {
   // Get an array of sub-property keys under the given config object.
   var targets = Object.keys(grunt.config.getRaw(taskname) || {});
+  // Remove invalid target properties.
+  targets = targets.filter(isValidMultiTaskTarget);
   // Fail if there are no actual properties to iterate over.
   if (targets.length === 0) {
     grunt.log.error('No "' + taskname + '" targets found.');
     return false;
   }
-  // Iterate over all valid target properties, running a task for each.
-  targets.filter(isValidMultiTaskTarget).forEach(function(target) {
+  // Iterate over all targets, running a task for each.
+  targets.forEach(function(target) {
     // Be sure to pass in any additionally specified args.
     task.run([taskname, target].concat(args || []).join(':'));
   });
@@ -321,9 +327,9 @@ function loadTask(filepath) {
       }
     });
     if (regCount === 0) {
-      grunt.verbose.error('No tasks were registered or unregistered.');
+      grunt.verbose.warn('No tasks were registered or unregistered.');
     }
-  } catch(e) {
+  } catch (e) {
     // Something went wrong.
     grunt.log.write(msg).error().verbose.error(e.stack).or.error(e);
   }
@@ -347,7 +353,7 @@ function loadTasks(tasksdir) {
     files.forEach(function(filename) {
       loadTask(path.join(tasksdir, filename));
     });
-  } catch(e) {
+  } catch (e) {
     grunt.log.verbose.error(e.stack).or.error(e);
   }
 }
@@ -410,11 +416,18 @@ task.init = function(tasks, options) {
 
   // Get any local Gruntfile or tasks that might exist. Use --gruntfile override
   // if specified, otherwise search the current directory or any parent.
-  var gruntfile = allInit ? null : grunt.option('gruntfile') ||
-    grunt.file.findup('Gruntfile.{js,coffee}', {nocase: true});
+  var gruntfile, msg;
+  if (allInit || options.gruntfile === false) {
+    gruntfile = null;
+  } else {
+    gruntfile = grunt.option('gruntfile') ||
+      grunt.file.findup('Gruntfile.{js,coffee}', {nocase: true});
+    msg = 'Reading "' + (gruntfile ? path.basename(gruntfile) : '???') + '" Gruntfile...';
+  }
 
-  var msg = 'Reading "' + (gruntfile ? path.basename(gruntfile) : '???') + '" Gruntfile...';
-  if (gruntfile && grunt.file.exists(gruntfile)) {
+  if (options.gruntfile === false) {
+    // Grunt was run as a lib with {gruntfile: false}.
+  } else if (gruntfile && grunt.file.exists(gruntfile)) {
     grunt.verbose.writeln().write(msg).ok();
     // Change working directory so that all paths are relative to the
     // Gruntfile's location (or the --base option, if specified).
@@ -439,7 +452,7 @@ task.init = function(tasks, options) {
   }
 
   // Load all user-specified --npm tasks.
-  (grunt.option('npm') || []).forEach(task.loadNpmTasks);
+  (grunt.option('npm') || []).map(String).forEach(task.loadNpmTasks);
   // Load all user-specified --tasks.
-  (grunt.option('tasks') || []).forEach(task.loadTasks);
+  (grunt.option('tasks') || []).map(String).forEach(task.loadTasks);
 };
diff --git a/lib/grunt/template.js b/lib/grunt/template.js
index 0db3bdb..d1967df 100644
--- a/lib/grunt/template.js
+++ b/lib/grunt/template.js
@@ -1,12 +1,3 @@
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2013 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
-
 'use strict';
 
 var grunt = require('../grunt');
@@ -51,7 +42,7 @@ template.setDelimiters = function(name) {
   // Get the appropriate delimiters.
   var delimiters = allDelimiters[name in allDelimiters ? name : 'config'];
   // Tell Lo-Dash which delimiters to use.
-  grunt.util._.templateSettings = delimiters.lodash;
+  grunt.util._.extend(grunt.util._.templateSettings, delimiters.lodash);
   // Return the delimiters.
   return delimiters;
 };
@@ -72,7 +63,7 @@ template.process = function(tmpl, options) {
     // As long as tmpl contains template tags, render it and get the result,
     // otherwise just use the template string.
     while (tmpl.indexOf(delimiters.opener) >= 0) {
-      tmpl = grunt.util._.template(tmpl, data);
+      tmpl = grunt.util._.template(tmpl, options)(data);
       // Abort if template didn't change - nothing left to process!
       if (tmpl === last) { break; }
       last = tmpl;
diff --git a/lib/grunt/util.js b/lib/grunt/util.js
deleted file mode 100644
index c14461c..0000000
--- a/lib/grunt/util.js
+++ /dev/null
@@ -1,189 +0,0 @@
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2013 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
-
-'use strict';
-
-// Nodejs libs.
-var spawn = require('child_process').spawn;
-var nodeUtil = require('util');
-var path = require('path');
-
-// The module to be exported.
-var util = module.exports = {};
-
-// A few internal utilites, exposed.
-util.task = require('../util/task');
-util.namespace = require('../util/namespace');
-// Use instead of process.exit to ensure stdout/stderr are flushed
-// before exiting in Windows (Tested in Node.js v0.8.7)
-util.exit = require('../util/exit').exit;
-
-// External libs.
-util.hooker = require('hooker');
-util.async = require('async');
-var _ = util._ = require('lodash');
-var which = require('which').sync;
-
-// Mixin Underscore.string methods.
-_.str = require('underscore.string');
-_.mixin(_.str.exports());
-
-// Return a function that normalizes the given function either returning a
-// value or accepting a "done" callback that accepts a single value.
-util.callbackify = function(fn) {
-  return function callbackable() {
-    // Invoke original function, getting its result.
-    var result = fn.apply(this, arguments);
-    // If the same number or less arguments were specified than fn accepts,
-    // assume the "done" callback was already handled.
-    var length = arguments.length;
-    if (length === fn.length) { return; }
-    // Otherwise, if the last argument is a function, assume it is a "done"
-    // callback and call it.
-    var done = arguments[length - 1];
-    if (typeof done === 'function') { done(result); }
-  };
-};
-
-// Create a new Error object, with an origError property that will be dumped
-// if grunt was run with the --debug=9 option.
-util.error = function(err, origError) {
-  if (!nodeUtil.isError(err)) { err = new Error(err); }
-  if (origError) { err.origError = origError; }
-  return err;
-};
-
-// The line feed char for the current system.
-util.linefeed = process.platform === 'win32' ? '\r\n' : '\n';
-
-// Normalize linefeeds in a string.
-util.normalizelf = function(str) {
-  return str.replace(/\r\n|\n/g, util.linefeed);
-};
-
-// What "kind" is a value?
-// I really need to rework https://github.com/cowboy/javascript-getclass
-var kindsOf = {};
-'Number String Boolean Function RegExp Array Date Error'.split(' ').forEach(function(k) {
-  kindsOf['[object ' + k + ']'] = k.toLowerCase();
-});
-util.kindOf = function(value) {
-  // Null or undefined.
-  if (value == null) { return String(value); }
-  // Everything else.
-  return kindsOf[kindsOf.toString.call(value)] || 'object';
-};
-
-// Coerce something to an Array.
-util.toArray = Function.call.bind(Array.prototype.slice);
-
-// Return the string `str` repeated `n` times.
-util.repeat = function(n, str) {
-  return new Array(n + 1).join(str || ' ');
-};
-
-// Given str of "a/b", If n is 1, return "a" otherwise "b".
-util.pluralize = function(n, str, separator) {
-  var parts = str.split(separator || '/');
-  return n === 1 ? (parts[0] || '') : (parts[1] || '');
-};
-
-// Recurse through objects and arrays, executing fn for each non-object.
-util.recurse = function recurse(value, fn, fnContinue) {
-  var obj;
-  if (fnContinue && fnContinue(value) === false) {
-    // Skip value if necessary.
-    return value;
-  } else if (util.kindOf(value) === 'array') {
-    // If value is an array, recurse.
-    return value.map(function(value) {
-      return recurse(value, fn, fnContinue);
-    });
-  } else if (util.kindOf(value) === 'object') {
-    // If value is an object, recurse.
-    obj = {};
-    Object.keys(value).forEach(function(key) {
-      obj[key] = recurse(value[key], fn, fnContinue);
-    });
-    return obj;
-  } else {
-    // Otherwise pass value into fn and return.
-    return fn(value);
-  }
-};
-
-// Spawn a child process, capturing its stdout and stderr.
-util.spawn = function(opts, done) {
-  // Build a result object and pass it (among other things) into the
-  // done function.
-  var callDone = function(code, stdout, stderr) {
-    // Remove trailing whitespace (newline)
-    stdout = _.rtrim(stdout);
-    stderr = _.rtrim(stderr);
-    // Create the result object.
-    var result = {
-      stdout: stdout,
-      stderr: stderr,
-      code: code,
-      toString: function() {
-        if (code === 0) {
-          return stdout;
-        } else if ('fallback' in opts) {
-          return opts.fallback;
-        } else if (opts.grunt) {
-          // grunt.log.error uses standard out, to be fixed in 0.5.
-          return stderr || stdout;
-        }
-        return stderr;
-      }
-    };
-    // On error (and no fallback) pass an error object, otherwise pass null.
-    done(code === 0 || 'fallback' in opts ? null : new Error(stderr), result, code);
-  };
-
-  var cmd, args;
-  var pathSeparatorRe = /[\\\/]/g;
-  if (opts.grunt) {
-    cmd = process.argv[0];
-    args = [process.argv[1]].concat(opts.args);
-  } else {
-    // On Windows, child_process.spawn will only file .exe files in the PATH,
-    // not other executable types (grunt issue #155).
-    try {
-      if (!pathSeparatorRe.test(opts.cmd)) {
-        // Only use which if cmd has no path component.
-        cmd = which(opts.cmd);
-      } else {
-        cmd = opts.cmd.replace(pathSeparatorRe, path.sep);
-      }
-    } catch (err) {
-      callDone(127, '', String(err));
-      return;
-    }
-    args = opts.args;
-  }
-
-  var child = spawn(cmd, args, opts.opts);
-  var stdout = new Buffer('');
-  var stderr = new Buffer('');
-  if (child.stdout) {
-    child.stdout.on('data', function(buf) {
-      stdout = Buffer.concat([stdout, new Buffer(buf)]);
-    });
-  }
-  if (child.stderr) {
-    child.stderr.on('data', function(buf) {
-      stderr = Buffer.concat([stderr, new Buffer(buf)]);
-    });
-  }
-  child.on('close', function(code) {
-    callDone(code, stdout.toString(), stderr.toString());
-  });
-  return child;
-};
diff --git a/lib/util/exit.js b/lib/util/exit.js
deleted file mode 100644
index 867849c..0000000
--- a/lib/util/exit.js
+++ /dev/null
@@ -1,26 +0,0 @@
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2013 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
-
-'use strict';
-
-// This seems to be required in Windows (as of Node.js 0.8.7) to ensure that
-// stdout/stderr are flushed before the process exits.
-
-// https://gist.github.com/3427148
-// https://gist.github.com/3427357
-
-exports.exit = function exit(exitCode) {
-  if (process.stdout._pendingWriteReqs || process.stderr._pendingWriteReqs) {
-    process.nextTick(function() {
-      exit(exitCode);
-    });
-  } else {
-    process.exit(exitCode);
-  }
-};
diff --git a/lib/util/namespace.js b/lib/util/namespace.js
deleted file mode 100644
index 48bbae1..0000000
--- a/lib/util/namespace.js
+++ /dev/null
@@ -1,63 +0,0 @@
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2013 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
-
-(function(exports) {
-
-  'use strict';
-
-  // Split strings on dot, but only if dot isn't preceded by a backslash. Since
-  // JavaScript doesn't support lookbehinds, use a placeholder for "\.", split
-  // on dot, then replace the placeholder character with a dot.
-  function getParts(str) {
-    return str.replace(/\\\./g, '\uffff').split('.').map(function(s) {
-      return s.replace(/\uffff/g, '.');
-    });
-  }
-
-  // Get the value of a deeply-nested property exist in an object.
-  exports.get = function(obj, parts, create) {
-    if (typeof parts === 'string') {
-      parts = getParts(parts);
-    }
-
-    var part;
-    while (typeof obj === 'object' && obj && parts.length) {
-      part = parts.shift();
-      if (!(part in obj) && create) {
-        obj[part] = {};
-      }
-      obj = obj[part];
-    }
-
-    return obj;
-  };
-
-  // Set a deeply-nested property in an object, creating intermediary objects
-  // as we go.
-  exports.set = function(obj, parts, value) {
-    parts = getParts(parts);
-
-    var prop = parts.pop();
-    obj = exports.get(obj, parts, true);
-    if (obj && typeof obj === 'object') {
-      return (obj[prop] = value);
-    }
-  };
-
-  // Does a deeply-nested property exist in an object?
-  exports.exists = function(obj, parts) {
-    parts = getParts(parts);
-
-    var prop = parts.pop();
-    obj = exports.get(obj, parts);
-
-    return typeof obj === 'object' && obj && prop in obj;
-  };
-
-}(typeof exports === 'object' && exports || this));
diff --git a/lib/util/task.js b/lib/util/task.js
index bb6feb0..d39ce1b 100644
--- a/lib/util/task.js
+++ b/lib/util/task.js
@@ -1,16 +1,9 @@
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2013 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
-
 (function(exports) {
 
   'use strict';
 
+  var grunt = require('../grunt');
+
   // Construct-o-rama.
   function Task() {
     // Information about the currently-running task.
@@ -21,7 +14,7 @@
     this._queue = [];
     // Queue placeholder (for dealing with nested tasks).
     this._placeholder = {placeholder: true};
-    // Queue marker (for clearing the queue programatically).
+    // Queue marker (for clearing the queue programmatically).
     this._marker = {marker: true};
     // Options.
     this._options = {};
@@ -85,10 +78,18 @@
     return !!this._tasks[name].fn.alias;
   };
 
+  // Has the specified task been registered?
+  Task.prototype.exists = function(name) {
+    return name in this._tasks;
+  };
+
   // Rename a task. This might be useful if you want to override the default
   // behavior of a task, while retaining the old name. This is a billion times
   // easier to implement than some kind of in-task "super" functionality.
   Task.prototype.renameTask = function(oldname, newname) {
+    if (!this._tasks[oldname]) {
+      throw new Error('Cannot rename missing "' + oldname + '" task.');
+    }
     // Rename task.
     this._tasks[newname] = this._tasks[oldname];
     // Update name property of task.
@@ -177,7 +178,7 @@
     return this;
   };
 
-  // Add a marker to the queue to facilitate clearing it programatically.
+  // Add a marker to the queue to facilitate clearing it programmatically.
   Task.prototype.mark = function() {
     this._push(this._marker);
     // Make chainable!
@@ -185,7 +186,7 @@
   };
 
   // Run a task function, handling this.async / return value.
-  Task.prototype.runTaskFn = function(context, fn, done) {
+  Task.prototype.runTaskFn = function(context, fn, done, asyncDone) {
     // Async flag.
     var async = false;
 
@@ -212,7 +213,15 @@
       if (!success && this._options.error) {
         this._options.error.call({name: context.name, nameArgs: context.nameArgs}, err);
       }
-      done(err, success);
+      // only call done async if explicitly requested to
+      // see: https://github.com/gruntjs/grunt/pull/1026
+      if (asyncDone) {
+        process.nextTick(function() {
+          done(err, success);
+        });
+      } else {
+        done(err, success);
+      }
     }.bind(this);
 
     // When called, sets the async flag and returns a function that can
@@ -221,9 +230,9 @@
       async = true;
       // The returned function should execute asynchronously in case
       // someone tries to do this.async()(); inside a task (WTF).
-      return function(success) {
+      return grunt.util._.once(function(success) {
         setTimeout(function() { complete(success); }, 1);
-      };
+      });
     };
 
     // Expose some information about the currently-running task.
@@ -243,7 +252,10 @@
   };
 
   // Begin task queue processing. Ie. run all tasks.
-  Task.prototype.start = function() {
+  Task.prototype.start = function(opts) {
+    if (!opts) {
+      opts = {};
+    }
     // Abort if already running.
     if (this._running) { return false; }
     // Actually process the next task.
@@ -280,7 +292,7 @@
       // Actually run the task function (handling this.async, etc)
       this.runTaskFn(context, function() {
         return thing.task.fn.apply(this, this.args);
-      }, nextTask);
+      }, nextTask, !!opts.asyncDone);
 
     }.bind(this);
 
diff --git a/package.json b/package.json
index 92f948a..85ce579 100644
--- a/package.json
+++ b/package.json
@@ -1,28 +1,21 @@
 {
   "name": "grunt",
   "description": "The JavaScript Task Runner",
-  "version": "0.4.1",
-  "author": "\"Cowboy\" Ben Alman (http://benalman.com/)",
+  "version": "1.0.1",
+  "author": "Grunt Development Team (http://gruntjs.com/development-team)",
   "homepage": "http://gruntjs.com/",
-  "repository": {
-    "type": "git",
-    "url": "git://github.com/gruntjs/grunt.git"
-  },
-  "bugs": {
-    "url": "http://github.com/gruntjs/grunt/issues"
+  "repository": "gruntjs/grunt",
+  "license": "MIT",
+  "engines": {
+    "node": ">=0.10.0"
   },
-  "licenses": [
-    {
-      "type": "MIT",
-      "url": "http://github.com/gruntjs/grunt/blob/master/LICENSE-MIT"
-    }
-  ],
-  "main": "lib/grunt",
   "scripts": {
-    "test": "grunt test"
+    "test": "grunt test",
+    "test-tap": "grunt test:tap"
   },
-  "engines": {
-    "node": ">= 0.8.0"
+  "main": "lib/grunt",
+  "bin": {
+    "grunt": "bin/grunt"
   },
   "keywords": [
     "task",
@@ -44,28 +37,36 @@
     "tool"
   ],
   "dependencies": {
-    "async": "~0.1.22",
-    "coffee-script": "~1.3.3",
-    "colors": "~0.6.0-1",
-    "dateformat": "1.0.2-1.2.3",
-    "eventemitter2": "~0.4.9",
-    "findup-sync": "~0.1.0",
-    "glob": "~3.1.21",
-    "hooker": "~0.2.3",
-    "iconv-lite": "~0.2.5",
-    "minimatch": "~0.2.6",
-    "nopt": "~1.0.10",
-    "rimraf": "~2.0.2",
-    "lodash": "~0.9.0",
-    "underscore.string": "~2.2.0rc",
-    "which": "~1.0.5",
-    "js-yaml": "~2.0.2"
+    "coffee-script": "~1.10.0",
+    "dateformat": "~1.0.12",
+    "eventemitter2": "~0.4.13",
+    "exit": "~0.1.1",
+    "findup-sync": "~0.3.0",
+    "glob": "~7.0.0",
+    "grunt-cli": "~1.2.0",
+    "grunt-known-options": "~1.1.0",
+    "grunt-legacy-log": "~1.0.0",
+    "grunt-legacy-util": "~1.0.0",
+    "iconv-lite": "~0.4.13",
+    "js-yaml": "~3.5.2",
+    "minimatch": "~3.0.0",
+    "nopt": "~3.0.6",
+    "path-is-absolute": "~1.0.0",
+    "rimraf": "~2.2.8"
   },
   "devDependencies": {
+    "difflet": "~0.2.3",
+    "grunt-contrib-jshint": "~1.0.0",
+    "grunt-contrib-nodeunit": "~0.4.1",
+    "grunt-contrib-watch": "~1.0.0",
+    "grunt-jscs": "~2.8.0",
+    "semver": "2.1.0",
+    "shelljs": "~0.5.3",
     "temporary": "~0.0.4",
-    "grunt-contrib-jshint": "~0.1.1",
-    "grunt-contrib-nodeunit": "~0.1.2",
-    "grunt-contrib-watch": "~0.2.0",
-    "difflet": "~0.2.3"
-  }
+    "through2": "~2.0.0"
+  },
+  "files": [
+    "lib",
+    "bin"
+  ]
 }
diff --git a/test/fixtures/Gruntfile-cli.js b/test/fixtures/Gruntfile-cli.js
new file mode 100644
index 0000000..44a2ea0
--- /dev/null
+++ b/test/fixtures/Gruntfile-cli.js
@@ -0,0 +1,16 @@
+module.exports = function(grunt) {
+
+  var obj = {};
+  grunt.registerTask('finalize', 'Print all option values.', function() {
+    console.log('###' + JSON.stringify(obj) + '###');
+  });
+
+  // Create a per-CLI-option task that stores the value of that option
+  // to be output via the "finalize" task.
+  Object.keys(grunt.cli.optlist).forEach(function(name) {
+    grunt.registerTask(name, 'Store the current "' + name + '" option value.', function() {
+      obj[this.name] = grunt.option(this.name);
+    });
+  });
+
+};
diff --git a/test/fixtures/Gruntfile-print-text.js b/test/fixtures/Gruntfile-print-text.js
deleted file mode 100644
index 910197d..0000000
--- a/test/fixtures/Gruntfile-print-text.js
+++ /dev/null
@@ -1,8 +0,0 @@
-module.exports = function(grunt) {
-
-  grunt.registerTask('print', 'Print the specified text.', function(text) {
-    console.log('OUTPUT: ' + text);
-    // console.log(process.cwd());
-  });
-
-};
diff --git a/test/fixtures/exec.cmd b/test/fixtures/exec.cmd
deleted file mode 100755
index 6e4a52b..0000000
--- a/test/fixtures/exec.cmd
+++ /dev/null
@@ -1 +0,0 @@
- at echo done
diff --git a/test/fixtures/exec.sh b/test/fixtures/exec.sh
deleted file mode 100755
index 8890799..0000000
--- a/test/fixtures/exec.sh
+++ /dev/null
@@ -1,2 +0,0 @@
-#!/bin/bash
-echo "done"
diff --git a/test/fixtures/expand/css/baz.css b/test/fixtures/expand/css/baz.css
index e69de29..3f95386 100644
--- a/test/fixtures/expand/css/baz.css
+++ b/test/fixtures/expand/css/baz.css
@@ -0,0 +1 @@
+baz
\ No newline at end of file
diff --git a/test/fixtures/expand/css/qux.css b/test/fixtures/expand/css/qux.css
index e69de29..78df5b0 100644
--- a/test/fixtures/expand/css/qux.css
+++ b/test/fixtures/expand/css/qux.css
@@ -0,0 +1 @@
+qux
\ No newline at end of file
diff --git a/test/fixtures/expand/deep/deep.txt b/test/fixtures/expand/deep/deep.txt
index e69de29..d1f857b 100644
--- a/test/fixtures/expand/deep/deep.txt
+++ b/test/fixtures/expand/deep/deep.txt
@@ -0,0 +1 @@
+deep
\ No newline at end of file
diff --git a/test/fixtures/expand/deep/deeper/deeper.txt b/test/fixtures/expand/deep/deeper/deeper.txt
index e69de29..2e4e0c1 100644
--- a/test/fixtures/expand/deep/deeper/deeper.txt
+++ b/test/fixtures/expand/deep/deeper/deeper.txt
@@ -0,0 +1 @@
+deeper
\ No newline at end of file
diff --git a/test/fixtures/expand/deep/deeper/deepest/deepest.txt b/test/fixtures/expand/deep/deeper/deepest/deepest.txt
index e69de29..d087677 100644
--- a/test/fixtures/expand/deep/deeper/deepest/deepest.txt
+++ b/test/fixtures/expand/deep/deeper/deepest/deepest.txt
@@ -0,0 +1 @@
+deepest
\ No newline at end of file
diff --git a/test/fixtures/expand/js/bar.js b/test/fixtures/expand/js/bar.js
index e69de29..ba0e162 100644
--- a/test/fixtures/expand/js/bar.js
+++ b/test/fixtures/expand/js/bar.js
@@ -0,0 +1 @@
+bar
\ No newline at end of file
diff --git a/test/fixtures/expand/js/foo.js b/test/fixtures/expand/js/foo.js
index e69de29..1910281 100644
--- a/test/fixtures/expand/js/foo.js
+++ b/test/fixtures/expand/js/foo.js
@@ -0,0 +1 @@
+foo
\ No newline at end of file
diff --git a/test/fixtures/load-npm-tasks/node_modules/grunt-foo-plugin/package.json b/test/fixtures/load-npm-tasks/node_modules/grunt-foo-plugin/package.json
new file mode 100644
index 0000000..decedf6
--- /dev/null
+++ b/test/fixtures/load-npm-tasks/node_modules/grunt-foo-plugin/package.json
@@ -0,0 +1,6 @@
+{
+  "private": true,
+  "name": "grunt-foo-plugin",
+  "description": "",
+  "version": "1.0.0"
+}
diff --git a/test/fixtures/load-npm-tasks/node_modules/grunt-foo-plugin/tasks/foo.js b/test/fixtures/load-npm-tasks/node_modules/grunt-foo-plugin/tasks/foo.js
new file mode 100644
index 0000000..8dd9048
--- /dev/null
+++ b/test/fixtures/load-npm-tasks/node_modules/grunt-foo-plugin/tasks/foo.js
@@ -0,0 +1,7 @@
+'use strict';
+
+module.exports = function(grunt) {
+  grunt.registerTask('foo', function() {
+    grunt.log.writeln(this.name + ' has ran.');
+  });
+};
diff --git a/test/fixtures/load-npm-tasks/package.json b/test/fixtures/load-npm-tasks/package.json
new file mode 100644
index 0000000..de5fb74
--- /dev/null
+++ b/test/fixtures/load-npm-tasks/package.json
@@ -0,0 +1,7 @@
+{
+  "private": true,
+  "name": "load-npm-tasks",
+  "devDependencies": {
+    "grunt-foo-plugin": "1.0.0"
+  }
+}
diff --git a/test/fixtures/spawn.js b/test/fixtures/spawn.js
index eb65657..14c7029 100644
--- a/test/fixtures/spawn.js
+++ b/test/fixtures/spawn.js
@@ -4,6 +4,5 @@ var code = Number(process.argv[2]);
 process.stdout.write('stdout\n');
 process.stderr.write('stderr\n');
 
-// Use instead of process.exit to ensure stdout/stderr are flushed
-// before exiting in Windows (Tested in Node.js v0.8.7)
-require('../../lib/util/exit').exit(code);
+// Instead of process.exit. See https://github.com/cowboy/node-exit
+require('exit')(code);
diff --git a/test/grunt/cli_test.js b/test/grunt/cli_test.js
new file mode 100644
index 0000000..beb5036
--- /dev/null
+++ b/test/grunt/cli_test.js
@@ -0,0 +1,53 @@
+'use strict';
+
+var grunt = require('../../lib/grunt');
+
+// Parse options printed by fixtures/Gruntfile-cli into an object.
+var optionValueRe = /###(.*?)###/;
+function getOptionValues(str) {
+  var matches = str.match(optionValueRe);
+  return matches ? JSON.parse(matches[1]) : {};
+}
+
+exports.cli = {
+  '--debug taskname': function(test) {
+    test.expect(1);
+    grunt.util.spawn({
+      grunt: true,
+      args: ['--gruntfile', 'test/fixtures/Gruntfile-cli.js', '--debug', 'debug', 'finalize'],
+    }, function(err, result) {
+      test.deepEqual(getOptionValues(result.stdout), {debug: 1}, 'Options should parse correctly.');
+      test.done();
+    });
+  },
+  'taskname --debug': function(test) {
+    test.expect(1);
+    grunt.util.spawn({
+      grunt: true,
+      args: ['--gruntfile', 'test/fixtures/Gruntfile-cli.js', 'debug', '--debug', 'finalize'],
+    }, function(err, result) {
+      test.deepEqual(getOptionValues(result.stdout), {debug: 1}, 'Options should parse correctly.');
+      test.done();
+    });
+  },
+  '--debug --verbose': function(test) {
+    test.expect(1);
+    grunt.util.spawn({
+      grunt: true,
+      args: ['--gruntfile', 'test/fixtures/Gruntfile-cli.js', '--debug', '--verbose', 'debug', 'verbose', 'finalize'],
+    }, function(err, result) {
+      test.deepEqual(getOptionValues(result.stdout), {debug: 1, verbose: true}, 'Options should parse correctly.');
+      test.done();
+    });
+  },
+  '--verbose --debug': function(test) {
+    test.expect(1);
+    grunt.util.spawn({
+      grunt: true,
+      args: ['--gruntfile', 'test/fixtures/Gruntfile-cli.js', '--verbose', '--debug', 'debug', 'verbose', 'finalize'],
+    }, function(err, result) {
+      test.deepEqual(getOptionValues(result.stdout), {debug: 1, verbose: true}, 'Options should parse correctly.');
+      test.done();
+    });
+  },
+};
diff --git a/test/grunt/config_test.js b/test/grunt/config_test.js
index 8b731d7..6da5422 100644
--- a/test/grunt/config_test.js
+++ b/test/grunt/config_test.js
@@ -2,7 +2,7 @@
 
 var grunt = require('../../lib/grunt');
 
-exports['config'] = {
+exports.config = {
   setUp: function(done) {
     this.origData = grunt.config.data;
     grunt.config.init({
@@ -18,6 +18,7 @@ exports['config'] = {
       bar: 'bar',
       arr: ['foo', '<%= obj.foo2 %>'],
       arr2: ['<%= arr %>', '<%= obj.Arr %>'],
+      buffer: new Buffer('test'),
     });
     done();
   },
@@ -48,16 +49,19 @@ exports['config'] = {
     test.done();
   },
   'config.process': function(test) {
-    test.expect(5);
+    test.expect(7);
     test.equal(grunt.config.process('<%= meta.foo %>'), 'bar', 'Should process templates.');
     test.equal(grunt.config.process('<%= foo %>'), 'bar', 'Should process templates recursively.');
     test.equal(grunt.config.process('<%= obj.foo %>'), 'bar', 'Should process deeply nested templates recursively.');
     test.deepEqual(grunt.config.process(['foo', '<%= obj.foo2 %>']), ['foo', 'bar'], 'Should process templates in arrays.');
     test.deepEqual(grunt.config.process(['<%= arr %>', '<%= obj.Arr %>']), [['foo', 'bar'], ['foo', 'bar']], 'Should expand <%= arr %> and <%= obj.Arr %> values as objects if possible.');
+    var buf = grunt.config.process('<%= buffer %>');
+    test.ok(Buffer.isBuffer(buf), 'Should retrieve Buffer instances as Buffer.');
+    test.deepEqual(buf, new Buffer('test'), 'Should return buffers as-is.');
     test.done();
   },
   'config.get': function(test) {
-    test.expect(8);
+    test.expect(10);
     test.equal(grunt.config.get('foo'), 'bar', 'Should process templates.');
     test.equal(grunt.config.get('foo2'), 'bar', 'Should process templates recursively.');
     test.equal(grunt.config.get('obj.foo2'), 'bar', 'Should process deeply nested templates recursively.');
@@ -66,6 +70,9 @@ exports['config'] = {
     test.deepEqual(grunt.config.get('obj.Arr'), ['foo', 'bar'], 'Should process templates in arrays.');
     test.deepEqual(grunt.config.get('arr2'), [['foo', 'bar'], ['foo', 'bar']], 'Should expand <%= arr %> and <%= obj.Arr %> values as objects if possible.');
     test.deepEqual(grunt.config.get(['obj', 'arr2']), [['foo', 'bar'], ['foo', 'bar']], 'Should expand <%= arr %> and <%= obj.Arr %> values as objects if possible.');
+    var buf = grunt.config.get('buffer');
+    test.ok(Buffer.isBuffer(buf), 'Should retrieve Buffer instances as Buffer.');
+    test.deepEqual(buf, new Buffer('test'), 'Should return buffers as-is.');
     test.done();
   },
   'config.set': function(test) {
@@ -78,6 +85,20 @@ exports['config'] = {
     test.equal(grunt.config.data.a.b.c, '<%= foo2 %>', 'Should have set the value.');
     test.done();
   },
+  'config.merge': function(test) {
+    test.expect(4);
+    test.deepEqual(grunt.config.merge({}), grunt.config.getRaw(), 'Should return internal data object.');
+    grunt.config.set('obj', {a: 12});
+    grunt.config.merge({
+      foo: 'test',
+      baz: '123',
+      obj: {a: 34, b: 56},
+    });
+    test.deepEqual(grunt.config.getRaw('foo'), 'test', 'Should overwrite existing properties.');
+    test.deepEqual(grunt.config.getRaw('baz'), '123', 'Should add new properties.');
+    test.deepEqual(grunt.config.getRaw('obj'), {a: 34, b: 56}, 'Should deep merge.');
+    test.done();
+  },
   'config': function(test) {
     test.expect(10);
     test.equal(grunt.config('foo'), 'bar', 'Should retrieve processed data.');
diff --git a/test/grunt/event_test.js b/test/grunt/event_test.js
index 0d72353..c8359dc 100644
--- a/test/grunt/event_test.js
+++ b/test/grunt/event_test.js
@@ -2,7 +2,7 @@
 
 var grunt = require('../../lib/grunt');
 
-exports['event'] = function(test) {
+exports.event = function(test) {
   test.expect(3);
   grunt.event.on('test.foo', function(a, b, c) {
     // This should get executed once (emit test.foo).
diff --git a/test/grunt/file_test.js b/test/grunt/file_test.js
index e996cf9..91466f2 100644
--- a/test/grunt/file_test.js
+++ b/test/grunt/file_test.js
@@ -8,9 +8,18 @@ var path = require('path');
 var Tempfile = require('temporary/lib/file');
 var Tempdir = require('temporary/lib/dir');
 
+var win32 = process.platform === 'win32';
+
 var tmpdir = new Tempdir();
-fs.symlinkSync(path.resolve('test/fixtures/octocat.png'), path.join(tmpdir.path, 'octocat.png'), 'file');
-fs.symlinkSync(path.resolve('test/fixtures/expand'), path.join(tmpdir.path, 'expand'), 'dir');
+try {
+  fs.symlinkSync(path.resolve('test/fixtures/octocat.png'), path.join(tmpdir.path, 'octocat.png'), 'file');
+  fs.symlinkSync(path.resolve('test/fixtures/expand'), path.join(tmpdir.path, 'expand'), 'dir');
+} catch (err) {
+  console.error('** ERROR: Cannot create symbolic links; link-related tests will fail.');
+  if (win32) {
+    console.error('** Tests must be run with Administrator privileges on Windows.');
+  }
+}
 
 exports['file.match'] = {
   'empty set': function(test) {
@@ -198,10 +207,10 @@ exports['file.expand*'] = {
   'exclusion': function(test) {
     test.expect(8);
     test.deepEqual(grunt.file.expand(['!js/*.js']), [], 'solitary exclusion should match nothing');
-    test.deepEqual(grunt.file.expand(['js/bar.js','!js/bar.js']), [], 'exclusion should cancel match');
+    test.deepEqual(grunt.file.expand(['js/bar.js', '!js/bar.js']), [], 'exclusion should cancel match');
     test.deepEqual(grunt.file.expand(['**/*.js', '!js/foo.js']), ['js/bar.js'], 'should omit single file from matched set');
     test.deepEqual(grunt.file.expand(['!js/foo.js', '**/*.js']), ['js/bar.js', 'js/foo.js'], 'inclusion / exclusion order matters');
-    test.deepEqual(grunt.file.expand(['**/*.js', '**/*.css', '!js/bar.js', '!css/baz.css']), ['js/foo.js','css/qux.css'], 'multiple exclusions should be removed from the set');
+    test.deepEqual(grunt.file.expand(['**/*.js', '**/*.css', '!js/bar.js', '!css/baz.css']), ['js/foo.js', 'css/qux.css'], 'multiple exclusions should be removed from the set');
     test.deepEqual(grunt.file.expand(['**/*.js', '**/*.css', '!**/*.css']), ['js/bar.js', 'js/foo.js'], 'excluded wildcards should be removed from the matched set');
     test.deepEqual(grunt.file.expand(['js/bar.js', 'js/foo.js', 'css/baz.css', 'css/qux.css', '!**/b*.*']), ['js/foo.js', 'css/qux.css'], 'different pattern for exclusion should still work');
     test.deepEqual(grunt.file.expand(['js/bar.js', '!**/b*.*', 'js/foo.js', 'css/baz.css', 'css/qux.css']), ['js/foo.js', 'css/baz.css', 'css/qux.css'], 'inclusion / exclusion order matters');
@@ -272,7 +281,7 @@ exports['file.expandMapping'] = {
     test.done();
   },
   'ext': function(test) {
-    test.expect(2);
+    test.expect(3);
     var actual, expected;
     actual = grunt.file.expandMapping(['expand/**/*.txt'], 'dest', {ext: '.foo'});
     expected = [
@@ -288,6 +297,35 @@ exports['file.expandMapping'] = {
       {dest: 'dest/expand-mapping-ext/file.foo', src: ['expand-mapping-ext/file.ext.ension']},
     ];
     test.deepEqual(actual, expected, 'specified extension should be added');
+    actual = grunt.file.expandMapping(['expand/**/*.txt'], 'dest', {ext: ''});
+    expected = [
+      {dest: 'dest/expand/deep/deep', src: ['expand/deep/deep.txt']},
+      {dest: 'dest/expand/deep/deeper/deeper', src: ['expand/deep/deeper/deeper.txt']},
+      {dest: 'dest/expand/deep/deeper/deepest/deepest', src: ['expand/deep/deeper/deepest/deepest.txt']},
+    ];
+    test.deepEqual(actual, expected, 'empty string extension should be added');
+    test.done();
+  },
+  'extDot': function(test) {
+    test.expect(2);
+    var actual, expected;
+
+    actual = grunt.file.expandMapping(['expand-mapping-ext/**/file*'], 'dest', {ext: '.foo', extDot: 'first'});
+    expected = [
+      {dest: 'dest/expand-mapping-ext/dir.ectory/file-no-extension.foo', src: ['expand-mapping-ext/dir.ectory/file-no-extension']},
+      {dest: 'dest/expand-mapping-ext/dir.ectory/sub.dir.ectory/file.foo', src: ['expand-mapping-ext/dir.ectory/sub.dir.ectory/file.ext.ension']},
+      {dest: 'dest/expand-mapping-ext/file.foo', src: ['expand-mapping-ext/file.ext.ension']},
+    ];
+    test.deepEqual(actual, expected, 'extDot of "first" should replace everything after the first dot in the filename.');
+
+    actual = grunt.file.expandMapping(['expand-mapping-ext/**/file*'], 'dest', {ext: '.foo', extDot: 'last'});
+    expected = [
+      {dest: 'dest/expand-mapping-ext/dir.ectory/file-no-extension.foo', src: ['expand-mapping-ext/dir.ectory/file-no-extension']},
+      {dest: 'dest/expand-mapping-ext/dir.ectory/sub.dir.ectory/file.ext.foo', src: ['expand-mapping-ext/dir.ectory/sub.dir.ectory/file.ext.ension']},
+      {dest: 'dest/expand-mapping-ext/file.ext.foo', src: ['expand-mapping-ext/file.ext.ension']},
+    ];
+    test.deepEqual(actual, expected, 'extDot of "last" should replace everything after the last dot in the filename.');
+
     test.done();
   },
   'cwd': function(test) {
@@ -324,6 +362,7 @@ exports['file.expandMapping'] = {
       filter: 'isFile',
       cwd: 'expand',
       flatten: true,
+      nosort: true,
       rename: function(destBase, destPath) {
         return path.join(destBase, 'all' + path.extname(destPath));
       }
@@ -339,7 +378,6 @@ exports['file.expandMapping'] = {
   },
 };
 
-
 // Compare two buffers. Returns true if they are equivalent.
 var compareBuffers = function(buf1, buf2) {
   if (!Buffer.isBuffer(buf1) || !Buffer.isBuffer(buf2)) { return false; }
@@ -355,26 +393,45 @@ var compareFiles = function(filepath1, filepath2) {
   return compareBuffers(fs.readFileSync(filepath1), fs.readFileSync(filepath2));
 };
 
-exports['file'] = {
+exports.file = {
   setUp: function(done) {
     this.defaultEncoding = grunt.file.defaultEncoding;
     grunt.file.defaultEncoding = 'utf8';
     this.string = 'Ação é isso aí\n';
     this.object = {foo: 'Ação é isso aí', bar: ['ømg', 'pønies']};
     this.writeOption = grunt.option('write');
+
+    // Testing that warnings were displayed.
+    this.oldFailWarnFn = grunt.fail.warn;
+    this.oldLogWarnFn = grunt.log.warn;
+    this.resetWarnCount = function() {
+      this.warnCount = 0;
+    }.bind(this);
+    grunt.fail.warn = grunt.log.warn = function() {
+      this.warnCount += 1;
+    }.bind(this);
+
     done();
   },
   tearDown: function(done) {
     grunt.file.defaultEncoding = this.defaultEncoding;
     grunt.option('write', this.writeOption);
+
+    grunt.fail.warn = this.oldFailWarnFn;
+    grunt.log.warn = this.oldLogWarnFn;
+
     done();
   },
   'read': function(test) {
-    test.expect(5);
+    test.expect(6);
     test.strictEqual(grunt.file.read('test/fixtures/utf8.txt'), this.string, 'file should be read as utf8 by default.');
     test.strictEqual(grunt.file.read('test/fixtures/iso-8859-1.txt', {encoding: 'iso-8859-1'}), this.string, 'file should be read using the specified encoding.');
     test.ok(compareBuffers(grunt.file.read('test/fixtures/octocat.png', {encoding: null}), fs.readFileSync('test/fixtures/octocat.png')), 'file should be read as a buffer if encoding is specified as null.');
+
     test.strictEqual(grunt.file.read('test/fixtures/BOM.txt'), 'foo', 'file should have BOM stripped.');
+    grunt.file.preserveBOM = true;
+    test.strictEqual(grunt.file.read('test/fixtures/BOM.txt'), '\ufeff' + 'foo', 'file should have BOM preserved.');
+    grunt.file.preserveBOM = false;
 
     grunt.file.defaultEncoding = 'iso-8859-1';
     test.strictEqual(grunt.file.read('test/fixtures/iso-8859-1.txt'), this.string, 'changing the default encoding should work.');
@@ -409,7 +466,7 @@ exports['file'] = {
     test.done();
   },
   'write': function(test) {
-    test.expect(5);
+    test.expect(6);
     var tmpfile;
     tmpfile = new Tempfile();
     grunt.file.write(tmpfile.path, this.string);
@@ -421,6 +478,13 @@ exports['file'] = {
     test.strictEqual(grunt.file.read(tmpfile.path, {encoding: 'iso-8859-1'}), this.string, 'file should be written using the specified encoding.');
     tmpfile.unlinkSync();
 
+    tmpfile = new Tempfile();
+    tmpfile.unlinkSync();
+    grunt.file.write(tmpfile.path, this.string, {mode: parseInt('0444', 8)});
+    test.strictEqual(fs.statSync(tmpfile.path).mode & parseInt('0222', 8), 0, 'file should be read only.');
+    fs.chmodSync(tmpfile.path, parseInt('0666', 8));
+    tmpfile.unlinkSync();
+
     grunt.file.defaultEncoding = 'iso-8859-1';
     tmpfile = new Tempfile();
     grunt.file.write(tmpfile.path, this.string);
@@ -465,12 +529,13 @@ exports['file'] = {
     test.done();
   },
   'copy and process': function(test) {
-    test.expect(13);
+    test.expect(14);
     var tmpfile;
     tmpfile = new Tempfile();
     grunt.file.copy('test/fixtures/utf8.txt', tmpfile.path, {
-      process: function(src, filepath) {
-        test.equal(filepath, 'test/fixtures/utf8.txt', 'filepath should be passed in, as-specified.');
+      process: function(src, srcpath, destpath) {
+        test.equal(srcpath, 'test/fixtures/utf8.txt', 'srcpath should be passed in, as-specified.');
+        test.equal(destpath, tmpfile.path, 'destpath should be passed in, as-specified.');
         test.equal(Buffer.isBuffer(src), false, 'when no encoding is specified, use default encoding and process src as a string');
         test.equal(typeof src, 'string', 'when no encoding is specified, use default encoding and process src as a string');
         return 'føø' + src + 'bår';
@@ -556,6 +621,32 @@ exports['file'] = {
 
     test.done();
   },
+  'copy directory recursively': function(test) {
+    test.expect(34);
+    var copyroot1 = path.join(tmpdir.path, 'copy-dir-1');
+    var copyroot2 = path.join(tmpdir.path, 'copy-dir-2');
+    grunt.file.copy('test/fixtures/expand/', copyroot1);
+    grunt.file.recurse('test/fixtures/expand/', function(srcpath, rootdir, subdir, filename) {
+      var destpath = path.join(copyroot1, subdir || '', filename);
+      test.ok(grunt.file.isFile(srcpath), 'file should have been copied.');
+      test.equal(grunt.file.read(srcpath), grunt.file.read(destpath), 'file contents should be the same.');
+    });
+    grunt.file.mkdir(path.join(copyroot1, 'empty'));
+    grunt.file.mkdir(path.join(copyroot1, 'deep/deeper/empty'));
+    grunt.file.copy(copyroot1, copyroot2, {
+      process: function(contents) {
+        return '<' + contents + '>';
+      },
+    });
+    test.ok(grunt.file.isDir(path.join(copyroot2, 'empty')), 'empty directory should have been created.');
+    test.ok(grunt.file.isDir(path.join(copyroot2, 'deep/deeper/empty')), 'empty directory should have been created.');
+    grunt.file.recurse('test/fixtures/expand/', function(srcpath, rootdir, subdir, filename) {
+      var destpath = path.join(copyroot2, subdir || '', filename);
+      test.ok(grunt.file.isFile(srcpath), 'file should have been copied.');
+      test.equal('<' + grunt.file.read(srcpath) + '>', grunt.file.read(destpath), 'file contents should be processed correctly.');
+    });
+    test.done();
+  },
   'delete': function(test) {
     test.expect(2);
     var oldBase = process.cwd();
@@ -570,16 +661,15 @@ exports['file'] = {
     test.done();
   },
   'delete nonexistent file': function(test) {
-    test.expect(1);
+    test.expect(2);
+    this.resetWarnCount();
     test.ok(!grunt.file.delete('nonexistent'), 'should return false if file does not exist.');
+    test.ok(this.warnCount, 'should issue a warning when deleting non-existent file');
     test.done();
   },
   'delete outside working directory': function(test) {
-    test.expect(3);
+    test.expect(4);
     var oldBase = process.cwd();
-    var oldWarn = grunt.fail.warn;
-    grunt.fail.warn = function() {};
-
     var cwd = path.resolve(tmpdir.path, 'delete', 'folder');
     var outsidecwd = path.resolve(tmpdir.path, 'delete', 'outsidecwd');
     grunt.file.mkdir(cwd);
@@ -587,30 +677,31 @@ exports['file'] = {
     grunt.file.setBase(cwd);
 
     grunt.file.write(path.join(outsidecwd, 'test.js'), 'var test;');
+
+    this.resetWarnCount();
     test.equal(grunt.file.delete(path.join(outsidecwd, 'test.js')), false, 'should not delete anything outside the cwd.');
+    test.ok(this.warnCount, 'should issue a warning when deleting outside working directory');
 
-    test.ok(grunt.file.delete(path.join(outsidecwd), {force:true}), 'should delete outside cwd when using the --force.');
+    test.ok(grunt.file.delete(path.join(outsidecwd), {force: true}), 'should delete outside cwd when using the --force.');
     test.equal(grunt.file.exists(outsidecwd), false, 'file outside cwd should have been deleted when using the --force.');
 
     grunt.file.setBase(oldBase);
-    grunt.fail.warn = oldWarn;
     test.done();
   },
   'dont delete current working directory': function(test) {
-    test.expect(2);
+    test.expect(3);
     var oldBase = process.cwd();
-    var oldWarn = grunt.fail.warn;
-    grunt.fail.warn = function() {};
-
     var cwd = path.resolve(tmpdir.path, 'dontdelete', 'folder');
     grunt.file.mkdir(cwd);
     grunt.file.setBase(cwd);
 
+    this.resetWarnCount();
     test.equal(grunt.file.delete(cwd), false, 'should not delete the cwd.');
+    test.ok(this.warnCount, 'should issue a warning when trying to delete cwd');
+
     test.ok(grunt.file.exists(cwd), 'the cwd should exist.');
 
     grunt.file.setBase(oldBase);
-    grunt.fail.warn = oldWarn;
     test.done();
   },
   'dont actually delete with no-write option on': function(test) {
@@ -710,12 +801,17 @@ exports['file'] = {
     test.done();
   },
   'isPathAbsolute': function(test) {
-    test.expect(5);
+    test.expect(6);
     test.ok(grunt.file.isPathAbsolute(path.resolve('/foo')), 'should return true');
     test.ok(grunt.file.isPathAbsolute(path.resolve('/foo') + path.sep), 'should return true');
     test.equal(grunt.file.isPathAbsolute('foo'), false, 'should return false');
     test.ok(grunt.file.isPathAbsolute(path.resolve('test/fixtures/a.js')), 'should return true');
     test.equal(grunt.file.isPathAbsolute('test/fixtures/a.js'), false, 'should return false');
+    if (win32) {
+      test.equal(grunt.file.isPathAbsolute('C:/Users/'), true, 'should return true');
+    } else {
+      test.equal(grunt.file.isPathAbsolute('/'), true, 'should return true');
+    }
     test.done();
   },
   'arePathsEquivalent': function(test) {
@@ -761,4 +857,27 @@ exports['file'] = {
     test.equal(grunt.file.isPathInCwd('nonexistent'), false, 'nonexistent path is not in cwd');
     test.done();
   },
+  'cwdUnderSymlink': {
+    setUp: function(done) {
+      this.cwd = process.cwd();
+      process.chdir(path.join(tmpdir.path, 'expand'));
+      done();
+    },
+    tearDown: function(done) {
+      process.chdir(this.cwd);
+      done();
+    },
+    'isPathCwd': function(test) {
+      test.expect(2);
+      test.ok(grunt.file.isPathCwd(process.cwd()), 'cwd is cwd');
+      test.ok(grunt.file.isPathCwd('.'), 'cwd is cwd');
+      test.done();
+    },
+    'isPathInCwd': function(test) {
+      test.expect(2);
+      test.ok(grunt.file.isPathInCwd('deep'), 'subdirectory is in cwd');
+      test.ok(grunt.file.isPathInCwd(path.resolve('deep')), 'subdirectory is in cwd');
+      test.done();
+    },
+  }
 };
diff --git a/test/grunt/log_test.js b/test/grunt/log_test.js
deleted file mode 100644
index 27b982c..0000000
--- a/test/grunt/log_test.js
+++ /dev/null
@@ -1,224 +0,0 @@
-'use strict';
-
-var grunt = require('../../lib/grunt');
-var log = grunt.log;
-
-var fooBuffer = new Buffer('foo');
-
-// Helper for testing stdout
-var hooker = grunt.util.hooker;
-var stdoutEqual = function(test, callback, expected) {
-  var actual = '';
-  // Hook process.stdout.write
-  hooker.hook(process.stdout, 'write', {
-    // This gets executed before the original process.stdout.write.
-    pre: function(result) {
-      // Concatenate uncolored result onto actual.
-      actual += log.uncolor(result);
-      // Prevent the original process.stdout.write from executing.
-      return hooker.preempt();
-    }
-  });
-  // Execute the logging code to be tested.
-  callback();
-  // Restore process.stdout.write to its original value.
-  hooker.unhook(process.stdout, 'write');
-  // Actually test the actually-logged stdout string to the expected value.
-  test.equal(actual, expected);
-};
-
-exports['log'] = {
-  'setUp': function(done) {
-    log.muted = false;
-    grunt.fail.errorcount = 0;
-    done();
-  },
-  'write': function(test) {
-    test.expect(4);
-
-    stdoutEqual(test, function() { log.write(''); }, '');
-    stdoutEqual(test, function() { log.write('foo'); }, 'foo');
-    stdoutEqual(test, function() { log.write('%s', 'foo'); }, 'foo');
-    stdoutEqual(test, function() { log.write(fooBuffer); }, 'foo');
-
-    test.done();
-  },
-  'writeln': function(test) {
-    test.expect(4);
-
-    stdoutEqual(test, function() { log.writeln(); }, '\n');
-    stdoutEqual(test, function() { log.writeln('foo'); }, 'foo\n');
-    stdoutEqual(test, function() { log.writeln('%s', 'foo'); }, 'foo\n');
-    stdoutEqual(test, function() { log.writeln(fooBuffer); }, 'foo\n');
-
-    test.done();
-  },
-  'warn': function(test) {
-    test.expect(5);
-
-    stdoutEqual(test, function() { log.warn(); }, 'ERROR\n');
-    stdoutEqual(test, function() { log.warn('foo'); }, '>> foo\n');
-    stdoutEqual(test, function() { log.warn('%s', 'foo'); }, '>> foo\n');
-    stdoutEqual(test, function() { log.warn(fooBuffer); }, '>> foo\n');
-    test.equal(grunt.fail.errorcount, 0);
-
-    test.done();
-  },
-  'error': function(test) {
-    test.expect(5);
-
-    stdoutEqual(test, function() { log.error(); }, 'ERROR\n');
-    stdoutEqual(test, function() { log.error('foo'); }, '>> foo\n');
-    stdoutEqual(test, function() { log.error('%s', 'foo'); }, '>> foo\n');
-    stdoutEqual(test, function() { log.error(fooBuffer); }, '>> foo\n');
-    test.equal(grunt.fail.errorcount, 4);
-
-    test.done();
-  },
-  'ok': function(test) {
-    test.expect(4);
-
-    stdoutEqual(test, function() { log.ok(); }, 'OK\n');
-    stdoutEqual(test, function() { log.ok('foo'); }, '>> foo\n');
-    stdoutEqual(test, function() { log.ok('%s', 'foo'); }, '>> foo\n');
-    stdoutEqual(test, function() { log.ok(fooBuffer); }, '>> foo\n');
-
-    test.done();
-  },
-  'errorlns': function(test) {
-    test.expect(2);
-
-    stdoutEqual(test, function() {
-      log.errorlns(grunt.util._.repeat('foo', 30, ' '));
-    }, '>> ' + grunt.util._.repeat('foo', 19, ' ') + '\n' +
-      '>> ' + grunt.util._.repeat('foo', 11, ' ') + '\n');
-    test.equal(grunt.fail.errorcount, 1);
-
-    test.done();
-  },
-  'oklns': function(test) {
-    test.expect(1);
-
-    stdoutEqual(test, function() {
-      log.oklns(grunt.util._.repeat('foo', 30, ' '));
-    }, '>> ' + grunt.util._.repeat('foo', 19, ' ') + '\n' +
-      '>> ' + grunt.util._.repeat('foo', 11, ' ') + '\n');
-
-    test.done();
-  },
-  'success': function(test) {
-    test.expect(4);
-
-    stdoutEqual(test, function() { log.success(); }, '\n');
-    stdoutEqual(test, function() { log.success('foo'); }, 'foo\n');
-    stdoutEqual(test, function() { log.success('%s', 'foo'); }, 'foo\n');
-    stdoutEqual(test, function() { log.success(fooBuffer); }, 'foo\n');
-
-    test.done();
-  },
-  'fail': function(test) {
-    test.expect(4);
-
-    stdoutEqual(test, function() { log.fail(); }, '\n');
-    stdoutEqual(test, function() { log.fail('foo'); }, 'foo\n');
-    stdoutEqual(test, function() { log.fail('%s', 'foo'); }, 'foo\n');
-    stdoutEqual(test, function() { log.fail(fooBuffer); }, 'foo\n');
-
-    test.done();
-  },
-  'header': function(test) {
-    test.expect(4);
-
-    stdoutEqual(test, function() { log.header(); }, '\n\n');
-    stdoutEqual(test, function() { log.header('foo'); }, '\nfoo\n');
-    stdoutEqual(test, function() { log.header('%s', 'foo'); }, '\nfoo\n');
-    stdoutEqual(test, function() { log.header(fooBuffer); }, '\nfoo\n');
-
-    test.done();
-  },
-  'subhead': function(test) {
-    test.expect(4);
-
-    stdoutEqual(test, function() { log.subhead(); }, '\n\n');
-    stdoutEqual(test, function() { log.subhead('foo'); }, '\nfoo\n');
-    stdoutEqual(test, function() { log.subhead('%s', 'foo'); }, '\nfoo\n');
-    stdoutEqual(test, function() { log.subhead(fooBuffer); }, '\nfoo\n');
-
-    test.done();
-  },
-  'debug': function(test) {
-    test.expect(5);
-    var debug = grunt.option('debug');
-
-    grunt.option('debug', true);
-    stdoutEqual(test, function() { log.debug(); }, '[D] \n');
-    stdoutEqual(test, function() { log.debug('foo'); }, '[D] foo\n');
-    stdoutEqual(test, function() { log.debug('%s', 'foo'); }, '[D] foo\n');
-    stdoutEqual(test, function() { log.debug(fooBuffer); }, '[D] foo\n');
-
-    grunt.option('debug', false);
-    stdoutEqual(test, function() { log.debug('foo'); }, '');
-
-    grunt.option('debug', debug);
-    test.done();
-  },
-  'writetableln': function(test) {
-    test.expect(1);
-
-    stdoutEqual(test, function() {
-      log.writetableln([10], [grunt.util._.repeat('foo', 10)]);
-    }, 'foofoofoof\noofoofoofo\nofoofoofoo\n');
-
-    test.done();
-  },
-  'writelns': function(test) {
-    test.expect(1);
-
-    stdoutEqual(test, function() {
-      log.writelns(grunt.util._.repeat('foo', 30, ' '));
-    }, grunt.util._.repeat('foo', 20, ' ') + '\n' +
-      grunt.util._.repeat('foo', 10, ' ') + '\n');
-
-    test.done();
-  },
-  'writeflags': function(test) {
-    test.expect(1);
-
-    stdoutEqual(test, function() {
-      log.writeflags(['foo', 'bar'], 'test');
-    }, 'test: foo, bar\n');
-
-    test.done();
-  },
-  'verbose': function(test) {
-    test.expect(1);
-    log.muted = true;
-
-    // Test a chain to make sure it's always returning the verbose object.
-    var obj;
-    ['write','writeln','warn','error','ok'].forEach(function(key) {
-      obj = obj ? obj[key]('foo') : log.verbose[key]('foo');
-    });
-
-    test.strictEqual(obj, log.verbose);
-
-    test.done();
-  },
-  'notverbose': function(test) {
-    test.expect(1);
-    hooker.hook(process.stdout, 'write', {
-      pre: function() { return hooker.preempt(); }
-    });
-
-    // Test a chain to make sure it's always returning the notverbose object.
-    var obj;
-    ['write','writeln','warn','error','ok'].forEach(function(key) {
-      obj = obj ? obj[key]('foo') : log.notverbose[key]('foo');
-    });
-
-    test.strictEqual(obj, log.notverbose);
-
-    hooker.unhook(process.stdout, 'write');
-    test.done();
-  }
-};
diff --git a/test/grunt/option_test.js b/test/grunt/option_test.js
index 8917a57..de84799 100644
--- a/test/grunt/option_test.js
+++ b/test/grunt/option_test.js
@@ -2,7 +2,7 @@
 
 var grunt = require('../../lib/grunt');
 
-exports['option'] = {
+exports.option = {
   setUp: function(done) {
     grunt.option.init();
     done();
@@ -13,15 +13,15 @@ exports['option'] = {
   },
   'option.init': function(test) {
     test.expect(1);
-    var expected = {foo:'bar',bool:true,'bar':{foo:'bar'}};
+    var expected = {foo: 'bar', bool: true, 'bar': {foo: 'bar'}};
     test.deepEqual(grunt.option.init(expected), expected);
     test.done();
   },
   'option': function(test) {
     test.expect(4);
     test.equal(grunt.option('foo', 'bar'), grunt.option('foo'));
-    grunt.option('foo', {foo:'bar'});
-    test.deepEqual(grunt.option('foo'), {foo:'bar'});
+    grunt.option('foo', {foo: 'bar'});
+    test.deepEqual(grunt.option('foo'), {foo: 'bar'});
     test.equal(grunt.option('no-there'), false);
     grunt.option('there', false);
     test.equal(grunt.option('no-there'), true);
diff --git a/test/grunt/template_test.js b/test/grunt/template_test.js
index b7903ac..e188b47 100644
--- a/test/grunt/template_test.js
+++ b/test/grunt/template_test.js
@@ -2,7 +2,7 @@
 
 var grunt = require('../../lib/grunt');
 
-exports['template'] = {
+exports.template = {
   'process': function(test) {
     test.expect(4);
     var obj = {
diff --git a/test/grunt/util_test.js b/test/grunt/util_test.js
deleted file mode 100644
index 01e8b1c..0000000
--- a/test/grunt/util_test.js
+++ /dev/null
@@ -1,301 +0,0 @@
-'use strict';
-
-var grunt = require('../../lib/grunt');
-
-var fs = require('fs');
-var path = require('path');
-
-var Tempfile = require('temporary/lib/file');
-
-exports['util.callbackify'] = {
-  'return': function(test) {
-    test.expect(1);
-    // This function returns a value.
-    function add(a, b) {
-      return a + b;
-    }
-    grunt.util.callbackify(add)(1, 2, function(result) {
-      test.equal(result, 3, 'should be the correct result.');
-      test.done();
-    });
-  },
-  'callback (sync)': function(test) {
-    test.expect(1);
-    // This function accepts a callback which it calls synchronously.
-    function add(a, b, done) {
-      done(a + b);
-    }
-    grunt.util.callbackify(add)(1, 2, function(result) {
-      test.equal(result, 3, 'should be the correct result.');
-      test.done();
-    });
-  },
-  'callback (async)': function(test) {
-    test.expect(1);
-    // This function accepts a callback which it calls asynchronously.
-    function add(a, b, done) {
-      setTimeout(done.bind(null, a + b), 0);
-    }
-    grunt.util.callbackify(add)(1, 2, function(result) {
-      test.equal(result, 3, 'should be the correct result.');
-      test.done();
-    });
-  }
-};
-
-exports['util'] = {
-  'error': function(test) {
-    test.expect(9);
-    var origError = new Error('Original error.');
-
-    var err = grunt.util.error('Test message.');
-    test.ok(err instanceof Error, 'Should be an Error.');
-    test.equal(err.name, 'Error', 'Should be an Error.');
-    test.equal(err.message, 'Test message.', 'Should have the correct message.');
-
-    err = grunt.util.error('Test message.', origError);
-    test.ok(err instanceof Error, 'Should be an Error.');
-    test.equal(err.name, 'Error', 'Should be an Error.');
-    test.equal(err.message, 'Test message.', 'Should have the correct message.');
-    test.equal(err.origError, origError, 'Should reflect the original error.');
-
-    var newError = new Error('Test message.');
-    err = grunt.util.error(newError, origError);
-    test.equal(err, newError, 'Should be the passed-in Error.');
-    test.equal(err.origError, origError, 'Should reflect the original error.');
-    test.done();
-  },
-  'linefeed': function(test) {
-    test.expect(1);
-    if (process.platform === 'win32') {
-      test.equal(grunt.util.linefeed, '\r\n', 'linefeed should be operating-system appropriate.');
-    } else {
-      test.equal(grunt.util.linefeed, '\n', 'linefeed should be operating-system appropriate.');
-    }
-    test.done();
-  },
-  'normalizelf': function(test) {
-    test.expect(1);
-    if (process.platform === 'win32') {
-      test.equal(grunt.util.normalizelf('foo\nbar\r\nbaz\r\n\r\nqux\n\nquux'), 'foo\r\nbar\r\nbaz\r\n\r\nqux\r\n\r\nquux', 'linefeeds should be normalized');
-    } else {
-      test.equal(grunt.util.normalizelf('foo\nbar\r\nbaz\r\n\r\nqux\n\nquux'), 'foo\nbar\nbaz\n\nqux\n\nquux', 'linefeeds should be normalized');
-    }
-    test.done();
-  }
-};
-
-exports['util.spawn'] = {
-  setUp: function(done) {
-    this.script = path.resolve('test/fixtures/spawn.js');
-    done();
-  },
-  'exit code 0': function(test) {
-    test.expect(6);
-    grunt.util.spawn({
-      cmd: process.execPath,
-      args: [ this.script, 0 ],
-    }, function(err, result, code) {
-      test.equals(err, null);
-      test.equals(code, 0);
-      test.equals(result.stdout, 'stdout');
-      test.equals(result.stderr, 'stderr');
-      test.equals(result.code, 0);
-      test.equals(String(result), 'stdout');
-      test.done();
-    });
-  },
-  'exit code 0, fallback': function(test) {
-    test.expect(6);
-    grunt.util.spawn({
-      cmd: process.execPath,
-      args: [ this.script, 0 ],
-      fallback: 'ignored if exit code is 0'
-    }, function(err, result, code) {
-      test.equals(err, null);
-      test.equals(code, 0);
-      test.equals(result.stdout, 'stdout');
-      test.equals(result.stderr, 'stderr');
-      test.equals(result.code, 0);
-      test.equals(String(result), 'stdout');
-      test.done();
-    });
-  },
-  'non-zero exit code': function(test) {
-    test.expect(7);
-    grunt.util.spawn({
-      cmd: process.execPath,
-      args: [ this.script, 123 ],
-    }, function(err, result, code) {
-      test.ok(err instanceof Error);
-      test.equals(err.message, 'stderr');
-      test.equals(code, 123);
-      test.equals(result.stdout, 'stdout');
-      test.equals(result.stderr, 'stderr');
-      test.equals(result.code, 123);
-      test.equals(String(result), 'stderr');
-      test.done();
-    });
-  },
-  'non-zero exit code, fallback': function(test) {
-    test.expect(6);
-    grunt.util.spawn({
-      cmd: process.execPath,
-      args: [ this.script, 123 ],
-      fallback: 'custom fallback'
-    }, function(err, result, code) {
-      test.equals(err, null);
-      test.equals(code, 123);
-      test.equals(result.stdout, 'stdout');
-      test.equals(result.stderr, 'stderr');
-      test.equals(result.code, 123);
-      test.equals(String(result), 'custom fallback');
-      test.done();
-    });
-  },
-  'cmd not found': function(test) {
-    test.expect(3);
-    grunt.util.spawn({
-      cmd: 'nodewtfmisspelled',
-    }, function(err, result, code) {
-      test.ok(err instanceof Error);
-      test.equals(code, 127);
-      test.equals(result.code, 127);
-      test.done();
-    });
-  },
-  'cmd not found, fallback': function(test) {
-    test.expect(4);
-    grunt.util.spawn({
-      cmd: 'nodewtfmisspelled',
-      fallback: 'use a fallback or good luck'
-    }, function(err, result, code) {
-      test.equals(err, null);
-      test.equals(code, 127);
-      test.equals(result.code, 127);
-      test.equals(String(result), 'use a fallback or good luck');
-      test.done();
-    });
-  },
-  'cmd not in path': function(test) {
-    test.expect(6);
-    var win32 = process.platform === 'win32';
-    grunt.util.spawn({
-      cmd: 'test\\fixtures\\exec' + (win32 ? '.cmd' : '.sh'),
-    }, function(err, result, code) {
-      test.equals(err, null);
-      test.equals(code, 0);
-      test.equals(result.stdout, 'done');
-      test.equals(result.stderr, '');
-      test.equals(result.code, 0);
-      test.equals(String(result), 'done');
-      test.done();
-    });
-  },
-  'cmd not in path (with cwd)': function(test) {
-    test.expect(6);
-    var win32 = process.platform === 'win32';
-    grunt.util.spawn({
-      cmd: './exec' + (win32 ? '.cmd' : '.sh'),
-      opts: {cwd: 'test/fixtures'},
-    }, function(err, result, code) {
-      test.equals(err, null);
-      test.equals(code, 0);
-      test.equals(result.stdout, 'done');
-      test.equals(result.stderr, '');
-      test.equals(result.code, 0);
-      test.equals(String(result), 'done');
-      test.done();
-    });
-  },
-  'grunt': function(test) {
-    test.expect(3);
-    grunt.util.spawn({
-      grunt: true,
-      args: [ '--gruntfile', 'test/fixtures/Gruntfile-print-text.js', 'print:foo' ],
-    }, function(err, result, code) {
-      test.equals(err, null);
-      test.equals(code, 0);
-      test.ok(/^OUTPUT: foo/m.test(result.stdout), 'stdout should contain output indicating the grunt task was run.');
-      test.done();
-    });
-  },
-  'grunt (with cwd)': function(test) {
-    test.expect(3);
-    grunt.util.spawn({
-      grunt: true,
-      args: [ '--gruntfile', 'Gruntfile-print-text.js', 'print:foo' ],
-      opts: {cwd: 'test/fixtures'},
-    }, function(err, result, code) {
-      test.equals(err, null);
-      test.equals(code, 0);
-      test.ok(/^OUTPUT: foo/m.test(result.stdout), 'stdout should contain output indicating the grunt task was run.');
-      test.done();
-    });
-  },
-  'grunt result.toString() with error': function(test) {
-    // grunt.log.error uses standard out, to be fixed in 0.5.
-    test.expect(4);
-    grunt.util.spawn({
-      grunt: true,
-      args: [ 'nonexistentTask' ]
-    }, function(err, result, code) {
-      test.ok(err instanceof Error, 'Should be an Error.');
-      test.equal(err.name, 'Error', 'Should be an Error.');
-      test.equals(code, 3);
-      test.ok(/Warning: Task "nonexistentTask" not found./m.test(result.toString()), 'stdout should contain output indicating the grunt task was (attempted to be) run.');
-      test.done();
-    });
-  },
-  'custom stdio stream(s)': function(test) {
-    test.expect(6);
-    var stdoutFile = new Tempfile();
-    var stderrFile = new Tempfile();
-    var stdout = fs.openSync(stdoutFile.path, 'a');
-    var stderr = fs.openSync(stderrFile.path, 'a');
-    var child = grunt.util.spawn({
-      cmd: process.execPath,
-      args: [ this.script, 0 ],
-      opts: {stdio: [null, stdout, stderr]},
-    }, function(err, result, code) {
-      test.equals(code, 0);
-      test.equals(String(fs.readFileSync(stdoutFile.path)), 'stdout\n', 'Child process stdout should have been captured via custom stream.');
-      test.equals(String(fs.readFileSync(stderrFile.path)), 'stderr\n', 'Child process stderr should have been captured via custom stream.');
-      stdoutFile.unlinkSync();
-      stderrFile.unlinkSync();
-      test.equals(result.stdout, '', 'Nothing will be passed to the stdout string when spawn stdio is a custom stream.');
-      test.done();
-    });
-    test.ok(!child.stdout, 'child should not have a stdout property.');
-    test.ok(!child.stderr, 'child should not have a stderr property.');
-  },
-};
-
-exports['util.spawn.multibyte'] = {
-  setUp: function(done) {
-    this.script = path.resolve('test/fixtures/spawn-multibyte.js');
-    done();
-  },
-  'partial stdout': function(test) {
-    test.expect(4);
-    grunt.util.spawn({
-      cmd: process.execPath,
-      args: [ this.script ],
-    }, function(err, result, code) {
-      test.equals(err, null);
-      test.equals(code, 0);
-      test.equals(result.stdout, 'こんにちは');
-      test.equals(result.stderr, 'こんにちは');
-      test.done();
-    });
-  }
-};
-
-exports['util.underscore.string'] = function(test) {
-  test.expect(4);
-  test.equals(grunt.util._.trim('    foo     '), 'foo', 'Should have trimmed the string.');
-  test.equals(grunt.util._.capitalize('foo'), 'Foo', 'Should have capitalized the first letter.');
-  test.equals(grunt.util._.words('one two three').length, 3, 'Should have counted three words.');
-  test.ok(grunt.util._.isBlank(' '), 'Should be blank.');
-  test.done();
-};
diff --git a/test/gruntfile/load-npm-tasks.js b/test/gruntfile/load-npm-tasks.js
new file mode 100644
index 0000000..078b315
--- /dev/null
+++ b/test/gruntfile/load-npm-tasks.js
@@ -0,0 +1,42 @@
+'use strict';
+
+var Log = require('grunt-legacy-log').Log;
+var assert = require('assert');
+var through = require('through2');
+
+module.exports = function(grunt) {
+  grunt.file.setBase('../fixtures/load-npm-tasks');
+
+  // Create a custom log to assert output
+  var stdout = [];
+  var oldlog = grunt.log;
+  var stream = through(function(data, enc, next) {
+    stdout.push(data.toString());
+    next(null, data);
+  });
+  stream.pipe(process.stdout);
+  var log = new Log({
+    grunt: grunt,
+    outStream: stream,
+  });
+  grunt.log = log;
+
+  // Load a npm task
+  grunt.loadNpmTasks('grunt-foo-plugin');
+
+  // Run them
+  grunt.registerTask('default', ['foo', 'done']);
+
+  // Assert they loaded and ran correctly
+  grunt.registerTask('done', function() {
+    grunt.log = oldlog;
+    stdout = stdout.join('\n');
+    try {
+      assert.ok(stdout.indexOf('foo has ran.') !== -1, 'oh-four task should have ran.');
+    } catch (err) {
+      grunt.log.subhead(err.message);
+      grunt.log.error('Expected ' + err.expected + ' but actually: ' + err.actual);
+      throw err;
+    }
+  });
+};
diff --git a/test/gruntfile/multi-task-files.js b/test/gruntfile/multi-task-files.js
index 3fda2e4..d170d06 100644
--- a/test/gruntfile/multi-task-files.js
+++ b/test/gruntfile/multi-task-files.js
@@ -1,12 +1,3 @@
-/*
- * grunt
- * http://gruntjs.com/
- *
- * Copyright (c) 2013 "Cowboy" Ben Alman
- * Licensed under the MIT license.
- * https://github.com/gruntjs/grunt/blob/master/LICENSE-MIT
- */
-
 // For now, run this "test suite" with:
 // grunt --gruntfile ./test/gruntfile/multi-task-files.js
 
@@ -34,7 +25,7 @@ module.exports = function(grunt) {
       'dist/built1.js': ['src/*1.js', 'src/*2.js'],
       // This is the "medium" format. The target name is arbitrary and can be
       // used like "grunt run:built". Supports per-target options, templated
-      // dest, and arbitrary "extra" paramters. Doesn't support >1 srcs-dest
+      // dest, and arbitrary "extra" parameters. Doesn't support >1 srcs-dest
       // grouping.
       built: {
         options: {a: 2, c: 22},
@@ -59,7 +50,7 @@ module.exports = function(grunt) {
           {'dist/built-<%= build %>-b.js': ['src/*1.js', 'src/*2.js']},
         ]
       },
-      // This "full" variant supports per srcs-dest arbitrary "extra" paramters.
+      // This "full" variant supports per srcs-dest arbitrary "extra" parameters.
       long3: {
         options: {a: 5, c: 55},
         files: [
@@ -90,6 +81,19 @@ module.exports = function(grunt) {
           }
         ]
       },
+      long4_mapping: {
+        options: {a: 8, c: 88},
+        files: [
+          '<%= run.long3_mapping.files %>'
+        ]
+      },
+      long5_mapping: {
+        options: {a: 9, c: 99},
+        files: [
+          '<%= run.long3_mapping.files %>',
+          '<%= run.long4_mapping.files %>'
+        ]
+      },
       // Need to ensure the task function is run if no files or options were
       // specified!
       no_files_or_options: {},
@@ -294,6 +298,94 @@ module.exports = function(grunt) {
         },
       ],
     },
+    'run:long4_mapping': {
+      options: {a: 8, b: 11, c: 88, d: 9},
+      files: [
+        {
+          dest: 'foo/baz/file1.bar',
+          src: ['src/file1.js'],
+          extra: 123,
+          orig: {
+            expand: true,
+            cwd: grunt.config.get('mappings.cwd'),
+            src: ['*1.js', '*2.js'],
+            dest: grunt.config.get('mappings.dest'),
+            rename: grunt.config.get('mappings.rename'),
+            extra: 123,
+          },
+        },
+        {
+          dest: 'foo/baz/file2.bar',
+          src: ['src/file2.js'],
+          extra: 123,
+          orig: {
+            expand: true,
+            cwd: grunt.config.get('mappings.cwd'),
+            src: ['*1.js', '*2.js'],
+            dest: grunt.config.get('mappings.dest'),
+            rename: grunt.config.get('run.built_mapping.rename'),
+            extra: 123,
+          },
+        },
+      ],
+    },
+    'run:long5_mapping': {
+      options: {a: 9, b: 11, c: 99, d: 9},
+      files: [
+        {
+          dest: 'foo/baz/file1.bar',
+          src: ['src/file1.js'],
+          extra: 123,
+          orig: {
+            expand: true,
+            cwd: grunt.config.get('mappings.cwd'),
+            src: ['*1.js', '*2.js'],
+            dest: grunt.config.get('mappings.dest'),
+            rename: grunt.config.get('mappings.rename'),
+            extra: 123,
+          },
+        },
+        {
+          dest: 'foo/baz/file2.bar',
+          src: ['src/file2.js'],
+          extra: 123,
+          orig: {
+            expand: true,
+            cwd: grunt.config.get('mappings.cwd'),
+            src: ['*1.js', '*2.js'],
+            dest: grunt.config.get('mappings.dest'),
+            rename: grunt.config.get('run.built_mapping.rename'),
+            extra: 123,
+          },
+        },
+        {
+          dest: 'foo/baz/file1.bar',
+          src: ['src/file1.js'],
+          extra: 123,
+          orig: {
+            expand: true,
+            cwd: grunt.config.get('mappings.cwd'),
+            src: ['*1.js', '*2.js'],
+            dest: grunt.config.get('mappings.dest'),
+            rename: grunt.config.get('mappings.rename'),
+            extra: 123,
+          },
+        },
+        {
+          dest: 'foo/baz/file2.bar',
+          src: ['src/file2.js'],
+          extra: 123,
+          orig: {
+            expand: true,
+            cwd: grunt.config.get('mappings.cwd'),
+            src: ['*1.js', '*2.js'],
+            dest: grunt.config.get('mappings.dest'),
+            rename: grunt.config.get('run.built_mapping.rename'),
+            extra: 123,
+          },
+        },
+      ],
+    },
   };
 
   var assert = require('assert');
@@ -353,6 +445,10 @@ module.exports = function(grunt) {
     'test:built_mapping',
     'run:long3_mapping',
     'test:long3_mapping',
+    'run:long4_mapping',
+    'test:long4_mapping',
+    'run:long5_mapping',
+    'test:long5_mapping',
     'run',
     'test:all',
     'test:counters',
diff --git a/test/util/namespace_test.js b/test/util/namespace_test.js
deleted file mode 100644
index 9a23616..0000000
--- a/test/util/namespace_test.js
+++ /dev/null
@@ -1,51 +0,0 @@
-'use strict';
-
-var namespace = require('../../lib/util/namespace.js');
-
-exports.get = {
-  'no create': function(test) {
-    var obj = {a: {b: {c: 1, d: '', e: null, f: undefined, 'g.h.i': 2}}};
-    test.strictEqual(namespace.get(obj, 'a'), obj.a, 'should get immediate properties.');
-    test.strictEqual(namespace.get(obj, 'a.b'), obj.a.b, 'should get nested properties.');
-    test.strictEqual(namespace.get(obj, 'a.x'), undefined, 'should return undefined for nonexistent properties.');
-    test.strictEqual(namespace.get(obj, 'a.b.c'), 1, 'should return values.');
-    test.strictEqual(namespace.get(obj, 'a.b.d'), '', 'should return values.');
-    test.strictEqual(namespace.get(obj, 'a.b.e'), null, 'should return values.');
-    test.strictEqual(namespace.get(obj, 'a.b.f'), undefined, 'should return values.');
-    test.strictEqual(namespace.get(obj, 'a.b.g\\.h\\.i'), 2, 'literal backslash should escape period in property name.');
-    test.done();
-  },
-  'create': function(test) {
-    var obj = {a: 1};
-    test.strictEqual(namespace.get(obj, 'a', true), obj.a, 'should just return existing properties.');
-    test.strictEqual(namespace.get(obj, 'b', true), obj.b, 'should create immediate properties.');
-    test.strictEqual(namespace.get(obj, 'c.d.e', true), obj.c.d.e, 'should create nested properties.');
-    test.done();
-  }
-};
-
-exports.set = function(test) {
-  var obj = {};
-  test.strictEqual(namespace.set(obj, 'a', 1), 1, 'should return immediate property value.');
-  test.strictEqual(obj.a, 1, 'should set property value.');
-  test.strictEqual(namespace.set(obj, 'b.c.d', 1), 1, 'should return nested property value.');
-  test.strictEqual(obj.b.c.d, 1, 'should set property value.');
-  test.strictEqual(namespace.set(obj, 'e\\.f\\.g', 1), 1, 'literal backslash should escape period in property name.');
-  test.strictEqual(obj['e.f.g'], 1, 'should set property value.');
-  test.done();
-};
-
-exports.exists = function(test) {
-  var obj = {a: {b: {c: 1, d: '', e: null, f: undefined, 'g.h.i': 2}}};
-  test.ok(namespace.exists(obj, 'a'), 'immediate property should exist.');
-  test.ok(namespace.exists(obj, 'a.b'), 'nested property should exist.');
-  test.ok(namespace.exists(obj, 'a.b.c'), 'nested property should exist.');
-  test.ok(namespace.exists(obj, 'a.b.d'), 'nested property should exist.');
-  test.ok(namespace.exists(obj, 'a.b.e'), 'nested property should exist.');
-  test.ok(namespace.exists(obj, 'a.b.f'), 'nested property should exist.');
-  test.ok(namespace.exists(obj, 'a.b.g\\.h\\.i'), 'literal backslash should escape period in property name.');
-  test.equal(namespace.exists(obj, 'x'), false, 'nonexistent property should not exist.');
-  test.equal(namespace.exists(obj, 'a.x'), false, 'nonexistent property should not exist.');
-  test.equal(namespace.exists(obj, 'a.b.x'), false, 'nonexistent property should not exist.');
-  test.done();
-};
diff --git a/test/util/task_test.js b/test/util/task_test.js
index fc893ac..3791620 100644
--- a/test/util/task_test.js
+++ b/test/util/task_test.js
@@ -26,7 +26,7 @@ exports['new Task'] = {
   }
 };
 
-exports['Tasks'] = {
+exports.Tasks = {
   setUp: function(done) {
     result.reset();
     this.task = requireTask().create();
@@ -169,6 +169,13 @@ exports['Tasks'] = {
     });
     task.run('syncs', 'asyncs').start();
   },
+  'Task#exists': function(test) {
+    test.expect(2);
+    var task = this.task;
+    test.equal(task.exists('nothing'), true, 'A task should not be exists (registered).');
+    test.equal(task.exists('notexistent'), false, 'A task should not be exists (registered).');
+    test.done();
+  },
   'Task#run (nested, exception handling)': function(test) {
     test.expect(2);
     var task = this.task;
@@ -271,6 +278,17 @@ exports['Tasks'] = {
     });
     task.run('a', 'h').start();
   },
+  'Task#run (async task with multiple callbacks)': function(test) {
+    test.expect(1);
+    var task = this.task;
+    task.registerTask('a', 'Call async callback twice.', function() { var done = this.async(); done(); done(); });
+    task.registerTask('b', 'Never call async callback.', function() { this.async(); });
+    task.run('a', 'b').start();
+    delay(function() {
+      test.deepEqual(task.current.name, 'b', 'Should be stuck on task with no async callback');
+      test.done();
+    });
+  },
   'Task#current': function(test) {
     test.expect(8);
     var task = this.task;

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/grunt.git



More information about the Pkg-javascript-commits mailing list