add windows symlink & junction support

Signed-off-by: walnuts1018 <r.juglans.1018@gmail.com>
This commit is contained in:
walnuts1018 2024-10-27 00:19:49 +09:00
parent 52efddc011
commit 231448ee85
7 changed files with 174 additions and 6 deletions

View file

@ -26,6 +26,6 @@ jobs:
with:
go-version-file: go.mod
- name: test
run: go test -coverprofile coverage.out -covermode atomic ./...
run: sudo go test -coverprofile coverage.out -covermode atomic ./...
- name: Send coverage
uses: codecov/codecov-action@v1

3
go.mod
View file

@ -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 (
@ -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
go.sum
View file

@ -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=
@ -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=

View file

@ -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)
}

View file

@ -2,7 +2,12 @@
package main
import "syscall"
import (
"os"
"path/filepath"
"strings"
"syscall"
)
func toFullPath(s string) (string, error) {
p := syscall.StringToUTF16(s)
@ -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
helpers_windows_test.go Normal file
View file

@ -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
}

View file

@ -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
}