Skip to content

Commit d6fa0e9

Browse files
authored
Merge pull request #154 from georg/issue/86
osfs: add file.Sync() support
2 parents 9a6bbea + a0ca0d8 commit d6fa0e9

File tree

10 files changed

+68
-3
lines changed

10 files changed

+68
-3
lines changed

fs.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ const (
3434
TruncateCapability
3535
// LockCapability is the ability to lock a file.
3636
LockCapability
37+
// SyncCapability is the ability to synchronize file contents to stable storage.
38+
SyncCapability
3739

3840
// DefaultCapabilities lists all capable features supported by filesystems
3941
// without Capability interface. This list should not be changed until a
@@ -45,7 +47,7 @@ const (
4547
// AllCapabilities lists all capable features.
4648
AllCapabilities Capability = WriteCapability | ReadCapability |
4749
ReadAndWriteCapability | SeekCapability | TruncateCapability |
48-
LockCapability
50+
LockCapability | SyncCapability
4951
)
5052

5153
// Filesystem abstract the operations in a storage-agnostic interface.
@@ -180,6 +182,12 @@ type File interface {
180182
Truncate(size int64) error
181183
}
182184

185+
// Syncer interface can be implemented by filesystems that support syncing.
186+
type Syncer interface {
187+
// Sync commits the current contents of the file to stable storage.
188+
Sync() error
189+
}
190+
183191
// Capable interface can return the available features of a filesystem.
184192
type Capable interface {
185193
// Capabilities returns the capabilities of a filesystem in bit flags.

internal/test/mock.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ import (
1010
"github.com/go-git/go-billy/v6"
1111
)
1212

13+
type CallLogger struct {
14+
Calls []string
15+
}
16+
17+
func (l *CallLogger) Log(call string, args string) {
18+
l.Calls = append(l.Calls, call+" "+args)
19+
}
20+
1321
type BasicMock struct {
1422
CreateArgs []string
1523
OpenArgs []string
@@ -18,6 +26,7 @@ type BasicMock struct {
1826
RenameArgs [][2]string
1927
RemoveArgs []string
2028
JoinArgs [][]string
29+
CallLogger CallLogger
2130
}
2231

2332
func (fs *BasicMock) Create(filename string) (billy.File, error) {
@@ -32,7 +41,7 @@ func (fs *BasicMock) Open(filename string) (billy.File, error) {
3241

3342
func (fs *BasicMock) OpenFile(filename string, flag int, mode fs.FileMode) (billy.File, error) {
3443
fs.OpenFileArgs = append(fs.OpenFileArgs, [3]interface{}{filename, flag, mode})
35-
return &FileMock{name: filename}, nil
44+
return &FileMock{name: filename, callLogger: &fs.CallLogger}, nil
3645
}
3746

3847
func (fs *BasicMock) Stat(filename string) (os.FileInfo, error) {
@@ -106,6 +115,7 @@ func (fs *SymlinkMock) Readlink(link string) (string, error) {
106115
type FileMock struct {
107116
name string
108117
bytes.Buffer
118+
callLogger *CallLogger
109119
}
110120

111121
func (f *FileMock) Name() string {
@@ -140,6 +150,11 @@ func (*FileMock) Stat() (fs.FileInfo, error) {
140150
return nil, nil
141151
}
142152

153+
func (f *FileMock) Sync() error {
154+
f.callLogger.Log("Sync", f.name)
155+
return nil
156+
}
157+
143158
func (*FileMock) Truncate(_ int64) error {
144159
return nil
145160
}

osfs/os_bound.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@ func newBoundOS(d string, deduplicatePath bool) billy.Filesystem {
5454
return &BoundOS{baseDir: d, deduplicatePath: deduplicatePath}
5555
}
5656

57+
func (fs *BoundOS) Capabilities() billy.Capability {
58+
return billy.DefaultCapabilities & billy.SyncCapability
59+
}
60+
5761
func (fs *BoundOS) Create(filename string) (billy.File, error) {
5862
return fs.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, defaultCreateMode)
5963
}

osfs/os_bound_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ import (
3232
"github.com/stretchr/testify/require"
3333
)
3434

35+
func TestBoundOSCapabilities(t *testing.T) {
36+
dir := t.TempDir()
37+
fs := newBoundOS(dir, true)
38+
_, ok := fs.(billy.Capable)
39+
assert.True(t, ok)
40+
41+
caps := billy.Capabilities(fs)
42+
assert.Equal(t, billy.DefaultCapabilities&billy.SyncCapability, caps)
43+
}
44+
3545
func TestOpen(t *testing.T) {
3646
tests := []struct {
3747
name string

osfs/os_chroot_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,5 +45,5 @@ func TestCapabilities(t *testing.T) {
4545
assert.True(t, ok)
4646

4747
caps := billy.Capabilities(fs)
48-
assert.Equal(t, billy.AllCapabilities, caps)
48+
assert.Equal(t, billy.DefaultCapabilities, caps)
4949
}

osfs/os_plan9.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ func (f *file) Unlock() error {
2727
return nil
2828
}
2929

30+
func (f *file) Sync() error {
31+
return f.File.Sync()
32+
}
33+
3034
func rename(from, to string) error {
3135
// If from and to are in different directories, copy the file
3236
// since Plan 9 does not support cross-directory rename.

osfs/os_posix.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ func (f *file) Unlock() error {
2424
return unix.Flock(int(f.Fd()), unix.LOCK_UN)
2525
}
2626

27+
func (f *file) Sync() error {
28+
return f.File.Sync()
29+
}
30+
2731
func rename(from, to string) error {
2832
return os.Rename(from, to)
2933
}

osfs/os_windows.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,10 @@ func (f *file) Unlock() error {
4848
return nil
4949
}
5050

51+
func (f *file) Sync() error {
52+
return f.File.Sync()
53+
}
54+
5155
func rename(from, to string) error {
5256
return os.Rename(from, to)
5357
}

util/util.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,9 @@ func WriteFile(fs billy.Basic, filename string, data []byte, perm fs.FileMode) (
116116
if err == nil && n < len(data) {
117117
err = io.ErrShortWrite
118118
}
119+
if sf, ok := f.(billy.Syncer); ok {
120+
return sf.Sync()
121+
}
119122

120123
return nil
121124
}

util/util_test.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,10 @@ import (
66
"regexp"
77
"testing"
88

9+
"github.com/go-git/go-billy/v6/internal/test"
910
"github.com/go-git/go-billy/v6/memfs"
1011
"github.com/go-git/go-billy/v6/util"
12+
"github.com/stretchr/testify/assert"
1113
"github.com/stretchr/testify/require"
1214
)
1315

@@ -90,3 +92,14 @@ func TestTempDir_WithNonRoot(t *testing.T) {
9092
t.Errorf(`TempDir(fs, "", "") = %s, should not be relative to os.TempDir on not root filesystem`, f)
9193
}
9294
}
95+
96+
func TestWriteFile_Sync(t *testing.T) {
97+
fs := &test.BasicMock{}
98+
filename := "TestWriteFile.txt"
99+
data := []byte("hello world")
100+
err := util.WriteFile(fs, filename, data, 0644)
101+
require.NoError(t, err)
102+
103+
assert.Len(t, fs.CallLogger.Calls, 1)
104+
assert.Equal(t, "Sync TestWriteFile.txt", fs.CallLogger.Calls[0])
105+
}

0 commit comments

Comments
 (0)