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

move: support changing case in case-insensitive systems #801

Closed
wants to merge 13 commits into from
81 changes: 16 additions & 65 deletions lib/move-sync/__tests__/move-sync-case-insensitive-paths.test.js
Expand Up @@ -4,7 +4,6 @@ const assert = require('assert')
const os = require('os')
const path = require('path')
const fs = require('../../')
const platform = os.platform()

/* global beforeEach, afterEach, describe, it */

Expand All @@ -25,22 +24,10 @@ describe('+ moveSync() - case insensitive paths', () => {
src = path.join(TEST_DIR, 'srcdir')
fs.outputFileSync(path.join(src, 'subdir', 'file.txt'), 'some data')
dest = path.join(TEST_DIR, 'srcDir')
let errThrown = false

try {
fs.moveSync(src, dest)
} catch (err) {
if (platform === 'darwin' || platform === 'win32') {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
errThrown = true
}
}
if (platform === 'darwin' || platform === 'win32') assert(errThrown)
if (platform === 'linux') {
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(path.join(dest, 'subdir', 'file.txt'), 'utf8'), 'some data')
assert(!errThrown)
}
fs.moveSync(src, dest)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(path.join(dest, 'subdir', 'file.txt'), 'utf8'), 'some data')
})
})

Expand All @@ -49,22 +36,10 @@ describe('+ moveSync() - case insensitive paths', () => {
src = path.join(TEST_DIR, 'srcfile')
fs.outputFileSync(src, 'some data')
dest = path.join(TEST_DIR, 'srcFile')
let errThrown = false

try {
fs.moveSync(src, dest)
} catch (err) {
if (platform === 'darwin' || platform === 'win32') {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
errThrown = true
}
}
if (platform === 'darwin' || platform === 'win32') assert(errThrown)
if (platform === 'linux') {
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data')
assert(!errThrown)
}
fs.moveSync(src, dest)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data')
})
})

Expand All @@ -75,24 +50,12 @@ describe('+ moveSync() - case insensitive paths', () => {
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(src, srcLink, 'dir')
dest = path.join(TEST_DIR, 'src-Symlink')
let errThrown = false

try {
fs.moveSync(srcLink, dest)
} catch (err) {
if (platform === 'darwin' || platform === 'win32') {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
errThrown = true
}
}
if (platform === 'darwin' || platform === 'win32') assert(errThrown)
if (platform === 'linux') {
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(path.join(dest, 'subdir', 'file.txt'), 'utf8'), 'some data')
const destLink = fs.readlinkSync(dest)
assert.strictEqual(destLink, src)
assert(!errThrown)
}
fs.moveSync(srcLink, dest)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(path.join(dest, 'subdir', 'file.txt'), 'utf8'), 'some data')
const destLink = fs.readlinkSync(dest)
assert.strictEqual(destLink, src)
})

it('should behave correctly based on the OS, symlink file', () => {
Expand All @@ -101,24 +64,12 @@ describe('+ moveSync() - case insensitive paths', () => {
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(src, srcLink, 'file')
dest = path.join(TEST_DIR, 'src-Symlink')
let errThrown = false

try {
fs.moveSync(srcLink, dest)
} catch (err) {
if (platform === 'darwin' || platform === 'win32') {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
errThrown = true
}
}
if (platform === 'darwin' || platform === 'win32') assert(errThrown)
if (platform === 'linux') {
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data')
const destLink = fs.readlinkSync(dest)
assert.strictEqual(destLink, src)
assert(!errThrown)
}
fs.moveSync(srcLink, dest)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data')
const destLink = fs.readlinkSync(dest)
assert.strictEqual(destLink, src)
})
})
})
7 changes: 4 additions & 3 deletions lib/move-sync/move-sync.js
Expand Up @@ -11,13 +11,14 @@ function moveSync (src, dest, opts) {
opts = opts || {}
const overwrite = opts.overwrite || opts.clobber || false

const { srcStat } = stat.checkPathsSync(src, dest, 'move', opts)
const { srcStat, isChangingCase = false } = stat.checkPathsSync(src, dest, 'move', opts)
stat.checkParentPathsSync(src, srcStat, dest, 'move')
mkdirpSync(path.dirname(dest))
return doRename(src, dest, overwrite)
return doRename(src, dest, overwrite, isChangingCase)
}

