Skip to content

Commit

Permalink
move: support changing case in case-insensitive systems (#801)
Browse files Browse the repository at this point in the history
Resolves #759
  • Loading branch information
manidlou authored and RyanZim committed Apr 1, 2021
1 parent f21048b commit c8bd383
Show file tree
Hide file tree
Showing 5 changed files with 62 additions and 116 deletions.
89 changes: 20 additions & 69 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 @@ -21,104 +20,56 @@ describe('+ moveSync() - case insensitive paths', () => {
afterEach(() => fs.removeSync(TEST_DIR))

describe('> when src is a directory', () => {
it('should behave correctly based on the OS', () => {
it('should move successfully', () => {
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')
})
})

describe('> when src is a file', () => {
it('should behave correctly based on the OS', () => {
it('should move successfully', () => {
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')
})
})

describe('> when src is a symlink', () => {
it('should behave correctly based on the OS, symlink dir', () => {
it('should move successfully, symlink dir', () => {
src = path.join(TEST_DIR, 'srcdir')
fs.outputFileSync(path.join(src, 'subdir', 'file.txt'), 'some data')
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', () => {
it('should move successfully, symlink file', () => {
src = path.join(TEST_DIR, 'srcfile')
fs.outputFileSync(src, 'some data')
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
61 changes: 20 additions & 41 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 @@ -21,86 +20,66 @@ describe('+ move() - case insensitive paths', () => {
afterEach(done => fs.remove(TEST_DIR, done))

describe('> when src is a directory', () => {
it('should behave correctly based on the OS', done => {
it('should move successfully', done => {
src = path.join(TEST_DIR, 'srcdir')
fs.outputFileSync(path.join(src, 'subdir', 'file.txt'), 'some data')
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()
})
})
})

describe('> when src is a file', () => {
it('should behave correctly based on the OS', done => {
it('should move successfully', done => {
src = path.join(TEST_DIR, 'srcfile')
fs.outputFileSync(src, 'some data')
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()
})
})
})

describe('> when src is a symlink', () => {
it('should behave correctly based on the OS, symlink dir', done => {
it('should move successfully, symlink dir', done => {
src = path.join(TEST_DIR, 'srcdir')
fs.outputFileSync(path.join(src, 'subdir', 'file.txt'), 'some data')
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(src, srcLink, 'dir')
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()
})
})

it('should behave correctly based on the OS, symlink file', done => {
it('should move successfully, symlink file', done => {
src = path.join(TEST_DIR, 'srcfile')
fs.outputFileSync(src, 'some data')
const srcLink = path.join(TEST_DIR, 'src-symlink')
fs.symlinkSync(src, srcLink, 'file')
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 => {
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

0 comments on commit c8bd383

Please sign in to comment.