Skip to content

Commit d97c785

Browse files
committed
fs: specify a file as existing if it's empty
If the object has a size of zero, then we already have its object: it's the empty file, and we shouldn't need to download it from anywhere. If we inquire if it exists, then say it does, and say its location is the system's equivalent of /dev/null. The only tricky case is if we're de-duplicating, linking, or writing to a file, in which case we should not try to use a link or replace on the file, since we neither want to link nor replace the system's /dev/null.
1 parent af30ce0 commit d97c785

File tree

8 files changed

+75
-6
lines changed

8 files changed

+75
-6
lines changed

commands/command_dedup.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,10 @@ func dedup(p *lfs.WrappedPointer) (success bool, err error) {
112112

113113
// Do clone
114114
srcFile := cfg.Filesystem().ObjectPathname(p.Oid)
115+
if srcFile == os.DevNull {
116+
return true, nil
117+
}
118+
115119
dstFile := filepath.Join(cfg.LocalWorkingDir(), p.Name)
116120

117121
// Clone the file. This overwrites the destination if it exists.

commands/command_fsck.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,11 @@ func fsckCommand(cmd *cobra.Command, args []string) {
107107

108108
for _, oid := range corruptOids {
109109
badFile := filepath.Join(badDir, oid)
110-
if err := os.Rename(cfg.Filesystem().ObjectPathname(oid), badFile); err != nil {
110+
srcFile := cfg.Filesystem().ObjectPathname(oid)
111+
if srcFile == os.DevNull {
112+
continue
113+
}
114+
if err := os.Rename(srcFile, badFile); err != nil {
111115
ExitWithError(err)
112116
}
113117
}

commands/command_prune.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -307,6 +307,9 @@ func pruneDeleteFiles(prunableObjects []string, logger *tasklog.Logger) {
307307
problems.WriteString(fmt.Sprintf("Unable to find media path for %v: %v\n", oid, err))
308308
continue
309309
}
310+
if mediaFile == os.DevNull {
311+
continue
312+
}
310313
err = os.Remove(mediaFile)
311314
if err != nil {
312315
problems.WriteString(fmt.Sprintf("Failed to remove file %v: %v\n", mediaFile, err))

fs/fs.go

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package fs
33
import (
44
"bufio"
55
"bytes"
6+
"crypto/sha256"
7+
"encoding/hex"
68
"fmt"
79
"io/ioutil"
810
"os"
@@ -16,7 +18,10 @@ import (
1618
"github.com/rubyist/tracerx"
1719
)
1820

19-
var oidRE = regexp.MustCompile(`\A[[:alnum:]]{64}`)
21+
var (
22+
oidRE = regexp.MustCompile(`\A[[:alnum:]]{64}`)
23+
EmptyObjectSHA256 = hex.EncodeToString(sha256.New().Sum(nil))
24+
)
2025

2126
// Environment is a copy of a subset of the interface
2227
// github.com/git-lfs/git-lfs/config.Environment.
@@ -61,13 +66,19 @@ func (f *Filesystem) EachObject(fn func(Object) error) error {
6166
}
6267

6368
func (f *Filesystem) ObjectExists(oid string, size int64) bool {
69+
if size == 0 {
70+
return true
71+
}
6472
return tools.FileExistsOfSize(f.ObjectPathname(oid), size)
6573
}
6674

6775
func (f *Filesystem) ObjectPath(oid string) (string, error) {
6876
if len(oid) < 4 {
6977
return "", fmt.Errorf("too short object ID: %q", oid)
7078
}
79+
if oid == EmptyObjectSHA256 {
80+
return os.DevNull, nil
81+
}
7182
dir := f.localObjectDir(oid)
7283
if err := tools.MkdirAll(dir, f); err != nil {
7384
return "", fmt.Errorf("error trying to create local storage directory in %q: %s", dir, err)
@@ -76,6 +87,9 @@ func (f *Filesystem) ObjectPath(oid string) (string, error) {
7687
}
7788

7889
func (f *Filesystem) ObjectPathname(oid string) string {
90+
if oid == EmptyObjectSHA256 {
91+
return os.DevNull
92+
}
7993
return filepath.Join(f.localObjectDir(oid), oid)
8094
}
8195

lfs/pointer.go

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@ package lfs
33
import (
44
"bufio"
55
"bytes"
6-
"crypto/sha256"
7-
"encoding/hex"
86
"fmt"
97
"io"
108
"os"
@@ -14,6 +12,7 @@ import (
1412
"strings"
1513

1614
"github.com/git-lfs/git-lfs/v3/errors"
15+
"github.com/git-lfs/git-lfs/v3/fs"
1716
"github.com/git-lfs/gitobj/v2"
1817
)
1918

@@ -82,8 +81,7 @@ func (p *Pointer) Encoded() string {
8281
}
8382

8483
func EmptyPointer() *Pointer {
85-
oid := hex.EncodeToString(sha256.New().Sum(nil))
86-
return NewPointer(oid, 0, nil)
84+
return NewPointer(fs.EmptyObjectSHA256, 0, nil)
8785
}
8886

8987
func EncodePointer(writer io.Writer, pointer *Pointer) (int, error) {

t/t-fetch.sh

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,23 @@ begin_test "fetch"
7171
)
7272
end_test
7373

74+
begin_test "fetch (empty file)"
75+
(
76+
set -e
77+
cd clone
78+
rm -rf .git/lfs/objects
79+
80+
touch empty.dat
81+
git add empty.dat
82+
git commit -m 'empty'
83+
84+
git lfs fetch
85+
86+
git lfs fsck 2>&1 | tee fsck.log
87+
grep "Git LFS fsck OK" fsck.log
88+
)
89+
end_test
90+
7491
begin_test "fetch (shared repository)"
7592
(
7693
set -e

t/t-prune.sh

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1038,6 +1038,27 @@ begin_test "prune --force"
10381038
)
10391039
end_test
10401040

1041+
begin_test "prune does not fail on empty files"
1042+
(
1043+
set -e
1044+
1045+
reponame="prune-empty-file"
1046+
setup_remote_repo "remote-$reponame"
1047+
1048+
clone_repo "remote-$reponame" "clone-$reponame"
1049+
1050+
git lfs track "*.dat" 2>&1 | tee track.log
1051+
grep "Tracking \"\*.dat\"" track.log
1052+
1053+
touch empty.dat
1054+
git add .gitattributes empty.dat
1055+
git commit -m 'Add empty'
1056+
git push origin main
1057+
1058+
git lfs prune --force
1059+
)
1060+
end_test
1061+
10411062
begin_test "prune does not invoke external diff programs"
10421063
(
10431064
set -e

t/t-pull.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,14 @@ begin_test "pull"
138138
git lfs pull -I "*.dat"
139139
assert_clean_status
140140

141+
echo "lfs pull with empty file"
142+
touch empty.dat
143+
git add empty.dat
144+
git commit -m 'empty'
145+
git lfs pull
146+
[ -z "$(cat empty.dat)" ]
147+
assert_clean_status
148+
141149
echo "lfs pull in subdir"
142150
cd dir
143151
git lfs pull

0 commit comments

Comments
 (0)