function doRename (src, dest, overwrite) {
function doRename (src, dest, overwrite, isChangingCase) {
if (isChangingCase) return rename(src, dest, overwrite)
if (overwrite) {
removeSync(dest)
return rename(src, dest, overwrite)
Expand Down
53 changes: 16 additions & 37 deletions lib/move/__tests__/move-case-insensitive-paths.test.js
Expand Up @@ -4,7 +4,6 @@ const assert = require('assert')
const os = require('os')
const path = require('path')
const fs = require('../../')
const platform = os.platform()

/* global beforeEach, afterEach, describe, it */

Expand All @@ -27,14 +26,9 @@ describe('+ move() - case insensitive paths', () => {
dest = path.join(TEST_DIR, 'srcDir')

fs.move(src, dest, err => {
if (platform === 'linux') {
assert.ifError(err)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(path.join(dest, 'subdir', 'file.txt'), 'utf8'), 'some data')
}
if (platform === 'darwin' || platform === 'win32') {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
}
assert.ifError(err)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(path.join(dest, 'subdir', 'file.txt'), 'utf8'), 'some data')
done()
})
})
Expand All @@ -47,14 +41,9 @@ describe('+ move() - case insensitive paths', () => {
dest = path.join(TEST_DIR, 'srcFile')

fs.move(src, dest, err => {
if (platform === 'linux') {
assert.ifError(err)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data')
}
if (platform === 'darwin' || platform === 'win32') {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
}
assert.ifError(err)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data')
done()
})
})
Expand All @@ -69,16 +58,11 @@ describe('+ move() - case insensitive paths', () => {
dest = path.join(TEST_DIR, 'src-Symlink')

fs.move(srcLink, dest, err => {
if (platform === 'linux') {
assert.ifError(err)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(path.join(dest, 'subdir', 'file.txt'), 'utf8'), 'some data')
const destLink = fs.readlinkSync(dest)
assert.strictEqual(destLink, src)
}
if (platform === 'darwin' || platform === 'win32') {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
}
assert.ifError(err)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(path.join(dest, 'subdir', 'file.txt'), 'utf8'), 'some data')
const destLink = fs.readlinkSync(dest)
assert.strictEqual(destLink, src)
done()
})
})
Expand All @@ -91,16 +75,11 @@ describe('+ move() - case insensitive paths', () => {
dest = path.join(TEST_DIR, 'src-Symlink')

fs.move(srcLink, dest, err => {
if (platform === 'linux') {
assert.ifError(err)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data')
const destLink = fs.readlinkSync(dest)
assert.strictEqual(destLink, src)
}
if (platform === 'darwin' || platform === 'win32') {
assert.strictEqual(err.message, 'Source and destination must not be the same.')
}
assert.ifError(err)
assert(fs.existsSync(dest))
assert.strictEqual(fs.readFileSync(dest, 'utf8'), 'some data')
const destLink = fs.readlinkSync(dest)
assert.strictEqual(destLink, src)
done()
})
})
Expand Down
7 changes: 4 additions & 3 deletions lib/move/move.js
Expand Up @@ -18,18 +18,19 @@ function move (src, dest, opts, cb) {

stat.checkPaths(src, dest, 'move', opts, (err, stats) => {
if (err) return cb(err)
const { srcStat } = stats
const { srcStat, isChangingCase = false } = stats
stat.checkParentPaths(src, srcStat, dest, 'move', err => {
if (err) return cb(err)
mkdirp(path.dirname(dest), err => {
if (err) return cb(err)
return doRename(src, dest, overwrite, cb)
return doRename(src, dest, overwrite, isChangingCase, cb)
})
})
})
}

function doRename (src, dest, overwrite, cb) {
function doRename (src, dest, overwrite, isChangingCase, cb) {
if (isChangingCase) return rename(src, dest, overwrite, cb)
if (overwrite) {
return remove(dest, err => {
manidlou marked this conversation as resolved.
Show resolved Hide resolved
if (err) return cb(err)
Expand Down
14 changes: 14 additions & 0 deletions lib/util/stat.js
Expand Up @@ -42,6 +42,13 @@ function checkPaths (src, dest, funcName, opts, cb) {

if (destStat) {
if (areIdentical(srcStat, destStat)) {
const srcBaseName = path.basename(src)
const destBaseName = path.basename(dest)
if (funcName === 'move' &&
srcBaseName !== destBaseName &&
srcBaseName.toLowerCase() === destBaseName.toLowerCase()) {
return cb(null, { srcStat, destStat, isChangingCase: true })
}
return cb(new Error('Source and destination must not be the same.'))
}
if (srcStat.isDirectory() && !destStat.isDirectory()) {
Expand All @@ -64,6 +71,13 @@ function checkPathsSync (src, dest, funcName, opts) {

if (destStat) {
if (areIdentical(srcStat, destStat)) {
const srcBaseName = path.basename(src)
const destBaseName = path.basename(dest)
if (funcName === 'move' &&
srcBaseName !== destBaseName &&
srcBaseName.toLowerCase() === destBaseName.toLowerCase()) {
return { srcStat, destStat, isChangingCase: true }
}
throw new Error('Source and destination must not be the same.')
}
if (srcStat.isDirectory() && !destStat.isDirectory()) {
Expand Down