Skip to content

Commit 0dccf70

Browse files
copy symlink files when calling the CopyDirTo
1 parent 0bc48d6 commit 0dccf70

File tree

5 files changed

+73
-8
lines changed

5 files changed

+73
-8
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../file

_testdata/folder_containing_symlinks/file2

Whitespace-only changes.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
../folder

paths.go

+33-8
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,13 @@ func (p *Path) Stat() (os.FileInfo, error) {
7676
return os.Stat(p.path)
7777
}
7878

79+
// Lstat returns a FileInfo describing the named file.
80+
// If the file is a symbolic link, the returned FileInfo
81+
// describes the symbolic link. Lstat makes no attempt to follow the link.
82+
func (p *Path) Lstat() (os.FileInfo, error) {
83+
return os.Lstat(p.path)
84+
}
85+
7986
// Clone create a copy of the Path object
8087
func (p *Path) Clone() *Path {
8188
return New(p.path)
@@ -311,6 +318,16 @@ func (p *Path) IsDirCheck() (bool, error) {
311318
return false, err
312319
}
313320

321+
// IsDirCheck return true if the path exists and is a symlink. In all the other
322+
// cases (and also in case of any error) false is returned.
323+
func (p *Path) IsSymlink() (bool, error) {
324+
info, err := p.Lstat()
325+
if err != nil {
326+
return false, fmt.Errorf("getting lstat info for %s: %s", p.path, err)
327+
}
328+
return info.Mode()&os.ModeSymlink != 0, nil
329+
}
330+
314331
// CopyTo copies the contents of the file named src to the file named
315332
// by dst. The file will be created if it does not already exist. If the
316333
// destination file exists, all it's contents will be replaced by the contents
@@ -382,22 +399,30 @@ func (p *Path) CopyDirTo(dst *Path) error {
382399
}
383400

384401
for _, srcPath := range srcFiles {
385-
srcPathInfo, err := srcPath.Stat()
402+
srcPathInfo, err := os.Lstat(srcPath.path)
386403
if err != nil {
387-
return fmt.Errorf("getting stat info for %s: %s", srcPath, err)
404+
return fmt.Errorf("getting lstat info for %s: %s", srcPath, err)
388405
}
389406
dstPath := dst.Join(srcPath.Base())
390407

391-
if srcPathInfo.IsDir() {
392-
if err := srcPath.CopyDirTo(dstPath); err != nil {
393-
return fmt.Errorf("copying %s to %s: %s", srcPath, dstPath, err)
408+
// In case is a symlink, copy the symlink
409+
if srcPathInfo.Mode()&os.ModeSymlink != 0 {
410+
namedLink, err := os.Readlink(srcPath.path)
411+
if err != nil {
412+
return fmt.Errorf("could not read symlink: %s", err.Error())
413+
}
414+
415+
if err := os.Symlink(namedLink, dstPath.path); err != nil {
416+
return fmt.Errorf("creating symlink (%s) of %s to %s: %s", namedLink, srcPath, dstPath, err)
394417
}
418+
395419
continue
396420
}
397421

398-
// Skip symlinks.
399-
if srcPathInfo.Mode()&os.ModeSymlink != 0 {
400-
// TODO
422+
if srcPathInfo.IsDir() {
423+
if err := srcPath.CopyDirTo(dstPath); err != nil {
424+
return fmt.Errorf("copying %s to %s: %s", srcPath, dstPath, err)
425+
}
401426
continue
402427
}
403428

paths_test.go

+38
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
package paths
3131

3232
import (
33+
"os"
3334
"path/filepath"
3435
"runtime"
3536
"strings"
@@ -243,6 +244,43 @@ func TestCopyDir(t *testing.T) {
243244

244245
err = src.Join("file").CopyDirTo(tmp.Join("dest2"))
245246
require.Error(t, err, "copying file as dir")
247+
248+
// Check if the file is a symlink
249+
isSymlink, err := tmp.Join("dest", "folder_containing_symlinks", "file").IsSymlink()
250+
require.True(t, isSymlink)
251+
require.NoError(t, err)
252+
253+
// Check if the folder is a symlink
254+
isSymlink, err = tmp.Join("dest", "folder_containing_symlinks", "folder").IsSymlink()
255+
require.True(t, isSymlink)
256+
require.NoError(t, err)
257+
258+
// Broken symlink is copied
259+
{
260+
// create broken folder containing the broken symlink file
261+
tmpDestFolder := New(tmp.Join("broken").String())
262+
require.NoError(t, tmpDestFolder.Mkdir())
263+
264+
// Create a symlink that will raise a too many levels of symblic links error
265+
err = os.Symlink("broken_symlink", tmpDestFolder.Join("broken_symlink").String())
266+
require.NoError(t, err)
267+
// Create a symlinking pointing to a not existing file
268+
err = os.Symlink("symlink", tmpDestFolder.Join("broken").String())
269+
require.NoError(t, err)
270+
271+
src := tmpDestFolder
272+
err = src.CopyDirTo(tmp.Join("broken_dest"))
273+
require.NoError(t, err)
274+
275+
exist, err = tmp.Join("broken_dest", "broken_symlink").ExistCheck()
276+
require.False(t, exist)
277+
require.Error(t, err)
278+
require.Contains(t, err.Error(), "too many levels of symbolic links")
279+
280+
exist, err = tmp.Join("broken_dest", "symlink").ExistCheck()
281+
require.False(t, exist)
282+
require.NoError(t, err)
283+
}
246284
}
247285

248286
func TestParents(t *testing.T) {

0 commit comments

Comments
 (0)