Skip to content

Commit 4dc476f

Browse files
committed
locking: don't assume files are lockable if no patterns exist
Whenever we use a file path filter and there are no included patterns, we default to assuming the files match. For many cases, this is fine, but this is not useful for checking whether a file is lockable: if no files are listed as lockable, then no files are lockable. Let's add an option for our filters to specify the default value if no patterns match. If that default is false, like it should be for locking, then skip traversing the exclude patterns altogether, since anything that's not included cannot change the result. We test both the case that lockable patterns are present and absent in our test.
1 parent ecaab56 commit 4dc476f

File tree

3 files changed

+76
-7
lines changed

3 files changed

+76
-7
lines changed

filepathfilter/filepathfilter.go

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,37 @@ type Pattern interface {
1616
}
1717

1818
type Filter struct {
19-
include []Pattern
20-
exclude []Pattern
19+
include []Pattern
20+
exclude []Pattern
21+
defaultValue bool
2122
}
2223

23-
func NewFromPatterns(include, exclude []Pattern) *Filter {
24-
return &Filter{include: include, exclude: exclude}
24+
type options struct {
25+
defaultValue bool
2526
}
2627

27-
func New(include, exclude []string) *Filter {
28+
type option func(*options)
29+
30+
// DefaultValue is an option representing the default value of a filepathfilter
31+
// if no patterns match. If this option is not provided, the default is true.
32+
func DefaultValue(val bool) option {
33+
return func(args *options) {
34+
args.defaultValue = val
35+
}
36+
}
37+
38+
func NewFromPatterns(include, exclude []Pattern, setters ...option) *Filter {
39+
args := &options{defaultValue: true}
40+
for _, setter := range setters {
41+
setter(args)
42+
}
43+
return &Filter{include: include, exclude: exclude, defaultValue: args.defaultValue}
44+
}
45+
46+
func New(include, exclude []string, setters ...option) *Filter {
2847
return NewFromPatterns(
2948
convertToWildmatch(include),
30-
convertToWildmatch(exclude))
49+
convertToWildmatch(exclude), setters...)
3150
}
3251

3352
// Include returns the result of calling String() on each Pattern in the
@@ -66,13 +85,23 @@ func (f *Filter) Allows(filename string) bool {
6685
return false
6786
}
6887

88+
// Beyond this point, the only values we can logically return are false
89+
// or the default value. If the default is false, then there's no point
90+
// traversing the exclude patterns because the return value will always
91+
// be false; we'd do extra work for no functional benefit.
92+
if !included && !f.defaultValue {
93+
tracerx.Printf("filepathfilter: rejecting %q", filename)
94+
return false
95+
}
96+
6997
for _, ex := range f.exclude {
7098
if ex.Match(filename) {
7199
tracerx.Printf("filepathfilter: rejecting %q via %q", filename, ex.String())
72100
return false
73101
}
74102
}
75103

104+
// No patterns matched and our default value is true.
76105
tracerx.Printf("filepathfilter: accepting %q", filename)
77106
return true
78107
}

locking/lockable.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ func (c *Client) refreshLockablePatterns() {
4747
c.lockablePatterns = append(c.lockablePatterns, filepath.ToSlash(p.Path))
4848
}
4949
}
50-
c.lockableFilter = filepathfilter.New(c.lockablePatterns, nil)
50+
c.lockableFilter = filepathfilter.New(c.lockablePatterns, nil, filepathfilter.DefaultValue(false))
5151
}
5252

5353
// IsFileLockable returns whether a specific file path is marked as Lockable,

t/t-unlock.sh

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -185,6 +185,46 @@ begin_test "unlocking nonexistent file"
185185
)
186186
end_test
187187

188+
begin_test "unlocking unlockable file"
189+
(
190+
set -e
191+
192+
reponame="unlock-unlockable-file"
193+
# Try with lockable patterns.
194+
setup_repo "$reponame" "a.dat"
195+
196+
touch README.md
197+
git add README.md
198+
git commit -m 'Add README'
199+
200+
git lfs lock --json "README.md" | tee lock.log
201+
id=$(assert_lock lock.log README.md)
202+
assert_server_lock "$reponame" "$id"
203+
assert_file_writeable "README.md"
204+
205+
git lfs unlock --force "README.md" 2>&1 | tee unlock.log
206+
refute_server_lock "$reponame" "$id"
207+
assert_file_writeable "README.md"
208+
209+
cd "$TRASHDIR"
210+
# Try without any lockable patterns.
211+
setup_remote_repo_with_file "$reponame-2" "a.dat"
212+
213+
touch README.md
214+
git add README.md
215+
git commit -m 'Add README'
216+
217+
git lfs lock --json "README.md" | tee lock.log
218+
id=$(assert_lock lock.log README.md)
219+
assert_server_lock "$reponame-2" "$id"
220+
assert_file_writeable "README.md"
221+
222+
git lfs unlock --force "README.md" 2>&1 | tee unlock.log
223+
refute_server_lock "$reponame-2" "$id"
224+
assert_file_writeable "README.md"
225+
)
226+
end_test
227+
188228
begin_test "unlocking a lock (--json)"
189229
(
190230
set -e

0 commit comments

Comments
 (0)