Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

httpfs: implement http.FileSystem interfaces #23

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ jobs:
test:
strategy:
matrix:
go-version: [1.14.x, 1.15.x, 1.16.x]
go-version: [1.18.x]
platform: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.platform }}
steps:
Expand All @@ -15,4 +15,4 @@ jobs:
- name: Checkout code
uses: actions/checkout@v2
- name: Test
run: go test ./...
run: go test ./...
13 changes: 8 additions & 5 deletions go.mod
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
module github.com/go-git/go-billy/v5

go 1.18

require (
github.com/kr/text v0.2.0 // indirect
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e // indirect
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c
)

go 1.13
require (
github.com/kr/pretty v0.2.1 // indirect
github.com/kr/text v0.2.0 // indirect
)
15 changes: 6 additions & 9 deletions go.sum
Original file line number Diff line number Diff line change
@@ -1,14 +1,11 @@
github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E=
github.com/go-git/go-billy v1.0.0 h1:bXR6Zu3opPSg0R4dDxqaLglY4rxw7ja7wS16qSpOKL4=
github.com/go-git/go-billy v3.1.0+incompatible h1:dwrJ8G2Jt1srYgIJs+lRjA36qBY68O2Lg5idKG8ef5M=
github.com/kr/pretty v0.2.1 h1:Fmg33tUaq4/8ym9TJN1x7sLJnHVwhP33CNkpYV/7rwI=
github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
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/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527 h1:uYVVQ9WP/Ds2ROhcaGPeIdVq0RIXVLwsHlnvJ+cT1So=
golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e h1:NHvCuwuS43lGnYhten69ZWqi2QOj/CiDNcKbVqwVoew=
golang.org/x/sys v0.0.0-20220712014510-0a85c31ab51e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
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=
52 changes: 52 additions & 0 deletions httpfs/dir.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package httpfs

import (
"errors"
"io/fs"
"net/http"
)

// Dir implements the HTTP directory.
type Dir struct {
// fs is the base filesysetm
fs BillyFs
// path is the path to this dir
path string
}

// NewDir constructs the Dir from a Billy Dir.
func NewDir(fs BillyFs, path string) *Dir {
return &Dir{fs: fs, path: path}
}

func (f *Dir) Stat() (fs.FileInfo, error) {
return f.fs.Stat(f.path)
}

// Readdir reads the directory contents.
func (f *Dir) Readdir(count int) ([]fs.FileInfo, error) {
ents, err := f.fs.ReadDir(f.path)
if err != nil {
return nil, err
}
if count > 0 && count > len(ents) {
ents = ents[:count]
}
return ents, err
}

func (f *Dir) Read(p []byte) (n int, err error) {
return 0, errors.New("not a file")
}

func (f *Dir) Seek(offset int64, whence int) (int64, error) {
return 0, errors.New("not a file")
}

func (f *Dir) Close() error {
// no-op.
return nil
}

// _ is a type assertion
var _ http.File = ((*Dir)(nil))
40 changes: 40 additions & 0 deletions httpfs/file.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package httpfs

import (
"errors"
"io/fs"
"net/http"

"github.com/go-git/go-billy/v5"
)

// File implements the HTTP file.
type File struct {
// File is the billy file
billy.File
// path is the path to File
path string
// fs is the filesystem
fs BillyFs
}

// NewFile constructs the File from a Billy File.
func NewFile(fs BillyFs, path string) (*File, error) {
f, err := fs.Open(path)
if err != nil {
return nil, err
}
return &File{File: f, path: path, fs: fs}, nil
}

func (f *File) Readdir(count int) ([]fs.FileInfo, error) {
// ENOTDIR
return nil, errors.New("not a directory")
}

func (f *File) Stat() (fs.FileInfo, error) {
return f.fs.Stat(f.path)
}

// _ is a type assertion
var _ http.File = ((*File)(nil))
58 changes: 58 additions & 0 deletions httpfs/filesystem.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package httpfs

import (
"net/http"
"path"
"strings"

"github.com/go-git/go-billy/v5"
)

// BillyFs is the set of required billy filesystem interfaces.
type BillyFs interface {
billy.Basic
billy.Dir
}

// FileSystem implements the HTTP filesystem.
type FileSystem struct {
// fs is the billy filesystem
fs BillyFs
// prefix is the filesystem prefix for HTTP
prefix string
}

// NewFileSystem constructs the FileSystem from a Billy FileSystem.
//
// Prefix is a path prefix to prepend to file paths for HTTP.
// The prefix is trimmed from the paths when opening files.
func NewFileSystem(fs BillyFs, prefix string) *FileSystem {
if len(prefix) != 0 {
prefix = path.Clean(prefix)
}
return &FileSystem{fs: fs, prefix: prefix}
}

// Open opens the file at the given path.
func (f *FileSystem) Open(name string) (http.File, error) {
name = path.Clean(name)
if len(f.prefix) != 0 {
name = strings.TrimPrefix(name, f.prefix)
name = path.Clean(name)
}
if strings.HasPrefix(name, "/") {
name = name[1:]
}

fi, err := f.fs.Stat(name)
if err != nil {
return nil, err
}
if fi.IsDir() {
return NewDir(f.fs, name), nil
}
return NewFile(f.fs, name)
}

// _ is a type assertion
var _ http.FileSystem = ((*FileSystem)(nil))
50 changes: 50 additions & 0 deletions httpfs/filesystem_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package httpfs

import (
"bytes"
"io/ioutil"
"net/http"
"net/http/httptest"
"testing"

"github.com/go-git/go-billy/v5/memfs"
"github.com/go-git/go-billy/v5/util"
)

// TestFileSystem tests the HTTP filesystem.
func TestFileSystem(t *testing.T) {
mfs := memfs.New()

err := mfs.MkdirAll("./stuff", 0755)
if err != nil {
t.Fatal(err.Error())
}

data := []byte("hello world!\n")
err = util.WriteFile(mfs, "./stuff/test.txt", data, 0755)
if err != nil {
t.Fatal(err.Error())
}

var hfs http.FileSystem = NewFileSystem(mfs, "/test")

mux := http.NewServeMux()
mux.Handle("/", http.FileServer(hfs))

req := httptest.NewRequest("GET", "/test/stuff/test.txt", nil)
rw := httptest.NewRecorder()
mux.ServeHTTP(rw, req)

res := rw.Result()
if res.StatusCode != 200 {
t.Fatalf("status code: %d", res.StatusCode)
}

readData, err := ioutil.ReadAll(res.Body)
if err != nil {
t.Fatal(err.Error())
}
if !bytes.Equal(readData, data) {
t.Fail()
}
}