-
-
Notifications
You must be signed in to change notification settings - Fork 155
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Symlink/Junction improvements #254
Conversation
Interesting! If I were feeling contrary, couldn't I claim that this works just because the vinyl post-symlink is carrying the wrong stats (namely those of the target as opposed to the link)? My point earlier, integration wise, was just this: shouldn't the output of E.g., shouldn't vfs.src('./in/my-dir')
.pipe(vfs.symlink('./tmp'))
.pipe(vfs.dest('./out')); be the same as doing the following, in series: vfs.src('./in/my-dir')
.pipe(vfs.symlink('./tmp')),
vfs.src('./tmp/my-dir', {resolveSymlinks: false})
.pipe(vfs.dest('./out')); ? I might try playing around with something like your better-stats to see if I can't make it remember stats about both link and target. That might simplify this conundrum, it seems to me, whilst not suffering any spurious calls (alternating between |
@erikkemperman I had an idea before and forgot it but you just re-sparked it. What if |
That should work, I think! |
@erikkemperman perfect! Will you have time to put that together? I'm a bit swamped this week and the little time I have I'm spending cleaning up the other modules for the 4.0 release. |
@phated I'll give it a shot! I briefly tried something today but it got a bit messy, and I think an extra fs call can't be avoided (on windows) without tapping into mode-glob's cache. Also got to wondering, what if an already symbolic vinyl is piped into |
I think we just create a link to that link (a user might cause themselves to create a recursive link but that's always a risk with symlinks). |
@erikkemperman where do we stand with this symlink stuff? I agree that it's not semantically perfect but I think we can ship it (no one really uses these features, otherwise we'd have tons of bug reports because it barely works at all currently). I just don't want us to get totally hung up on this as the only blocker. |
@phated I tried your idea of making the |
9bd02c5
to
28123ec
Compare
@erikkemperman I'm adding more unit tests here before I take a look at the implementation. I've just added the tests for emitting symbolic vinyls. What other tests do you think we need here? |
@phated Coincidentally, I was working on this earlier :-) I think this makes it functionally correct but it isn't especially pretty (an extra stat call in Windows). In terms of tests I thought we could do with some more on how the vinyl looks post- Maybe we could merge ours efforts? EDIT fixed link |
So I can't find where it was, but at some point I suggested we start with a simple, if ugly, approach that does the right things, and take it from there. That's what the commit I linked yesterday was attempting. I think the idea with making the But looking back through some of these threads it seems I had repressed the complication you mentioned about users manipulating the vinyl or injecting it without sourcing anything (in particular effecting its Will need to look into this further :-( |
Ok the tests pass without calling those mock |
Also, I wanted to add an integration test similar to yours but instead making the link with |
@erikkemperman I was working on some solutions based on your changes. Something to note is that constructing a |
Also, I think I have a clean-ish solution that does the right thing in 99% of the cases and I'm fine not handling the 1% of edge cases; however, I want us to have a full test suite for everything we want to support before I work on the implementation. Can you help with those remaining test cases? |
Should Edit: It's also the only case in which we don't check the Vinyl method |
@erikkemperman do you know if we can check flags to find out if something is a junction? It looks like they set something on flags to create them |
^ It looks like native code is required to |
My solution is 4-fold:
@erikkemperman please review the above and let me know if I'm missing anything. I believe it handles your concerns and my concern about non-vinyl-fs-created Vinyl objects being streamed through the pipeline. If this handles everything, what tests do we need to ensure the steps have full coverage? |
About testing About detecting junctions: the tests currently look for a trailing slash out of Your 4-point strategy looks sound at a first casual glance, I might find time for a more thorough review, and perhaps suggestions on testing the whole thing, later today. |
test/dest-symlinks.js
Outdated
var file = new File({ | ||
base: inputBase, | ||
path: inputPath, | ||
contents: null, | ||
stat: { | ||
isSymbolic: isSymbolic, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This seems wrong -- we'll want to mock either file.isSymbolic()
or file.stat.isSymbolicLink()
right?
https://nodejs.org/api/fs.html#fs_class_fs_stats
https://github.com/gulpjs/vinyl/blob/master/index.js#L84
test/dest-symlinks.js
Outdated
contents: null, | ||
stat: { | ||
isSymbolic: function() { | ||
return false; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above.
test/dest-symlinks.js
Outdated
path: inputPath, | ||
contents: null, | ||
stat: { | ||
isSymbolic: isSymbolic, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Same as above.
test/dest-symlinks.js
Outdated
|
||
function assert(err) { | ||
expect(err).toExist(); | ||
// TODO: Should we assert anything else about this err? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW, elsewhere we're testing the messages of (our custom) errors.
vfs.dest(outputDirpath), | ||
concat(assert), | ||
], done); | ||
}); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I was thinking it might make sense to additionally test the case of directly sourcing a symlinked directory and copying it:
it('(*nix) sources a symlink and copies it', function(done) {
if (isWindows) {
this.skip();
return;
}
fs.mkdirSync(base);
fs.mkdirSync(symlinkDirpath);
fs.symlinkSync(inputDirpath, outputSymlink);
function assert(files) {
var destResult = fs.readlinkSync(outputDirpathSymlink);
expect(destResult).toEqual(inputDirpath);
expect(files[0].symlink).toEqual(inputDirpath);
}
pipe([
vfs.src(outputSymlink, { resolveSymlinks: false }),
vfs.dest(outputDirpath),
concat(assert),
], done);
});
it('(windows) sources a junction and copies it', function(done) {
if (!isWindows) {
this.skip();
return;
}
fs.mkdirSync(base);
fs.mkdirSync(symlinkDirpath);
fs.symlinkSync(inputDirpath, outputSymlink, 'junction');
function assert(files) {
// Junctions add an ending separator
var expected = inputDirpath + path.sep;
var destResult = fs.readlinkSync(outputDirpathSymlink);
expect(destResult).toEqual(expected);
expect(files[0].symlink).toEqual(inputDirpath);
}
pipe([
vfs.src(outputSymlink, { resolveSymlinks: false }),
vfs.dest(outputDirpath),
concat(assert),
], done);
});
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added in 9c021e4
Some addenda:
|
Also, I'm wondering about re-using the |
Sorry for so many messages :-) I've played around with the property you mentioned, but unless I'm missing something it's not getting us around the need for an extra So at that point we'll only have the stats of the link, not the target, and other than (slightly awkwardly) detecting 'junction' by looking for a trailing slash out of If the above is accurate, then IMHO it's cleaner to do the stat call just before we need it, in For what it's worth, I've rebased by earlier effort on this branch, and the whole thing on latest master, and then updated to reflected the discussion here: |
@erikkemperman I think our fundamental disagreement around this is that I am working under the ethos that |
I think I'm missing a test that outlines my above philosophy. |
I think once all of this symlink stuff has been improved, it'll actually be correct to test for |
@phated Pushed a few more changes pushed to the branch linked in the comment above. Mostly to use the new I also figured out what is going wrong on Windows, see this commit which makes the build pass -- but I still need to track this down further to see what changed in the libuv source, I guess. |
Reported the Windows quirk, FYI: |
Thanks for digging into that! |
@phated I took a quick stab at documenting the caveats on dangling links on Windows, pushed it to my branch here: With something like that in place, I believe my branch now more or less covers all three bullets you listed earlier. But having just written that caveats section, I wondered if instead of a (Windows only) Thoughts? |
@erikkemperman I'm getting back to this today. I've cherry-picked some of your work but left out the 2nd round of |
file.cwd = cwd; | ||
file.base = basePath; | ||
file.path = writePath; | ||
if (!file.isSymbolic()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Need to document this in the options
@@ -13,7 +13,7 @@ function writeContents(optResolver) { | |||
|
|||
function writeFile(file, enc, callback) { | |||
// Write it as a symlink | |||
if (file.symlink) { | |||
if (file.isSymbolic()) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not sure if the documents talk about .symlink
but if so, it'll need to be updated
4b21b97
to
03c5693
Compare
lib/file-operations.js
Outdated
@@ -363,6 +391,7 @@ WriteStream.prototype.open = function() { | |||
// Use our `end` method since it is patched for flush | |||
WriteStream.prototype.destroySoon = WriteStream.prototype.end; | |||
// Use node's `fs.WriteStream` methods | |||
WriteStream.prototype._destroy = fs.WriteStream.prototype._destroy; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@erikkemperman this is why your latest tests were failing for node 8 - they changed .destroy
to ._destroy
on the prototype.
03c5693
to
c02e0c1
Compare
isDirectory
usage
(Title changed to reflect what happened here) @erikkemperman @stevelacy @contra could I get a thorough review of this? It is the last thing blocking vinyl-fs 3.0.0 and I want to make sure it's done correct. |
My only nitpick would be |
@phated @contra @stevelacy I just had a bit of time to compare the diff of the current state of this PR against what I had in my branch, but superficially this looks good to me! Could I just ask you guys to take a close look at the wording of the README changes I made, and which look like they made it in here verbatim? English isn't my first language :-) Specifically the caveats section about links on Windows. Thanks! |
Looks good, if I were to nitpick I'd add a link to the caveats section from the |
@stevelacy great minds think alike - I'm going to do a doc review pass before I ship 3.0 and that was on my list. @contra I think we talked about that in another issue/PR and decided to be more verbose for the Windows users. @erikkemperman I actually fully reviewed the docs and found them great, which is why there was no comment from me 😛 |
Thanks everyone for the help - especially you @erikkemperman, most of this is your work 😄 |
@phated Excellent, very good to see this epic landed! |
As a talking point for #248, I wanted to create a couple of "integration" tests that show why we should keep the
file.isDirectory()
call inside.dest
and.symlink
methods.@erikkemperman what do you think?
TODO: remove the
.only
call on the integration suite