Skip to content

Commit

Permalink
fs: support copy of relative links with cp and cpSync
Browse files Browse the repository at this point in the history
Fixes: nodejs#41693

Signed-off-by: Marcos Bjoerkelund <[email protected]>
  • Loading branch information
marcosbc committed Feb 2, 2022
1 parent 1387bb5 commit 2a572ac
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 7 deletions.
6 changes: 6 additions & 0 deletions doc/api/fs.md
Original file line number Diff line number Diff line change
Expand Up @@ -896,6 +896,8 @@ added: v16.7.0
* `preserveTimestamps` {boolean} When `true` timestamps from `src` will
be preserved. **Default:** `false`.
* `recursive` {boolean} copy directories recursively **Default:** `false`
* `absolute` {boolean} convert relative symlinks to absolute symlinks.
**Default:** `true`
* Returns: {Promise} Fulfills with `undefined` upon success.
Asynchronously copies the entire directory structure from `src` to `dest`,
Expand Down Expand Up @@ -2083,6 +2085,8 @@ added: v16.7.0
* `preserveTimestamps` {boolean} When `true` timestamps from `src` will
be preserved. **Default:** `false`.
* `recursive` {boolean} copy directories recursively **Default:** `false`
* `absolute` {boolean} convert relative symlinks to absolute symlinks.
**Default:** `true`
* `callback` {Function}
Asynchronously copies the entire directory structure from `src` to `dest`,
Expand Down Expand Up @@ -4665,6 +4669,8 @@ added: v16.7.0
* `preserveTimestamps` {boolean} When `true` timestamps from `src` will
be preserved. **Default:** `false`.
* `recursive` {boolean} copy directories recursively **Default:** `false`
* `absolute` {boolean} convert relative symlinks to absolute symlinks.
**Default:** `true`
Synchronously copies the entire directory structure from `src` to `dest`,
including subdirectories and files.
Expand Down
6 changes: 3 additions & 3 deletions lib/internal/fs/cp/cp-sync.js
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ function getStats(destStat, src, dest, opts) {
srcStat.isBlockDevice()) {
return onFile(srcStat, destStat, src, dest, opts);
} else if (srcStat.isSymbolicLink()) {
return onLink(destStat, src, dest);
return onLink(destStat, src, dest, opts);
} else if (srcStat.isSocket()) {
throw new ERR_FS_CP_SOCKET({
message: `cannot copy a socket file: ${dest}`,
Expand Down Expand Up @@ -293,9 +293,9 @@ function copyDir(src, dest, opts) {
}
}

function onLink(destStat, src, dest) {
function onLink(destStat, src, dest, opts) {
let resolvedSrc = readlinkSync(src);
if (!isAbsolute(resolvedSrc)) {
if (opts.absolute && !isAbsolute(resolvedSrc)) {
resolvedSrc = resolve(dirname(src), resolvedSrc);
}
if (!destStat) {
Expand Down
6 changes: 3 additions & 3 deletions lib/internal/fs/cp/cp.js
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ async function getStatsForCopy(destStat, src, dest, opts) {
srcStat.isBlockDevice()) {
return onFile(srcStat, destStat, src, dest, opts);
} else if (srcStat.isSymbolicLink()) {
return onLink(destStat, src, dest);
return onLink(destStat, src, dest, opts);
} else if (srcStat.isSocket()) {
throw new ERR_FS_CP_SOCKET({
message: `cannot copy a socket file: ${dest}`,
Expand Down Expand Up @@ -335,9 +335,9 @@ async function copyDir(src, dest, opts) {
}
}

async function onLink(destStat, src, dest) {
async function onLink(destStat, src, dest, opts) {
let resolvedSrc = await readlink(src);
if (!isAbsolute(resolvedSrc)) {
if (opts.absolute && !isAbsolute(resolvedSrc)) {
resolvedSrc = resolve(dirname(src), resolvedSrc);
}
if (!destStat) {
Expand Down
2 changes: 2 additions & 0 deletions lib/internal/fs/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -724,6 +724,7 @@ const defaultCpOptions = {
force: true,
preserveTimestamps: false,
recursive: false,
absolute: true,
};

const defaultRmOptions = {
Expand All @@ -749,6 +750,7 @@ const validateCpOptions = hideStackFrames((options) => {
validateBoolean(options.force, 'options.force');
validateBoolean(options.preserveTimestamps, 'options.preserveTimestamps');
validateBoolean(options.recursive, 'options.recursive');
validateBoolean(options.absolute, 'options.absolute');
if (options.filter !== undefined) {
validateFunction(options.filter, 'options.filter');
}
Expand Down
35 changes: 34 additions & 1 deletion test/parallel/test-fs-cp.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ const {
writeFileSync,
} = fs;
import net from 'net';
import { join } from 'path';
import { join, isAbsolute } from 'path';
import { pathToFileURL } from 'url';
import { setTimeout } from 'timers/promises';

Expand Down Expand Up @@ -95,6 +95,39 @@ function nextdir() {
}


// It resolves relative symlinks to absolute path when absolute is true
{
const src = nextdir();
mkdirSync(src, { recursive: true });
writeFileSync(join(src, 'foo.js'), 'foo', 'utf8');
symlinkSync('foo.js', join(src, 'bar.js'));

const dest = nextdir();
mkdirSync(dest, { recursive: true });

cpSync(src, dest, { recursive: true, absolute: true });
const link = readlinkSync(join(dest, 'bar.js'));
assert.strictEqual(link, join(src, 'foo.js'));
}


// It copies relative symlink when absolute is false
{
const src = nextdir();
mkdirSync(src, { recursive: true });
writeFileSync(join(src, 'foo.js'), 'foo', 'utf8');
symlinkSync('foo.js', join(src, 'bar.js'));

const dest = nextdir();
mkdirSync(dest, { recursive: true });
const destFile = join(dest, 'foo.js');

cpSync(src, dest, { recursive: true, absolute: false });
const link = readlinkSync(join(dest, 'bar.js'));
assert.strictEqual(link, 'foo.js');
}


// It throws error when src and dest are identical.
{
const src = './test/fixtures/copy/kitchen-sink';
Expand Down

0 comments on commit 2a572ac

Please sign in to comment.