From 50847164f1c8e38970c6ef084fecbfdba8e7ab71 Mon Sep 17 00:00:00 2001 From: isaacs Date: Fri, 15 Jan 2021 12:39:45 -0800 Subject: [PATCH] Extract .gitignore from hosted tarball for prepare Occasionally, a package installed from a hosted git repo requires its .gitignore in order to properly run its install/prepare process. Since we should treat git dependencies as being just like installing from an install cut from the actual git repo itself, it's inappropriate to munge the .gitignore into .npmignore at this step (even though it _is_ appropriate to do so in most other cases with remote tarballs). This adds the `allowGitIgnore` flag, which is then in turn used by the GitFetcher when it makes its call to RemoteFetcher to download and extract the hosted tarball for preparation. This was not an issue in npm v6, because prior to npm v7, hosted git snapshot tarballs were not used, so it never would go through the code path of using a 'remote' type fetcher to download the contents of a git repository (which was also much slower). Fixes: https://github.com/npm/cli/issues/2144 --- lib/fetcher.js | 4 ++- lib/git.js | 1 + .../prepare-requires-gitignore-1.2.3.tgz | Bin 0 -> 5632 bytes .../prepare-requires-gitignore/.gitignore | 1 + .../prepare-requires-gitignore/index.js | 1 + .../prepare-requires-gitignore/package.json | 11 ++++++++ .../prepare-requires-gitignore/prepare.js | 4 +++ test/git.js | 25 +++++++++++++++++- 8 files changed, 45 insertions(+), 2 deletions(-) create mode 100644 test/fixtures/prepare-requires-gitignore-1.2.3.tgz create mode 100644 test/fixtures/prepare-requires-gitignore/.gitignore create mode 100644 test/fixtures/prepare-requires-gitignore/index.js create mode 100644 test/fixtures/prepare-requires-gitignore/package.json create mode 100644 test/fixtures/prepare-requires-gitignore/prepare.js diff --git a/lib/fetcher.js b/lib/fetcher.js index a0a1447a..c4e5852d 100644 --- a/lib/fetcher.js +++ b/lib/fetcher.js @@ -47,6 +47,8 @@ class FetcherBase { throw new TypeError('options object is required') this.spec = npa(spec, opts.where) + this.allowGitIgnore = !!opts.allowGitIgnore + // a bit redundant because presumably the caller already knows this, // but it makes it easier to not have to keep track of the requested // spec when we're dispatching thousands of these at once, and normalizing @@ -414,7 +416,7 @@ class FetcherBase { const base = basename(entry.path) if (base === '.npmignore') sawIgnores.add(entry.path) - else if (base === '.gitignore') { + else if (base === '.gitignore' && !this.allowGitIgnore) { // rename, but only if there's not already a .npmignore const ni = entry.path.replace(/\.gitignore$/, '.npmignore') if (sawIgnores.has(ni)) diff --git a/lib/git.js b/lib/git.js index 81f7ca25..b9665b5f 100644 --- a/lib/git.js +++ b/lib/git.js @@ -207,6 +207,7 @@ class GitFetcher extends Fetcher { const nameat = this.spec.name ? `${this.spec.name}@` : '' return new RemoteFetcher(h.tarball({ noCommittish: false }), { ...this.opts, + allowGitIgnore: true, pkgid: `git:${nameat}${this.resolved}`, resolved: this.resolved, integrity: null, // it'll always be different, if we have one diff --git a/test/fixtures/prepare-requires-gitignore-1.2.3.tgz b/test/fixtures/prepare-requires-gitignore-1.2.3.tgz new file mode 100644 index 0000000000000000000000000000000000000000..c1f5be9abd9a229b776a815dacbfc0a01ae274bf GIT binary patch literal 5632 zcmeHL!H%0S5Y0JXF`|`{m9oSbgQZHn_6K^bs>C8D>s@N74Q-<+|K2f4HriDoNaWDf z+KL3W-wgH}-Y`yOWF?HeH}YqrjI{S#RjaKoja*J{)d(RxNnqlx>G4V^Yx_9VHi#G@ zQJN5n0KDNWof`!BVlE$i1J7^TrFg}jD%wcit8$u9tE*slP?4O*jjL<9FWSO)!Z;E0UApCpl z)@DXkwR7QCb1`wx(d9 { case '/not-tar.tgz': res.statusCode = 200 return res.end('this is not a gzipped tarball tho') + case '/prepare-requires-gitignore-1.2.3.tgz': try { + const data = fs.readFileSync(prepIgnore) + return res.end(data) + } catch (er) { + res.statusCode = 500 + return res.end(er.stack) + } default: res.statusCode = 404 return res.end('not found') @@ -389,6 +398,20 @@ t.test('extract from tarball from hosted git service', t => { })) }) +t.test('include .gitignore in hosted tarballs for preparation', async t => { + const spec = npa(`localhost:foo/y#${REPO_HEAD}`) + spec.hosted.tarball = () => + `http://localhost:${httpPort}/prepare-requires-gitignore-1.2.3.tgz` + const g = new GitFetcher(spec, {cache}) + const dir = t.testdir() + await g.extract(dir) + t.strictSame(fs.readdirSync(dir).sort((a,b) => a.localeCompare(b)), [ + 'index.js', + 'package.json', + 'prepare_ran_successfully', + ]) +}) + t.test('add git sha to hosted git shorthand', t => new GitFetcher('localhost:repo/x', {cache}) // it adds the git+ because it thinks it's https