pax_global_header00006660000000000000000000000064146000004610014501gustar00rootroot0000000000000052 comment=17abae92671e2bebb75e069f1138a7d2e100695a golang-github-go-git-go-billy-5.5.0/000077500000000000000000000000001460000046100171375ustar00rootroot00000000000000golang-github-go-git-go-billy-5.5.0/.github/000077500000000000000000000000001460000046100204775ustar00rootroot00000000000000golang-github-go-git-go-billy-5.5.0/.github/workflows/000077500000000000000000000000001460000046100225345ustar00rootroot00000000000000golang-github-go-git-go-billy-5.5.0/.github/workflows/codeql.yml000066400000000000000000000023631460000046100245320ustar00rootroot00000000000000name: "CodeQL" on: push: branches: [ "master" ] pull_request: branches: [ "master" ] schedule: - cron: '00 5 * * 1' jobs: analyze: name: Analyze runs-on: ubuntu-latest permissions: actions: read contents: read security-events: write strategy: fail-fast: false matrix: language: [ 'go' ] steps: - name: Checkout code uses: actions/checkout@8e5e7e5ab8b370d6c329ec480221332ada57f0ab # v3.5.2 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL uses: github/codeql-action/init@29b1f65c5e92e24fe6b6647da1eaabe529cec70f # v2.3.3 with: languages: ${{ matrix.language }} # xref: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs # xref: https://codeql.github.com/codeql-query-help/go/ queries: security-and-quality - name: Manual Build run: go build ./... - name: Perform CodeQL Analysis uses: github/codeql-action/analyze@29b1f65c5e92e24fe6b6647da1eaabe529cec70f # v2.3.3 with: category: "/language:${{matrix.language}}" golang-github-go-git-go-billy-5.5.0/.github/workflows/test.yml000066400000000000000000000007011460000046100242340ustar00rootroot00000000000000on: [push, pull_request] name: Test permissions: {} jobs: test: strategy: matrix: go-version: [1.20.x,1.21.x] platform: [ubuntu-latest, macos-latest, windows-latest] runs-on: ${{ matrix.platform }} steps: - name: Install Go uses: actions/setup-go@v3 with: go-version: ${{ matrix.go-version }} - name: Checkout code uses: actions/checkout@v3 - name: Test run: make test golang-github-go-git-go-billy-5.5.0/.github/workflows/test_js.yml000066400000000000000000000012141460000046100247300ustar00rootroot00000000000000on: [push, pull_request] name: Test JS permissions: {} jobs: test: strategy: matrix: go-version: [1.20.x,1.21.x] runs-on: ubuntu-latest steps: - name: Install Go uses: actions/setup-go@v3 with: go-version: ${{ matrix.go-version }} - name: Install wasmbrowsertest run: | go install github.com/agnivade/wasmbrowsertest@latest mv $HOME/go/bin/wasmbrowsertest $HOME/go/bin/go_js_wasm_exec - name: Checkout code uses: actions/checkout@v3 - name: Test run: go test -exec="$HOME/go/bin/go_js_wasm_exec" ./... env: GOOS: js GOARCH: wasm golang-github-go-git-go-billy-5.5.0/.gitignore000066400000000000000000000000541460000046100211260ustar00rootroot00000000000000/coverage.txt /vendor Gopkg.lock Gopkg.toml golang-github-go-git-go-billy-5.5.0/LICENSE000066400000000000000000000261331460000046100201510ustar00rootroot00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS APPENDIX: How to apply the Apache License to your work. To apply the Apache License to your work, attach the following boilerplate notice, with the fields enclosed by brackets "{}" replaced with your own identifying information. (Don't include the brackets!) The text should be enclosed in the appropriate comment syntax for the file format. We also recommend that a file or class name and description of purpose be included on the same "printed page" as the copyright notice for easier identification within third-party archives. Copyright 2017 Sourced Technologies S.L. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. golang-github-go-git-go-billy-5.5.0/Makefile000066400000000000000000000003551460000046100206020ustar00rootroot00000000000000# Go parameters GOCMD = go GOTEST = $(GOCMD) test .PHONY: test test: $(GOTEST) -race ./... test-coverage: echo "" > $(COVERAGE_REPORT); \ $(GOTEST) -coverprofile=$(COVERAGE_REPORT) -coverpkg=./... -covermode=$(COVERAGE_MODE) ./... golang-github-go-git-go-billy-5.5.0/README.md000066400000000000000000000040171460000046100204200ustar00rootroot00000000000000# go-billy [![GoDoc](https://godoc.org/gopkg.in/go-git/go-billy.v5?status.svg)](https://pkg.go.dev/github.com/go-git/go-billy/v5) [![Test](https://github.com/go-git/go-billy/workflows/Test/badge.svg)](https://github.com/go-git/go-billy/actions?query=workflow%3ATest) The missing interface filesystem abstraction for Go. Billy implements an interface based on the `os` standard library, allowing to develop applications without dependency on the underlying storage. Makes it virtually free to implement mocks and testing over filesystem operations. Billy was born as part of [go-git/go-git](https://github.com/go-git/go-git) project. ## Installation ```go import "github.com/go-git/go-billy/v5" // with go modules enabled (GO111MODULE=on or outside GOPATH) import "github.com/go-git/go-billy" // with go modules disabled ``` ## Usage Billy exposes filesystems using the [`Filesystem` interface](https://pkg.go.dev/github.com/go-git/go-billy/v5?tab=doc#Filesystem). Each filesystem implementation gives you a `New` method, whose arguments depend on the implementation itself, that returns a new `Filesystem`. The following example caches in memory all readable files in a directory from any billy's filesystem implementation. ```go func LoadToMemory(origin billy.Filesystem, path string) (*memory.Memory, error) { memory := memory.New() files, err := origin.ReadDir("/") if err != nil { return nil, err } for _, file := range files { if file.IsDir() { continue } src, err := origin.Open(file.Name()) if err != nil { return nil, err } dst, err := memory.Create(file.Name()) if err != nil { return nil, err } if _, err = io.Copy(dst, src); err != nil { return nil, err } if err := dst.Close(); err != nil { return nil, err } if err := src.Close(); err != nil { return nil, err } } return memory, nil } ``` ## Why billy? The library billy deals with storage systems and Billy is the name of a well-known, IKEA bookcase. That's it. ## License Apache License Version 2.0, see [LICENSE](LICENSE) golang-github-go-git-go-billy-5.5.0/fs.go000066400000000000000000000175521460000046100201100ustar00rootroot00000000000000package billy import ( "errors" "io" "os" "time" ) var ( ErrReadOnly = errors.New("read-only filesystem") ErrNotSupported = errors.New("feature not supported") ErrCrossedBoundary = errors.New("chroot boundary crossed") ) // Capability holds the supported features of a billy filesystem. This does // not mean that the capability has to be supported by the underlying storage. // For example, a billy filesystem may support WriteCapability but the // storage be mounted in read only mode. type Capability uint64 const ( // WriteCapability means that the fs is writable. WriteCapability Capability = 1 << iota // ReadCapability means that the fs is readable. ReadCapability // ReadAndWriteCapability is the ability to open a file in read and write mode. ReadAndWriteCapability // SeekCapability means it is able to move position inside the file. SeekCapability // TruncateCapability means that a file can be truncated. TruncateCapability // LockCapability is the ability to lock a file. LockCapability // DefaultCapabilities lists all capable features supported by filesystems // without Capability interface. This list should not be changed until a // major version is released. DefaultCapabilities Capability = WriteCapability | ReadCapability | ReadAndWriteCapability | SeekCapability | TruncateCapability | LockCapability // AllCapabilities lists all capable features. AllCapabilities Capability = WriteCapability | ReadCapability | ReadAndWriteCapability | SeekCapability | TruncateCapability | LockCapability ) // Filesystem abstract the operations in a storage-agnostic interface. // Each method implementation mimics the behavior of the equivalent functions // at the os package from the standard library. type Filesystem interface { Basic TempFile Dir Symlink Chroot } // Basic abstract the basic operations in a storage-agnostic interface as // an extension to the Basic interface. type Basic interface { // Create creates the named file with mode 0666 (before umask), truncating // it if it already exists. If successful, methods on the returned File can // be used for I/O; the associated file descriptor has mode O_RDWR. Create(filename string) (File, error) // Open opens the named file for reading. If successful, methods on the // returned file can be used for reading; the associated file descriptor has // mode O_RDONLY. Open(filename string) (File, error) // OpenFile is the generalized open call; most users will use Open or Create // instead. It opens the named file with specified flag (O_RDONLY etc.) and // perm, (0666 etc.) if applicable. If successful, methods on the returned // File can be used for I/O. OpenFile(filename string, flag int, perm os.FileMode) (File, error) // Stat returns a FileInfo describing the named file. Stat(filename string) (os.FileInfo, error) // Rename renames (moves) oldpath to newpath. If newpath already exists and // is not a directory, Rename replaces it. OS-specific restrictions may // apply when oldpath and newpath are in different directories. Rename(oldpath, newpath string) error // Remove removes the named file or directory. Remove(filename string) error // Join joins any number of path elements into a single path, adding a // Separator if necessary. Join calls filepath.Clean on the result; in // particular, all empty strings are ignored. On Windows, the result is a // UNC path if and only if the first path element is a UNC path. Join(elem ...string) string } type TempFile interface { // TempFile creates a new temporary file in the directory dir with a name // beginning with prefix, opens the file for reading and writing, and // returns the resulting *os.File. If dir is the empty string, TempFile // uses the default directory for temporary files (see os.TempDir). // Multiple programs calling TempFile simultaneously will not choose the // same file. The caller can use f.Name() to find the pathname of the file. // It is the caller's responsibility to remove the file when no longer // needed. TempFile(dir, prefix string) (File, error) } // Dir abstract the dir related operations in a storage-agnostic interface as // an extension to the Basic interface. type Dir interface { // ReadDir reads the directory named by dirname and returns a list of // directory entries sorted by filename. ReadDir(path string) ([]os.FileInfo, error) // MkdirAll creates a directory named path, along with any necessary // parents, and returns nil, or else returns an error. The permission bits // perm are used for all directories that MkdirAll creates. If path is/ // already a directory, MkdirAll does nothing and returns nil. MkdirAll(filename string, perm os.FileMode) error } // Symlink abstract the symlink related operations in a storage-agnostic // interface as an extension to the Basic interface. type Symlink interface { // Lstat returns a FileInfo describing the named file. If the file is a // symbolic link, the returned FileInfo describes the symbolic link. Lstat // makes no attempt to follow the link. Lstat(filename string) (os.FileInfo, error) // Symlink creates a symbolic-link from link to target. target may be an // absolute or relative path, and need not refer to an existing node. // Parent directories of link are created as necessary. Symlink(target, link string) error // Readlink returns the target path of link. Readlink(link string) (string, error) } // Change abstract the FileInfo change related operations in a storage-agnostic // interface as an extension to the Basic interface type Change interface { // Chmod changes the mode of the named file to mode. If the file is a // symbolic link, it changes the mode of the link's target. Chmod(name string, mode os.FileMode) error // Lchown changes the numeric uid and gid of the named file. If the file is // a symbolic link, it changes the uid and gid of the link itself. Lchown(name string, uid, gid int) error // Chown changes the numeric uid and gid of the named file. If the file is a // symbolic link, it changes the uid and gid of the link's target. Chown(name string, uid, gid int) error // Chtimes changes the access and modification times of the named file, // similar to the Unix utime() or utimes() functions. // // The underlying filesystem may truncate or round the values to a less // precise time unit. Chtimes(name string, atime time.Time, mtime time.Time) error } // Chroot abstract the chroot related operations in a storage-agnostic interface // as an extension to the Basic interface. type Chroot interface { // Chroot returns a new filesystem from the same type where the new root is // the given path. Files outside of the designated directory tree cannot be // accessed. Chroot(path string) (Filesystem, error) // Root returns the root path of the filesystem. Root() string } // File represent a file, being a subset of the os.File type File interface { // Name returns the name of the file as presented to Open. Name() string io.Writer io.Reader io.ReaderAt io.Seeker io.Closer // Lock locks the file like e.g. flock. It protects against access from // other processes. Lock() error // Unlock unlocks the file. Unlock() error // Truncate the file. Truncate(size int64) error } // Capable interface can return the available features of a filesystem. type Capable interface { // Capabilities returns the capabilities of a filesystem in bit flags. Capabilities() Capability } // Capabilities returns the features supported by a filesystem. If the FS // does not implement Capable interface it returns all features. func Capabilities(fs Basic) Capability { capable, ok := fs.(Capable) if !ok { return DefaultCapabilities } return capable.Capabilities() } // CapabilityCheck tests the filesystem for the provided capabilities and // returns true in case it supports all of them. func CapabilityCheck(fs Basic, capabilities Capability) bool { fsCaps := Capabilities(fs) return fsCaps&capabilities == capabilities } golang-github-go-git-go-billy-5.5.0/fs_test.go000066400000000000000000000017121460000046100211360ustar00rootroot00000000000000package billy_test import ( "testing" . "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/test" . "gopkg.in/check.v1" ) type FSSuite struct{} func Test(t *testing.T) { TestingT(t) } var _ = Suite(&FSSuite{}) func (s *FSSuite) TestCapabilities(c *C) { cases := []struct { caps Capability expected bool }{ {LockCapability, false}, {ReadCapability, true}, {ReadCapability | WriteCapability, true}, {ReadCapability | WriteCapability | ReadAndWriteCapability | TruncateCapability, true}, {ReadCapability | WriteCapability | ReadAndWriteCapability | TruncateCapability | LockCapability, false}, {TruncateCapability | LockCapability, false}, } // This filesystem supports all capabilities except for LockCapability fs := new(test.NoLockCapFs) for _, e := range cases { c.Assert(CapabilityCheck(fs, e.caps), Equals, e.expected) } dummy := new(test.BasicMock) c.Assert(Capabilities(dummy), Equals, DefaultCapabilities) } golang-github-go-git-go-billy-5.5.0/go.mod000066400000000000000000000010751460000046100202500ustar00rootroot00000000000000module github.com/go-git/go-billy/v5 // go-git supports the last 3 stable Go versions. go 1.19 require ( github.com/cyphar/filepath-securejoin v0.2.4 github.com/onsi/gomega v1.27.10 golang.org/x/sys v0.12.0 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c ) require ( github.com/google/go-cmp v0.5.9 // indirect github.com/kr/pretty v0.3.1 // indirect github.com/kr/text v0.2.0 // indirect github.com/rogpeppe/go-internal v1.11.0 // indirect golang.org/x/net v0.15.0 // indirect golang.org/x/text v0.13.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) golang-github-go-git-go-billy-5.5.0/go.sum000066400000000000000000000055741460000046100203050ustar00rootroot00000000000000github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg= github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4= github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ= github.com/go-task/slim-sprig v0.0.0-20230315185526-52ccab3ef572 h1:tfuBGBXKqDEevZMzYi5KSi8KkcZtzBcTgAUUtapy0OI= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/pprof v0.0.0-20210407192527-94a9f03dee38 h1:yAJXTCF9TqKcTiHJAE8dj7HMvPfh66eeA2JYW7eFpSE= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/onsi/ginkgo/v2 v2.11.0 h1:WgqUCUt/lT6yXoQ8Wef0fsNn5cAuMK7+KT9UFRz2tcU= github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI= github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M= github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA= golang.org/x/net v0.15.0 h1:ugBLEUaxABaB5AJqW9enI0ACdci2RUd4eP51NTBvuJ8= golang.org/x/net v0.15.0/go.mod h1:idbUs1IY1+zTqbi8yxTbhexhEEk5ur9LInksu6HrEpk= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k= golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= golang.org/x/tools v0.9.3 h1:Gn1I8+64MsuTb/HpH+LmQtNas23LhUVr3rYZ0eKuaMM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= golang-github-go-git-go-billy-5.5.0/helper/000077500000000000000000000000001460000046100204165ustar00rootroot00000000000000golang-github-go-git-go-billy-5.5.0/helper/chroot/000077500000000000000000000000001460000046100217145ustar00rootroot00000000000000golang-github-go-git-go-billy-5.5.0/helper/chroot/chroot.go000066400000000000000000000122051460000046100235410ustar00rootroot00000000000000package chroot import ( "os" "path/filepath" "strings" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/helper/polyfill" ) // ChrootHelper is a helper to implement billy.Chroot. type ChrootHelper struct { underlying billy.Filesystem base string } // New creates a new filesystem wrapping up the given 'fs'. // The created filesystem has its base in the given ChrootHelperectory of the // underlying filesystem. func New(fs billy.Basic, base string) billy.Filesystem { return &ChrootHelper{ underlying: polyfill.New(fs), base: base, } } func (fs *ChrootHelper) underlyingPath(filename string) (string, error) { if isCrossBoundaries(filename) { return "", billy.ErrCrossedBoundary } return fs.Join(fs.Root(), filename), nil } func isCrossBoundaries(path string) bool { path = filepath.ToSlash(path) path = filepath.Clean(path) return strings.HasPrefix(path, ".."+string(filepath.Separator)) } func (fs *ChrootHelper) Create(filename string) (billy.File, error) { fullpath, err := fs.underlyingPath(filename) if err != nil { return nil, err } f, err := fs.underlying.Create(fullpath) if err != nil { return nil, err } return newFile(fs, f, filename), nil } func (fs *ChrootHelper) Open(filename string) (billy.File, error) { fullpath, err := fs.underlyingPath(filename) if err != nil { return nil, err } f, err := fs.underlying.Open(fullpath) if err != nil { return nil, err } return newFile(fs, f, filename), nil } func (fs *ChrootHelper) OpenFile(filename string, flag int, mode os.FileMode) (billy.File, error) { fullpath, err := fs.underlyingPath(filename) if err != nil { return nil, err } f, err := fs.underlying.OpenFile(fullpath, flag, mode) if err != nil { return nil, err } return newFile(fs, f, filename), nil } func (fs *ChrootHelper) Stat(filename string) (os.FileInfo, error) { fullpath, err := fs.underlyingPath(filename) if err != nil { return nil, err } return fs.underlying.Stat(fullpath) } func (fs *ChrootHelper) Rename(from, to string) error { var err error from, err = fs.underlyingPath(from) if err != nil { return err } to, err = fs.underlyingPath(to) if err != nil { return err } return fs.underlying.Rename(from, to) } func (fs *ChrootHelper) Remove(path string) error { fullpath, err := fs.underlyingPath(path) if err != nil { return err } return fs.underlying.Remove(fullpath) } func (fs *ChrootHelper) Join(elem ...string) string { return fs.underlying.Join(elem...) } func (fs *ChrootHelper) TempFile(dir, prefix string) (billy.File, error) { fullpath, err := fs.underlyingPath(dir) if err != nil { return nil, err } f, err := fs.underlying.(billy.TempFile).TempFile(fullpath, prefix) if err != nil { return nil, err } return newFile(fs, f, fs.Join(dir, filepath.Base(f.Name()))), nil } func (fs *ChrootHelper) ReadDir(path string) ([]os.FileInfo, error) { fullpath, err := fs.underlyingPath(path) if err != nil { return nil, err } return fs.underlying.(billy.Dir).ReadDir(fullpath) } func (fs *ChrootHelper) MkdirAll(filename string, perm os.FileMode) error { fullpath, err := fs.underlyingPath(filename) if err != nil { return err } return fs.underlying.(billy.Dir).MkdirAll(fullpath, perm) } func (fs *ChrootHelper) Lstat(filename string) (os.FileInfo, error) { fullpath, err := fs.underlyingPath(filename) if err != nil { return nil, err } return fs.underlying.(billy.Symlink).Lstat(fullpath) } func (fs *ChrootHelper) Symlink(target, link string) error { target = filepath.FromSlash(target) // only rewrite target if it's already absolute if filepath.IsAbs(target) || strings.HasPrefix(target, string(filepath.Separator)) { target = fs.Join(fs.Root(), target) target = filepath.Clean(filepath.FromSlash(target)) } link, err := fs.underlyingPath(link) if err != nil { return err } return fs.underlying.(billy.Symlink).Symlink(target, link) } func (fs *ChrootHelper) Readlink(link string) (string, error) { fullpath, err := fs.underlyingPath(link) if err != nil { return "", err } target, err := fs.underlying.(billy.Symlink).Readlink(fullpath) if err != nil { return "", err } if !filepath.IsAbs(target) && !strings.HasPrefix(target, string(filepath.Separator)) { return target, nil } target, err = filepath.Rel(fs.base, target) if err != nil { return "", err } return string(os.PathSeparator) + target, nil } func (fs *ChrootHelper) Chroot(path string) (billy.Filesystem, error) { fullpath, err := fs.underlyingPath(path) if err != nil { return nil, err } return New(fs.underlying, fullpath), nil } func (fs *ChrootHelper) Root() string { return fs.base } func (fs *ChrootHelper) Underlying() billy.Basic { return fs.underlying } // Capabilities implements the Capable interface. func (fs *ChrootHelper) Capabilities() billy.Capability { return billy.Capabilities(fs.underlying) } type file struct { billy.File name string } func newFile(fs billy.Filesystem, f billy.File, filename string) billy.File { filename = fs.Join(fs.Root(), filename) filename, _ = filepath.Rel(fs.Root(), filename) return &file{ File: f, name: filename, } } func (f *file) Name() string { return f.name } golang-github-go-git-go-billy-5.5.0/helper/chroot/chroot_test.go000066400000000000000000000206521460000046100246050ustar00rootroot00000000000000package chroot import ( "os" "path/filepath" "testing" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/test" . "gopkg.in/check.v1" ) func Test(t *testing.T) { TestingT(t) } var _ = Suite(&ChrootSuite{}) type ChrootSuite struct{} func (s *ChrootSuite) TestCreate(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") f, err := fs.Create("bar/qux") c.Assert(err, IsNil) c.Assert(f.Name(), Equals, filepath.Join("bar", "qux")) c.Assert(m.CreateArgs, HasLen, 1) c.Assert(m.CreateArgs[0], Equals, "/foo/bar/qux") } func (s *ChrootSuite) TestCreateErrCrossedBoundary(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") _, err := fs.Create("../foo") c.Assert(err, Equals, billy.ErrCrossedBoundary) } func (s *ChrootSuite) TestLeadingPeriodsPathNotCrossedBoundary(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") f, err := fs.Create("..foo") c.Assert(err, IsNil) c.Assert(f.Name(), Equals, "..foo") } func (s *ChrootSuite) TestOpen(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") f, err := fs.Open("bar/qux") c.Assert(err, IsNil) c.Assert(f.Name(), Equals, filepath.Join("bar", "qux")) c.Assert(m.OpenArgs, HasLen, 1) c.Assert(m.OpenArgs[0], Equals, "/foo/bar/qux") } func (s *ChrootSuite) TestChroot(c *C) { m := &test.BasicMock{} fs, _ := New(m, "/foo").Chroot("baz") f, err := fs.Open("bar/qux") c.Assert(err, IsNil) c.Assert(f.Name(), Equals, filepath.Join("bar", "qux")) c.Assert(m.OpenArgs, HasLen, 1) c.Assert(m.OpenArgs[0], Equals, "/foo/baz/bar/qux") } func (s *ChrootSuite) TestChrootErrCrossedBoundary(c *C) { m := &test.BasicMock{} fs, err := New(m, "/foo").Chroot("../qux") c.Assert(fs, IsNil) c.Assert(err, Equals, billy.ErrCrossedBoundary) } func (s *ChrootSuite) TestOpenErrCrossedBoundary(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") _, err := fs.Open("../foo") c.Assert(err, Equals, billy.ErrCrossedBoundary) } func (s *ChrootSuite) TestOpenFile(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") f, err := fs.OpenFile("bar/qux", 42, 0777) c.Assert(err, IsNil) c.Assert(f.Name(), Equals, filepath.Join("bar", "qux")) c.Assert(m.OpenFileArgs, HasLen, 1) c.Assert(m.OpenFileArgs[0], Equals, [3]interface{}{"/foo/bar/qux", 42, os.FileMode(0777)}) } func (s *ChrootSuite) TestOpenFileErrCrossedBoundary(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") _, err := fs.OpenFile("../foo", 42, 0777) c.Assert(err, Equals, billy.ErrCrossedBoundary) } func (s *ChrootSuite) TestStat(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") _, err := fs.Stat("bar/qux") c.Assert(err, IsNil) c.Assert(m.StatArgs, HasLen, 1) c.Assert(m.StatArgs[0], Equals, "/foo/bar/qux") } func (s *ChrootSuite) TestStatErrCrossedBoundary(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") _, err := fs.Stat("../foo") c.Assert(err, Equals, billy.ErrCrossedBoundary) } func (s *ChrootSuite) TestRename(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") err := fs.Rename("bar/qux", "qux/bar") c.Assert(err, IsNil) c.Assert(m.RenameArgs, HasLen, 1) c.Assert(m.RenameArgs[0], Equals, [2]string{"/foo/bar/qux", "/foo/qux/bar"}) } func (s *ChrootSuite) TestRenameErrCrossedBoundary(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") err := fs.Rename("../foo", "bar") c.Assert(err, Equals, billy.ErrCrossedBoundary) err = fs.Rename("foo", "../bar") c.Assert(err, Equals, billy.ErrCrossedBoundary) } func (s *ChrootSuite) TestRemove(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") err := fs.Remove("bar/qux") c.Assert(err, IsNil) c.Assert(m.RemoveArgs, HasLen, 1) c.Assert(m.RemoveArgs[0], Equals, "/foo/bar/qux") } func (s *ChrootSuite) TestRemoveErrCrossedBoundary(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") err := fs.Remove("../foo") c.Assert(err, Equals, billy.ErrCrossedBoundary) } func (s *ChrootSuite) TestTempFile(c *C) { m := &test.TempFileMock{} fs := New(m, "/foo") _, err := fs.TempFile("bar", "qux") c.Assert(err, IsNil) c.Assert(m.TempFileArgs, HasLen, 1) c.Assert(m.TempFileArgs[0], Equals, [2]string{"/foo/bar", "qux"}) } func (s *ChrootSuite) TestTempFileErrCrossedBoundary(c *C) { m := &test.TempFileMock{} fs := New(m, "/foo") _, err := fs.TempFile("../foo", "qux") c.Assert(err, Equals, billy.ErrCrossedBoundary) } func (s *ChrootSuite) TestTempFileWithBasic(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") _, err := fs.TempFile("", "") c.Assert(err, Equals, billy.ErrNotSupported) } func (s *ChrootSuite) TestReadDir(c *C) { m := &test.DirMock{} fs := New(m, "/foo") _, err := fs.ReadDir("bar") c.Assert(err, IsNil) c.Assert(m.ReadDirArgs, HasLen, 1) c.Assert(m.ReadDirArgs[0], Equals, "/foo/bar") } func (s *ChrootSuite) TestReadDirErrCrossedBoundary(c *C) { m := &test.DirMock{} fs := New(m, "/foo") _, err := fs.ReadDir("../foo") c.Assert(err, Equals, billy.ErrCrossedBoundary) } func (s *ChrootSuite) TestReadDirWithBasic(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") _, err := fs.ReadDir("") c.Assert(err, Equals, billy.ErrNotSupported) } func (s *ChrootSuite) TestMkDirAll(c *C) { m := &test.DirMock{} fs := New(m, "/foo") err := fs.MkdirAll("bar", 0777) c.Assert(err, IsNil) c.Assert(m.MkdirAllArgs, HasLen, 1) c.Assert(m.MkdirAllArgs[0], Equals, [2]interface{}{"/foo/bar", os.FileMode(0777)}) } func (s *ChrootSuite) TestMkdirAllErrCrossedBoundary(c *C) { m := &test.DirMock{} fs := New(m, "/foo") err := fs.MkdirAll("../foo", 0777) c.Assert(err, Equals, billy.ErrCrossedBoundary) } func (s *ChrootSuite) TestMkdirAllWithBasic(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") err := fs.MkdirAll("", 0) c.Assert(err, Equals, billy.ErrNotSupported) } func (s *ChrootSuite) TestLstat(c *C) { m := &test.SymlinkMock{} fs := New(m, "/foo") _, err := fs.Lstat("qux") c.Assert(err, IsNil) c.Assert(m.LstatArgs, HasLen, 1) c.Assert(m.LstatArgs[0], Equals, "/foo/qux") } func (s *ChrootSuite) TestLstatErrCrossedBoundary(c *C) { m := &test.SymlinkMock{} fs := New(m, "/foo") _, err := fs.Lstat("../qux") c.Assert(err, Equals, billy.ErrCrossedBoundary) } func (s *ChrootSuite) TestLstatWithBasic(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") _, err := fs.Lstat("") c.Assert(err, Equals, billy.ErrNotSupported) } func (s *ChrootSuite) TestSymlink(c *C) { m := &test.SymlinkMock{} fs := New(m, "/foo") err := fs.Symlink("../baz", "qux/bar") c.Assert(err, IsNil) c.Assert(m.SymlinkArgs, HasLen, 1) c.Assert(m.SymlinkArgs[0], Equals, [2]string{filepath.FromSlash("../baz"), "/foo/qux/bar"}) } func (s *ChrootSuite) TestSymlinkWithAbsoluteTarget(c *C) { m := &test.SymlinkMock{} fs := New(m, "/foo") err := fs.Symlink("/bar", "qux/baz") c.Assert(err, IsNil) c.Assert(m.SymlinkArgs, HasLen, 1) c.Assert(m.SymlinkArgs[0], Equals, [2]string{filepath.FromSlash("/foo/bar"), "/foo/qux/baz"}) } func (s *ChrootSuite) TestSymlinkErrCrossedBoundary(c *C) { m := &test.SymlinkMock{} fs := New(m, "/foo") err := fs.Symlink("qux", "../foo") c.Assert(err, Equals, billy.ErrCrossedBoundary) } func (s *ChrootSuite) TestSymlinkWithBasic(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") err := fs.Symlink("qux", "bar") c.Assert(err, Equals, billy.ErrNotSupported) } func (s *ChrootSuite) TestReadlink(c *C) { m := &test.SymlinkMock{} fs := New(m, "/foo") link, err := fs.Readlink("/qux") c.Assert(err, IsNil) c.Assert(link, Equals, filepath.FromSlash("/qux")) c.Assert(m.ReadlinkArgs, HasLen, 1) c.Assert(m.ReadlinkArgs[0], Equals, "/foo/qux") } func (s *ChrootSuite) TestReadlinkWithRelative(c *C) { m := &test.SymlinkMock{} fs := New(m, "/foo") link, err := fs.Readlink("qux/bar") c.Assert(err, IsNil) c.Assert(link, Equals, filepath.FromSlash("/qux/bar")) c.Assert(m.ReadlinkArgs, HasLen, 1) c.Assert(m.ReadlinkArgs[0], Equals, "/foo/qux/bar") } func (s *ChrootSuite) TestReadlinkErrCrossedBoundary(c *C) { m := &test.SymlinkMock{} fs := New(m, "/foo") _, err := fs.Readlink("../qux") c.Assert(err, Equals, billy.ErrCrossedBoundary) } func (s *ChrootSuite) TestReadlinkWithBasic(c *C) { m := &test.BasicMock{} fs := New(m, "/foo") _, err := fs.Readlink("") c.Assert(err, Equals, billy.ErrNotSupported) } func (s *ChrootSuite) TestCapabilities(c *C) { testCapabilities(c, new(test.BasicMock)) testCapabilities(c, new(test.OnlyReadCapFs)) testCapabilities(c, new(test.NoLockCapFs)) } func testCapabilities(c *C, basic billy.Basic) { baseCapabilities := billy.Capabilities(basic) fs := New(basic, "/foo") capabilities := billy.Capabilities(fs) c.Assert(capabilities, Equals, baseCapabilities) } golang-github-go-git-go-billy-5.5.0/helper/mount/000077500000000000000000000000001460000046100215605ustar00rootroot00000000000000golang-github-go-git-go-billy-5.5.0/helper/mount/mount.go000066400000000000000000000133431460000046100232550ustar00rootroot00000000000000package mount import ( "io" "os" "path/filepath" "strings" "fmt" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/helper/polyfill" ) var separator = string(filepath.Separator) // Mount is a helper that allows to emulate the behavior of mount in memory. // Very usufull to create a temporal dir, on filesystem where is a performance // penalty in doing so. type Mount struct { underlying billy.Filesystem source billy.Filesystem mountpoint string } // New creates a new filesystem wrapping up 'fs' the intercepts all the calls // made to `mountpoint` path and redirecting it to `source` filesystem. func New(fs billy.Basic, mountpoint string, source billy.Basic) *Mount { return &Mount{ underlying: polyfill.New(fs), source: polyfill.New(source), mountpoint: cleanPath(mountpoint), } } func (h *Mount) Create(path string) (billy.File, error) { fs, fullpath := h.getBasicAndPath(path) if fullpath == "." { return nil, os.ErrInvalid } f, err := fs.Create(fullpath) return wrapFile(f, path), err } func (h *Mount) Open(path string) (billy.File, error) { fs, fullpath := h.getBasicAndPath(path) if fullpath == "." { return nil, os.ErrInvalid } f, err := fs.Open(fullpath) return wrapFile(f, path), err } func (h *Mount) OpenFile(path string, flag int, mode os.FileMode) (billy.File, error) { fs, fullpath := h.getBasicAndPath(path) if fullpath == "." { return nil, os.ErrInvalid } f, err := fs.OpenFile(fullpath, flag, mode) return wrapFile(f, path), err } func (h *Mount) Rename(from, to string) error { fromInSource := h.isMountpoint(from) toInSource := h.isMountpoint(to) var fromFS, toFS billy.Basic switch { case fromInSource && toInSource: from = h.mustRelToMountpoint(from) to = h.mustRelToMountpoint(to) return h.source.Rename(from, to) case !fromInSource && !toInSource: return h.underlying.Rename(from, to) case fromInSource && !toInSource: fromFS = h.source from = h.mustRelToMountpoint(from) toFS = h.underlying to = cleanPath(to) case !fromInSource && toInSource: fromFS = h.underlying from = cleanPath(from) toFS = h.source to = h.mustRelToMountpoint(to) } if err := copyPath(fromFS, toFS, from, to); err != nil { return err } return fromFS.Remove(from) } func (h *Mount) Stat(path string) (os.FileInfo, error) { fs, fullpath := h.getBasicAndPath(path) return fs.Stat(fullpath) } func (h *Mount) Remove(path string) error { fs, fullpath := h.getBasicAndPath(path) if fullpath == "." { return os.ErrInvalid } return fs.Remove(fullpath) } func (h *Mount) ReadDir(path string) ([]os.FileInfo, error) { fs, fullpath, err := h.getDirAndPath(path) if err != nil { return nil, err } return fs.ReadDir(fullpath) } func (h *Mount) MkdirAll(filename string, perm os.FileMode) error { fs, fullpath, err := h.getDirAndPath(filename) if err != nil { return err } return fs.MkdirAll(fullpath, perm) } func (h *Mount) Symlink(target, link string) error { fs, fullpath, err := h.getSymlinkAndPath(link) if err != nil { return err } resolved := filepath.Join(filepath.Dir(link), target) if h.isMountpoint(resolved) != h.isMountpoint(link) { return fmt.Errorf("invalid symlink, target is crossing filesystems") } return fs.Symlink(target, fullpath) } func (h *Mount) Join(elem ...string) string { return h.underlying.Join(elem...) } func (h *Mount) Readlink(link string) (string, error) { fs, fullpath, err := h.getSymlinkAndPath(link) if err != nil { return "", err } return fs.Readlink(fullpath) } func (h *Mount) Lstat(path string) (os.FileInfo, error) { fs, fullpath, err := h.getSymlinkAndPath(path) if err != nil { return nil, err } return fs.Lstat(fullpath) } func (h *Mount) Underlying() billy.Basic { return h.underlying } // Capabilities implements the Capable interface. func (fs *Mount) Capabilities() billy.Capability { return billy.Capabilities(fs.underlying) & billy.Capabilities(fs.source) } func (fs *Mount) getBasicAndPath(path string) (billy.Basic, string) { path = cleanPath(path) if !fs.isMountpoint(path) { return fs.underlying, path } return fs.source, fs.mustRelToMountpoint(path) } func (fs *Mount) getDirAndPath(path string) (billy.Dir, string, error) { path = cleanPath(path) if !fs.isMountpoint(path) { return fs.underlying.(billy.Dir), path, nil } return fs.source.(billy.Dir), fs.mustRelToMountpoint(path), nil } func (fs *Mount) getSymlinkAndPath(path string) (billy.Symlink, string, error) { path = cleanPath(path) if !fs.isMountpoint(path) { return fs.underlying.(billy.Symlink), path, nil } return fs.source.(billy.Symlink), fs.mustRelToMountpoint(path), nil } func (fs *Mount) mustRelToMountpoint(path string) string { path = cleanPath(path) fullpath, err := filepath.Rel(fs.mountpoint, path) if err != nil { panic(err) } return fullpath } func (fs *Mount) isMountpoint(path string) bool { path = cleanPath(path) return strings.HasPrefix(path, fs.mountpoint) } func cleanPath(path string) string { path = filepath.FromSlash(path) rel, err := filepath.Rel(separator, path) if err == nil { path = rel } return filepath.Clean(path) } // copyPath copies a file across filesystems. func copyPath(src, dst billy.Basic, srcPath, dstPath string) error { dstFile, err := dst.Create(dstPath) if err != nil { return err } srcFile, err := src.Open(srcPath) if err != nil { return err } _, err = io.Copy(dstFile, srcFile) if err != nil { return err } err = dstFile.Close() if err != nil { _ = srcFile.Close() return err } return srcFile.Close() } type file struct { billy.File name string } func wrapFile(f billy.File, filename string) billy.File { if f == nil { return nil } return &file{ File: f, name: cleanPath(filename), } } func (f *file) Name() string { return f.name } golang-github-go-git-go-billy-5.5.0/helper/mount/mount_test.go000066400000000000000000000247751460000046100243270ustar00rootroot00000000000000package mount import ( "os" "path/filepath" "testing" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/memfs" "github.com/go-git/go-billy/v5/test" "github.com/go-git/go-billy/v5/util" . "gopkg.in/check.v1" ) func Test(t *testing.T) { TestingT(t) } var _ = Suite(&MountSuite{}) type MountSuite struct { Helper *Mount Underlying mock Source mock } type mock struct { test.BasicMock test.DirMock test.SymlinkMock } func (s *MountSuite) SetUpTest(c *C) { s.Underlying.BasicMock = test.BasicMock{} s.Underlying.DirMock = test.DirMock{} s.Underlying.SymlinkMock = test.SymlinkMock{} s.Source.BasicMock = test.BasicMock{} s.Source.DirMock = test.DirMock{} s.Source.SymlinkMock = test.SymlinkMock{} s.Helper = New(&s.Underlying, "/foo", &s.Source) } func (s *MountSuite) TestCreate(c *C) { f, err := s.Helper.Create("bar/qux") c.Assert(err, IsNil) c.Assert(f.Name(), Equals, filepath.Join("bar", "qux")) c.Assert(s.Underlying.CreateArgs, HasLen, 1) c.Assert(s.Underlying.CreateArgs[0], Equals, filepath.Join("bar", "qux")) c.Assert(s.Source.CreateArgs, HasLen, 0) } func (s *MountSuite) TestCreateMountPoint(c *C) { f, err := s.Helper.Create("foo") c.Assert(f, IsNil) c.Assert(err, Equals, os.ErrInvalid) } func (s *MountSuite) TestCreateInMount(c *C) { f, err := s.Helper.Create("foo/bar/qux") c.Assert(err, IsNil) c.Assert(f.Name(), Equals, filepath.Join("foo", "bar", "qux")) c.Assert(s.Underlying.CreateArgs, HasLen, 0) c.Assert(s.Source.CreateArgs, HasLen, 1) c.Assert(s.Source.CreateArgs[0], Equals, filepath.Join("bar", "qux")) } func (s *MountSuite) TestOpen(c *C) { f, err := s.Helper.Open("bar/qux") c.Assert(err, IsNil) c.Assert(f.Name(), Equals, filepath.Join("bar", "qux")) c.Assert(s.Underlying.OpenArgs, HasLen, 1) c.Assert(s.Underlying.OpenArgs[0], Equals, filepath.Join("bar", "qux")) c.Assert(s.Source.OpenArgs, HasLen, 0) } func (s *MountSuite) TestOpenMountPoint(c *C) { f, err := s.Helper.Open("foo") c.Assert(f, IsNil) c.Assert(err, Equals, os.ErrInvalid) } func (s *MountSuite) TestOpenInMount(c *C) { f, err := s.Helper.Open("foo/bar/qux") c.Assert(err, IsNil) c.Assert(f.Name(), Equals, filepath.Join("foo", "bar", "qux")) c.Assert(s.Underlying.OpenArgs, HasLen, 0) c.Assert(s.Source.OpenArgs, HasLen, 1) c.Assert(s.Source.OpenArgs[0], Equals, filepath.Join("bar", "qux")) } func (s *MountSuite) TestOpenFile(c *C) { f, err := s.Helper.OpenFile("bar/qux", 42, 0777) c.Assert(err, IsNil) c.Assert(f.Name(), Equals, filepath.Join("bar", "qux")) c.Assert(s.Underlying.OpenFileArgs, HasLen, 1) c.Assert(s.Underlying.OpenFileArgs[0], Equals, [3]interface{}{filepath.Join("bar", "qux"), 42, os.FileMode(0777)}) c.Assert(s.Source.OpenFileArgs, HasLen, 0) } func (s *MountSuite) TestOpenFileMountPoint(c *C) { f, err := s.Helper.OpenFile("foo", 42, 0777) c.Assert(f, IsNil) c.Assert(err, Equals, os.ErrInvalid) } func (s *MountSuite) TestOpenFileInMount(c *C) { f, err := s.Helper.OpenFile("foo/bar/qux", 42, 0777) c.Assert(err, IsNil) c.Assert(f.Name(), Equals, filepath.Join("foo", "bar", "qux")) c.Assert(s.Underlying.OpenFileArgs, HasLen, 0) c.Assert(s.Source.OpenFileArgs, HasLen, 1) c.Assert(s.Source.OpenFileArgs[0], Equals, [3]interface{}{filepath.Join("bar", "qux"), 42, os.FileMode(0777)}) } func (s *MountSuite) TestStat(c *C) { _, err := s.Helper.Stat("bar/qux") c.Assert(err, IsNil) c.Assert(s.Underlying.StatArgs, HasLen, 1) c.Assert(s.Underlying.StatArgs[0], Equals, filepath.Join("bar", "qux")) c.Assert(s.Source.StatArgs, HasLen, 0) } func (s *MountSuite) TestStatInMount(c *C) { _, err := s.Helper.Stat("foo/bar/qux") c.Assert(err, IsNil) c.Assert(s.Underlying.StatArgs, HasLen, 0) c.Assert(s.Source.StatArgs, HasLen, 1) c.Assert(s.Source.StatArgs[0], Equals, filepath.Join("bar", "qux")) } func (s *MountSuite) TestRename(c *C) { err := s.Helper.Rename("bar/qux", "qux") c.Assert(err, IsNil) c.Assert(s.Underlying.RenameArgs, HasLen, 1) c.Assert(s.Underlying.RenameArgs[0], Equals, [2]string{"bar/qux", "qux"}) c.Assert(s.Source.RenameArgs, HasLen, 0) } func (s *MountSuite) TestRenameInMount(c *C) { err := s.Helper.Rename("foo/bar/qux", "foo/qux") c.Assert(err, IsNil) c.Assert(s.Underlying.RenameArgs, HasLen, 0) c.Assert(s.Source.RenameArgs, HasLen, 1) c.Assert(s.Source.RenameArgs[0], Equals, [2]string{filepath.Join("bar", "qux"), "qux"}) } func (s *MountSuite) TestRenameCross(c *C) { underlying := memfs.New() source := memfs.New() util.WriteFile(underlying, "file", []byte("foo"), 0777) fs := New(underlying, "/foo", source) err := fs.Rename("file", "foo/file") c.Assert(err, IsNil) _, err = underlying.Stat("file") c.Assert(err, Equals, os.ErrNotExist) _, err = source.Stat("file") c.Assert(err, IsNil) err = fs.Rename("foo/file", "file") c.Assert(err, IsNil) _, err = underlying.Stat("file") c.Assert(err, IsNil) _, err = source.Stat("file") c.Assert(err, Equals, os.ErrNotExist) } func (s *MountSuite) TestRemove(c *C) { err := s.Helper.Remove("bar/qux") c.Assert(err, IsNil) c.Assert(s.Underlying.RemoveArgs, HasLen, 1) c.Assert(s.Underlying.RemoveArgs[0], Equals, filepath.Join("bar", "qux")) c.Assert(s.Source.RemoveArgs, HasLen, 0) } func (s *MountSuite) TestRemoveMountPoint(c *C) { err := s.Helper.Remove("foo") c.Assert(err, Equals, os.ErrInvalid) } func (s *MountSuite) TestRemoveInMount(c *C) { err := s.Helper.Remove("foo/bar/qux") c.Assert(err, IsNil) c.Assert(s.Underlying.RemoveArgs, HasLen, 0) c.Assert(s.Source.RemoveArgs, HasLen, 1) c.Assert(s.Source.RemoveArgs[0], Equals, filepath.Join("bar", "qux")) } func (s *MountSuite) TestReadDir(c *C) { _, err := s.Helper.ReadDir("bar/qux") c.Assert(err, IsNil) c.Assert(s.Underlying.ReadDirArgs, HasLen, 1) c.Assert(s.Underlying.ReadDirArgs[0], Equals, filepath.Join("bar", "qux")) c.Assert(s.Source.ReadDirArgs, HasLen, 0) } func (s *MountSuite) TestJoin(c *C) { s.Helper.Join("foo", "bar") c.Assert(s.Underlying.JoinArgs, HasLen, 1) c.Assert(s.Underlying.JoinArgs[0], DeepEquals, []string{"foo", "bar"}) c.Assert(s.Source.JoinArgs, HasLen, 0) } func (s *MountSuite) TestReadDirInMount(c *C) { _, err := s.Helper.ReadDir("foo/bar/qux") c.Assert(err, IsNil) c.Assert(s.Underlying.ReadDirArgs, HasLen, 0) c.Assert(s.Source.ReadDirArgs, HasLen, 1) c.Assert(s.Source.ReadDirArgs[0], Equals, filepath.Join("bar", "qux")) } func (s *MountSuite) TestMkdirAll(c *C) { err := s.Helper.MkdirAll("bar/qux", 0777) c.Assert(err, IsNil) c.Assert(s.Underlying.MkdirAllArgs, HasLen, 1) c.Assert(s.Underlying.MkdirAllArgs[0], Equals, [2]interface{}{filepath.Join("bar", "qux"), os.FileMode(0777)}) c.Assert(s.Source.MkdirAllArgs, HasLen, 0) } func (s *MountSuite) TestMkdirAllInMount(c *C) { err := s.Helper.MkdirAll("foo/bar/qux", 0777) c.Assert(err, IsNil) c.Assert(s.Underlying.MkdirAllArgs, HasLen, 0) c.Assert(s.Source.MkdirAllArgs, HasLen, 1) c.Assert(s.Source.MkdirAllArgs[0], Equals, [2]interface{}{filepath.Join("bar", "qux"), os.FileMode(0777)}) } func (s *MountSuite) TestLstat(c *C) { _, err := s.Helper.Lstat("bar/qux") c.Assert(err, IsNil) c.Assert(s.Underlying.LstatArgs, HasLen, 1) c.Assert(s.Underlying.LstatArgs[0], Equals, filepath.Join("bar", "qux")) c.Assert(s.Source.LstatArgs, HasLen, 0) } func (s *MountSuite) TestLstatInMount(c *C) { _, err := s.Helper.Lstat("foo/bar/qux") c.Assert(err, IsNil) c.Assert(s.Underlying.LstatArgs, HasLen, 0) c.Assert(s.Source.LstatArgs, HasLen, 1) c.Assert(s.Source.LstatArgs[0], Equals, filepath.Join("bar", "qux")) } func (s *MountSuite) TestSymlink(c *C) { err := s.Helper.Symlink("../baz", "bar/qux") c.Assert(err, IsNil) c.Assert(s.Underlying.SymlinkArgs, HasLen, 1) c.Assert(s.Underlying.SymlinkArgs[0], Equals, [2]string{"../baz", filepath.Join("bar", "qux")}) c.Assert(s.Source.SymlinkArgs, HasLen, 0) } func (s *MountSuite) TestSymlinkCrossMount(c *C) { err := s.Helper.Symlink("../foo", "bar/qux") c.Assert(err, NotNil) err = s.Helper.Symlink("../foo/qux", "bar/qux") c.Assert(err, NotNil) err = s.Helper.Symlink("../baz", "foo") c.Assert(err, NotNil) err = s.Helper.Symlink("../../../foo", "foo/bar/qux") c.Assert(err, NotNil) } func (s *MountSuite) TestSymlinkInMount(c *C) { err := s.Helper.Symlink("../baz", "foo/bar/qux") c.Assert(err, IsNil) c.Assert(s.Underlying.SymlinkArgs, HasLen, 0) c.Assert(s.Source.SymlinkArgs, HasLen, 1) c.Assert(s.Source.SymlinkArgs[0], Equals, [2]string{"../baz", filepath.Join("bar", "qux")}) } func (s *MountSuite) TestRadlink(c *C) { _, err := s.Helper.Readlink("bar/qux") c.Assert(err, IsNil) c.Assert(s.Underlying.ReadlinkArgs, HasLen, 1) c.Assert(s.Underlying.ReadlinkArgs[0], Equals, filepath.Join("bar", "qux")) c.Assert(s.Source.ReadlinkArgs, HasLen, 0) } func (s *MountSuite) TestReadlinkInMount(c *C) { _, err := s.Helper.Readlink("foo/bar/qux") c.Assert(err, IsNil) c.Assert(s.Underlying.ReadlinkArgs, HasLen, 0) c.Assert(s.Source.ReadlinkArgs, HasLen, 1) c.Assert(s.Source.ReadlinkArgs[0], Equals, filepath.Join("bar", "qux")) } func (s *MountSuite) TestUnderlyingNotSupported(c *C) { h := New(&test.BasicMock{}, "/foo", &test.BasicMock{}) _, err := h.ReadDir("qux") c.Assert(err, Equals, billy.ErrNotSupported) _, err = h.Readlink("qux") c.Assert(err, Equals, billy.ErrNotSupported) } func (s *MountSuite) TestSourceNotSupported(c *C) { h := New(&s.Underlying, "/foo", &test.BasicMock{}) _, err := h.ReadDir("foo") c.Assert(err, Equals, billy.ErrNotSupported) _, err = h.Readlink("foo") c.Assert(err, Equals, billy.ErrNotSupported) } func (s *MountSuite) TestCapabilities(c *C) { testCapabilities(c, new(test.BasicMock), new(test.BasicMock)) testCapabilities(c, new(test.BasicMock), new(test.OnlyReadCapFs)) testCapabilities(c, new(test.BasicMock), new(test.NoLockCapFs)) testCapabilities(c, new(test.OnlyReadCapFs), new(test.BasicMock)) testCapabilities(c, new(test.OnlyReadCapFs), new(test.OnlyReadCapFs)) testCapabilities(c, new(test.OnlyReadCapFs), new(test.NoLockCapFs)) testCapabilities(c, new(test.NoLockCapFs), new(test.BasicMock)) testCapabilities(c, new(test.NoLockCapFs), new(test.OnlyReadCapFs)) testCapabilities(c, new(test.NoLockCapFs), new(test.NoLockCapFs)) } func testCapabilities(c *C, a, b billy.Basic) { aCapabilities := billy.Capabilities(a) bCapabilities := billy.Capabilities(b) fs := New(a, "/foo", b) capabilities := billy.Capabilities(fs) unionCapabilities := aCapabilities & bCapabilities c.Assert(capabilities, Equals, unionCapabilities) fs = New(b, "/foo", a) capabilities = billy.Capabilities(fs) unionCapabilities = aCapabilities & bCapabilities c.Assert(capabilities, Equals, unionCapabilities) } golang-github-go-git-go-billy-5.5.0/helper/polyfill/000077500000000000000000000000001460000046100222505ustar00rootroot00000000000000golang-github-go-git-go-billy-5.5.0/helper/polyfill/polyfill.go000066400000000000000000000043711460000046100244360ustar00rootroot00000000000000package polyfill import ( "os" "path/filepath" "github.com/go-git/go-billy/v5" ) // Polyfill is a helper that implements all missing method from billy.Filesystem. type Polyfill struct { billy.Basic c capabilities } type capabilities struct{ tempfile, dir, symlink, chroot bool } // New creates a new filesystem wrapping up 'fs' the intercepts all the calls // made and errors if fs doesn't implement any of the billy interfaces. func New(fs billy.Basic) billy.Filesystem { if original, ok := fs.(billy.Filesystem); ok { return original } h := &Polyfill{Basic: fs} _, h.c.tempfile = h.Basic.(billy.TempFile) _, h.c.dir = h.Basic.(billy.Dir) _, h.c.symlink = h.Basic.(billy.Symlink) _, h.c.chroot = h.Basic.(billy.Chroot) return h } func (h *Polyfill) TempFile(dir, prefix string) (billy.File, error) { if !h.c.tempfile { return nil, billy.ErrNotSupported } return h.Basic.(billy.TempFile).TempFile(dir, prefix) } func (h *Polyfill) ReadDir(path string) ([]os.FileInfo, error) { if !h.c.dir { return nil, billy.ErrNotSupported } return h.Basic.(billy.Dir).ReadDir(path) } func (h *Polyfill) MkdirAll(filename string, perm os.FileMode) error { if !h.c.dir { return billy.ErrNotSupported } return h.Basic.(billy.Dir).MkdirAll(filename, perm) } func (h *Polyfill) Symlink(target, link string) error { if !h.c.symlink { return billy.ErrNotSupported } return h.Basic.(billy.Symlink).Symlink(target, link) } func (h *Polyfill) Readlink(link string) (string, error) { if !h.c.symlink { return "", billy.ErrNotSupported } return h.Basic.(billy.Symlink).Readlink(link) } func (h *Polyfill) Lstat(path string) (os.FileInfo, error) { if !h.c.symlink { return nil, billy.ErrNotSupported } return h.Basic.(billy.Symlink).Lstat(path) } func (h *Polyfill) Chroot(path string) (billy.Filesystem, error) { if !h.c.chroot { return nil, billy.ErrNotSupported } return h.Basic.(billy.Chroot).Chroot(path) } func (h *Polyfill) Root() string { if !h.c.chroot { return string(filepath.Separator) } return h.Basic.(billy.Chroot).Root() } func (h *Polyfill) Underlying() billy.Basic { return h.Basic } // Capabilities implements the Capable interface. func (h *Polyfill) Capabilities() billy.Capability { return billy.Capabilities(h.Basic) } golang-github-go-git-go-billy-5.5.0/helper/polyfill/polyfill_test.go000066400000000000000000000033601460000046100254720ustar00rootroot00000000000000package polyfill import ( "path/filepath" "testing" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/test" . "gopkg.in/check.v1" ) func Test(t *testing.T) { TestingT(t) } var _ = Suite(&PolyfillSuite{}) type PolyfillSuite struct { Helper billy.Filesystem Underlying billy.Filesystem } func (s *PolyfillSuite) SetUpTest(c *C) { s.Helper = New(&test.BasicMock{}) } func (s *PolyfillSuite) TestTempFile(c *C) { _, err := s.Helper.TempFile("", "") c.Assert(err, Equals, billy.ErrNotSupported) } func (s *PolyfillSuite) TestReadDir(c *C) { _, err := s.Helper.ReadDir("") c.Assert(err, Equals, billy.ErrNotSupported) } func (s *PolyfillSuite) TestMkdirAll(c *C) { err := s.Helper.MkdirAll("", 0) c.Assert(err, Equals, billy.ErrNotSupported) } func (s *PolyfillSuite) TestSymlink(c *C) { err := s.Helper.Symlink("", "") c.Assert(err, Equals, billy.ErrNotSupported) } func (s *PolyfillSuite) TestReadlink(c *C) { _, err := s.Helper.Readlink("") c.Assert(err, Equals, billy.ErrNotSupported) } func (s *PolyfillSuite) TestLstat(c *C) { _, err := s.Helper.Lstat("") c.Assert(err, Equals, billy.ErrNotSupported) } func (s *PolyfillSuite) TestChroot(c *C) { _, err := s.Helper.Chroot("") c.Assert(err, Equals, billy.ErrNotSupported) } func (s *PolyfillSuite) TestRoot(c *C) { c.Assert(s.Helper.Root(), Equals, string(filepath.Separator)) } func (s *PolyfillSuite) TestCapabilities(c *C) { testCapabilities(c, new(test.BasicMock)) testCapabilities(c, new(test.OnlyReadCapFs)) testCapabilities(c, new(test.NoLockCapFs)) } func testCapabilities(c *C, basic billy.Basic) { baseCapabilities := billy.Capabilities(basic) fs := New(basic) capabilities := billy.Capabilities(fs) c.Assert(capabilities, Equals, baseCapabilities) } golang-github-go-git-go-billy-5.5.0/helper/temporal/000077500000000000000000000000001460000046100222415ustar00rootroot00000000000000golang-github-go-git-go-billy-5.5.0/helper/temporal/temporal.go000066400000000000000000000013251460000046100244140ustar00rootroot00000000000000package temporal import ( "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/util" ) // Temporal is a helper that implements billy.TempFile over any filesystem. type Temporal struct { billy.Filesystem defaultDir string } // New creates a new filesystem wrapping up 'fs' the intercepts the calls to // the TempFile method. The param defaultDir is used as default directory were // the tempfiles are created. func New(fs billy.Filesystem, defaultDir string) billy.Filesystem { return &Temporal{ Filesystem: fs, defaultDir: defaultDir, } } func (h *Temporal) TempFile(dir, prefix string) (billy.File, error) { if dir == "" { dir = h.defaultDir } return util.TempFile(h.Filesystem, dir, prefix) } golang-github-go-git-go-billy-5.5.0/helper/temporal/temporal_test.go000066400000000000000000000012201460000046100254450ustar00rootroot00000000000000package temporal import ( "strings" "testing" "github.com/go-git/go-billy/v5/memfs" "github.com/go-git/go-billy/v5/test" . "gopkg.in/check.v1" ) func Test(t *testing.T) { TestingT(t) } var _ = Suite(&TemporalSuite{}) type TemporalSuite struct { test.FilesystemSuite } func (s *TemporalSuite) SetUpTest(c *C) { fs := New(memfs.New(), "foo") s.FilesystemSuite = test.NewFilesystemSuite(fs) } func (s *TemporalSuite) TestTempFileDefaultPath(c *C) { fs := New(memfs.New(), "foo") f, err := fs.TempFile("", "bar") c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) c.Assert(strings.HasPrefix(f.Name(), fs.Join("foo", "bar")), Equals, true) } golang-github-go-git-go-billy-5.5.0/memfs/000077500000000000000000000000001460000046100202465ustar00rootroot00000000000000golang-github-go-git-go-billy-5.5.0/memfs/memory.go000066400000000000000000000177121460000046100221150ustar00rootroot00000000000000// Package memfs provides a billy filesystem base on memory. package memfs // import "github.com/go-git/go-billy/v5/memfs" import ( "errors" "fmt" "io" "os" "path/filepath" "sort" "strings" "time" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/helper/chroot" "github.com/go-git/go-billy/v5/util" ) const separator = filepath.Separator // Memory a very convenient filesystem based on memory files type Memory struct { s *storage tempCount int } //New returns a new Memory filesystem. func New() billy.Filesystem { fs := &Memory{s: newStorage()} return chroot.New(fs, string(separator)) } func (fs *Memory) Create(filename string) (billy.File, error) { return fs.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) } func (fs *Memory) Open(filename string) (billy.File, error) { return fs.OpenFile(filename, os.O_RDONLY, 0) } func (fs *Memory) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) { f, has := fs.s.Get(filename) if !has { if !isCreate(flag) { return nil, os.ErrNotExist } var err error f, err = fs.s.New(filename, perm, flag) if err != nil { return nil, err } } else { if isExclusive(flag) { return nil, os.ErrExist } if target, isLink := fs.resolveLink(filename, f); isLink { return fs.OpenFile(target, flag, perm) } } if f.mode.IsDir() { return nil, fmt.Errorf("cannot open directory: %s", filename) } return f.Duplicate(filename, perm, flag), nil } var errNotLink = errors.New("not a link") func (fs *Memory) resolveLink(fullpath string, f *file) (target string, isLink bool) { if !isSymlink(f.mode) { return fullpath, false } target = string(f.content.bytes) if !isAbs(target) { target = fs.Join(filepath.Dir(fullpath), target) } return target, true } // On Windows OS, IsAbs validates if a path is valid based on if stars with a // unit (eg.: `C:\`) to assert that is absolute, but in this mem implementation // any path starting by `separator` is also considered absolute. func isAbs(path string) bool { return filepath.IsAbs(path) || strings.HasPrefix(path, string(separator)) } func (fs *Memory) Stat(filename string) (os.FileInfo, error) { f, has := fs.s.Get(filename) if !has { return nil, os.ErrNotExist } fi, _ := f.Stat() var err error if target, isLink := fs.resolveLink(filename, f); isLink { fi, err = fs.Stat(target) if err != nil { return nil, err } } // the name of the file should always the name of the stated file, so we // overwrite the Stat returned from the storage with it, since the // filename may belong to a link. fi.(*fileInfo).name = filepath.Base(filename) return fi, nil } func (fs *Memory) Lstat(filename string) (os.FileInfo, error) { f, has := fs.s.Get(filename) if !has { return nil, os.ErrNotExist } return f.Stat() } type ByName []os.FileInfo func (a ByName) Len() int { return len(a) } func (a ByName) Less(i, j int) bool { return a[i].Name() < a[j].Name() } func (a ByName) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (fs *Memory) ReadDir(path string) ([]os.FileInfo, error) { if f, has := fs.s.Get(path); has { if target, isLink := fs.resolveLink(path, f); isLink { return fs.ReadDir(target) } } var entries []os.FileInfo for _, f := range fs.s.Children(path) { fi, _ := f.Stat() entries = append(entries, fi) } sort.Sort(ByName(entries)) return entries, nil } func (fs *Memory) MkdirAll(path string, perm os.FileMode) error { _, err := fs.s.New(path, perm|os.ModeDir, 0) return err } func (fs *Memory) TempFile(dir, prefix string) (billy.File, error) { return util.TempFile(fs, dir, prefix) } func (fs *Memory) getTempFilename(dir, prefix string) string { fs.tempCount++ filename := fmt.Sprintf("%s_%d_%d", prefix, fs.tempCount, time.Now().UnixNano()) return fs.Join(dir, filename) } func (fs *Memory) Rename(from, to string) error { return fs.s.Rename(from, to) } func (fs *Memory) Remove(filename string) error { return fs.s.Remove(filename) } func (fs *Memory) Join(elem ...string) string { return filepath.Join(elem...) } func (fs *Memory) Symlink(target, link string) error { _, err := fs.Stat(link) if err == nil { return os.ErrExist } if !os.IsNotExist(err) { return err } return util.WriteFile(fs, link, []byte(target), 0777|os.ModeSymlink) } func (fs *Memory) Readlink(link string) (string, error) { f, has := fs.s.Get(link) if !has { return "", os.ErrNotExist } if !isSymlink(f.mode) { return "", &os.PathError{ Op: "readlink", Path: link, Err: fmt.Errorf("not a symlink"), } } return string(f.content.bytes), nil } // Capabilities implements the Capable interface. func (fs *Memory) Capabilities() billy.Capability { return billy.WriteCapability | billy.ReadCapability | billy.ReadAndWriteCapability | billy.SeekCapability | billy.TruncateCapability } type file struct { name string content *content position int64 flag int mode os.FileMode isClosed bool } func (f *file) Name() string { return f.name } func (f *file) Read(b []byte) (int, error) { n, err := f.ReadAt(b, f.position) f.position += int64(n) if err == io.EOF && n != 0 { err = nil } return n, err } func (f *file) ReadAt(b []byte, off int64) (int, error) { if f.isClosed { return 0, os.ErrClosed } if !isReadAndWrite(f.flag) && !isReadOnly(f.flag) { return 0, errors.New("read not supported") } n, err := f.content.ReadAt(b, off) return n, err } func (f *file) Seek(offset int64, whence int) (int64, error) { if f.isClosed { return 0, os.ErrClosed } switch whence { case io.SeekCurrent: f.position += offset case io.SeekStart: f.position = offset case io.SeekEnd: f.position = int64(f.content.Len()) + offset } return f.position, nil } func (f *file) Write(p []byte) (int, error) { if f.isClosed { return 0, os.ErrClosed } if !isReadAndWrite(f.flag) && !isWriteOnly(f.flag) { return 0, errors.New("write not supported") } n, err := f.content.WriteAt(p, f.position) f.position += int64(n) return n, err } func (f *file) Close() error { if f.isClosed { return os.ErrClosed } f.isClosed = true return nil } func (f *file) Truncate(size int64) error { if size < int64(len(f.content.bytes)) { f.content.bytes = f.content.bytes[:size] } else if more := int(size) - len(f.content.bytes); more > 0 { f.content.bytes = append(f.content.bytes, make([]byte, more)...) } return nil } func (f *file) Duplicate(filename string, mode os.FileMode, flag int) billy.File { new := &file{ name: filename, content: f.content, mode: mode, flag: flag, } if isTruncate(flag) { new.content.Truncate() } if isAppend(flag) { new.position = int64(new.content.Len()) } return new } func (f *file) Stat() (os.FileInfo, error) { return &fileInfo{ name: f.Name(), mode: f.mode, size: f.content.Len(), }, nil } // Lock is a no-op in memfs. func (f *file) Lock() error { return nil } // Unlock is a no-op in memfs. func (f *file) Unlock() error { return nil } type fileInfo struct { name string size int mode os.FileMode } func (fi *fileInfo) Name() string { return fi.name } func (fi *fileInfo) Size() int64 { return int64(fi.size) } func (fi *fileInfo) Mode() os.FileMode { return fi.mode } func (*fileInfo) ModTime() time.Time { return time.Now() } func (fi *fileInfo) IsDir() bool { return fi.mode.IsDir() } func (*fileInfo) Sys() interface{} { return nil } func (c *content) Truncate() { c.bytes = make([]byte, 0) } func (c *content) Len() int { return len(c.bytes) } func isCreate(flag int) bool { return flag&os.O_CREATE != 0 } func isExclusive(flag int) bool { return flag&os.O_EXCL != 0 } func isAppend(flag int) bool { return flag&os.O_APPEND != 0 } func isTruncate(flag int) bool { return flag&os.O_TRUNC != 0 } func isReadAndWrite(flag int) bool { return flag&os.O_RDWR != 0 } func isReadOnly(flag int) bool { return flag == os.O_RDONLY } func isWriteOnly(flag int) bool { return flag&os.O_WRONLY != 0 } func isSymlink(m os.FileMode) bool { return m&os.ModeSymlink != 0 } golang-github-go-git-go-billy-5.5.0/memfs/memory_test.go000066400000000000000000000043101460000046100231420ustar00rootroot00000000000000package memfs import ( "fmt" "io" "os" "testing" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/test" "github.com/go-git/go-billy/v5/util" . "gopkg.in/check.v1" ) func Test(t *testing.T) { TestingT(t) } type MemorySuite struct { test.FilesystemSuite path string } var _ = Suite(&MemorySuite{}) func (s *MemorySuite) SetUpTest(c *C) { s.FilesystemSuite = test.NewFilesystemSuite(New()) } func (s *MemorySuite) TestCapabilities(c *C) { _, ok := s.FS.(billy.Capable) c.Assert(ok, Equals, true) caps := billy.Capabilities(s.FS) c.Assert(caps, Equals, billy.DefaultCapabilities&^billy.LockCapability) } func (s *MemorySuite) TestNegativeOffsets(c *C) { f, err := s.FS.Create("negative") c.Assert(err, IsNil) buf := make([]byte, 100) _, err = f.ReadAt(buf, -100) c.Assert(err, ErrorMatches, "readat negative: negative offset") _, err = f.Seek(-100, io.SeekCurrent) c.Assert(err, IsNil) _, err = f.Write(buf) c.Assert(err, ErrorMatches, "writeat negative: negative offset") } func (s *MemorySuite) TestExclusive(c *C) { f, err := s.FS.OpenFile("exclusive", os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666) c.Assert(err, IsNil) fmt.Fprint(f, "mememememe") err = f.Close() c.Assert(err, IsNil) _, err = s.FS.OpenFile("exclusive", os.O_CREATE|os.O_EXCL|os.O_RDWR, 0666) c.Assert(err, ErrorMatches, os.ErrExist.Error()) } func (s *MemorySuite) TestOrder(c *C) { var err error files := []string{ "a", "b", "c", } for _, f := range files { _, err = s.FS.Create(f) c.Assert(err, IsNil) } attempts := 30 for n := 0; n < attempts; n++ { actual, err := s.FS.ReadDir("") c.Assert(err, IsNil) for i, f := range files { c.Assert(actual[i].Name(), Equals, f) } } } func (s *MemorySuite) TestTruncateAppend(c *C) { err := util.WriteFile(s.FS, "truncate_append", []byte("file-content"), 0666) c.Assert(err, IsNil) f, err := s.FS.OpenFile("truncate_append", os.O_WRONLY|os.O_TRUNC|os.O_APPEND, 0666) c.Assert(err, IsNil) n, err := f.Write([]byte("replace")) c.Assert(err, IsNil) c.Assert(n, Equals, len("replace")) err = f.Close() c.Assert(err, IsNil) data, err := util.ReadFile(s.FS, "truncate_append") c.Assert(err, IsNil) c.Assert(string(data), Equals, "replace") } golang-github-go-git-go-billy-5.5.0/memfs/storage.go000066400000000000000000000077311460000046100222510ustar00rootroot00000000000000package memfs import ( "errors" "fmt" "io" "os" "path/filepath" "sync" ) type storage struct { files map[string]*file children map[string]map[string]*file } func newStorage() *storage { return &storage{ files: make(map[string]*file, 0), children: make(map[string]map[string]*file, 0), } } func (s *storage) Has(path string) bool { path = clean(path) _, ok := s.files[path] return ok } func (s *storage) New(path string, mode os.FileMode, flag int) (*file, error) { path = clean(path) if s.Has(path) { if !s.MustGet(path).mode.IsDir() { return nil, fmt.Errorf("file already exists %q", path) } return nil, nil } name := filepath.Base(path) f := &file{ name: name, content: &content{name: name}, mode: mode, flag: flag, } s.files[path] = f s.createParent(path, mode, f) return f, nil } func (s *storage) createParent(path string, mode os.FileMode, f *file) error { base := filepath.Dir(path) base = clean(base) if f.Name() == string(separator) { return nil } if _, err := s.New(base, mode.Perm()|os.ModeDir, 0); err != nil { return err } if _, ok := s.children[base]; !ok { s.children[base] = make(map[string]*file, 0) } s.children[base][f.Name()] = f return nil } func (s *storage) Children(path string) []*file { path = clean(path) l := make([]*file, 0) for _, f := range s.children[path] { l = append(l, f) } return l } func (s *storage) MustGet(path string) *file { f, ok := s.Get(path) if !ok { panic(fmt.Errorf("couldn't find %q", path)) } return f } func (s *storage) Get(path string) (*file, bool) { path = clean(path) if !s.Has(path) { return nil, false } file, ok := s.files[path] return file, ok } func (s *storage) Rename(from, to string) error { from = clean(from) to = clean(to) if !s.Has(from) { return os.ErrNotExist } move := [][2]string{{from, to}} for pathFrom := range s.files { if pathFrom == from || !filepath.HasPrefix(pathFrom, from) { continue } rel, _ := filepath.Rel(from, pathFrom) pathTo := filepath.Join(to, rel) move = append(move, [2]string{pathFrom, pathTo}) } for _, ops := range move { from := ops[0] to := ops[1] if err := s.move(from, to); err != nil { return err } } return nil } func (s *storage) move(from, to string) error { s.files[to] = s.files[from] s.files[to].name = filepath.Base(to) s.children[to] = s.children[from] defer func() { delete(s.children, from) delete(s.files, from) delete(s.children[filepath.Dir(from)], filepath.Base(from)) }() return s.createParent(to, 0644, s.files[to]) } func (s *storage) Remove(path string) error { path = clean(path) f, has := s.Get(path) if !has { return os.ErrNotExist } if f.mode.IsDir() && len(s.children[path]) != 0 { return fmt.Errorf("dir: %s contains files", path) } base, file := filepath.Split(path) base = filepath.Clean(base) delete(s.children[base], file) delete(s.files, path) return nil } func clean(path string) string { return filepath.Clean(filepath.FromSlash(path)) } type content struct { name string bytes []byte m sync.RWMutex } func (c *content) WriteAt(p []byte, off int64) (int, error) { if off < 0 { return 0, &os.PathError{ Op: "writeat", Path: c.name, Err: errors.New("negative offset"), } } c.m.Lock() prev := len(c.bytes) diff := int(off) - prev if diff > 0 { c.bytes = append(c.bytes, make([]byte, diff)...) } c.bytes = append(c.bytes[:off], p...) if len(c.bytes) < prev { c.bytes = c.bytes[:prev] } c.m.Unlock() return len(p), nil } func (c *content) ReadAt(b []byte, off int64) (n int, err error) { if off < 0 { return 0, &os.PathError{ Op: "readat", Path: c.name, Err: errors.New("negative offset"), } } c.m.RLock() size := int64(len(c.bytes)) if off >= size { c.m.RUnlock() return 0, io.EOF } l := int64(len(b)) if off+l > size { l = size - off } btr := c.bytes[off : off+l] n = copy(b, btr) if len(btr) < len(b) { err = io.EOF } c.m.RUnlock() return } golang-github-go-git-go-billy-5.5.0/osfs/000077500000000000000000000000001460000046100201115ustar00rootroot00000000000000golang-github-go-git-go-billy-5.5.0/osfs/os.go000066400000000000000000000051671460000046100210720ustar00rootroot00000000000000//go:build !js // +build !js // Package osfs provides a billy filesystem for the OS. package osfs import ( "fmt" "io/fs" "os" "sync" "github.com/go-git/go-billy/v5" ) const ( defaultDirectoryMode = 0o755 defaultCreateMode = 0o666 ) // Default Filesystem representing the root of the os filesystem. var Default = &ChrootOS{} // New returns a new OS filesystem. // By default paths are deduplicated, but still enforced // under baseDir. For more info refer to WithDeduplicatePath. func New(baseDir string, opts ...Option) billy.Filesystem { o := &options{ deduplicatePath: true, } for _, opt := range opts { opt(o) } if o.Type == BoundOSFS { return newBoundOS(baseDir, o.deduplicatePath) } return newChrootOS(baseDir) } // WithBoundOS returns the option of using a Bound filesystem OS. func WithBoundOS() Option { return func(o *options) { o.Type = BoundOSFS } } // WithChrootOS returns the option of using a Chroot filesystem OS. func WithChrootOS() Option { return func(o *options) { o.Type = ChrootOSFS } } // WithDeduplicatePath toggles the deduplication of the base dir in the path. // This occurs when absolute links are being used. // Assuming base dir /base/dir and an absolute symlink /base/dir/target: // // With DeduplicatePath (default): /base/dir/target // Without DeduplicatePath: /base/dir/base/dir/target // // This option is only used by the BoundOS OS type. func WithDeduplicatePath(enabled bool) Option { return func(o *options) { o.deduplicatePath = enabled } } type options struct { Type deduplicatePath bool } type Type int const ( ChrootOSFS Type = iota BoundOSFS ) func readDir(dir string) ([]os.FileInfo, error) { entries, err := os.ReadDir(dir) if err != nil { return nil, err } infos := make([]fs.FileInfo, 0, len(entries)) for _, entry := range entries { fi, err := entry.Info() if err != nil { return nil, err } infos = append(infos, fi) } return infos, nil } func tempFile(dir, prefix string) (billy.File, error) { f, err := os.CreateTemp(dir, prefix) if err != nil { return nil, err } return &file{File: f}, nil } func openFile(fn string, flag int, perm os.FileMode, createDir func(string) error) (billy.File, error) { if flag&os.O_CREATE != 0 { if createDir == nil { return nil, fmt.Errorf("createDir func cannot be nil if file needs to be opened in create mode") } if err := createDir(fn); err != nil { return nil, err } } f, err := os.OpenFile(fn, flag, perm) if err != nil { return nil, err } return &file{File: f}, err } // file is a wrapper for an os.File which adds support for file locking. type file struct { *os.File m sync.Mutex } golang-github-go-git-go-billy-5.5.0/osfs/os_bound.go000066400000000000000000000153151460000046100222550ustar00rootroot00000000000000//go:build !js // +build !js /* Copyright 2022 The Flux authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package osfs import ( "fmt" "os" "path/filepath" "strings" securejoin "github.com/cyphar/filepath-securejoin" "github.com/go-git/go-billy/v5" ) // BoundOS is a fs implementation based on the OS filesystem which is bound to // a base dir. // Prefer this fs implementation over ChrootOS. // // Behaviours of note: // 1. Read and write operations can only be directed to files which descends // from the base dir. // 2. Symlinks don't have their targets modified, and therefore can point // to locations outside the base dir or to non-existent paths. // 3. Readlink and Lstat ensures that the link file is located within the base // dir, evaluating any symlinks that file or base dir may contain. type BoundOS struct { baseDir string deduplicatePath bool } func newBoundOS(d string, deduplicatePath bool) billy.Filesystem { return &BoundOS{baseDir: d, deduplicatePath: deduplicatePath} } func (fs *BoundOS) Create(filename string) (billy.File, error) { return fs.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, defaultCreateMode) } func (fs *BoundOS) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) { fn, err := fs.abs(filename) if err != nil { return nil, err } return openFile(fn, flag, perm, fs.createDir) } func (fs *BoundOS) ReadDir(path string) ([]os.FileInfo, error) { dir, err := fs.abs(path) if err != nil { return nil, err } return readDir(dir) } func (fs *BoundOS) Rename(from, to string) error { f, err := fs.abs(from) if err != nil { return err } t, err := fs.abs(to) if err != nil { return err } // MkdirAll for target name. if err := fs.createDir(t); err != nil { return err } return os.Rename(f, t) } func (fs *BoundOS) MkdirAll(path string, perm os.FileMode) error { dir, err := fs.abs(path) if err != nil { return err } return os.MkdirAll(dir, perm) } func (fs *BoundOS) Open(filename string) (billy.File, error) { return fs.OpenFile(filename, os.O_RDONLY, 0) } func (fs *BoundOS) Stat(filename string) (os.FileInfo, error) { filename, err := fs.abs(filename) if err != nil { return nil, err } return os.Stat(filename) } func (fs *BoundOS) Remove(filename string) error { fn, err := fs.abs(filename) if err != nil { return err } return os.Remove(fn) } // TempFile creates a temporary file. If dir is empty, the file // will be created within the OS Temporary dir. If dir is provided // it must descend from the current base dir. func (fs *BoundOS) TempFile(dir, prefix string) (billy.File, error) { if dir != "" { var err error dir, err = fs.abs(dir) if err != nil { return nil, err } } return tempFile(dir, prefix) } func (fs *BoundOS) Join(elem ...string) string { return filepath.Join(elem...) } func (fs *BoundOS) RemoveAll(path string) error { dir, err := fs.abs(path) if err != nil { return err } return os.RemoveAll(dir) } func (fs *BoundOS) Symlink(target, link string) error { ln, err := fs.abs(link) if err != nil { return err } // MkdirAll for containing dir. if err := fs.createDir(ln); err != nil { return err } return os.Symlink(target, ln) } func (fs *BoundOS) Lstat(filename string) (os.FileInfo, error) { filename = filepath.Clean(filename) if !filepath.IsAbs(filename) { filename = filepath.Join(fs.baseDir, filename) } if ok, err := fs.insideBaseDirEval(filename); !ok { return nil, err } return os.Lstat(filename) } func (fs *BoundOS) Readlink(link string) (string, error) { if !filepath.IsAbs(link) { link = filepath.Clean(filepath.Join(fs.baseDir, link)) } if ok, err := fs.insideBaseDirEval(link); !ok { return "", err } return os.Readlink(link) } // Chroot returns a new OS filesystem, with the base dir set to the // result of joining the provided path with the underlying base dir. func (fs *BoundOS) Chroot(path string) (billy.Filesystem, error) { joined, err := securejoin.SecureJoin(fs.baseDir, path) if err != nil { return nil, err } return New(joined), nil } // Root returns the current base dir of the billy.Filesystem. // This is required in order for this implementation to be a drop-in // replacement for other upstream implementations (e.g. memory and osfs). func (fs *BoundOS) Root() string { return fs.baseDir } func (fs *BoundOS) createDir(fullpath string) error { dir := filepath.Dir(fullpath) if dir != "." { if err := os.MkdirAll(dir, defaultDirectoryMode); err != nil { return err } } return nil } // abs transforms filename to an absolute path, taking into account the base dir. // Relative paths won't be allowed to ascend the base dir, so `../file` will become // `/working-dir/file`. // // Note that if filename is a symlink, the returned address will be the target of the // symlink. func (fs *BoundOS) abs(filename string) (string, error) { if filename == fs.baseDir { filename = string(filepath.Separator) } path, err := securejoin.SecureJoin(fs.baseDir, filename) if err != nil { return "", nil } if fs.deduplicatePath { vol := filepath.VolumeName(fs.baseDir) dup := filepath.Join(fs.baseDir, fs.baseDir[len(vol):]) if strings.HasPrefix(path, dup+string(filepath.Separator)) { return fs.abs(path[len(dup):]) } } return path, nil } // insideBaseDir checks whether filename is located within // the fs.baseDir. func (fs *BoundOS) insideBaseDir(filename string) (bool, error) { if filename == fs.baseDir { return true, nil } if !strings.HasPrefix(filename, fs.baseDir+string(filepath.Separator)) { return false, fmt.Errorf("path outside base dir") } return true, nil } // insideBaseDirEval checks whether filename is contained within // a dir that is within the fs.baseDir, by first evaluating any symlinks // that either filename or fs.baseDir may contain. func (fs *BoundOS) insideBaseDirEval(filename string) (bool, error) { dir, err := filepath.EvalSymlinks(filepath.Dir(filename)) if dir == "" || os.IsNotExist(err) { dir = filepath.Dir(filename) } wd, err := filepath.EvalSymlinks(fs.baseDir) if wd == "" || os.IsNotExist(err) { wd = fs.baseDir } if filename != wd && dir != wd && !strings.HasPrefix(dir, wd+string(filepath.Separator)) { return false, fmt.Errorf("path outside base dir") } return true, nil } golang-github-go-git-go-billy-5.5.0/osfs/os_bound_test.go000066400000000000000000000773611460000046100233250ustar00rootroot00000000000000//go:build !js // +build !js /* Copyright 2022 The Flux authors. Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ package osfs import ( "fmt" "os" "path/filepath" "runtime" "strings" "testing" "github.com/go-git/go-billy/v5" "github.com/onsi/gomega" ) func TestOpen(t *testing.T) { tests := []struct { name string filename string makeAbs bool before func(dir string) billy.Filesystem wantErr string }{ { name: "file: rel same dir", before: func(dir string) billy.Filesystem { os.WriteFile(filepath.Join(dir, "test-file"), []byte("anything"), 0o600) return newBoundOS(dir, true) }, filename: "test-file", }, { name: "file: rel path to above cwd", before: func(dir string) billy.Filesystem { os.WriteFile(filepath.Join(dir, "rel-above-cwd"), []byte("anything"), 0o600) return newBoundOS(dir, true) }, filename: "../../rel-above-cwd", }, { name: "file: rel path to below cwd", before: func(dir string) billy.Filesystem { os.Mkdir(filepath.Join(dir, "sub"), 0o700) os.WriteFile(filepath.Join(dir, "sub/rel-below-cwd"), []byte("anything"), 0o600) return newBoundOS(dir, true) }, filename: "sub/rel-below-cwd", }, { name: "file: abs inside cwd", before: func(dir string) billy.Filesystem { os.WriteFile(filepath.Join(dir, "abs-test-file"), []byte("anything"), 0o600) return newBoundOS(dir, true) }, filename: "abs-test-file", makeAbs: true, }, { name: "file: abs outside cwd", before: func(dir string) billy.Filesystem { return newBoundOS(dir, true) }, filename: "/some/path/outside/cwd", wantErr: notFoundError(), }, { name: "symlink: same dir", before: func(dir string) billy.Filesystem { target := filepath.Join(dir, "target-file") os.WriteFile(target, []byte("anything"), 0o600) os.Symlink(target, filepath.Join(dir, "symlink")) return newBoundOS(dir, true) }, filename: "symlink", }, { name: "symlink: rel outside cwd", before: func(dir string) billy.Filesystem { os.Symlink("../../../../../../outside/cwd", filepath.Join(dir, "symlink")) return newBoundOS(dir, true) }, filename: "symlink", makeAbs: true, wantErr: notFoundError(), }, { name: "symlink: abs outside cwd", before: func(dir string) billy.Filesystem { os.Symlink("/some/path/outside/cwd", filepath.Join(dir, "symlink")) return newBoundOS(dir, true) }, filename: "symlink", makeAbs: true, wantErr: notFoundError(), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := gomega.NewWithT(t) dir := t.TempDir() fs := newBoundOS(dir, true) if tt.before != nil { fs = tt.before(dir) } filename := tt.filename if tt.makeAbs { filename = filepath.Join(dir, filename) } fi, err := fs.Open(filename) if tt.wantErr != "" { g.Expect(err).To(gomega.HaveOccurred()) g.Expect(err.Error()).To(gomega.ContainSubstring(tt.wantErr)) g.Expect(fi).To(gomega.BeNil()) } else { g.Expect(err).To(gomega.BeNil()) g.Expect(fi).ToNot(gomega.BeNil()) g.Expect(fi.Close()).To(gomega.Succeed()) } }) } } func Test_Symlink(t *testing.T) { // The umask value set at OS level can impact this test, so // it is set to 0 during the duration of this test and then // reverted back to the original value. // Outside of linux this is a no-op. defer umask(0)() tests := []struct { name string link string target string before func(dir string) billy.Filesystem wantStatErr string }{ { name: "link to abs valid target", link: "symlink", target: filepath.FromSlash("/etc/passwd"), }, { name: "link to abs inexistent target", link: "symlink", target: filepath.FromSlash("/some/random/path"), }, { name: "link to rel valid target", link: "symlink", target: filepath.FromSlash("../../../../../../../../../etc/passwd"), }, { name: "link to rel inexistent target", link: "symlink", target: filepath.FromSlash("../../../some/random/path"), }, { name: "auto create dir", link: "new-dir/symlink", target: filepath.FromSlash("../../../some/random/path"), }, { name: "keep dir filemode if exists", link: "new-dir/symlink", before: func(dir string) billy.Filesystem { os.Mkdir(filepath.Join(dir, "new-dir"), 0o701) return newBoundOS(dir, true) }, target: filepath.FromSlash("../../../some/random/path"), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := gomega.NewWithT(t) dir := t.TempDir() fs := newBoundOS(dir, true) if tt.before != nil { fs = tt.before(dir) } // Even if CWD is changed outside of the fs instance, // the base dir must still be observed. err := os.Chdir(os.TempDir()) g.Expect(err).ToNot(gomega.HaveOccurred()) link := filepath.Join(dir, tt.link) diBefore, _ := os.Lstat(filepath.Dir(link)) err = fs.Symlink(tt.target, tt.link) g.Expect(err).ToNot(gomega.HaveOccurred()) fi, err := os.Lstat(link) if tt.wantStatErr != "" { g.Expect(err).To(gomega.HaveOccurred()) g.Expect(err.Error()).To(gomega.ContainSubstring(tt.wantStatErr)) } else { g.Expect(err).ToNot(gomega.HaveOccurred()) g.Expect(fi).ToNot(gomega.BeNil()) } got, err := os.Readlink(link) g.Expect(err).ToNot(gomega.HaveOccurred()) g.Expect(got).To(gomega.Equal(tt.target)) diAfter, err := os.Lstat(filepath.Dir(link)) g.Expect(err).ToNot(gomega.HaveOccurred()) if diBefore != nil { g.Expect(diAfter.Mode()).To(gomega.Equal(diBefore.Mode())) } }) } } func TestTempFile(t *testing.T) { g := gomega.NewWithT(t) dir := t.TempDir() fs := newBoundOS(dir, true) f, err := fs.TempFile("", "prefix") g.Expect(err).ToNot(gomega.HaveOccurred()) g.Expect(f).ToNot(gomega.BeNil()) g.Expect(f.Name()).To(gomega.ContainSubstring(os.TempDir())) g.Expect(f.Close()).ToNot(gomega.HaveOccurred()) f, err = fs.TempFile("/above/cwd", "prefix") g.Expect(err).To(gomega.HaveOccurred()) g.Expect(err.Error()).To(gomega.ContainSubstring(fmt.Sprint(dir, filepath.FromSlash("/above/cwd/prefix")))) g.Expect(f).To(gomega.BeNil()) tempDir := os.TempDir() // For windows, volume name must be removed. if v := filepath.VolumeName(tempDir); v != "" { tempDir = strings.TrimPrefix(tempDir, v) } f, err = fs.TempFile(tempDir, "prefix") g.Expect(err).To(gomega.HaveOccurred()) g.Expect(err.Error()).To(gomega.ContainSubstring(filepath.Join(dir, tempDir, "prefix"))) g.Expect(f).To(gomega.BeNil()) } func TestChroot(t *testing.T) { g := gomega.NewWithT(t) tmp := t.TempDir() fs := newBoundOS(tmp, true) f, err := fs.Chroot("test") g.Expect(err).ToNot(gomega.HaveOccurred()) g.Expect(f).ToNot(gomega.BeNil()) g.Expect(f.Root()).To(gomega.Equal(filepath.Join(tmp, "test"))) } func TestRoot(t *testing.T) { g := gomega.NewWithT(t) dir := t.TempDir() fs := newBoundOS(dir, true) root := fs.Root() g.Expect(root).To(gomega.Equal(dir)) } func TestReadLink(t *testing.T) { tests := []struct { name string filename string makeAbs bool expected string makeExpectedAbs bool before func(dir string) billy.Filesystem wantErr string }{ { name: "symlink: pointing to abs outside cwd", before: func(dir string) billy.Filesystem { os.Symlink("/etc/passwd", filepath.Join(dir, "symlink")) return newBoundOS(dir, true) }, filename: "symlink", expected: filepath.FromSlash("/etc/passwd"), }, { name: "file: rel pointing to abs above cwd", filename: "../../file", wantErr: "path outside base dir", }, { name: "symlink: abs symlink pointing outside cwd", before: func(dir string) billy.Filesystem { os.Symlink("/etc/passwd", filepath.Join(dir, "symlink")) return newBoundOS(dir, true) }, filename: "symlink", makeAbs: true, expected: filepath.FromSlash("/etc/passwd"), }, { name: "symlink: dir pointing outside cwd", before: func(dir string) billy.Filesystem { cwd := filepath.Join(dir, "current-dir") outside := filepath.Join(dir, "outside-cwd") os.Mkdir(cwd, 0o700) os.Mkdir(outside, 0o700) os.Symlink(outside, filepath.Join(cwd, "symlink")) os.WriteFile(filepath.Join(outside, "file"), []byte("anything"), 0o600) return newBoundOS(cwd, true) }, filename: "current-dir/symlink/file", makeAbs: true, wantErr: "path outside base dir", }, { name: "symlink: within cwd + baseDir symlink", before: func(dir string) billy.Filesystem { cwd := filepath.Join(dir, "symlink-dir") cwdAlt := filepath.Join(dir, "symlink-altdir") cwdTarget := filepath.Join(dir, "cwd-target") os.MkdirAll(cwdTarget, 0o700) os.WriteFile(filepath.Join(cwdTarget, "file"), []byte{}, 0o600) os.Symlink(cwdTarget, cwd) os.Symlink(cwdTarget, cwdAlt) os.Symlink(filepath.Join(cwdTarget, "file"), filepath.Join(cwdAlt, "symlink-file")) return newBoundOS(cwd, true) }, filename: "symlink-file", expected: filepath.Join("cwd-target/file"), makeExpectedAbs: true, }, { name: "symlink: outside cwd + baseDir symlink", before: func(dir string) billy.Filesystem { cwd := filepath.Join(dir, "symlink-dir") outside := filepath.Join(cwd, "symlink-outside") cwdTarget := filepath.Join(dir, "cwd-target") outsideDir := filepath.Join(dir, "outside") os.Mkdir(cwdTarget, 0o700) os.Mkdir(outsideDir, 0o700) os.WriteFile(filepath.Join(cwdTarget, "file"), []byte{}, 0o600) os.Symlink(cwdTarget, cwd) os.Symlink(outsideDir, outside) os.Symlink(filepath.Join(cwdTarget, "file"), filepath.Join(outside, "symlink-file")) return newBoundOS(cwd, true) }, filename: "symlink-outside/symlink-file", wantErr: "path outside base dir", }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := gomega.NewWithT(t) dir := t.TempDir() fs := newBoundOS(dir, true) if tt.before != nil { fs = tt.before(dir) } filename := tt.filename if tt.makeAbs { filename = filepath.Join(dir, filename) } expected := tt.expected if tt.makeExpectedAbs { expected = filepath.Join(dir, expected) } got, err := fs.Readlink(filename) if tt.wantErr != "" { g.Expect(err).To(gomega.HaveOccurred()) g.Expect(err.Error()).To(gomega.ContainSubstring(tt.wantErr)) g.Expect(got).To(gomega.BeEmpty()) } else { g.Expect(err).To(gomega.BeNil()) g.Expect(got).To(gomega.Equal(expected)) } }) } } func TestLstat(t *testing.T) { tests := []struct { name string filename string makeAbs bool before func(dir string) billy.Filesystem wantErr string }{ { name: "rel symlink: pointing to abs outside cwd", before: func(dir string) billy.Filesystem { os.Symlink("/etc/passwd", filepath.Join(dir, "symlink")) return newBoundOS(dir, true) }, filename: "symlink", }, { name: "rel symlink: pointing to rel path above cwd", before: func(dir string) billy.Filesystem { os.Symlink("../../../../../../../../etc/passwd", filepath.Join(dir, "symlink")) return newBoundOS(dir, true) }, filename: "symlink", }, { name: "abs symlink: pointing to abs outside cwd", before: func(dir string) billy.Filesystem { os.Symlink("/etc/passwd", filepath.Join(dir, "symlink")) return newBoundOS(dir, true) }, filename: "symlink", makeAbs: true, }, { name: "abs symlink: pointing to rel outside cwd", before: func(dir string) billy.Filesystem { os.Symlink("../../../../../../../../etc/passwd", filepath.Join(dir, "symlink")) return newBoundOS(dir, true) }, filename: "symlink", makeAbs: false, }, { name: "symlink: within cwd + baseDir symlink", before: func(dir string) billy.Filesystem { cwd := filepath.Join(dir, "symlink-dir") cwdAlt := filepath.Join(dir, "symlink-altdir") cwdTarget := filepath.Join(dir, "cwd-target") os.MkdirAll(cwdTarget, 0o700) os.WriteFile(filepath.Join(cwdTarget, "file"), []byte{}, 0o600) os.Symlink(cwdTarget, cwd) os.Symlink(cwdTarget, cwdAlt) os.Symlink(filepath.Join(cwdTarget, "file"), filepath.Join(cwdAlt, "symlink-file")) return newBoundOS(cwd, true) }, filename: "symlink-file", makeAbs: false, }, { name: "symlink: outside cwd + baseDir symlink", before: func(dir string) billy.Filesystem { cwd := filepath.Join(dir, "symlink-dir") outside := filepath.Join(cwd, "symlink-outside") cwdTarget := filepath.Join(dir, "cwd-target") outsideDir := filepath.Join(dir, "outside") os.Mkdir(cwdTarget, 0o700) os.Mkdir(outsideDir, 0o700) os.WriteFile(filepath.Join(cwdTarget, "file"), []byte{}, 0o600) os.Symlink(cwdTarget, cwd) os.Symlink(outsideDir, outside) os.Symlink(filepath.Join(cwdTarget, "file"), filepath.Join(outside, "symlink-file")) return newBoundOS(cwd, true) }, filename: "symlink-outside/symlink-file", makeAbs: false, wantErr: "path outside base dir", }, { name: "path: rel pointing to abs above cwd", filename: "../../file", wantErr: "path outside base dir", }, { name: "path: abs pointing outside cwd", filename: "/etc/passwd", wantErr: "path outside base dir", }, { name: "file: rel", before: func(dir string) billy.Filesystem { os.WriteFile(filepath.Join(dir, "test-file"), []byte("anything"), 0o600) return newBoundOS(dir, true) }, filename: "test-file", }, { name: "file: abs", before: func(dir string) billy.Filesystem { os.WriteFile(filepath.Join(dir, "test-file"), []byte("anything"), 0o600) return newBoundOS(dir, true) }, filename: "test-file", makeAbs: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := gomega.NewWithT(t) dir := t.TempDir() fs := newBoundOS(dir, true) if tt.before != nil { fs = tt.before(dir) } filename := tt.filename if tt.makeAbs { filename = filepath.Join(dir, filename) } fi, err := fs.Lstat(filename) if tt.wantErr != "" { g.Expect(err).To(gomega.HaveOccurred()) g.Expect(err.Error()).To(gomega.ContainSubstring(tt.wantErr)) g.Expect(fi).To(gomega.BeNil()) } else { g.Expect(err).To(gomega.BeNil()) g.Expect(fi).ToNot(gomega.BeNil()) g.Expect(fi.Name()).To(gomega.Equal(filepath.Base(tt.filename))) } }) } } func TestStat(t *testing.T) { tests := []struct { name string filename string makeAbs bool before func(dir string) billy.Filesystem wantErr string }{ { name: "rel symlink: pointing to abs outside cwd", before: func(dir string) billy.Filesystem { os.Symlink("/etc/passwd", filepath.Join(dir, "symlink")) return newBoundOS(dir, true) }, filename: "symlink", wantErr: notFoundError(), }, { name: "rel symlink: pointing to rel path above cwd", before: func(dir string) billy.Filesystem { os.Symlink("../../../../../../../../etc/passwd", filepath.Join(dir, "symlink")) return newBoundOS(dir, true) }, filename: "symlink", wantErr: notFoundError(), }, { name: "abs symlink: pointing to abs outside cwd", before: func(dir string) billy.Filesystem { os.Symlink("/etc/passwd", filepath.Join(dir, "symlink")) return newBoundOS(dir, true) }, filename: "symlink", makeAbs: true, wantErr: notFoundError(), }, { name: "abs symlink: pointing to rel outside cwd", before: func(dir string) billy.Filesystem { os.Symlink("../../../../../../../../etc/passwd", filepath.Join(dir, "symlink")) return newBoundOS(dir, true) }, filename: "symlink", makeAbs: false, wantErr: notFoundError(), }, { name: "path: rel pointing to abs above cwd", filename: "../../file", wantErr: notFoundError(), }, { name: "path: abs pointing outside cwd", filename: "/etc/passwd", wantErr: notFoundError(), }, { name: "rel file", before: func(dir string) billy.Filesystem { os.WriteFile(filepath.Join(dir, "test-file"), []byte("anything"), 0o600) return newBoundOS(dir, true) }, filename: "test-file", }, { name: "abs file", before: func(dir string) billy.Filesystem { os.WriteFile(filepath.Join(dir, "test-file"), []byte("anything"), 0o600) return newBoundOS(dir, true) }, filename: "test-file", makeAbs: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := gomega.NewWithT(t) dir := t.TempDir() fs := newBoundOS(dir, true) if tt.before != nil { fs = tt.before(dir) } filename := tt.filename if tt.makeAbs { filename = filepath.Join(dir, filename) } fi, err := fs.Stat(filename) if tt.wantErr != "" { g.Expect(err).To(gomega.HaveOccurred()) g.Expect(err.Error()).To(gomega.ContainSubstring(tt.wantErr)) g.Expect(fi).To(gomega.BeNil()) } else { g.Expect(err).To(gomega.BeNil()) g.Expect(fi).ToNot(gomega.BeNil()) } }) } } func TestRemove(t *testing.T) { tests := []struct { name string filename string makeAbs bool before func(dir string) billy.Filesystem wantErr string }{ { name: "path: rel pointing outside cwd w forward slash", filename: "/some/path/outside/cwd", wantErr: notFoundError(), }, { name: "path: rel pointing outside cwd", filename: "../../../../path/outside/cwd", wantErr: notFoundError(), }, { name: "inexistent dir", before: func(dir string) billy.Filesystem { return newBoundOS(dir, true) }, filename: "inexistent", wantErr: notFoundError(), }, { name: "same dir file", before: func(dir string) billy.Filesystem { os.WriteFile(filepath.Join(dir, "test-file"), []byte("anything"), 0o600) return newBoundOS(dir, true) }, filename: "test-file", }, { name: "symlink: same dir", before: func(dir string) billy.Filesystem { target := filepath.Join(dir, "target-file") os.WriteFile(target, []byte("anything"), 0o600) os.Symlink(target, filepath.Join(dir, "symlink")) return newBoundOS(dir, true) }, filename: "symlink", }, { name: "rel path to file above cwd", before: func(dir string) billy.Filesystem { os.WriteFile(filepath.Join(dir, "rel-above-cwd"), []byte("anything"), 0o600) return newBoundOS(dir, true) }, filename: "../../rel-above-cwd", }, { name: "abs file", before: func(dir string) billy.Filesystem { os.WriteFile(filepath.Join(dir, "abs-test-file"), []byte("anything"), 0o600) return newBoundOS(dir, true) }, filename: "abs-test-file", makeAbs: true, }, { name: "abs symlink: pointing outside is forced to descend", before: func(dir string) billy.Filesystem { cwd := filepath.Join(dir, "current-dir") outsideFile := filepath.Join(dir, "outside-cwd/file") os.Mkdir(cwd, 0o700) os.MkdirAll(filepath.Dir(outsideFile), 0o700) os.WriteFile(outsideFile, []byte("anything"), 0o600) os.Symlink(outsideFile, filepath.Join(cwd, "remove-abs-symlink")) return newBoundOS(cwd, true) }, filename: "remove-abs-symlink", wantErr: notFoundError(), }, { name: "rel symlink: pointing outside is forced to descend", before: func(dir string) billy.Filesystem { cwd := filepath.Join(dir, "current-dir") outsideFile := filepath.Join(dir, "outside-cwd", "file2") os.Mkdir(cwd, 0o700) os.MkdirAll(filepath.Dir(outsideFile), 0o700) os.WriteFile(outsideFile, []byte("anything"), 0o600) os.Symlink(filepath.Join("..", "outside-cwd", "file2"), filepath.Join(cwd, "remove-abs-symlink2")) return newBoundOS(cwd, true) }, filename: "remove-rel-symlink", wantErr: notFoundError(), }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := gomega.NewWithT(t) dir := t.TempDir() fs := newBoundOS(dir, true) if tt.before != nil { fs = tt.before(dir) } filename := tt.filename if tt.makeAbs { filename = filepath.Join(dir, filename) } err := fs.Remove(filename) if tt.wantErr != "" { g.Expect(err).To(gomega.HaveOccurred()) g.Expect(err.Error()).To(gomega.ContainSubstring(tt.wantErr)) } else { g.Expect(err).To(gomega.BeNil()) } }) } } func TestRemoveAll(t *testing.T) { tests := []struct { name string filename string makeAbs bool before func(dir string) billy.Filesystem wantErr string }{ { name: "parent with children", before: func(dir string) billy.Filesystem { os.MkdirAll(filepath.Join(dir, "parent/children"), 0o600) return newBoundOS(dir, true) }, filename: "parent", }, { name: "inexistent dir", filename: "inexistent", }, { name: "same dir file", before: func(dir string) billy.Filesystem { os.WriteFile(filepath.Join(dir, "test-file"), []byte("anything"), 0o600) return newBoundOS(dir, true) }, filename: "test-file", }, { name: "same dir symlink", before: func(dir string) billy.Filesystem { target := filepath.Join(dir, "target-file") os.WriteFile(target, []byte("anything"), 0o600) os.Symlink(target, filepath.Join(dir, "symlink")) return newBoundOS(dir, true) }, filename: "symlink", }, { name: "rel path to file above cwd", before: func(dir string) billy.Filesystem { os.WriteFile(filepath.Join(dir, "rel-above-cwd"), []byte("anything"), 0o600) return newBoundOS(dir, true) }, filename: "../../rel-above-cwd", }, { name: "abs file", before: func(dir string) billy.Filesystem { os.WriteFile(filepath.Join(dir, "abs-test-file"), []byte("anything"), 0o600) return newBoundOS(dir, true) }, filename: "abs-test-file", makeAbs: true, }, { name: "abs symlink", before: func(dir string) billy.Filesystem { os.Symlink("/etc/passwd", filepath.Join(dir, "symlink")) return newBoundOS(dir, true) }, filename: "symlink", makeAbs: true, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := gomega.NewWithT(t) dir := t.TempDir() fs := newBoundOS(dir, true).(*BoundOS) if tt.before != nil { fs = tt.before(dir).(*BoundOS) } filename := tt.filename if tt.makeAbs { filename = filepath.Join(dir, filename) } err := fs.RemoveAll(filename) if tt.wantErr != "" { g.Expect(err).To(gomega.HaveOccurred()) g.Expect(err.Error()).To(gomega.ContainSubstring(tt.wantErr)) } else { g.Expect(err).To(gomega.BeNil()) } }) } } func TestJoin(t *testing.T) { tests := []struct { elems []string wanted string }{ { elems: []string{}, wanted: "", }, { elems: []string{"/a", "b", "c"}, wanted: filepath.FromSlash("/a/b/c"), }, { elems: []string{"/a", "b/c"}, wanted: filepath.FromSlash("/a/b/c"), }, { elems: []string{"/a", ""}, wanted: filepath.FromSlash("/a"), }, { elems: []string{"/a", "/", "b"}, wanted: filepath.FromSlash("/a/b"), }, } for _, tt := range tests { t.Run(tt.wanted, func(t *testing.T) { g := gomega.NewWithT(t) fs := newBoundOS(t.TempDir(), true) got := fs.Join(tt.elems...) g.Expect(got).To(gomega.Equal(tt.wanted)) }) } } func TestAbs(t *testing.T) { tests := []struct { name string cwd string filename string makeAbs bool expected string makeExpectedAbs bool wantErr string deduplicatePath bool before func(dir string) }{ { name: "path: same dir rel file", cwd: "/working/dir", filename: "./file", expected: filepath.FromSlash("/working/dir/file"), }, { name: "path: descending rel file", cwd: "/working/dir", filename: "file", expected: filepath.FromSlash("/working/dir/file"), }, { name: "path: ascending rel file 1", cwd: "/working/dir", filename: "../file", expected: filepath.FromSlash("/working/dir/file"), }, { name: "path: ascending rel file 2", cwd: "/working/dir", filename: "../../file", expected: filepath.FromSlash("/working/dir/file"), }, { name: "path: ascending rel file 3", cwd: "/working/dir", filename: "/../../file", expected: filepath.FromSlash("/working/dir/file"), }, { name: "path: abs file within cwd", cwd: filepath.FromSlash("/working/dir"), filename: filepath.FromSlash("/working/dir/abs-file"), expected: filepath.FromSlash("/working/dir/abs-file"), deduplicatePath: true, }, { name: "path: abs file within cwd wo deduplication", cwd: filepath.FromSlash("/working/dir"), filename: filepath.FromSlash("/working/dir/abs-file"), expected: filepath.FromSlash("/working/dir/working/dir/abs-file"), }, { name: "path: abs file within cwd", cwd: "/working/dir", filename: "/outside/dir/abs-file", expected: filepath.FromSlash("/working/dir/outside/dir/abs-file"), }, { name: "abs symlink: within cwd w abs descending target", filename: "ln-cwd-cwd", makeAbs: true, expected: "within-cwd", makeExpectedAbs: true, before: func(dir string) { os.Symlink(filepath.Join(dir, "within-cwd"), filepath.Join(dir, "ln-cwd-cwd")) }, deduplicatePath: true, }, { name: "abs symlink: within cwd w rel descending target", filename: "ln-rel-cwd-cwd", makeAbs: true, expected: "within-cwd", makeExpectedAbs: true, before: func(dir string) { os.Symlink("within-cwd", filepath.Join(dir, "ln-rel-cwd-cwd")) }, deduplicatePath: true, }, { name: "abs symlink: within cwd w abs ascending target", filename: "ln-cwd-up", makeAbs: true, expected: "/some/outside/dir", makeExpectedAbs: true, before: func(dir string) { os.Symlink("/some/outside/dir", filepath.Join(dir, "ln-cwd-up")) }, deduplicatePath: true, }, { name: "abs symlink: within cwd w rel ascending target", filename: "ln-rel-cwd-up", makeAbs: true, expected: "outside-cwd", makeExpectedAbs: true, before: func(dir string) { os.Symlink("../../outside-cwd", filepath.Join(dir, "ln-rel-cwd-up")) }, deduplicatePath: true, }, { name: "rel symlink: within cwd w abs descending target", filename: "ln-cwd-cwd", expected: "within-cwd", makeExpectedAbs: true, before: func(dir string) { os.Symlink(filepath.Join(dir, "within-cwd"), filepath.Join(dir, "ln-cwd-cwd")) }, deduplicatePath: true, }, { name: "rel symlink: within cwd w rel descending target", filename: "ln-rel-cwd-cwd2", expected: "within-cwd", makeExpectedAbs: true, before: func(dir string) { os.Symlink("within-cwd", filepath.Join(dir, "ln-rel-cwd-cwd2")) }, }, { name: "rel symlink: within cwd w abs ascending target", filename: "ln-cwd-up2", expected: "/outside/path/up", makeExpectedAbs: true, before: func(dir string) { os.Symlink("/outside/path/up", filepath.Join(dir, "ln-cwd-up2")) }, }, { name: "rel symlink: within cwd w rel ascending target", filename: "ln-rel-cwd-up2", expected: "outside", makeExpectedAbs: true, before: func(dir string) { os.Symlink("../../../../outside", filepath.Join(dir, "ln-rel-cwd-up2")) }, }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { g := gomega.NewWithT(t) cwd := tt.cwd if cwd == "" { cwd = t.TempDir() } fs := newBoundOS(cwd, tt.deduplicatePath).(*BoundOS) if tt.before != nil { tt.before(cwd) } filename := tt.filename if tt.makeAbs { filename = filepath.Join(cwd, filename) } expected := tt.expected if tt.makeExpectedAbs { expected = filepath.Join(cwd, expected) } got, err := fs.abs(filename) if tt.wantErr != "" { g.Expect(err).To(gomega.HaveOccurred()) g.Expect(err.Error()).To(gomega.ContainSubstring(tt.wantErr)) } else { g.Expect(err).ToNot(gomega.HaveOccurred()) } g.Expect(got).To(gomega.Equal(expected)) }) } } func TestReadDir(t *testing.T) { g := gomega.NewWithT(t) dir := t.TempDir() fs := newBoundOS(dir, true) f, err := os.Create(filepath.Join(dir, "file1")) g.Expect(err).ToNot(gomega.HaveOccurred()) g.Expect(f).ToNot(gomega.BeNil()) g.Expect(f.Close()).To(gomega.Succeed()) f, err = os.Create(filepath.Join(dir, "file2")) g.Expect(err).ToNot(gomega.HaveOccurred()) g.Expect(f).ToNot(gomega.BeNil()) g.Expect(f.Close()).To(gomega.Succeed()) dirs, err := fs.ReadDir(dir) g.Expect(err).ToNot(gomega.HaveOccurred()) g.Expect(dirs).ToNot(gomega.BeNil()) g.Expect(dirs).To(gomega.HaveLen(2)) dirs, err = fs.ReadDir(".") g.Expect(err).ToNot(gomega.HaveOccurred()) g.Expect(dirs).ToNot(gomega.BeNil()) g.Expect(dirs).To(gomega.HaveLen(2)) os.Symlink("/some/path/outside/cwd", filepath.Join(dir, "symlink")) dirs, err = fs.ReadDir("symlink") g.Expect(err).To(gomega.HaveOccurred()) g.Expect(err.Error()).To(gomega.ContainSubstring(notFoundError())) g.Expect(dirs).To(gomega.BeNil()) } func TestMkdirAll(t *testing.T) { g := gomega.NewWithT(t) root := t.TempDir() cwd := filepath.Join(root, "cwd") target := "abc" targetAbs := filepath.Join(cwd, target) fs := newBoundOS(cwd, true) // Even if CWD is changed outside of the fs instance, // the base dir must still be observed. err := os.Chdir(os.TempDir()) g.Expect(err).ToNot(gomega.HaveOccurred()) err = fs.MkdirAll(target, 0o700) g.Expect(err).ToNot(gomega.HaveOccurred()) fi, err := os.Stat(targetAbs) g.Expect(err).ToNot(gomega.HaveOccurred()) g.Expect(fi).ToNot(gomega.BeNil()) err = os.Mkdir(filepath.Join(root, "outside"), 0o700) g.Expect(err).ToNot(gomega.HaveOccurred()) err = os.Symlink(filepath.Join(root, "outside"), filepath.Join(cwd, "symlink")) g.Expect(err).ToNot(gomega.HaveOccurred()) err = fs.MkdirAll(filepath.Join(cwd, "symlink", "new-dir"), 0o700) g.Expect(err).ToNot(gomega.HaveOccurred()) // For windows, the volume name must be removed from the path or // it will lead to an invalid path. if vol := filepath.VolumeName(root); vol != "" { root = root[len(vol):] } mustExist(filepath.Join(cwd, root, "outside", "new-dir")) } func TestRename(t *testing.T) { g := gomega.NewWithT(t) dir := t.TempDir() fs := newBoundOS(dir, true) oldFile := "old-file" newFile := filepath.Join("newdir", "newfile") // Even if CWD is changed outside of the fs instance, // the base dir must still be observed. err := os.Chdir(os.TempDir()) g.Expect(err).ToNot(gomega.HaveOccurred()) f, err := fs.Create(oldFile) g.Expect(err).ToNot(gomega.HaveOccurred()) g.Expect(f.Close()).To(gomega.Succeed()) err = fs.Rename(oldFile, newFile) g.Expect(err).ToNot(gomega.HaveOccurred()) fi, err := os.Stat(filepath.Join(dir, newFile)) g.Expect(err).ToNot(gomega.HaveOccurred()) g.Expect(fi).ToNot(gomega.BeNil()) err = fs.Rename(filepath.FromSlash("/tmp/outside/cwd/file1"), newFile) g.Expect(err).To(gomega.HaveOccurred()) g.Expect(err.Error()).To(gomega.ContainSubstring(notFoundError())) err = fs.Rename(oldFile, filepath.FromSlash("/tmp/outside/cwd/file2")) g.Expect(err).To(gomega.HaveOccurred()) g.Expect(err.Error()).To(gomega.ContainSubstring(notFoundError())) } func mustExist(filename string) { fi, err := os.Stat(filename) if err != nil || fi == nil { panic(fmt.Sprintf("file %s should exist", filename)) } } func notFoundError() string { switch runtime.GOOS { case "windows": return "The system cannot find the " // {path,file} specified default: return "no such file or directory" } } golang-github-go-git-go-billy-5.5.0/osfs/os_chroot.go000066400000000000000000000054501460000046100224430ustar00rootroot00000000000000//go:build !js // +build !js package osfs import ( "os" "path/filepath" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/helper/chroot" ) // ChrootOS is a legacy filesystem based on a "soft chroot" of the os filesystem. // Although this is still the default os filesystem, consider using BoundOS instead. // // Behaviours of note: // 1. A "soft chroot" translates the base dir to "/" for the purposes of the // fs abstraction. // 2. Symlinks targets may be modified to be kept within the chroot bounds. // 3. Some file modes does not pass-through the fs abstraction. // 4. The combination of 1 and 2 may cause go-git to think that a Git repository // is dirty, when in fact it isn't. type ChrootOS struct{} func newChrootOS(baseDir string) billy.Filesystem { return chroot.New(&ChrootOS{}, baseDir) } func (fs *ChrootOS) Create(filename string) (billy.File, error) { return fs.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, defaultCreateMode) } func (fs *ChrootOS) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) { return openFile(filename, flag, perm, fs.createDir) } func (fs *ChrootOS) createDir(fullpath string) error { dir := filepath.Dir(fullpath) if dir != "." { if err := os.MkdirAll(dir, defaultDirectoryMode); err != nil { return err } } return nil } func (fs *ChrootOS) ReadDir(dir string) ([]os.FileInfo, error) { return readDir(dir) } func (fs *ChrootOS) Rename(from, to string) error { if err := fs.createDir(to); err != nil { return err } return rename(from, to) } func (fs *ChrootOS) MkdirAll(path string, perm os.FileMode) error { return os.MkdirAll(path, defaultDirectoryMode) } func (fs *ChrootOS) Open(filename string) (billy.File, error) { return fs.OpenFile(filename, os.O_RDONLY, 0) } func (fs *ChrootOS) Stat(filename string) (os.FileInfo, error) { return os.Stat(filename) } func (fs *ChrootOS) Remove(filename string) error { return os.Remove(filename) } func (fs *ChrootOS) TempFile(dir, prefix string) (billy.File, error) { if err := fs.createDir(dir + string(os.PathSeparator)); err != nil { return nil, err } return tempFile(dir, prefix) } func (fs *ChrootOS) Join(elem ...string) string { return filepath.Join(elem...) } func (fs *ChrootOS) RemoveAll(path string) error { return os.RemoveAll(filepath.Clean(path)) } func (fs *ChrootOS) Lstat(filename string) (os.FileInfo, error) { return os.Lstat(filepath.Clean(filename)) } func (fs *ChrootOS) Symlink(target, link string) error { if err := fs.createDir(link); err != nil { return err } return os.Symlink(target, link) } func (fs *ChrootOS) Readlink(link string) (string, error) { return os.Readlink(link) } // Capabilities implements the Capable interface. func (fs *ChrootOS) Capabilities() billy.Capability { return billy.DefaultCapabilities } golang-github-go-git-go-billy-5.5.0/osfs/os_chroot_test.go000066400000000000000000000026251460000046100235030ustar00rootroot00000000000000//go:build !js // +build !js package osfs import ( "io/ioutil" "os" "path/filepath" "runtime" "testing" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/test" . "gopkg.in/check.v1" ) func Test(t *testing.T) { TestingT(t) } type ChrootOSSuite struct { test.FilesystemSuite path string } var _ = Suite(&ChrootOSSuite{}) func (s *ChrootOSSuite) SetUpTest(c *C) { s.path, _ = ioutil.TempDir(os.TempDir(), "go-billy-osfs-test") if runtime.GOOS == "plan9" { // On Plan 9, permission mode of newly created files // or directories are based on the permission mode of // the containing directory (see http://man.cat-v.org/plan_9/5/open). // Since TestOpenFileWithModes and TestStat creates files directly // in the temporary directory, we need to make it more permissive. c.Assert(os.Chmod(s.path, 0777), IsNil) } s.FilesystemSuite = test.NewFilesystemSuite(newChrootOS(s.path)) } func (s *ChrootOSSuite) TearDownTest(c *C) { err := os.RemoveAll(s.path) c.Assert(err, IsNil) } func (s *ChrootOSSuite) TestOpenDoesNotCreateDir(c *C) { _, err := s.FS.Open("dir/non-existent") c.Assert(err, NotNil) _, err = os.Stat(filepath.Join(s.path, "dir")) c.Assert(os.IsNotExist(err), Equals, true) } func (s *ChrootOSSuite) TestCapabilities(c *C) { _, ok := s.FS.(billy.Capable) c.Assert(ok, Equals, true) caps := billy.Capabilities(s.FS) c.Assert(caps, Equals, billy.AllCapabilities) } golang-github-go-git-go-billy-5.5.0/osfs/os_js.go000066400000000000000000000010401460000046100215500ustar00rootroot00000000000000//go:build js // +build js package osfs import ( "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/helper/chroot" "github.com/go-git/go-billy/v5/memfs" ) // globalMemFs is the global memory fs var globalMemFs = memfs.New() // Default Filesystem representing the root of in-memory filesystem for a // js/wasm environment. var Default = memfs.New() // New returns a new OS filesystem. func New(baseDir string, _ ...Option) billy.Filesystem { return chroot.New(Default, Default.Join("/", baseDir)) } type options struct { } golang-github-go-git-go-billy-5.5.0/osfs/os_js_test.go000066400000000000000000000023701460000046100226160ustar00rootroot00000000000000//go:build js // +build js package osfs import ( "fmt" "os" "path/filepath" "reflect" "testing" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/helper/chroot" "github.com/go-git/go-billy/v5/test" . "gopkg.in/check.v1" ) func Test(t *testing.T) { TestingT(t) } type OSSuite struct { test.FilesystemSuite path string tempCounter int } var _ = Suite(&OSSuite{}) func (s *OSSuite) SetUpTest(c *C) { s.tempCounter++ s.path = fmt.Sprintf("test_%d", s.tempCounter) s.FilesystemSuite = test.NewFilesystemSuite(New(s.path)) } func (s *OSSuite) TestOpenDoesNotCreateDir(c *C) { _, err := s.FS.Open("dir/non-existent") c.Assert(err, NotNil) _, err = s.FS.Stat(filepath.Join(s.path, "dir")) c.Assert(os.IsNotExist(err), Equals, true) } func (s *OSSuite) TestCapabilities(c *C) { _, ok := s.FS.(billy.Capable) c.Assert(ok, Equals, true) caps := billy.Capabilities(s.FS) c.Assert(caps, Equals, billy.DefaultCapabilities&^billy.LockCapability) } func TestDefault(t *testing.T) { want := &chroot.ChrootHelper{} // memfs is wrapped around ChrootHelper. got := Default if reflect.TypeOf(got) != reflect.TypeOf(want) { t.Errorf("wanted Default to be %T got %T", want, got) } } func TestNewAPI(t *testing.T) { _ = New("/") } golang-github-go-git-go-billy-5.5.0/osfs/os_options.go000066400000000000000000000000511460000046100226300ustar00rootroot00000000000000package osfs type Option func(*options) golang-github-go-git-go-billy-5.5.0/osfs/os_plan9.go000066400000000000000000000043541460000046100221720ustar00rootroot00000000000000//go:build plan9 // +build plan9 package osfs import ( "io" "os" "path/filepath" "syscall" ) func (f *file) Lock() error { // Plan 9 uses a mode bit instead of explicit lock/unlock syscalls. // // Per http://man.cat-v.org/plan_9/5/stat: “Exclusive use files may be open // for I/O by only one fid at a time across all clients of the server. If a // second open is attempted, it draws an error.” // // There is no obvious way to implement this function using the exclusive use bit. // See https://golang.org/src/cmd/go/internal/lockedfile/lockedfile_plan9.go // for how file locking is done by the go tool on Plan 9. return nil } func (f *file) Unlock() error { return nil } func rename(from, to string) error { // If from and to are in different directories, copy the file // since Plan 9 does not support cross-directory rename. if filepath.Dir(from) != filepath.Dir(to) { fi, err := os.Stat(from) if err != nil { return &os.LinkError{"rename", from, to, err} } if fi.Mode().IsDir() { return &os.LinkError{"rename", from, to, syscall.EISDIR} } fromFile, err := os.Open(from) if err != nil { return &os.LinkError{"rename", from, to, err} } toFile, err := os.OpenFile(to, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, fi.Mode()) if err != nil { return &os.LinkError{"rename", from, to, err} } _, err = io.Copy(toFile, fromFile) if err != nil { return &os.LinkError{"rename", from, to, err} } // Copy mtime and mode from original file. // We need only one syscall if we avoid os.Chmod and os.Chtimes. dir := fi.Sys().(*syscall.Dir) var d syscall.Dir d.Null() d.Mtime = dir.Mtime d.Mode = dir.Mode if err = dirwstat(to, &d); err != nil { return &os.LinkError{"rename", from, to, err} } // Remove original file. err = os.Remove(from) if err != nil { return &os.LinkError{"rename", from, to, err} } return nil } return os.Rename(from, to) } func dirwstat(name string, d *syscall.Dir) error { var buf [syscall.STATFIXLEN]byte n, err := d.Marshal(buf[:]) if err != nil { return &os.PathError{"dirwstat", name, err} } if err = syscall.Wstat(name, buf[:n]); err != nil { return &os.PathError{"dirwstat", name, err} } return nil } func umask(new int) func() { return func() { } } golang-github-go-git-go-billy-5.5.0/osfs/os_posix.go000066400000000000000000000012321460000046100223010ustar00rootroot00000000000000//go:build !plan9 && !windows && !js // +build !plan9,!windows,!js package osfs import ( "os" "syscall" "golang.org/x/sys/unix" ) func (f *file) Lock() error { f.m.Lock() defer f.m.Unlock() return unix.Flock(int(f.File.Fd()), unix.LOCK_EX) } func (f *file) Unlock() error { f.m.Lock() defer f.m.Unlock() return unix.Flock(int(f.File.Fd()), unix.LOCK_UN) } func rename(from, to string) error { return os.Rename(from, to) } // umask sets umask to a new value, and returns a func which allows the // caller to reset it back to what it was originally. func umask(new int) func() { old := syscall.Umask(new) return func() { syscall.Umask(old) } } golang-github-go-git-go-billy-5.5.0/osfs/os_test.go000066400000000000000000000005601460000046100221210ustar00rootroot00000000000000//go:build !js // +build !js package osfs import ( "reflect" "testing" ) func TestDefault(t *testing.T) { want := &ChrootOS{} got := Default if reflect.TypeOf(got) != reflect.TypeOf(want) { t.Errorf("wanted Default to be %T got %T", want, got) } } func TestNewAPI(t *testing.T) { _ = New("/") _ = New("/", WithBoundOS()) _ = New("/", WithChrootOS()) } golang-github-go-git-go-billy-5.5.0/osfs/os_windows.go000066400000000000000000000020321460000046100226300ustar00rootroot00000000000000//go:build windows // +build windows package osfs import ( "os" "runtime" "unsafe" "golang.org/x/sys/windows" ) var ( kernel32DLL = windows.NewLazySystemDLL("kernel32.dll") lockFileExProc = kernel32DLL.NewProc("LockFileEx") unlockFileProc = kernel32DLL.NewProc("UnlockFile") ) const ( lockfileExclusiveLock = 0x2 ) func (f *file) Lock() error { f.m.Lock() defer f.m.Unlock() var overlapped windows.Overlapped // err is always non-nil as per sys/windows semantics. ret, _, err := lockFileExProc.Call(f.File.Fd(), lockfileExclusiveLock, 0, 0xFFFFFFFF, 0, uintptr(unsafe.Pointer(&overlapped))) runtime.KeepAlive(&overlapped) if ret == 0 { return err } return nil } func (f *file) Unlock() error { f.m.Lock() defer f.m.Unlock() // err is always non-nil as per sys/windows semantics. ret, _, err := unlockFileProc.Call(f.File.Fd(), 0, 0, 0xFFFFFFFF, 0) if ret == 0 { return err } return nil } func rename(from, to string) error { return os.Rename(from, to) } func umask(new int) func() { return func() { } } golang-github-go-git-go-billy-5.5.0/test/000077500000000000000000000000001460000046100201165ustar00rootroot00000000000000golang-github-go-git-go-billy-5.5.0/test/basic.go000066400000000000000000000326071460000046100215360ustar00rootroot00000000000000package test import ( "bytes" "fmt" "io" "io/ioutil" "os" "path/filepath" . "gopkg.in/check.v1" . "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/util" ) // BasicSuite is a convenient test suite to validate any implementation of // billy.Basic type BasicSuite struct { FS Basic } func (s *BasicSuite) TestCreate(c *C) { f, err := s.FS.Create("foo") c.Assert(err, IsNil) c.Assert(f.Name(), Equals, "foo") c.Assert(f.Close(), IsNil) } func (s *BasicSuite) TestCreateDepth(c *C) { f, err := s.FS.Create("bar/foo") c.Assert(err, IsNil) c.Assert(f.Name(), Equals, s.FS.Join("bar", "foo")) c.Assert(f.Close(), IsNil) } func (s *BasicSuite) TestCreateDepthAbsolute(c *C) { f, err := s.FS.Create("/bar/foo") c.Assert(err, IsNil) c.Assert(f.Name(), Equals, s.FS.Join("bar", "foo")) c.Assert(f.Close(), IsNil) } func (s *BasicSuite) TestCreateOverwrite(c *C) { for i := 0; i < 3; i++ { f, err := s.FS.Create("foo") c.Assert(err, IsNil) l, err := f.Write([]byte(fmt.Sprintf("foo%d", i))) c.Assert(err, IsNil) c.Assert(l, Equals, 4) err = f.Close() c.Assert(err, IsNil) } f, err := s.FS.Open("foo") c.Assert(err, IsNil) wrote, err := ioutil.ReadAll(f) c.Assert(err, IsNil) c.Assert(string(wrote), DeepEquals, "foo2") c.Assert(f.Close(), IsNil) } func (s *BasicSuite) TestCreateAndClose(c *C) { f, err := s.FS.Create("foo") c.Assert(err, IsNil) _, err = f.Write([]byte("foo")) c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) f, err = s.FS.Open(f.Name()) c.Assert(err, IsNil) wrote, err := ioutil.ReadAll(f) c.Assert(err, IsNil) c.Assert(string(wrote), DeepEquals, "foo") c.Assert(f.Close(), IsNil) } func (s *BasicSuite) TestOpen(c *C) { f, err := s.FS.Create("foo") c.Assert(err, IsNil) c.Assert(f.Name(), Equals, "foo") c.Assert(f.Close(), IsNil) f, err = s.FS.Open("foo") c.Assert(err, IsNil) c.Assert(f.Name(), Equals, "foo") c.Assert(f.Close(), IsNil) } func (s *BasicSuite) TestOpenNotExists(c *C) { f, err := s.FS.Open("not-exists") c.Assert(err, NotNil) c.Assert(f, IsNil) } func (s *BasicSuite) TestOpenFile(c *C) { defaultMode := os.FileMode(0666) f, err := s.FS.OpenFile("foo1", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, defaultMode) c.Assert(err, IsNil) s.testWriteClose(c, f, "foo1") // Truncate if it exists f, err = s.FS.OpenFile("foo1", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, defaultMode) c.Assert(err, IsNil) c.Assert(f.Name(), Equals, "foo1") s.testWriteClose(c, f, "foo1overwritten") // Read-only if it exists f, err = s.FS.OpenFile("foo1", os.O_RDONLY, defaultMode) c.Assert(err, IsNil) c.Assert(f.Name(), Equals, "foo1") s.testReadClose(c, f, "foo1overwritten") // Create when it does exist f, err = s.FS.OpenFile("foo1", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, defaultMode) c.Assert(err, IsNil) c.Assert(f.Name(), Equals, "foo1") s.testWriteClose(c, f, "bar") f, err = s.FS.OpenFile("foo1", os.O_RDONLY, defaultMode) c.Assert(err, IsNil) s.testReadClose(c, f, "bar") } func (s *BasicSuite) TestOpenFileNoTruncate(c *C) { defaultMode := os.FileMode(0666) // Create when it does not exist f, err := s.FS.OpenFile("foo1", os.O_CREATE|os.O_WRONLY, defaultMode) c.Assert(err, IsNil) c.Assert(f.Name(), Equals, "foo1") s.testWriteClose(c, f, "foo1") f, err = s.FS.OpenFile("foo1", os.O_RDONLY, defaultMode) c.Assert(err, IsNil) s.testReadClose(c, f, "foo1") // Create when it does exist f, err = s.FS.OpenFile("foo1", os.O_CREATE|os.O_WRONLY, defaultMode) c.Assert(err, IsNil) c.Assert(f.Name(), Equals, "foo1") s.testWriteClose(c, f, "bar") f, err = s.FS.OpenFile("foo1", os.O_RDONLY, defaultMode) c.Assert(err, IsNil) s.testReadClose(c, f, "bar1") } func (s *BasicSuite) TestOpenFileAppend(c *C) { defaultMode := os.FileMode(0666) f, err := s.FS.OpenFile("foo1", os.O_CREATE|os.O_WRONLY|os.O_APPEND, defaultMode) c.Assert(err, IsNil) c.Assert(f.Name(), Equals, "foo1") s.testWriteClose(c, f, "foo1") f, err = s.FS.OpenFile("foo1", os.O_WRONLY|os.O_APPEND, defaultMode) c.Assert(err, IsNil) c.Assert(f.Name(), Equals, "foo1") s.testWriteClose(c, f, "bar1") f, err = s.FS.OpenFile("foo1", os.O_RDONLY, defaultMode) c.Assert(err, IsNil) s.testReadClose(c, f, "foo1bar1") } func (s *BasicSuite) TestOpenFileReadWrite(c *C) { defaultMode := os.FileMode(0666) f, err := s.FS.OpenFile("foo1", os.O_CREATE|os.O_TRUNC|os.O_RDWR, defaultMode) c.Assert(err, IsNil) c.Assert(f.Name(), Equals, "foo1") written, err := f.Write([]byte("foobar")) c.Assert(written, Equals, 6) c.Assert(err, IsNil) _, err = f.Seek(0, os.SEEK_SET) c.Assert(err, IsNil) written, err = f.Write([]byte("qux")) c.Assert(written, Equals, 3) c.Assert(err, IsNil) _, err = f.Seek(0, os.SEEK_SET) c.Assert(err, IsNil) s.testReadClose(c, f, "quxbar") } func (s *BasicSuite) TestOpenFileWithModes(c *C) { f, err := s.FS.OpenFile("foo", os.O_CREATE|os.O_WRONLY|os.O_TRUNC, customMode) c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) fi, err := s.FS.Stat("foo") c.Assert(err, IsNil) c.Assert(fi.Mode(), Equals, os.FileMode(customMode)) } func (s *BasicSuite) testWriteClose(c *C, f File, content string) { written, err := f.Write([]byte(content)) c.Assert(written, Equals, len(content)) c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) } func (s *BasicSuite) testReadClose(c *C, f File, content string) { read, err := ioutil.ReadAll(f) c.Assert(err, IsNil) c.Assert(string(read), Equals, content) c.Assert(f.Close(), IsNil) } func (s *BasicSuite) TestFileWrite(c *C) { f, err := s.FS.Create("foo") c.Assert(err, IsNil) n, err := f.Write([]byte("foo")) c.Assert(err, IsNil) c.Assert(n, Equals, 3) f.Seek(0, io.SeekStart) all, err := ioutil.ReadAll(f) c.Assert(err, IsNil) c.Assert(string(all), Equals, "foo") c.Assert(f.Close(), IsNil) } func (s *BasicSuite) TestFileWriteClose(c *C) { f, err := s.FS.Create("foo") c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) _, err = f.Write([]byte("foo")) c.Assert(err, NotNil) } func (s *BasicSuite) TestFileRead(c *C) { err := util.WriteFile(s.FS, "foo", []byte("foo"), 0644) c.Assert(err, IsNil) f, err := s.FS.Open("foo") c.Assert(err, IsNil) all, err := ioutil.ReadAll(f) c.Assert(err, IsNil) c.Assert(string(all), Equals, "foo") c.Assert(f.Close(), IsNil) } func (s *BasicSuite) TestFileClosed(c *C) { err := util.WriteFile(s.FS, "foo", []byte("foo"), 0644) c.Assert(err, IsNil) f, err := s.FS.Open("foo") c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) _, err = ioutil.ReadAll(f) c.Assert(err, NotNil) } func (s *BasicSuite) TestFileNonRead(c *C) { err := util.WriteFile(s.FS, "foo", []byte("foo"), 0644) c.Assert(err, IsNil) f, err := s.FS.OpenFile("foo", os.O_WRONLY, 0) c.Assert(err, IsNil) _, err = ioutil.ReadAll(f) c.Assert(err, NotNil) c.Assert(f.Close(), IsNil) } func (s *BasicSuite) TestFileSeekstart(c *C) { s.testFileSeek(c, 10, io.SeekStart) } func (s *BasicSuite) TestFileSeekCurrent(c *C) { s.testFileSeek(c, 5, io.SeekCurrent) } func (s *BasicSuite) TestFileSeekEnd(c *C) { s.testFileSeek(c, -26, io.SeekEnd) } func (s *BasicSuite) testFileSeek(c *C, offset int64, whence int) { err := util.WriteFile(s.FS, "foo", []byte("0123456789abcdefghijklmnopqrstuvwxyz"), 0644) c.Assert(err, IsNil) f, err := s.FS.Open("foo") c.Assert(err, IsNil) some := make([]byte, 5) _, err = f.Read(some) c.Assert(err, IsNil) c.Assert(string(some), Equals, "01234") p, err := f.Seek(offset, whence) c.Assert(err, IsNil) c.Assert(int(p), Equals, 10) all, err := ioutil.ReadAll(f) c.Assert(err, IsNil) c.Assert(all, HasLen, 26) c.Assert(string(all), Equals, "abcdefghijklmnopqrstuvwxyz") c.Assert(f.Close(), IsNil) } func (s *BasicSuite) TestSeekToEndAndWrite(c *C) { defaultMode := os.FileMode(0666) f, err := s.FS.OpenFile("foo1", os.O_CREATE|os.O_TRUNC|os.O_RDWR, defaultMode) c.Assert(err, IsNil) c.Assert(f.Name(), Equals, "foo1") _, err = f.Seek(10, io.SeekEnd) c.Assert(err, IsNil) n, err := f.Write([]byte(`TEST`)) c.Assert(err, IsNil) c.Assert(n, Equals, 4) _, err = f.Seek(0, io.SeekStart) c.Assert(err, IsNil) s.testReadClose(c, f, "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00TEST") } func (s *BasicSuite) TestFileSeekClosed(c *C) { err := util.WriteFile(s.FS, "foo", []byte("foo"), 0644) c.Assert(err, IsNil) f, err := s.FS.Open("foo") c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) _, err = f.Seek(0, 0) c.Assert(err, NotNil) } func (s *BasicSuite) TestFileCloseTwice(c *C) { f, err := s.FS.Create("foo") c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) c.Assert(f.Close(), NotNil) } func (s *BasicSuite) TestStat(c *C) { util.WriteFile(s.FS, "foo/bar", []byte("foo"), customMode) fi, err := s.FS.Stat("foo/bar") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "bar") c.Assert(fi.Size(), Equals, int64(3)) c.Assert(fi.Mode(), Equals, customMode) c.Assert(fi.ModTime().IsZero(), Equals, false) c.Assert(fi.IsDir(), Equals, false) } func (s *BasicSuite) TestStatNonExistent(c *C) { fi, err := s.FS.Stat("non-existent") comment := Commentf("error: %s", err) c.Assert(os.IsNotExist(err), Equals, true, comment) c.Assert(fi, IsNil) } func (s *BasicSuite) TestRename(c *C) { err := util.WriteFile(s.FS, "foo", nil, 0644) c.Assert(err, IsNil) err = s.FS.Rename("foo", "bar") c.Assert(err, IsNil) foo, err := s.FS.Stat("foo") c.Assert(foo, IsNil) c.Assert(os.IsNotExist(err), Equals, true) bar, err := s.FS.Stat("bar") c.Assert(err, IsNil) c.Assert(bar, NotNil) } func (s *BasicSuite) TestOpenAndWrite(c *C) { err := util.WriteFile(s.FS, "foo", nil, 0644) c.Assert(err, IsNil) foo, err := s.FS.Open("foo") c.Assert(foo, NotNil) c.Assert(err, IsNil) n, err := foo.Write([]byte("foo")) c.Assert(err, NotNil) c.Assert(n, Equals, 0) c.Assert(foo.Close(), IsNil) } func (s *BasicSuite) TestOpenAndStat(c *C) { err := util.WriteFile(s.FS, "foo", []byte("foo"), 0644) c.Assert(err, IsNil) foo, err := s.FS.Open("foo") c.Assert(foo, NotNil) c.Assert(foo.Name(), Equals, "foo") c.Assert(err, IsNil) c.Assert(foo.Close(), IsNil) stat, err := s.FS.Stat("foo") c.Assert(stat, NotNil) c.Assert(err, IsNil) c.Assert(stat.Name(), Equals, "foo") c.Assert(stat.Size(), Equals, int64(3)) } func (s *BasicSuite) TestRemove(c *C) { f, err := s.FS.Create("foo") c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) err = s.FS.Remove("foo") c.Assert(err, IsNil) } func (s *BasicSuite) TestRemoveNonExisting(c *C) { err := s.FS.Remove("NON-EXISTING") c.Assert(err, NotNil) c.Assert(os.IsNotExist(err), Equals, true) } func (s *BasicSuite) TestRemoveNotEmptyDir(c *C) { err := util.WriteFile(s.FS, "foo", nil, 0644) c.Assert(err, IsNil) err = s.FS.Remove("no-exists") c.Assert(err, NotNil) } func (s *BasicSuite) TestJoin(c *C) { c.Assert(s.FS.Join("foo", "bar"), Equals, fmt.Sprintf("foo%cbar", filepath.Separator)) } func (s *BasicSuite) TestReadAtOnReadWrite(c *C) { f, err := s.FS.Create("foo") c.Assert(err, IsNil) _, err = f.Write([]byte("abcdefg")) c.Assert(err, IsNil) rf, ok := f.(io.ReaderAt) c.Assert(ok, Equals, true) b := make([]byte, 3) n, err := rf.ReadAt(b, 2) c.Assert(err, IsNil) c.Assert(n, Equals, 3) c.Assert(string(b), Equals, "cde") c.Assert(f.Close(), IsNil) } func (s *BasicSuite) TestReadAtOnReadOnly(c *C) { err := util.WriteFile(s.FS, "foo", []byte("abcdefg"), 0644) c.Assert(err, IsNil) f, err := s.FS.Open("foo") c.Assert(err, IsNil) rf, ok := f.(io.ReaderAt) c.Assert(ok, Equals, true) b := make([]byte, 3) n, err := rf.ReadAt(b, 2) c.Assert(err, IsNil) c.Assert(n, Equals, 3) c.Assert(string(b), Equals, "cde") c.Assert(f.Close(), IsNil) } func (s *BasicSuite) TestReadAtEOF(c *C) { err := util.WriteFile(s.FS, "foo", []byte("TEST"), 0644) c.Assert(err, IsNil) f, err := s.FS.Open("foo") c.Assert(err, IsNil) b := make([]byte, 5) n, err := f.ReadAt(b, 0) c.Assert(err, Equals, io.EOF) c.Assert(n, Equals, 4) c.Assert(string(b), Equals, "TEST\x00") err = f.Close() c.Assert(err, IsNil) } func (s *BasicSuite) TestReadAtOffset(c *C) { err := util.WriteFile(s.FS, "foo", []byte("TEST"), 0644) c.Assert(err, IsNil) f, err := s.FS.Open("foo") c.Assert(err, IsNil) rf, ok := f.(io.ReaderAt) c.Assert(ok, Equals, true) o, err := f.Seek(0, io.SeekCurrent) c.Assert(err, IsNil) c.Assert(o, Equals, int64(0)) b := make([]byte, 4) n, err := rf.ReadAt(b, 0) c.Assert(err, IsNil) c.Assert(n, Equals, 4) c.Assert(string(b), Equals, "TEST") o, err = f.Seek(0, io.SeekCurrent) c.Assert(err, IsNil) c.Assert(o, Equals, int64(0)) err = f.Close() c.Assert(err, IsNil) } func (s *BasicSuite) TestReadWriteLargeFile(c *C) { f, err := s.FS.Create("foo") c.Assert(err, IsNil) size := 1 << 20 n, err := f.Write(bytes.Repeat([]byte("F"), size)) c.Assert(err, IsNil) c.Assert(n, Equals, size) c.Assert(f.Close(), IsNil) f, err = s.FS.Open("foo") c.Assert(err, IsNil) b, err := ioutil.ReadAll(f) c.Assert(err, IsNil) c.Assert(len(b), Equals, size) c.Assert(f.Close(), IsNil) } func (s *BasicSuite) TestWriteFile(c *C) { err := util.WriteFile(s.FS, "foo", []byte("bar"), 0777) c.Assert(err, IsNil) f, err := s.FS.Open("foo") c.Assert(err, IsNil) wrote, err := ioutil.ReadAll(f) c.Assert(err, IsNil) c.Assert(string(wrote), DeepEquals, "bar") c.Assert(f.Close(), IsNil) } func (s *BasicSuite) TestTruncate(c *C) { f, err := s.FS.Create("foo") c.Assert(err, IsNil) for _, sz := range []int64{4, 7, 2, 30, 0, 1} { err = f.Truncate(sz) c.Assert(err, IsNil) bs, err := ioutil.ReadAll(f) c.Assert(err, IsNil) c.Assert(len(bs), Equals, int(sz)) _, err = f.Seek(0, io.SeekStart) c.Assert(err, IsNil) } c.Assert(f.Close(), IsNil) } golang-github-go-git-go-billy-5.5.0/test/chroot.go000066400000000000000000000056401460000046100217500ustar00rootroot00000000000000package test import ( "os" . "gopkg.in/check.v1" . "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/util" ) // ChrootSuite is a convenient test suite to validate any implementation of // billy.Chroot type ChrootSuite struct { FS interface { Basic Chroot } } func (s *ChrootSuite) TestCreateWithChroot(c *C) { fs, _ := s.FS.Chroot("foo") f, err := fs.Create("bar") c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) c.Assert(f.Name(), Equals, "bar") f, err = s.FS.Open("foo/bar") c.Assert(err, IsNil) c.Assert(f.Name(), Equals, s.FS.Join("foo", "bar")) c.Assert(f.Close(), IsNil) } func (s *ChrootSuite) TestOpenWithChroot(c *C) { fs, _ := s.FS.Chroot("foo") f, err := fs.Create("bar") c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) c.Assert(f.Name(), Equals, "bar") f, err = fs.Open("bar") c.Assert(err, IsNil) c.Assert(f.Name(), Equals, "bar") c.Assert(f.Close(), IsNil) } func (s *ChrootSuite) TestOpenOutOffBoundary(c *C) { err := util.WriteFile(s.FS, "bar", nil, 0644) c.Assert(err, IsNil) fs, _ := s.FS.Chroot("foo") f, err := fs.Open("../bar") c.Assert(err, Equals, ErrCrossedBoundary) c.Assert(f, IsNil) } func (s *ChrootSuite) TestStatOutOffBoundary(c *C) { err := util.WriteFile(s.FS, "bar", nil, 0644) c.Assert(err, IsNil) fs, _ := s.FS.Chroot("foo") f, err := fs.Stat("../bar") c.Assert(err, Equals, ErrCrossedBoundary) c.Assert(f, IsNil) } func (s *ChrootSuite) TestStatWithChroot(c *C) { files := []string{"foo", "bar", "qux/baz", "qux/qux"} for _, name := range files { err := util.WriteFile(s.FS, name, nil, 0644) c.Assert(err, IsNil) } // Some implementations detect directories based on a prefix // for all files; it's easy to miss path separator handling there. fi, err := s.FS.Stat("qu") c.Assert(os.IsNotExist(err), Equals, true, Commentf("error: %s", err)) c.Assert(fi, IsNil) fi, err = s.FS.Stat("qux") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "qux") c.Assert(fi.IsDir(), Equals, true) qux, _ := s.FS.Chroot("qux") fi, err = qux.Stat("baz") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "baz") c.Assert(fi.IsDir(), Equals, false) fi, err = qux.Stat("/baz") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "baz") c.Assert(fi.IsDir(), Equals, false) } func (s *ChrootSuite) TestRenameOutOffBoundary(c *C) { err := util.WriteFile(s.FS, "foo/foo", nil, 0644) c.Assert(err, IsNil) err = util.WriteFile(s.FS, "bar", nil, 0644) c.Assert(err, IsNil) fs, _ := s.FS.Chroot("foo") err = fs.Rename("../bar", "foo") c.Assert(err, Equals, ErrCrossedBoundary) err = fs.Rename("foo", "../bar") c.Assert(err, Equals, ErrCrossedBoundary) } func (s *ChrootSuite) TestRemoveOutOffBoundary(c *C) { err := util.WriteFile(s.FS, "bar", nil, 0644) c.Assert(err, IsNil) fs, _ := s.FS.Chroot("foo") err = fs.Remove("../bar") c.Assert(err, Equals, ErrCrossedBoundary) } func (s *FilesystemSuite) TestRoot(c *C) { c.Assert(s.FS.Root(), Not(Equals), "") } golang-github-go-git-go-billy-5.5.0/test/common_posix.go000066400000000000000000000002221460000046100231530ustar00rootroot00000000000000// +build !windows package test import "os" var ( customMode os.FileMode = 0755 expectedSymlinkTarget = "/dir/file" ) golang-github-go-git-go-billy-5.5.0/test/common_windows.go000066400000000000000000000002231460000046100235040ustar00rootroot00000000000000// +build windows package test import "os" var ( customMode os.FileMode = 0666 expectedSymlinkTarget = "\\dir\\file" ) golang-github-go-git-go-billy-5.5.0/test/dir.go000066400000000000000000000142731460000046100212320ustar00rootroot00000000000000package test import ( "os" "strconv" . "gopkg.in/check.v1" . "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/util" ) // DirSuite is a convenient test suite to validate any implementation of // billy.Dir type DirSuite struct { FS interface { Basic Dir } } func (s *DirSuite) TestMkdirAll(c *C) { err := s.FS.MkdirAll("empty", os.FileMode(0755)) c.Assert(err, IsNil) fi, err := s.FS.Stat("empty") c.Assert(err, IsNil) c.Assert(fi.IsDir(), Equals, true) } func (s *DirSuite) TestMkdirAllNested(c *C) { err := s.FS.MkdirAll("foo/bar/baz", os.FileMode(0755)) c.Assert(err, IsNil) fi, err := s.FS.Stat("foo/bar/baz") c.Assert(err, IsNil) c.Assert(fi.IsDir(), Equals, true) fi, err = s.FS.Stat("foo/bar") c.Assert(err, IsNil) c.Assert(fi.IsDir(), Equals, true) fi, err = s.FS.Stat("foo") c.Assert(err, IsNil) c.Assert(fi.IsDir(), Equals, true) } func (s *DirSuite) TestMkdirAllIdempotent(c *C) { err := s.FS.MkdirAll("empty", 0755) c.Assert(err, IsNil) fi, err := s.FS.Stat("empty") c.Assert(err, IsNil) c.Assert(fi.IsDir(), Equals, true) // idempotent err = s.FS.MkdirAll("empty", 0755) c.Assert(err, IsNil) fi, err = s.FS.Stat("empty") c.Assert(err, IsNil) c.Assert(fi.IsDir(), Equals, true) } func (s *DirSuite) TestMkdirAllAndCreate(c *C) { err := s.FS.MkdirAll("dir", os.FileMode(0755)) c.Assert(err, IsNil) f, err := s.FS.Create("dir/bar/foo") c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) fi, err := s.FS.Stat("dir/bar/foo") c.Assert(err, IsNil) c.Assert(fi.IsDir(), Equals, false) } func (s *DirSuite) TestMkdirAllWithExistingFile(c *C) { f, err := s.FS.Create("dir/foo") c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) err = s.FS.MkdirAll("dir/foo", os.FileMode(0755)) c.Assert(err, NotNil) fi, err := s.FS.Stat("dir/foo") c.Assert(err, IsNil) c.Assert(fi.IsDir(), Equals, false) } func (s *DirSuite) TestStatDir(c *C) { s.FS.MkdirAll("foo/bar", 0755) fi, err := s.FS.Stat("foo/bar") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "bar") c.Assert(fi.Mode().IsDir(), Equals, true) c.Assert(fi.ModTime().IsZero(), Equals, false) c.Assert(fi.IsDir(), Equals, true) } func (s *BasicSuite) TestStatDeep(c *C) { files := []string{"foo", "bar", "qux/baz", "qux/qux"} for _, name := range files { err := util.WriteFile(s.FS, name, nil, 0644) c.Assert(err, IsNil) } // Some implementations detect directories based on a prefix // for all files; it's easy to miss path separator handling there. fi, err := s.FS.Stat("qu") c.Assert(os.IsNotExist(err), Equals, true, Commentf("error: %s", err)) c.Assert(fi, IsNil) fi, err = s.FS.Stat("qux") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "qux") c.Assert(fi.IsDir(), Equals, true) fi, err = s.FS.Stat("qux/baz") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "baz") c.Assert(fi.IsDir(), Equals, false) } func (s *DirSuite) TestReadDir(c *C) { files := []string{"foo", "bar", "qux/baz", "qux/qux"} for _, name := range files { err := util.WriteFile(s.FS, name, nil, 0644) c.Assert(err, IsNil) } info, err := s.FS.ReadDir("/") c.Assert(err, IsNil) c.Assert(info, HasLen, 3) info, err = s.FS.ReadDir("/qux") c.Assert(err, IsNil) c.Assert(info, HasLen, 2) } func (s *DirSuite) TestReadDirNested(c *C) { max := 100 path := "/" for i := 0; i <= max; i++ { path = s.FS.Join(path, strconv.Itoa(i)) } files := []string{s.FS.Join(path, "f1"), s.FS.Join(path, "f2")} for _, name := range files { err := util.WriteFile(s.FS, name, nil, 0644) c.Assert(err, IsNil) } path = "/" for i := 0; i < max; i++ { path = s.FS.Join(path, strconv.Itoa(i)) info, err := s.FS.ReadDir(path) c.Assert(err, IsNil) c.Assert(info, HasLen, 1) } path = s.FS.Join(path, strconv.Itoa(max)) info, err := s.FS.ReadDir(path) c.Assert(err, IsNil) c.Assert(info, HasLen, 2) } func (s *DirSuite) TestReadDirWithMkDirAll(c *C) { err := s.FS.MkdirAll("qux", 0755) c.Assert(err, IsNil) files := []string{"qux/baz", "qux/qux"} for _, name := range files { err := util.WriteFile(s.FS, name, nil, 0644) c.Assert(err, IsNil) } info, err := s.FS.ReadDir("/") c.Assert(err, IsNil) c.Assert(info, HasLen, 1) c.Assert(info[0].IsDir(), Equals, true) info, err = s.FS.ReadDir("/qux") c.Assert(err, IsNil) c.Assert(info, HasLen, 2) } func (s *DirSuite) TestReadDirFileInfo(c *C) { err := util.WriteFile(s.FS, "foo", []byte{'F', 'O', 'O'}, 0644) c.Assert(err, IsNil) info, err := s.FS.ReadDir("/") c.Assert(err, IsNil) c.Assert(info, HasLen, 1) c.Assert(info[0].Size(), Equals, int64(3)) c.Assert(info[0].IsDir(), Equals, false) c.Assert(info[0].Name(), Equals, "foo") } func (s *DirSuite) TestReadDirFileInfoDirs(c *C) { files := []string{"qux/baz/foo"} for _, name := range files { err := util.WriteFile(s.FS, name, []byte{'F', 'O', 'O'}, 0644) c.Assert(err, IsNil) } info, err := s.FS.ReadDir("qux") c.Assert(err, IsNil) c.Assert(info, HasLen, 1) c.Assert(info[0].IsDir(), Equals, true) c.Assert(info[0].Name(), Equals, "baz") info, err = s.FS.ReadDir("qux/baz") c.Assert(err, IsNil) c.Assert(info, HasLen, 1) c.Assert(info[0].Size(), Equals, int64(3)) c.Assert(info[0].IsDir(), Equals, false) c.Assert(info[0].Name(), Equals, "foo") c.Assert(info[0].Mode(), Not(Equals), 0) } func (s *DirSuite) TestRenameToDir(c *C) { err := util.WriteFile(s.FS, "foo", nil, 0644) c.Assert(err, IsNil) err = s.FS.Rename("foo", "bar/qux") c.Assert(err, IsNil) old, err := s.FS.Stat("foo") c.Assert(old, IsNil) c.Assert(os.IsNotExist(err), Equals, true) dir, err := s.FS.Stat("bar") c.Assert(dir, NotNil) c.Assert(err, IsNil) file, err := s.FS.Stat("bar/qux") c.Assert(file.Name(), Equals, "qux") c.Assert(err, IsNil) } func (s *DirSuite) TestRenameDir(c *C) { err := s.FS.MkdirAll("foo", 0755) c.Assert(err, IsNil) err = util.WriteFile(s.FS, "foo/bar", nil, 0644) c.Assert(err, IsNil) err = s.FS.Rename("foo", "bar") c.Assert(err, IsNil) dirfoo, err := s.FS.Stat("foo") c.Assert(dirfoo, IsNil) c.Assert(os.IsNotExist(err), Equals, true) dirbar, err := s.FS.Stat("bar") c.Assert(err, IsNil) c.Assert(dirbar, NotNil) foo, err := s.FS.Stat("foo/bar") c.Assert(os.IsNotExist(err), Equals, true) c.Assert(foo, IsNil) bar, err := s.FS.Stat("bar/bar") c.Assert(err, IsNil) c.Assert(bar, NotNil) } golang-github-go-git-go-billy-5.5.0/test/fs.go000066400000000000000000000121661460000046100210630ustar00rootroot00000000000000package test import ( "os" "runtime" . "gopkg.in/check.v1" . "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/util" ) // FilesystemSuite is a convenient test suite to validate any implementation of // billy.Filesystem type FilesystemSuite struct { FS Filesystem BasicSuite DirSuite SymlinkSuite TempFileSuite ChrootSuite } // NewFilesystemSuite returns a new FilesystemSuite based on the given fs. func NewFilesystemSuite(fs Filesystem) FilesystemSuite { s := FilesystemSuite{FS: fs} s.BasicSuite.FS = s.FS s.DirSuite.FS = s.FS s.SymlinkSuite.FS = s.FS s.TempFileSuite.FS = s.FS s.ChrootSuite.FS = s.FS return s } func (s *FilesystemSuite) TestSymlinkToDir(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } err := s.FS.MkdirAll("dir", 0755) c.Assert(err, IsNil) err = s.FS.Symlink("dir", "link") c.Assert(err, IsNil) fi, err := s.FS.Stat("link") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "link") c.Assert(fi.IsDir(), Equals, true) } func (s *FilesystemSuite) TestSymlinkReadDir(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } err := util.WriteFile(s.FS, "dir/file", []byte("foo"), 0644) c.Assert(err, IsNil) err = s.FS.Symlink("dir", "link") c.Assert(err, IsNil) info, err := s.FS.ReadDir("link") c.Assert(err, IsNil) c.Assert(info, HasLen, 1) c.Assert(info[0].Size(), Equals, int64(3)) c.Assert(info[0].IsDir(), Equals, false) c.Assert(info[0].Name(), Equals, "file") } func (s *FilesystemSuite) TestCreateWithExistantDir(c *C) { err := s.FS.MkdirAll("foo", 0644) c.Assert(err, IsNil) f, err := s.FS.Create("foo") c.Assert(err, NotNil) c.Assert(f, IsNil) } func (s *ChrootSuite) TestReadDirWithChroot(c *C) { files := []string{"foo", "bar", "qux/baz", "qux/qux"} for _, name := range files { err := util.WriteFile(s.FS, name, nil, 0644) c.Assert(err, IsNil) } qux, _ := s.FS.Chroot("/qux") info, err := qux.(Filesystem).ReadDir("/") c.Assert(err, IsNil) c.Assert(info, HasLen, 2) } func (s *FilesystemSuite) TestSymlinkWithChrootBasic(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } qux, _ := s.FS.Chroot("/qux") err := util.WriteFile(qux, "file", nil, 0644) c.Assert(err, IsNil) err = qux.(Filesystem).Symlink("file", "link") c.Assert(err, IsNil) fi, err := qux.Stat("link") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "link") fi, err = s.FS.Stat("qux/link") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "link") } func (s *FilesystemSuite) TestSymlinkWithChrootCrossBounders(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } qux, _ := s.FS.Chroot("/qux") util.WriteFile(s.FS, "file", []byte("foo"), customMode) err := qux.Symlink("../../file", "qux/link") c.Assert(err, Equals, nil) fi, err := qux.Stat("qux/link") c.Assert(fi, NotNil) c.Assert(err, Equals, nil) } func (s *FilesystemSuite) TestReadDirWithLink(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } util.WriteFile(s.FS, "foo/bar", []byte("foo"), customMode) s.FS.Symlink("bar", "foo/qux") info, err := s.FS.ReadDir("/foo") c.Assert(err, IsNil) c.Assert(info, HasLen, 2) } func (s *FilesystemSuite) TestRemoveAllNonExistent(c *C) { c.Assert(util.RemoveAll(s.FS, "non-existent"), IsNil) } func (s *FilesystemSuite) TestRemoveAllEmptyDir(c *C) { c.Assert(s.FS.MkdirAll("empty", os.FileMode(0755)), IsNil) c.Assert(util.RemoveAll(s.FS, "empty"), IsNil) _, err := s.FS.Stat("empty") c.Assert(err, NotNil) c.Assert(os.IsNotExist(err), Equals, true) } func (s *FilesystemSuite) TestRemoveAll(c *C) { fnames := []string{ "foo/1", "foo/2", "foo/bar/1", "foo/bar/2", "foo/bar/baz/1", "foo/bar/baz/qux/1", "foo/bar/baz/qux/2", "foo/bar/baz/qux/3", } for _, fname := range fnames { err := util.WriteFile(s.FS, fname, nil, 0644) c.Assert(err, IsNil) } c.Assert(util.RemoveAll(s.FS, "foo"), IsNil) for _, fname := range fnames { _, err := s.FS.Stat(fname) comment := Commentf("not removed: %s %s", fname, err) c.Assert(os.IsNotExist(err), Equals, true, comment) } } func (s *FilesystemSuite) TestRemoveAllRelative(c *C) { fnames := []string{ "foo/1", "foo/2", "foo/bar/1", "foo/bar/2", "foo/bar/baz/1", "foo/bar/baz/qux/1", "foo/bar/baz/qux/2", "foo/bar/baz/qux/3", } for _, fname := range fnames { err := util.WriteFile(s.FS, fname, nil, 0644) c.Assert(err, IsNil) } c.Assert(util.RemoveAll(s.FS, "foo/bar/.."), IsNil) for _, fname := range fnames { _, err := s.FS.Stat(fname) comment := Commentf("not removed: %s %s", fname, err) c.Assert(os.IsNotExist(err), Equals, true, comment) } } func (s *FilesystemSuite) TestReadDir(c *C) { err := s.FS.MkdirAll("qux", 0755) c.Assert(err, IsNil) files := []string{"foo", "bar", "qux/baz", "qux/qux"} for _, name := range files { err := util.WriteFile(s.FS, name, nil, 0644) c.Assert(err, IsNil) } info, err := s.FS.ReadDir("/") c.Assert(err, IsNil) c.Assert(info, HasLen, 3) info, err = s.FS.ReadDir("/qux") c.Assert(err, IsNil) c.Assert(info, HasLen, 2) } golang-github-go-git-go-billy-5.5.0/test/mock.go000066400000000000000000000064571460000046100214120ustar00rootroot00000000000000package test import ( "bytes" "os" "path" "path/filepath" "github.com/go-git/go-billy/v5" ) type BasicMock struct { CreateArgs []string OpenArgs []string OpenFileArgs [][3]interface{} StatArgs []string RenameArgs [][2]string RemoveArgs []string JoinArgs [][]string } func (fs *BasicMock) Create(filename string) (billy.File, error) { fs.CreateArgs = append(fs.CreateArgs, filename) return &FileMock{name: filename}, nil } func (fs *BasicMock) Open(filename string) (billy.File, error) { fs.OpenArgs = append(fs.OpenArgs, filename) return &FileMock{name: filename}, nil } func (fs *BasicMock) OpenFile(filename string, flag int, mode os.FileMode) (billy.File, error) { fs.OpenFileArgs = append(fs.OpenFileArgs, [3]interface{}{filename, flag, mode}) return &FileMock{name: filename}, nil } func (fs *BasicMock) Stat(filename string) (os.FileInfo, error) { fs.StatArgs = append(fs.StatArgs, filename) return nil, nil } func (fs *BasicMock) Rename(target, link string) error { fs.RenameArgs = append(fs.RenameArgs, [2]string{target, link}) return nil } func (fs *BasicMock) Remove(filename string) error { fs.RemoveArgs = append(fs.RemoveArgs, filename) return nil } func (fs *BasicMock) Join(elem ...string) string { fs.JoinArgs = append(fs.JoinArgs, elem) return path.Join(elem...) } type TempFileMock struct { BasicMock TempFileArgs [][2]string } func (fs *TempFileMock) TempFile(dir, prefix string) (billy.File, error) { fs.TempFileArgs = append(fs.TempFileArgs, [2]string{dir, prefix}) return &FileMock{name: "/tmp/hardcoded/mock/temp"}, nil } type DirMock struct { BasicMock ReadDirArgs []string MkdirAllArgs [][2]interface{} } func (fs *DirMock) ReadDir(path string) ([]os.FileInfo, error) { fs.ReadDirArgs = append(fs.ReadDirArgs, path) return nil, nil } func (fs *DirMock) MkdirAll(filename string, perm os.FileMode) error { fs.MkdirAllArgs = append(fs.MkdirAllArgs, [2]interface{}{filename, perm}) return nil } type SymlinkMock struct { BasicMock LstatArgs []string SymlinkArgs [][2]string ReadlinkArgs []string } func (fs *SymlinkMock) Lstat(filename string) (os.FileInfo, error) { fs.LstatArgs = append(fs.LstatArgs, filename) return nil, nil } func (fs *SymlinkMock) Symlink(target, link string) error { fs.SymlinkArgs = append(fs.SymlinkArgs, [2]string{target, link}) return nil } func (fs *SymlinkMock) Readlink(link string) (string, error) { fs.ReadlinkArgs = append(fs.ReadlinkArgs, link) return filepath.FromSlash(link), nil } type FileMock struct { name string bytes.Buffer } func (f *FileMock) Name() string { return f.name } func (*FileMock) ReadAt(b []byte, off int64) (int, error) { return 0, nil } func (*FileMock) Seek(offset int64, whence int) (int64, error) { return 0, nil } func (*FileMock) Close() error { return nil } func (*FileMock) Lock() error { return nil } func (*FileMock) Unlock() error { return nil } func (*FileMock) Truncate(size int64) error { return nil } type OnlyReadCapFs struct { BasicMock } func (o *OnlyReadCapFs) Capabilities() billy.Capability { return billy.ReadCapability } type NoLockCapFs struct { BasicMock } func (o *NoLockCapFs) Capabilities() billy.Capability { return billy.WriteCapability | billy.ReadCapability | billy.ReadAndWriteCapability | billy.SeekCapability | billy.TruncateCapability } golang-github-go-git-go-billy-5.5.0/test/symlink.go000066400000000000000000000154101460000046100221340ustar00rootroot00000000000000package test import ( "io/ioutil" "os" "runtime" . "gopkg.in/check.v1" . "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/util" ) // SymlinkSuite is a convenient test suite to validate any implementation of // billy.Symlink type SymlinkSuite struct { FS interface { Basic Symlink } } func (s *SymlinkSuite) TestSymlink(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } err := util.WriteFile(s.FS, "file", nil, 0644) c.Assert(err, IsNil) err = s.FS.Symlink("file", "link") c.Assert(err, IsNil) fi, err := s.FS.Stat("link") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "link") } func (s *SymlinkSuite) TestSymlinkCrossDirs(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } err := util.WriteFile(s.FS, "foo/file", nil, 0644) c.Assert(err, IsNil) err = s.FS.Symlink("../foo/file", "bar/link") c.Assert(err, IsNil) fi, err := s.FS.Stat("bar/link") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "link") } func (s *SymlinkSuite) TestSymlinkNested(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } err := util.WriteFile(s.FS, "file", []byte("hello world!"), 0644) c.Assert(err, IsNil) err = s.FS.Symlink("file", "linkA") c.Assert(err, IsNil) err = s.FS.Symlink("linkA", "linkB") c.Assert(err, IsNil) fi, err := s.FS.Stat("linkB") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "linkB") c.Assert(fi.Size(), Equals, int64(12)) } func (s *SymlinkSuite) TestSymlinkWithNonExistentdTarget(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } err := s.FS.Symlink("file", "link") c.Assert(err, IsNil) _, err = s.FS.Stat("link") c.Assert(os.IsNotExist(err), Equals, true) } func (s *SymlinkSuite) TestSymlinkWithExistingLink(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } err := util.WriteFile(s.FS, "link", nil, 0644) c.Assert(err, IsNil) err = s.FS.Symlink("file", "link") c.Assert(err, Not(IsNil)) } func (s *SymlinkSuite) TestOpenWithSymlinkToRelativePath(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } err := util.WriteFile(s.FS, "dir/file", []byte("foo"), 0644) c.Assert(err, IsNil) err = s.FS.Symlink("file", "dir/link") c.Assert(err, IsNil) f, err := s.FS.Open("dir/link") c.Assert(err, IsNil) all, err := ioutil.ReadAll(f) c.Assert(err, IsNil) c.Assert(string(all), Equals, "foo") c.Assert(f.Close(), IsNil) } func (s *SymlinkSuite) TestOpenWithSymlinkToAbsolutePath(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } err := util.WriteFile(s.FS, "dir/file", []byte("foo"), 0644) c.Assert(err, IsNil) err = s.FS.Symlink("/dir/file", "dir/link") c.Assert(err, IsNil) f, err := s.FS.Open("dir/link") c.Assert(err, IsNil) all, err := ioutil.ReadAll(f) c.Assert(err, IsNil) c.Assert(string(all), Equals, "foo") c.Assert(f.Close(), IsNil) } func (s *SymlinkSuite) TestReadlink(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } err := util.WriteFile(s.FS, "file", nil, 0644) c.Assert(err, IsNil) _, err = s.FS.Readlink("file") c.Assert(err, Not(IsNil)) } func (s *SymlinkSuite) TestReadlinkWithRelativePath(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } err := util.WriteFile(s.FS, "dir/file", nil, 0644) c.Assert(err, IsNil) err = s.FS.Symlink("file", "dir/link") c.Assert(err, IsNil) oldname, err := s.FS.Readlink("dir/link") c.Assert(err, IsNil) c.Assert(oldname, Equals, "file") } func (s *SymlinkSuite) TestReadlinkWithAbsolutePath(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } err := util.WriteFile(s.FS, "dir/file", nil, 0644) c.Assert(err, IsNil) err = s.FS.Symlink("/dir/file", "dir/link") c.Assert(err, IsNil) oldname, err := s.FS.Readlink("dir/link") c.Assert(err, IsNil) c.Assert(oldname, Equals, expectedSymlinkTarget) } func (s *SymlinkSuite) TestReadlinkWithNonExistentTarget(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } err := s.FS.Symlink("file", "link") c.Assert(err, IsNil) oldname, err := s.FS.Readlink("link") c.Assert(err, IsNil) c.Assert(oldname, Equals, "file") } func (s *SymlinkSuite) TestReadlinkWithNonExistentLink(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } _, err := s.FS.Readlink("link") c.Assert(os.IsNotExist(err), Equals, true) } func (s *SymlinkSuite) TestStatLink(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } util.WriteFile(s.FS, "foo/bar", []byte("foo"), customMode) s.FS.Symlink("bar", "foo/qux") fi, err := s.FS.Stat("foo/qux") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "qux") c.Assert(fi.Size(), Equals, int64(3)) c.Assert(fi.Mode(), Equals, customMode) c.Assert(fi.ModTime().IsZero(), Equals, false) c.Assert(fi.IsDir(), Equals, false) } func (s *SymlinkSuite) TestLstat(c *C) { util.WriteFile(s.FS, "foo/bar", []byte("foo"), customMode) fi, err := s.FS.Lstat("foo/bar") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "bar") c.Assert(fi.Size(), Equals, int64(3)) c.Assert(fi.Mode()&os.ModeSymlink != 0, Equals, false) c.Assert(fi.ModTime().IsZero(), Equals, false) c.Assert(fi.IsDir(), Equals, false) } func (s *SymlinkSuite) TestLstatLink(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } util.WriteFile(s.FS, "foo/bar", []byte("fosddddaaao"), customMode) s.FS.Symlink("bar", "foo/qux") fi, err := s.FS.Lstat("foo/qux") c.Assert(err, IsNil) c.Assert(fi.Name(), Equals, "qux") c.Assert(fi.Mode()&os.ModeSymlink != 0, Equals, true) c.Assert(fi.ModTime().IsZero(), Equals, false) c.Assert(fi.IsDir(), Equals, false) } func (s *SymlinkSuite) TestRenameWithSymlink(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } err := s.FS.Symlink("file", "link") c.Assert(err, IsNil) err = s.FS.Rename("link", "newlink") c.Assert(err, IsNil) _, err = s.FS.Readlink("newlink") c.Assert(err, IsNil) } func (s *SymlinkSuite) TestRemoveWithSymlink(c *C) { if runtime.GOOS == "plan9" { c.Skip("skipping on Plan 9; symlinks are not supported") } err := util.WriteFile(s.FS, "file", []byte("foo"), 0644) c.Assert(err, IsNil) err = s.FS.Symlink("file", "link") c.Assert(err, IsNil) err = s.FS.Remove("link") c.Assert(err, IsNil) _, err = s.FS.Readlink("link") c.Assert(os.IsNotExist(err), Equals, true) _, err = s.FS.Stat("link") c.Assert(os.IsNotExist(err), Equals, true) _, err = s.FS.Stat("file") c.Assert(err, IsNil) } golang-github-go-git-go-billy-5.5.0/test/tempfile.go000066400000000000000000000040401460000046100222500ustar00rootroot00000000000000package test import ( "strings" . "gopkg.in/check.v1" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/util" ) // TempFileSuite is a convenient test suite to validate any implementation of // billy.TempFile type TempFileSuite struct { FS interface { billy.Basic billy.TempFile } } func (s *TempFileSuite) TestTempFile(c *C) { f, err := s.FS.TempFile("", "bar") c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) c.Assert(strings.Index(f.Name(), "bar"), Not(Equals), -1) } func (s *TempFileSuite) TestTempFileWithPath(c *C) { f, err := s.FS.TempFile("foo", "bar") c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) c.Assert(strings.HasPrefix(f.Name(), s.FS.Join("foo", "bar")), Equals, true) } func (s *TempFileSuite) TestTempFileFullWithPath(c *C) { f, err := s.FS.TempFile("/foo", "bar") c.Assert(err, IsNil) c.Assert(f.Close(), IsNil) c.Assert(strings.Index(f.Name(), s.FS.Join("foo", "bar")), Not(Equals), -1) } func (s *TempFileSuite) TestRemoveTempFile(c *C) { f, err := s.FS.TempFile("test-dir", "test-prefix") c.Assert(err, IsNil) fn := f.Name() c.Assert(f.Close(), IsNil) c.Assert(s.FS.Remove(fn), IsNil) } func (s *TempFileSuite) TestRenameTempFile(c *C) { f, err := s.FS.TempFile("test-dir", "test-prefix") c.Assert(err, IsNil) fn := f.Name() c.Assert(f.Close(), IsNil) c.Assert(s.FS.Rename(fn, "other-path"), IsNil) } func (s *TempFileSuite) TestTempFileMany(c *C) { for i := 0; i < 1024; i++ { var fs []billy.File for j := 0; j < 100; j++ { f, err := s.FS.TempFile("test-dir", "test-prefix") c.Assert(err, IsNil) fs = append(fs, f) } for _, f := range fs { c.Assert(f.Close(), IsNil) c.Assert(s.FS.Remove(f.Name()), IsNil) } } } func (s *TempFileSuite) TestTempFileManyWithUtil(c *C) { for i := 0; i < 1024; i++ { var fs []billy.File for j := 0; j < 100; j++ { f, err := util.TempFile(s.FS, "test-dir", "test-prefix") c.Assert(err, IsNil) fs = append(fs, f) } for _, f := range fs { c.Assert(f.Close(), IsNil) c.Assert(s.FS.Remove(f.Name()), IsNil) } } } golang-github-go-git-go-billy-5.5.0/util/000077500000000000000000000000001460000046100201145ustar00rootroot00000000000000golang-github-go-git-go-billy-5.5.0/util/glob.go000066400000000000000000000050071460000046100213700ustar00rootroot00000000000000package util import ( "path/filepath" "sort" "strings" "github.com/go-git/go-billy/v5" ) // Glob returns the names of all files matching pattern or nil // if there is no matching file. The syntax of patterns is the same // as in Match. The pattern may describe hierarchical names such as // /usr/*/bin/ed (assuming the Separator is '/'). // // Glob ignores file system errors such as I/O errors reading directories. // The only possible returned error is ErrBadPattern, when pattern // is malformed. // // Function originally from https://golang.org/src/path/filepath/match_test.go func Glob(fs billy.Filesystem, pattern string) (matches []string, err error) { if !hasMeta(pattern) { if _, err = fs.Lstat(pattern); err != nil { return nil, nil } return []string{pattern}, nil } dir, file := filepath.Split(pattern) // Prevent infinite recursion. See issue 15879. if dir == pattern { return nil, filepath.ErrBadPattern } var m []string m, err = Glob(fs, cleanGlobPath(dir)) if err != nil { return } for _, d := range m { matches, err = glob(fs, d, file, matches) if err != nil { return } } return } // cleanGlobPath prepares path for glob matching. func cleanGlobPath(path string) string { switch path { case "": return "." case string(filepath.Separator): // do nothing to the path return path default: return path[0 : len(path)-1] // chop off trailing separator } } // glob searches for files matching pattern in the directory dir // and appends them to matches. If the directory cannot be // opened, it returns the existing matches. New matches are // added in lexicographical order. func glob(fs billy.Filesystem, dir, pattern string, matches []string) (m []string, e error) { m = matches fi, err := fs.Stat(dir) if err != nil { return } if !fi.IsDir() { return } names, _ := readdirnames(fs, dir) sort.Strings(names) for _, n := range names { matched, err := filepath.Match(pattern, n) if err != nil { return m, err } if matched { m = append(m, filepath.Join(dir, n)) } } return } // hasMeta reports whether path contains any of the magic characters // recognized by Match. func hasMeta(path string) bool { // TODO(niemeyer): Should other magic characters be added here? return strings.ContainsAny(path, "*?[") } func readdirnames(fs billy.Filesystem, dir string) ([]string, error) { files, err := fs.ReadDir(dir) if err != nil { return nil, err } var names []string for _, file := range files { names = append(names, file.Name()) } return names, nil } golang-github-go-git-go-billy-5.5.0/util/glob_test.go000066400000000000000000000012341460000046100224250ustar00rootroot00000000000000package util_test import ( "path/filepath" "sort" "testing" . "gopkg.in/check.v1" "github.com/go-git/go-billy/v5/memfs" "github.com/go-git/go-billy/v5/util" ) func Test(t *testing.T) { TestingT(t) } var _ = Suite(&UtilSuite{}) type UtilSuite struct{} func (s *UtilSuite) TestCreate(c *C) { fs := memfs.New() util.WriteFile(fs, "foo/qux", nil, 0644) util.WriteFile(fs, "foo/bar", nil, 0644) util.WriteFile(fs, "foo/baz/foo", nil, 0644) names, err := util.Glob(fs, "*/b*") c.Assert(err, IsNil) c.Assert(names, HasLen, 2) sort.Strings(names) c.Assert(names, DeepEquals, []string{ filepath.Join("foo", "bar"), filepath.Join("foo", "baz"), }) } golang-github-go-git-go-billy-5.5.0/util/util.go000066400000000000000000000145311460000046100214240ustar00rootroot00000000000000package util import ( "io" "os" "path/filepath" "strconv" "sync" "time" "github.com/go-git/go-billy/v5" ) // RemoveAll removes path and any children it contains. It removes everything it // can but returns the first error it encounters. If the path does not exist, // RemoveAll returns nil (no error). func RemoveAll(fs billy.Basic, path string) error { fs, path = getUnderlyingAndPath(fs, path) if r, ok := fs.(removerAll); ok { return r.RemoveAll(path) } return removeAll(fs, path) } type removerAll interface { RemoveAll(string) error } func removeAll(fs billy.Basic, path string) error { // This implementation is adapted from os.RemoveAll. // Simple case: if Remove works, we're done. err := fs.Remove(path) if err == nil || os.IsNotExist(err) { return nil } // Otherwise, is this a directory we need to recurse into? dir, serr := fs.Stat(path) if serr != nil { if os.IsNotExist(serr) { return nil } return serr } if !dir.IsDir() { // Not a directory; return the error from Remove. return err } dirfs, ok := fs.(billy.Dir) if !ok { return billy.ErrNotSupported } // Directory. fis, err := dirfs.ReadDir(path) if err != nil { if os.IsNotExist(err) { // Race. It was deleted between the Lstat and Open. // Return nil per RemoveAll's docs. return nil } return err } // Remove contents & return first error. err = nil for _, fi := range fis { cpath := fs.Join(path, fi.Name()) err1 := removeAll(fs, cpath) if err == nil { err = err1 } } // Remove directory. err1 := fs.Remove(path) if err1 == nil || os.IsNotExist(err1) { return nil } if err == nil { err = err1 } return err } // WriteFile writes data to a file named by filename in the given filesystem. // If the file does not exist, WriteFile creates it with permissions perm; // otherwise WriteFile truncates it before writing. func WriteFile(fs billy.Basic, filename string, data []byte, perm os.FileMode) error { f, err := fs.OpenFile(filename, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, perm) if err != nil { return err } n, err := f.Write(data) if err == nil && n < len(data) { err = io.ErrShortWrite } if err1 := f.Close(); err == nil { err = err1 } return err } // Random number state. // We generate random temporary file names so that there's a good // chance the file doesn't exist yet - keeps the number of tries in // TempFile to a minimum. var rand uint32 var randmu sync.Mutex func reseed() uint32 { return uint32(time.Now().UnixNano() + int64(os.Getpid())) } func nextSuffix() string { randmu.Lock() r := rand if r == 0 { r = reseed() } r = r*1664525 + 1013904223 // constants from Numerical Recipes rand = r randmu.Unlock() return strconv.Itoa(int(1e9 + r%1e9))[1:] } // TempFile creates a new temporary file in the directory dir with a name // beginning with prefix, opens the file for reading and writing, and returns // the resulting *os.File. If dir is the empty string, TempFile uses the default // directory for temporary files (see os.TempDir). Multiple programs calling // TempFile simultaneously will not choose the same file. The caller can use // f.Name() to find the pathname of the file. It is the caller's responsibility // to remove the file when no longer needed. func TempFile(fs billy.Basic, dir, prefix string) (f billy.File, err error) { // This implementation is based on stdlib ioutil.TempFile. if dir == "" { dir = getTempDir(fs) } nconflict := 0 for i := 0; i < 10000; i++ { name := filepath.Join(dir, prefix+nextSuffix()) f, err = fs.OpenFile(name, os.O_RDWR|os.O_CREATE|os.O_EXCL, 0600) if os.IsExist(err) { if nconflict++; nconflict > 10 { randmu.Lock() rand = reseed() randmu.Unlock() } continue } break } return } // TempDir creates a new temporary directory in the directory dir // with a name beginning with prefix and returns the path of the // new directory. If dir is the empty string, TempDir uses the // default directory for temporary files (see os.TempDir). // Multiple programs calling TempDir simultaneously // will not choose the same directory. It is the caller's responsibility // to remove the directory when no longer needed. func TempDir(fs billy.Dir, dir, prefix string) (name string, err error) { // This implementation is based on stdlib ioutil.TempDir if dir == "" { dir = getTempDir(fs.(billy.Basic)) } nconflict := 0 for i := 0; i < 10000; i++ { try := filepath.Join(dir, prefix+nextSuffix()) err = fs.MkdirAll(try, 0700) if os.IsExist(err) { if nconflict++; nconflict > 10 { randmu.Lock() rand = reseed() randmu.Unlock() } continue } if os.IsNotExist(err) { if _, err := os.Stat(dir); os.IsNotExist(err) { return "", err } } if err == nil { name = try } break } return } func getTempDir(fs billy.Basic) string { ch, ok := fs.(billy.Chroot) if !ok || ch.Root() == "" || ch.Root() == "/" || ch.Root() == string(filepath.Separator) { return os.TempDir() } return ".tmp" } type underlying interface { Underlying() billy.Basic } func getUnderlyingAndPath(fs billy.Basic, path string) (billy.Basic, string) { u, ok := fs.(underlying) if !ok { return fs, path } if ch, ok := fs.(billy.Chroot); ok { path = fs.Join(ch.Root(), path) } return u.Underlying(), path } // ReadFile reads the named file and returns the contents from the given filesystem. // A successful call returns err == nil, not err == EOF. // Because ReadFile reads the whole file, it does not treat an EOF from Read // as an error to be reported. func ReadFile(fs billy.Basic, name string) ([]byte, error) { f, err := fs.Open(name) if err != nil { return nil, err } defer f.Close() var size int if info, err := fs.Stat(name); err == nil { size64 := info.Size() if int64(int(size64)) == size64 { size = int(size64) } } size++ // one byte for final read at EOF // If a file claims a small size, read at least 512 bytes. // In particular, files in Linux's /proc claim size 0 but // then do not work right if read in small pieces, // so an initial read of 1 byte would not work correctly. if size < 512 { size = 512 } data := make([]byte, 0, size) for { if len(data) >= cap(data) { d := append(data[:cap(data)], 0) data = d[:len(data)] } n, err := f.Read(data[len(data):cap(data)]) data = data[:len(data)+n] if err != nil { if err == io.EOF { err = nil } return data, err } } } golang-github-go-git-go-billy-5.5.0/util/util_test.go000066400000000000000000000035451460000046100224660ustar00rootroot00000000000000package util_test import ( "os" "path/filepath" "regexp" "testing" "github.com/go-git/go-billy/v5/memfs" "github.com/go-git/go-billy/v5/util" ) func TestTempFile(t *testing.T) { fs := memfs.New() dir, err := util.TempDir(fs, "", "util_test") if err != nil { t.Fatal(err) } defer util.RemoveAll(fs, dir) f, err := util.TempFile(fs, dir, "foo") if f == nil || err != nil { t.Errorf("TempFile(%q, `foo`) = %v, %v", dir, f, err) } } func TestTempDir_WithDir(t *testing.T) { fs := memfs.New() dir := os.TempDir() name, err := util.TempDir(fs, dir, "util_test") if name == "" || err != nil { t.Errorf("TempDir(dir, `util_test`) = %v, %v", name, err) } if name != "" { util.RemoveAll(fs, name) re := regexp.MustCompile("^" + regexp.QuoteMeta(filepath.Join(dir, "util_test")) + "[0-9]+$") if !re.MatchString(name) { t.Errorf("TempDir(`"+dir+"`, `util_test`) created bad name %s", name) } } } func TestReadFile(t *testing.T) { fs := memfs.New() f, err := util.TempFile(fs, "", "") if err != nil { t.Fatal(err) } f.Write([]byte("foo")) f.Close() data, err := util.ReadFile(fs, f.Name()) if err != nil { t.Fatal(err) } if string(data) != "foo" || err != nil { t.Errorf("ReadFile(%q, %q) = %v, %v", fs, f.Name(), data, err) } } func TestTempDir(t *testing.T) { fs := memfs.New() f, err := util.TempDir(fs, "", "") if err != nil { t.Fatal(err) } _, err = filepath.Rel(os.TempDir(), f) if err != nil { t.Errorf(`TempDir(fs, "", "") = %s, should be relative to os.TempDir if root filesystem`, f) } } func TestTempDir_WithNonRoot(t *testing.T) { fs := memfs.New() fs, _ = fs.Chroot("foo") f, err := util.TempDir(fs, "", "") if err != nil { t.Fatal(err) } _, err = filepath.Rel(os.TempDir(), f) if err == nil { t.Errorf(`TempDir(fs, "", "") = %s, should not be relative to os.TempDir on not root filesystem`, f) } } golang-github-go-git-go-billy-5.5.0/util/walk.go000066400000000000000000000042271460000046100214060ustar00rootroot00000000000000package util import ( "os" "path/filepath" "github.com/go-git/go-billy/v5" ) // walk recursively descends path, calling walkFn // adapted from https://golang.org/src/path/filepath/path.go func walk(fs billy.Filesystem, path string, info os.FileInfo, walkFn filepath.WalkFunc) error { if !info.IsDir() { return walkFn(path, info, nil) } names, err := readdirnames(fs, path) err1 := walkFn(path, info, err) // If err != nil, walk can't walk into this directory. // err1 != nil means walkFn want walk to skip this directory or stop walking. // Therefore, if one of err and err1 isn't nil, walk will return. if err != nil || err1 != nil { // The caller's behavior is controlled by the return value, which is decided // by walkFn. walkFn may ignore err and return nil. // If walkFn returns SkipDir, it will be handled by the caller. // So walk should return whatever walkFn returns. return err1 } for _, name := range names { filename := filepath.Join(path, name) fileInfo, err := fs.Lstat(filename) if err != nil { if err := walkFn(filename, fileInfo, err); err != nil && err != filepath.SkipDir { return err } } else { err = walk(fs, filename, fileInfo, walkFn) if err != nil { if !fileInfo.IsDir() || err != filepath.SkipDir { return err } } } } return nil } // Walk walks the file tree rooted at root, calling fn for each file or // directory in the tree, including root. All errors that arise visiting files // and directories are filtered by fn: see the WalkFunc documentation for // details. // // The files are walked in lexical order, which makes the output deterministic // but requires Walk to read an entire directory into memory before proceeding // to walk that directory. Walk does not follow symbolic links. // // Function adapted from https://github.com/golang/go/blob/3b770f2ccb1fa6fecc22ea822a19447b10b70c5c/src/path/filepath/path.go#L500 func Walk(fs billy.Filesystem, root string, walkFn filepath.WalkFunc) error { info, err := fs.Lstat(root) if err != nil { err = walkFn(root, nil, err) } else { err = walk(fs, root, info, walkFn) } if err == filepath.SkipDir { return nil } return err } golang-github-go-git-go-billy-5.5.0/util/walk_test.go000066400000000000000000000156221460000046100224460ustar00rootroot00000000000000package util_test import ( "errors" "fmt" "os" "path/filepath" "reflect" "testing" "github.com/go-git/go-billy/v5" "github.com/go-git/go-billy/v5/memfs" "github.com/go-git/go-billy/v5/util" . "gopkg.in/check.v1" ) type WalkSuite struct{} func TestWalk(t *testing.T) { TestingT(t) } var _ = Suite(&WalkSuite{}) var targetSubfolder = filepath.FromSlash("path/to/some/subfolder") func (s *WalkSuite) TestWalkCanSkipTopDirectory(c *C) { filesystem := memfs.New() c.Assert(util.Walk(filesystem, "/root/that/does/not/exist", func(path string, info os.FileInfo, err error) error { return filepath.SkipDir }), IsNil) } func (s *WalkSuite) TestWalkReturnsAnErrorWhenRootDoesNotExist(c *C) { filesystem := memfs.New() c.Assert(util.Walk(filesystem, "/root/that/does/not/exist", func(path string, info os.FileInfo, err error) error { return err }), NotNil) } func (s *WalkSuite) TestWalkOnPlainFile(c *C) { filesystem := memfs.New() createFile(c, filesystem, "./README.md") discoveredPaths := []string{} c.Assert(util.Walk(filesystem, "./README.md", func(path string, info os.FileInfo, err error) error { discoveredPaths = append(discoveredPaths, path) return nil }), IsNil) c.Assert(discoveredPaths, DeepEquals, []string{"./README.md"}) } func (s *WalkSuite) TestWalkOnExistingFolder(c *C) { filesystem := memfs.New() createFile(c, filesystem, "path/to/some/subfolder/that/contain/file") createFile(c, filesystem, "path/to/some/file") discoveredPaths := []string{} c.Assert(util.Walk(filesystem, "path", func(path string, info os.FileInfo, err error) error { discoveredPaths = append(discoveredPaths, path) return nil }), IsNil) c.Assert(discoveredPaths, Contains, "path") c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to")) c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to/some")) c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to/some/file")) c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to/some/subfolder")) c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to/some/subfolder/that")) c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to/some/subfolder/that/contain")) c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to/some/subfolder/that/contain/file")) } func (s *WalkSuite) TestWalkCanSkipFolder(c *C) { filesystem := memfs.New() createFile(c, filesystem, "path/to/some/subfolder/that/contain/file") createFile(c, filesystem, "path/to/some/file") discoveredPaths := []string{} c.Assert(util.Walk(filesystem, "path", func(path string, info os.FileInfo, err error) error { discoveredPaths = append(discoveredPaths, path) if path == targetSubfolder { return filepath.SkipDir } return nil }), IsNil) c.Assert(discoveredPaths, Contains, "path") c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to")) c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to/some")) c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to/some/file")) c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to/some/subfolder")) c.Assert(discoveredPaths, NotContain, filepath.FromSlash("path/to/some/subfolder/that")) c.Assert(discoveredPaths, NotContain, filepath.FromSlash("path/to/some/subfolder/that/contain")) c.Assert(discoveredPaths, NotContain, filepath.FromSlash("path/to/some/subfolder/that/contain/file")) } func (s *WalkSuite) TestWalkStopsOnError(c *C) { filesystem := memfs.New() createFile(c, filesystem, "path/to/some/subfolder/that/contain/file") createFile(c, filesystem, "path/to/some/file") discoveredPaths := []string{} c.Assert(util.Walk(filesystem, "path", func(path string, info os.FileInfo, err error) error { discoveredPaths = append(discoveredPaths, path) if path == targetSubfolder { return errors.New("uncaught error") } return nil }), NotNil) c.Assert(discoveredPaths, Contains, "path") c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to")) c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to/some")) c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to/some/file")) c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to/some/subfolder")) c.Assert(discoveredPaths, NotContain, filepath.FromSlash("path/to/some/subfolder/that")) c.Assert(discoveredPaths, NotContain, filepath.FromSlash("path/to/some/subfolder/that/contain")) c.Assert(discoveredPaths, NotContain, filepath.FromSlash("path/to/some/subfolder/that/contain/file")) } func (s *WalkSuite) TestWalkForwardsStatErrors(c *C) { memFilesystem := memfs.New() filesystem := &fnFs{ Filesystem: memFilesystem, lstat: func(path string) (os.FileInfo, error) { if path == targetSubfolder { return nil, errors.New("uncaught error") } return memFilesystem.Lstat(path) }, } createFile(c, filesystem, "path/to/some/subfolder/that/contain/file") createFile(c, filesystem, "path/to/some/file") discoveredPaths := []string{} c.Assert(util.Walk(filesystem, "path", func(path string, info os.FileInfo, err error) error { discoveredPaths = append(discoveredPaths, path) if path == targetSubfolder { c.Assert(err, NotNil) } return err }), NotNil) c.Assert(discoveredPaths, Contains, "path") c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to")) c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to/some")) c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to/some/file")) c.Assert(discoveredPaths, Contains, filepath.FromSlash("path/to/some/subfolder")) c.Assert(discoveredPaths, NotContain, filepath.FromSlash("path/to/some/subfolder/that")) c.Assert(discoveredPaths, NotContain, filepath.FromSlash("path/to/some/subfolder/that/contain")) c.Assert(discoveredPaths, NotContain, filepath.FromSlash("path/to/some/subfolder/that/contain/file")) } func createFile(c *C, filesystem billy.Filesystem, path string) { fd, err := filesystem.Create(path) c.Assert(err, IsNil) if err != nil { fd.Close() } } type fnFs struct { billy.Filesystem lstat func(path string) (os.FileInfo, error) } func (f *fnFs) Lstat(path string) (os.FileInfo, error) { if f.lstat != nil { return f.lstat(path) } return nil, errors.New("not implemented") } type containsChecker struct { *CheckerInfo } func (checker *containsChecker) Check(params []interface{}, names []string) (result bool, err string) { defer func() { if v := recover(); v != nil { result = false err = fmt.Sprint(v) } }() value := reflect.ValueOf(params[0]) result = false err = fmt.Sprintf("%v does not contain %v", params[0], params[1]) switch value.Kind() { case reflect.Array, reflect.Slice: for i := 0; i < value.Len(); i++ { r := reflect.DeepEqual(value.Index(i).Interface(), params[1]) if r { result = true err = "" } } default: return false, "obtained value type is not iterable" } return } var Contains Checker = &containsChecker{ &CheckerInfo{Name: "Contains", Params: []string{"obtained", "expected"}}, } var NotContain Checker = Not(Contains)