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.
This commit is contained in:
brian m. carlson 2020-06-25 18:59:46 +00:00
parent ecaab56e70
commit 4dc476f286
No known key found for this signature in database
GPG Key ID: 2D0C9BC12F82B3A1
3 changed files with 76 additions and 7 deletions

@ -16,18 +16,37 @@ type Pattern interface {
}
type Filter struct {
include []Pattern
exclude []Pattern
include []Pattern
exclude []Pattern
defaultValue bool
}
func NewFromPatterns(include, exclude []Pattern) *Filter {
return &Filter{include: include, exclude: exclude}
type options struct {
defaultValue bool
}
func New(include, exclude []string) *Filter {
type option func(*options)
// DefaultValue is an option representing the default value of a filepathfilter
// if no patterns match. If this option is not provided, the default is true.
func DefaultValue(val bool) option {
return func(args *options) {
args.defaultValue = val
}
}
func NewFromPatterns(include, exclude []Pattern, setters ...option) *Filter {
args := &options{defaultValue: true}
for _, setter := range setters {
setter(args)
}
return &Filter{include: include, exclude: exclude, defaultValue: args.defaultValue}
}
func New(include, exclude []string, setters ...option) *Filter {
return NewFromPatterns(
convertToWildmatch(include),
convertToWildmatch(exclude))
convertToWildmatch(exclude), setters...)
}
// Include returns the result of calling String() on each Pattern in the
@ -66,6 +85,15 @@ func (f *Filter) Allows(filename string) bool {
return false
}
// Beyond this point, the only values we can logically return are false
// or the default value. If the default is false, then there's no point
// traversing the exclude patterns because the return value will always
// be false; we'd do extra work for no functional benefit.
if !included && !f.defaultValue {
tracerx.Printf("filepathfilter: rejecting %q", filename)
return false
}
for _, ex := range f.exclude {
if ex.Match(filename) {
tracerx.Printf("filepathfilter: rejecting %q via %q", filename, ex.String())
@ -73,6 +101,7 @@ func (f *Filter) Allows(filename string) bool {
}
}
// No patterns matched and our default value is true.
tracerx.Printf("filepathfilter: accepting %q", filename)
return true
}

@ -47,7 +47,7 @@ func (c *Client) refreshLockablePatterns() {
c.lockablePatterns = append(c.lockablePatterns, filepath.ToSlash(p.Path))
}
}
c.lockableFilter = filepathfilter.New(c.lockablePatterns, nil)
c.lockableFilter = filepathfilter.New(c.lockablePatterns, nil, filepathfilter.DefaultValue(false))
}
// IsFileLockable returns whether a specific file path is marked as Lockable,

@ -185,6 +185,46 @@ begin_test "unlocking nonexistent file"
)
end_test
begin_test "unlocking unlockable file"
(
set -e
reponame="unlock-unlockable-file"
# Try with lockable patterns.
setup_repo "$reponame" "a.dat"
touch README.md
git add README.md
git commit -m 'Add README'
git lfs lock --json "README.md" | tee lock.log
id=$(assert_lock lock.log README.md)
assert_server_lock "$reponame" "$id"
assert_file_writeable "README.md"
git lfs unlock --force "README.md" 2>&1 | tee unlock.log
refute_server_lock "$reponame" "$id"
assert_file_writeable "README.md"
cd "$TRASHDIR"
# Try without any lockable patterns.
setup_remote_repo_with_file "$reponame-2" "a.dat"
touch README.md
git add README.md
git commit -m 'Add README'
git lfs lock --json "README.md" | tee lock.log
id=$(assert_lock lock.log README.md)
assert_server_lock "$reponame-2" "$id"
assert_file_writeable "README.md"
git lfs unlock --force "README.md" 2>&1 | tee unlock.log
refute_server_lock "$reponame-2" "$id"
assert_file_writeable "README.md"
)
end_test
begin_test "unlocking a lock (--json)"
(
set -e