Skip to content
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

Fixes package linking when a filename casing changes #3843

Merged
merged 2 commits into from
Jul 7, 2017

Conversation

arcanis
Copy link
Member

@arcanis arcanis commented Jul 6, 2017

Summary

Fixes an issue where files were being removed when their name casing changed (State.js => state.js). Only occured on case-insensitive filesystems (OSX, Windows).

Test plan

Tests should pass, + a new test to make sure it doesn't happen again

@arcanis arcanis requested a review from BYK July 6, 2017 15:27
@arcanis arcanis force-pushed the case-sensitivity branch from 46c91db to 1a06032 Compare July 6, 2017 16:45
@arcanis
Copy link
Member Author

arcanis commented Jul 6, 2017

Damn, the tests work fine on OSX, but apparently not on Linux. I'll need to check this at home.

@@ -296,7 +296,17 @@ export default class PackageLinker {
// remove all extraneous files that weren't in the tree
for (const loc of possibleExtraneous) {
this.reporter.verbose(this.reporter.lang('verboseFileRemoveExtraneous', loc));
await fs.unlink(loc);

Copy link
Member

@bestander bestander Jul 6, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we just ignore case in fs.buildActionsForCopy instead?

fs.js: 153

  for (const loc of possibleExtraneous) {
    if (files.has(loc)) {
      possibleExtraneous.delete(loc);
    }
  }

Just call toLocaleLowerCase() when dealing with files Set.

Same thing should be applied to buildActionsForHardlink function

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm maybe ... do you think the following would work?

files.add(loc);

to

files.add(loc);
files.add(loc.toLowerCase());

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This way, the .has() checks wouldn't have to change. As far as I see, the files set doesn't look used apart from those checks, so maybe it's enough?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

files set is used just in 4 places.
So yeah, I think doing files.add(loc.toLowerCase()); and file.has(loc.toLowerCase()) should work

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you need to add loc twice, that would cause confusion

@@ -296,7 +296,17 @@ export default class PackageLinker {
// remove all extraneous files that weren't in the tree
for (const loc of possibleExtraneous) {
this.reporter.verbose(this.reporter.lang('verboseFileRemoveExtraneous', loc));
await fs.unlink(loc);

const base = path.basename(loc);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

path.parse would work here! 😁

// we need to do this because of case-insensitive file systems, where deleting a.js will delete A.js
for (let file of await fs.readdir(dir)) {
if (base === file) {
await fs.unlink(path.join(dir, file));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need to do this sequentially? If not we can probably do Promise.all() and remove all in parallel.

src/util/fs.js Outdated
@@ -532,7 +532,11 @@ export async function copyBulk(
const cleanup = () => delete currentlyWriting[data.dest];
reporter.verbose(reporter.lang('verboseFileCopy', data.src, data.dest));
return (currentlyWriting[data.dest] = readFileBuffer(data.src)
.then(d => {
.then(async (d) => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

async in a then is a bit weird isn't it? May be we can refactor this piece to only use async/await in a future iteration?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, we don't seem to do anything concurrent here, so it could be refactored to use await

// we iterate once again on every directory of our extraneous files to find out if they really exist
// we need to do this because of case-insensitive file systems, where deleting a.js will delete A.js
for (const file of await fs.readdir(dir)) {
if (base === file) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wouldn't this be a single file? If so why can't we use fs.exists directly?

@arcanis arcanis force-pushed the case-sensitivity branch from 1a06032 to 6a05e71 Compare July 7, 2017 10:26
@arcanis arcanis merged commit 15f53dd into yarnpkg:master Jul 7, 2017
.then(async d => {
// we need to do this because of case-insensitive filesystems, which wouldn't properly
// change the file name in case of a file being renamed
await unlink(data.dest);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great work, @arcanis.
I am a bit worried about the extra IO operation here.
What impact would it cause for normal installation?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants