Repository: bower/bower
Pull request #2371 (2016-09-30 00:37:52)

Prefer exact versions when dissecting dependencies
When a project has multiple dependencies on the same package, we try to find one that resolves to a version that also satisfies all of the other constraints. Dependencies are checked in the order that they were resolved, which is dependent on network latency and essentially random. If there are multiple valid resolutions, running `bower install` with the same `bower.json` file won't always produce the same result, which can lead to inconsistent behaviour across environments.

If one of the dependencies specifies an exact version, we should prefer it over others that specify a range of versions. This is consistent with how we already deprioritise wildcard targets - we want to choose the most specific target of the highest version which resolves correctly.



Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index c421a6e5..da85d254 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,22 @@
 # Changelog
 
+## 1.8.0 - 2016-11-07
+
+- Download tar archives from GitHub when possible (#2263)
+  - Change default shorthand resolver for github from `git://` to `https://`
+- Fix ssl handling by not setting GIT_SSL_NO_VERIFY=false (#2361)
+- Allow for removing components with url instead of name (#2368)
+- Show in warning message location of malformed bower.json (#2357)
+- Improve handling of non-semver versions in git resolver (#2316)
+- Fix handling of cached releases pluginResolverFactory (#2356)
+- Allow to type the entire version when conflict occured (#2243)
+- Allow `owner/reponame` shorthand for registering components (#2248)
+- Allow single-char repo names and package names (#2249)
+- Make `bower version` no longer honor `version` in bower.json (#2232)
+- Add `postinstall` hook (#2252)
+- Allow for `@` instead of `#` for `install` and `info` commands (#2322)
+- Upgrade all bundled modules
+
 ## 1.7.9 - 2016-04-05
 
 - Show warnings for invalid bower.json fields
@@ -83,7 +100,7 @@ https://github.com/npm/npm/issues/11227
 - Update bower config
   - Loads the .bowerrc file from the cwd specified on the command line
   - Allow the use of environment variables in .bowerrc ([#41](https://github.com/bower/config/issues/41))
-	- Allow for array notation in ENV variables ([#44](https://github.com/bower/config/issues/44))
+  - Allow for array notation in ENV variables ([#44](https://github.com/bower/config/issues/44))
 
 ## 1.6.9 - 2015-12-04
 
diff --git a/lib/core/Manager.js b/lib/core/Manager.js
index 73f4b341..f1f9fcde 100644
--- a/lib/core/Manager.js
+++ b/lib/core/Manager.js
@@ -587,6 +587,17 @@ Manager.prototype._dissect = function () {
                 }
             }
 
+            // If they are equal and one of them is an exact target,
+            // give higher priority
+            if (!result) {
+                if (first.target === first.pkgMeta.version) {
+                    return -1;
+                }
+                if (second.target === second.pkgMeta.version) {
+                    return 1;
+                }
+            }
+
             return result;
         });
 
diff --git a/lib/core/Project.js b/lib/core/Project.js
index 2c4f3410..62f649b7 100644
--- a/lib/core/Project.js
+++ b/lib/core/Project.js
@@ -213,6 +213,20 @@ Project.prototype.update = function (names, options) {
     });
 };
 
+function resolveUrlNames(names, flattened)
+{
+    for (var i = 0; i < names.length; i++)
+        if (! flattened[names[i]])
+    {
+            var url = names[i].trim().replace(/\/$/, '');
+            var packName;
+            for (packName in flattened)
+                if (! ( !flattened[packName].source))
+                    if (url == flattened[packName].source.trim().replace(/\/$/, ''))
+                        names[i] = packName;
+        }
+}
+
 Project.prototype.uninstall = function (names, options) {
     var that = this;
     var packages = {};
@@ -230,6 +244,7 @@ Project.prototype.uninstall = function (names, options) {
     // Fill in the packages to be uninstalled
     .spread(function (json, tree, flattened) {
         var promise = Q.resolve();
+        resolveUrlNames(names, flattened);
 
         names.forEach(function (name) {
             var decEndpoint = flattened[name];
diff --git a/lib/core/resolvers/GitHubResolver.js b/lib/core/resolvers/GitHubResolver.js
index 2644ecbc..b9bb61eb 100644
--- a/lib/core/resolvers/GitHubResolver.js
+++ b/lib/core/resolvers/GitHubResolver.js
@@ -29,9 +29,6 @@ function GitHubResolver(decEndpoint, config, logger) {
         this._source += '.git';
     }
 
-    // Check if it's public
-    this._public = mout.string.startsWith(this._source, 'git://');
-
     // Use https:// rather than git:// if on a proxy
     if (this._config.proxy || this._config.httpsProxy) {
         this._source = this._source.replace('git://', 'https://');
@@ -49,14 +46,10 @@ mout.object.mixIn(GitHubResolver, GitRemoteResolver);
 // -----------------
 
 GitHubResolver.prototype._checkout = function () {
-    // Only fully works with public repositories and tags
-    // Could work with https/ssh protocol but not with 100% certainty
-    if (!this._public || !this._resolution.tag) {
-        return GitRemoteResolver.prototype._checkout.call(this);
-    }
-
     var msg;
-    var tarballUrl = 'https://github.com/' + this._org + '/' + this._repo + '/archive/' + this._resolution.tag + '.tar.gz';
+    var name = this._resolution.tag || this._resolution.branch || this._resolution.commit;
+    var tarballUrl = 'https://github.com/' + this._org + '/' + this._repo + '/archive/' + name + '.tar.gz';
+
     var file = path.join(this._tempDir, 'archive.tar.gz');
     var reqHeaders = {};
     var that = this;
@@ -121,7 +114,6 @@ GitHubResolver.prototype._checkout = function () {
 
         return that._cleanTempDir()
         .then(GitRemoteResolver.prototype._checkout.bind(that));
-
     });
 };
 
diff --git a/lib/core/resolvers/GitResolver.js b/lib/core/resolvers/GitResolver.js
index 3c161a3f..b34a839a 100644
--- a/lib/core/resolvers/GitResolver.js
+++ b/lib/core/resolvers/GitResolver.js
@@ -26,8 +26,18 @@ function GitResolver(decEndpoint, config, logger) {
     // anyway
     mkdirp.sync(config.storage.empty);
     process.env.GIT_TEMPLATE_DIR = config.storage.empty;
-    process.env.GIT_SSL_NO_VERIFY = (!config.strictSsl).toString();
-    process.env.GIT_TERMINAL_PROMPT = config.interactive ? '1' : '0';
+
+    if (!config.strictSsl) {
+        process.env.GIT_SSL_NO_VERIFY = 'true';
+    }
+
+    if (!config.interactive) {
+        process.env.GIT_TERMINAL_PROMPT = '0';
+
+        if (!process.env.SSH_ASKPASS) {
+            process.env.SSH_ASKPASS = 'echo';
+        }
+    }
 
     Resolver.call(this, decEndpoint, config, logger);
 
@@ -206,7 +216,7 @@ GitResolver.prototype._savePkgMeta = function (meta) {
         version = semver.clean(this._resolution.tag);
 
         // Warn if the package meta version is different than the resolved one
-        if (typeof meta.version === 'string' && semver.neq(meta.version, version)) {
+        if (typeof meta.version === 'string' && semver.valid(meta.version) && semver.neq(meta.version, version)) {
             this._logger.warn('mismatch', 'Version declared in the json (' + meta.version + ') is different than the resolved one (' + version + ')', {
                 resolution: this._resolution,
                 pkgMeta: meta
diff --git a/lib/core/resolvers/pluginResolverFactory.js b/lib/core/resolvers/pluginResolverFactory.js
index 799203c8..d751d1f5 100644
--- a/lib/core/resolvers/pluginResolverFactory.js
+++ b/lib/core/resolvers/pluginResolverFactory.js
@@ -109,7 +109,7 @@ function pluginResolverFactory(resolverFactory, bower) {
                             throw createError('Resolver did not provide releases of package.');
                         }
 
-                        var releases = this._releases = result;
+                        var releases = that._releases = result;
 
                         var versions = releases.filter(function (target) {
                             return semver.clean(target.version);
diff --git a/lib/util/readJson.js b/lib/util/readJson.js
index 370cb8a1..4b3d51fb 100644
--- a/lib/util/readJson.js
+++ b/lib/util/readJson.js
@@ -19,7 +19,9 @@ function readJson(file, options) {
 
         if (options.logger) {
             var issues = bowerJson.getIssues(json);
-
+            if (issues.warnings.length > 0) {
+                options.logger.warn('invalid-meta', 'for:' + jsonFile);
+            }
             issues.warnings.forEach(function (warning) {
                 options.logger.warn('invalid-meta', warning);
             });
diff --git a/package.json b/package.json
index b1331c86..6dbe1501 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "bower",
-  "version": "1.7.9",
+  "version": "1.8.0",
   "description": "The browser package manager",
   "author": "Twitter",
   "license": "MIT",
@@ -17,7 +17,7 @@
   "dependencies": {
     "abbrev": "^1.0.5",
     "archy": "1.0.0",
-    "bower-config": "^1.3.1",
+    "bower-config": "^1.4.0",
     "bower-endpoint-parser": "^0.2.2",
     "bower-json": "^0.8.1",
     "bower-logger": "^0.2.2",
diff --git a/packages/bower-config/package.json b/packages/bower-config/package.json
index cc1f3f91..feac4880 100644
--- a/packages/bower-config/package.json
+++ b/packages/bower-config/package.json
@@ -4,7 +4,7 @@
   "description": "The Bower config reader and writer.",
   "author": "Twitter",
   "license": "MIT",
-  "repository": "bower/config",
+  "repository": "https://github.com/bower/bower/tree/master/packages/bower-config",
   "main": "lib/Config",
   "homepage": "http://bower.io",
   "engines": {
diff --git a/packages/bower-endpoint-parser/package.json b/packages/bower-endpoint-parser/package.json
index aa726064..145e25c5 100644
--- a/packages/bower-endpoint-parser/package.json
+++ b/packages/bower-endpoint-parser/package.json
@@ -9,10 +9,7 @@
       "url": "https://github.com/bower/endpoint-parser/blob/master/LICENSE"
     }
   ],
-  "repository": {
-    "type": "git",
-    "url": "git://github.com/bower/endpoint-parser.git"
-  },
+  "repository": "https://github.com/bower/bower/tree/master/packages/bower-endpoint-parser",
   "main": "index.js",
   "engines": {
     "node": ">=0.8.0"
diff --git a/packages/bower-json/package.json b/packages/bower-json/package.json
index a145b194..e6187cbb 100644
--- a/packages/bower-json/package.json
+++ b/packages/bower-json/package.json
@@ -4,7 +4,7 @@
   "description": "Read bower.json files with semantics, normalisation, defaults and validation",
   "author": "Twitter",
   "license": "MIT",
-  "repository": "bower/json",
+  "repository": "https://github.com/bower/bower/tree/master/packages/bower-json",
   "main": "lib/json",
   "engines": {
     "node": ">=0.10.0"
diff --git a/packages/bower-logger/package.json b/packages/bower-logger/package.json
index a3c78190..26eed872 100644
--- a/packages/bower-logger/package.json
+++ b/packages/bower-logger/package.json
@@ -9,7 +9,7 @@
       "url": "https://github.com/bower/logger/blob/master/LICENSE"
     }
   ],
-  "repository": "bower/logger",
+  "repository": "https://github.com/bower/bower/tree/master/packages/bower-logger",
   "main": "lib/Logger",
   "engines": {
     "node": ">=0.10.0"
diff --git a/packages/bower-registry-client/package.json b/packages/bower-registry-client/package.json
index e7eb5658..74c206fa 100644
--- a/packages/bower-registry-client/package.json
+++ b/packages/bower-registry-client/package.json
@@ -4,7 +4,7 @@
   "description": "Provides easy interaction with the Bower registry",
   "author": "Twitter",
   "license": "MIT",
-  "repository": "bower/registry-client",
+  "repository": "https://github.com/bower/bower/tree/master/packages/bower-registry-client",
   "main": "Client",
   "engines": {
     "node": ">=0.10.0"


Test patch:
diff --git a/test/commands/uninstall.js b/test/commands/uninstall.js
index 5e02b7e0..db3ecbb5 100644
--- a/test/commands/uninstall.js
+++ b/test/commands/uninstall.js
@@ -99,4 +99,21 @@ describe('bower uninstall', function () {
         });
     });
 
+    it('removes a project with url from absolute path', function () {
+        var targetPath = path.resolve(tempDir.path, 'other_directory/underscore');
+        mkdirp.sync(targetPath);
+        fs.writeFileSync(path.join(targetPath, '.bower.json'), '{ "name": "underscore", "_source": "git://github.com/user/repo.git" }');
+
+        return helpers.run(uninstall, [['git://github.com/user/repo.git'], undefined, {
+            cwd: tempDir.path,
+            directory: path.resolve(tempDir.path, 'other_directory'),
+            interactive: true
+        }])
+            .then(function () {
+                expect(function () {
+                    fs.statSync(targetPath);
+                }).to.throwException(/no such file or directory/);
+            });
+    });
+
 });
diff --git a/test/core/Manager.js b/test/core/Manager.js
index e1529291..f47ec20c 100644
--- a/test/core/Manager.js
+++ b/test/core/Manager.js
@@ -34,6 +34,31 @@ describe('Manager', function () {
         next();
     });
 
+    describe('resolve', function () {
+        it('prefers exact versions over ranges', function () {
+            manager._resolved = {
+                ember: [
+                    {
+                        target: '>=1.4',
+                        pkgMeta: { version: '2.7.0' }
+                    },
+                    {
+                        target: '2.7.0',
+                        pkgMeta: { version: '2.7.0' }
+                    }
+                ]
+            };
+
+            return manager.resolve().then(function () {
+                expect(manager._dissected).to.eql({
+                    ember: {
+                        target: '2.7.0',
+                        pkgMeta: { version: '2.7.0' }
+                    }
+                });
+            });
+        });
+    });
 
     describe('_areCompatible', function () {
         describe('resolved is being fetched', function () {
diff --git a/test/core/resolvers/gitResolver.js b/test/core/resolvers/gitResolver.js
index 2ec33ad8..92a4242e 100644
--- a/test/core/resolvers/gitResolver.js
+++ b/test/core/resolvers/gitResolver.js
@@ -50,16 +50,17 @@ describe('GitResolver', function () {
             expect(process.env).to.not.have.property('GIT_SSL_NO_VERIFY');
 
             resolver = new GitResolver(decEndpoint, defaultConfig(), logger);
-            expect(process.env).to.have.property('GIT_SSL_NO_VERIFY', 'false');
-            delete process.env.GIT_SSL_NO_VERIFY;
+            expect(process.env).to.not.have.property('GIT_SSL_NO_VERIFY');
 
             resolver = new GitResolver(decEndpoint, defaultConfig({strictSsl: false}), logger);
             expect(process.env).to.have.property('GIT_SSL_NO_VERIFY', 'true');
             delete process.env.GIT_SSL_NO_VERIFY;
 
+            // git only checks the existence of GIT_SSL_NO_VERIFY.
+            // git does NOT check whether is true of false.
+            // Hence not exporting GIT_SSL_NO_VERIFY is effectively equivalent to 'false'
             resolver = new GitResolver(decEndpoint, defaultConfig({strictSsl: true}), logger);
-            expect(process.env).to.have.property('GIT_SSL_NO_VERIFY', 'false');
-            delete process.env.GIT_SSL_NO_VERIFY;
+            expect(process.env).to.not.have.property('GIT_SSL_NO_VERIFY');
         });
     });