Skip to content

Commit

Permalink
Merge pull request #401 from walnuts1018/master
Browse files Browse the repository at this point in the history
Add Windows Hardlink & Symbolic Link Support
  • Loading branch information
Songmu authored Oct 27, 2024
2 parents 52efddc + 83eab51 commit c9e0c4e
Show file tree
Hide file tree
Showing 6 changed files with 173 additions and 5 deletions.
3 changes: 2 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ require (
github.com/saracen/walker v0.1.4
github.com/urfave/cli/v2 v2.27.2
golang.org/x/net v0.27.0
golang.org/x/sync v0.7.0
golang.org/x/sync v0.8.0
)

require (
Expand All @@ -23,6 +23,7 @@ require (
github.com/russross/blackfriday/v2 v2.1.0 // indirect
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
golang.org/x/sys v0.22.0 // indirect
golang.org/x/text v0.19.0
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
6 changes: 4 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -84,8 +84,8 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys=
golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE=
golang.org/x/sync v0.7.0 h1:YsImfSBoP9QPYL0xyKJPq0gcaJdG3rInoqxTWbfQu9M=
golang.org/x/sync v0.7.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
Expand All @@ -104,6 +104,8 @@ golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9sn
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
golang.org/x/xerrors v0.0.0-20220609144429-65e65417b02f/go.mod h1:K8+ghG5WaK9qNqU5K3HdILfMLy1f3aNYFI/wnl100a8=
Expand Down
6 changes: 6 additions & 0 deletions helpers_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

package main

import "path/filepath"

func toFullPath(s string) (string, error) {
return s, nil
}

func evalSymlinks(path string) (string, error) {
return filepath.EvalSymlinks(path)
}
46 changes: 45 additions & 1 deletion helpers_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@

package main

import "syscall"
import (
"os"
"path/filepath"
"strings"
"syscall"
)

func toFullPath(s string) (string, error) {
p := syscall.StringToUTF16(s)
Expand All @@ -21,3 +26,42 @@ func toFullPath(s string) (string, error) {
b = b[:n]
return syscall.UTF16ToString(b), nil
}

func evalSymlinks(path string) (string, error) {
_, err := os.Stat(path)
if err != nil {
return "", err
}

list := filepathSplitAll(path)
evaled := list[0]
for i := 1; i < len(list); i++ {
evaled = filepath.Join(evaled, list[i])

linkSrc, err := os.Readlink(evaled)
if err != nil {
// not symlink
continue
} else {
if filepath.IsAbs(linkSrc) {
evaled = linkSrc
} else {
evaled = filepath.Join(filepath.Dir(evaled), linkSrc)
}
}
}

return evaled, nil
}

func filepathSplitAll(path string) []string {
path = filepath.Clean(path)
path = filepath.ToSlash(path)

vol := filepath.VolumeName(path)

path = path[len(vol):]
list := strings.Split(path, "/")
list[0] = vol + string(filepath.Separator) + list[0]
return list
}
115 changes: 115 additions & 0 deletions helpers_windows_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
//go:build windows

package main

import (
"bytes"
"fmt"
"io"
"os"
"os/exec"
"path/filepath"
"testing"

"golang.org/x/text/encoding/japanese"
"golang.org/x/text/transform"
)

type testEvalSymlinksMode int

const (
testEvalSymlinksNotLink testEvalSymlinksMode = iota
testEvalSymlinksSymbolicLink
testEvalSymlinksJunction
)

func Test_evalSymlinks(t *testing.T) {
type args struct {
path string
}
tests := []struct {
name string
mode testEvalSymlinksMode
linkBasePath string
args args
want string
wantErr bool
}{
{
name: "not link",
mode: testEvalSymlinksNotLink,
args: args{
path: filepath.Join(os.TempDir(), "not_link"),
},
want: filepath.Join(os.TempDir(), "not_link"),
wantErr: false,
},
{
name: "symbolic link",
mode: testEvalSymlinksSymbolicLink,
linkBasePath: filepath.Join(os.TempDir(), "link_base"),
args: args{
path: filepath.Join(os.TempDir(), "symbolic_link"),
},
want: filepath.Join(os.TempDir(), "link_base"),
wantErr: false,
},
{
name: "junction",
mode: testEvalSymlinksJunction,
linkBasePath: filepath.Join(os.TempDir(), "link_base"),
args: args{
path: filepath.Join(os.TempDir(), "junction"),
},
want: filepath.Join(os.TempDir(), "link_base"),
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := createLink(tt.linkBasePath, tt.args.path, tt.mode); err != nil {
t.Errorf("failed to create link: %v", err)
return
}

got, err := evalSymlinks(tt.args.path)
if (err != nil) != tt.wantErr {
t.Errorf("evalSymlinks() error = %v, wantErr %v", err, tt.wantErr)
return
}
if got != tt.want {
t.Errorf("evalSymlinks() = %v, want %v", got, tt.want)
}
})
}
}

func createLink(linkBasePath, path string, mode testEvalSymlinksMode) error {
if err := os.RemoveAll(path); err != nil {
return err
}

if mode == testEvalSymlinksNotLink {
return os.MkdirAll(path, 0755)
}

if err := os.MkdirAll(linkBasePath, 0755); err != nil {
return err
}

switch mode {
case testEvalSymlinksSymbolicLink:
return os.Symlink(linkBasePath, path)
case testEvalSymlinksJunction:
output, err := exec.Command("cmd", "/c", "mklink", "/J", path, linkBasePath).CombinedOutput()
if err != nil {
output, err := io.ReadAll(transform.NewReader(bytes.NewBuffer(output), japanese.ShiftJIS.NewDecoder()))
if err != nil {
return fmt.Errorf("failed to transform output: %w", err)
}
return fmt.Errorf("failed to create junction: %s, %w", string(output), err)
}
return nil
}
return nil
}
2 changes: 1 addition & 1 deletion local_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,7 @@ func localRepositoryRoots(all bool) ([]string, error) {
for _, v := range roots {
path := filepath.Clean(v)
if _, err := os.Stat(path); err == nil {
if path, err = filepath.EvalSymlinks(path); err != nil {
if path, err = evalSymlinks(path); err != nil {
_localRepoErr = err
return
}
Expand Down

0 comments on commit c9e0c4e

Please sign in to comment.