Files
go-task/internal/fingerprint/gitignore_test.go

255 lines
7.2 KiB
Go

package fingerprint
import (
"os"
"path/filepath"
"strings"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/go-task/task/v3/taskfile/ast"
)
func initGitRepo(t *testing.T, dir string) {
t.Helper()
require.NoError(t, os.MkdirAll(filepath.Join(dir, ".git"), 0o755))
}
func TestGlobsWithGitignore(t *testing.T) {
t.Parallel()
dir := t.TempDir()
initGitRepo(t, dir)
require.NoError(t, os.WriteFile(filepath.Join(dir, "included.txt"), []byte("included"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(dir, "ignored.log"), []byte("ignored"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(dir, "also-included.txt"), []byte("also included"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(dir, ".gitignore"), []byte("*.log\n"), 0o644))
globs := []*ast.Glob{
{Glob: "./*"},
}
filesWithout, err := Globs(dir, globs, false)
require.NoError(t, err)
filesWith, err := Globs(dir, globs, true)
require.NoError(t, err)
hasLog := false
for _, f := range filesWithout {
if filepath.Base(f) == "ignored.log" {
hasLog = true
break
}
}
assert.True(t, hasLog, "ignored.log should be present without gitignore filter")
hasLog = false
for _, f := range filesWith {
if filepath.Base(f) == "ignored.log" {
hasLog = true
break
}
}
assert.False(t, hasLog, "ignored.log should be excluded with gitignore filter")
txtCount := 0
for _, f := range filesWith {
if filepath.Ext(f) == ".txt" {
txtCount++
}
}
assert.Equal(t, 2, txtCount, "both .txt files should remain")
}
func TestGlobsWithGitignoreParentDirIgnored(t *testing.T) {
t.Parallel()
dir := t.TempDir()
initGitRepo(t, dir)
buildDir := filepath.Join(dir, "build")
require.NoError(t, os.MkdirAll(buildDir, 0o755))
require.NoError(t, os.WriteFile(filepath.Join(buildDir, "keep.txt"), []byte("keep"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(buildDir, "other.txt"), []byte("other"), 0o644))
// Git cannot re-include a file under an ignored directory.
require.NoError(t, os.WriteFile(filepath.Join(dir, ".gitignore"), []byte("build/\n"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(buildDir, ".gitignore"), []byte("!keep.txt\n"), 0o644))
globs := []*ast.Glob{
{Glob: "./**/*"},
}
files, err := Globs(dir, globs, true)
require.NoError(t, err)
for _, f := range files {
base := filepath.Base(f)
assert.NotEqual(t, "keep.txt", base, "keep.txt must stay excluded under ignored build/")
assert.NotEqual(t, "other.txt", base, "other.txt must stay excluded under ignored build/")
}
}
func TestGlobsWithGitignoreNested(t *testing.T) {
t.Parallel()
dir := t.TempDir()
initGitRepo(t, dir)
subDir := filepath.Join(dir, "sub")
require.NoError(t, os.MkdirAll(subDir, 0o755))
require.NoError(t, os.WriteFile(filepath.Join(subDir, "keep.txt"), []byte("keep"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(subDir, "build.out"), []byte("build"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(dir, ".gitignore"), []byte("*.log\n"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(subDir, ".gitignore"), []byte("*.out\n"), 0o644))
globs := []*ast.Glob{
{Glob: "./*"},
}
files, err := Globs(subDir, globs, true)
require.NoError(t, err)
for _, f := range files {
assert.NotEqual(t, "build.out", filepath.Base(f), "build.out should be excluded by nested .gitignore")
}
}
func TestGlobsWithGitignoreCrossFileNegation(t *testing.T) {
t.Parallel()
dir := t.TempDir()
initGitRepo(t, dir)
subDir := filepath.Join(dir, "sub")
require.NoError(t, os.MkdirAll(subDir, 0o755))
require.NoError(t, os.WriteFile(filepath.Join(subDir, "debug.log"), []byte("debug"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(subDir, "other.log"), []byte("other"), 0o644))
// Root ignores all *.log; a nested .gitignore re-includes debug.log.
require.NoError(t, os.WriteFile(filepath.Join(dir, ".gitignore"), []byte("*.log\n"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(subDir, ".gitignore"), []byte("!debug.log\n"), 0o644))
globs := []*ast.Glob{
{Glob: "./*"},
}
files, err := Globs(subDir, globs, true)
require.NoError(t, err)
hasDebug, hasOther := false, false
for _, f := range files {
switch filepath.Base(f) {
case "debug.log":
hasDebug = true
case "other.log":
hasOther = true
}
}
assert.True(t, hasDebug, "debug.log should be re-included by the nested negation")
assert.False(t, hasOther, "other.log should remain excluded by the root *.log rule")
}
func TestGlobsWithGitignoreDeepGlob(t *testing.T) {
t.Parallel()
dir := t.TempDir()
initGitRepo(t, dir)
subDir := filepath.Join(dir, "sub")
require.NoError(t, os.MkdirAll(subDir, 0o755))
require.NoError(t, os.WriteFile(filepath.Join(subDir, "keep.txt"), []byte("keep"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(subDir, "gen.out"), []byte("gen"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(subDir, ".gitignore"), []byte("*.out\n"), 0o644))
globs := []*ast.Glob{
{Glob: "./**/*"},
}
files, err := Globs(dir, globs, true)
require.NoError(t, err)
for _, f := range files {
assert.NotEqual(t, "gen.out", filepath.Base(f), "gen.out should be excluded by the nested .gitignore reached via deep glob")
}
}
func TestGlobsWithGitignoreDoubleDotFile(t *testing.T) {
t.Parallel()
dir := t.TempDir()
initGitRepo(t, dir)
// A ".."-prefixed name must not be skipped by the out-of-tree guard.
require.NoError(t, os.WriteFile(filepath.Join(dir, "..keep.log"), []byte("x"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(dir, "keep.txt"), []byte("y"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(dir, ".gitignore"), []byte("*.log\n"), 0o644))
globs := []*ast.Glob{
{Glob: "./*"},
}
files, err := Globs(dir, globs, true)
require.NoError(t, err)
for _, f := range files {
assert.NotEqual(t, "..keep.log", filepath.Base(f), "..keep.log should be excluded by *.log")
}
}
func TestGlobsWithGitignoreLongLine(t *testing.T) {
t.Parallel()
dir := t.TempDir()
initGitRepo(t, dir)
require.NoError(t, os.WriteFile(filepath.Join(dir, "ignored.log"), []byte("x"), 0o644))
require.NoError(t, os.WriteFile(filepath.Join(dir, "keep.txt"), []byte("y"), 0o644))
// A line over bufio.Scanner's 64KB limit triggers a scan error; patterns
// parsed before it must survive.
longLine := strings.Repeat("a", 70*1024)
content := "*.log\n" + longLine + "\n"
require.NoError(t, os.WriteFile(filepath.Join(dir, ".gitignore"), []byte(content), 0o644))
globs := []*ast.Glob{
{Glob: "./*"},
}
files, err := Globs(dir, globs, true)
require.NoError(t, err)
for _, f := range files {
assert.NotEqual(t, "ignored.log", filepath.Base(f), "*.log parsed before the long line should still apply")
}
}
func TestGlobsWithGitignoreNoRepo(t *testing.T) {
t.Parallel()
// Cannot use t.TempDir() here because it creates a dir inside the
// go-task repo which has a .git parent, defeating the "no repo" test.
dir, err := os.MkdirTemp("", "task-gitignore-norepo-*") //nolint:usetesting
require.NoError(t, err)
t.Cleanup(func() { os.RemoveAll(dir) })
require.NoError(t, os.WriteFile(filepath.Join(dir, "file.txt"), []byte("content"), 0o644))
globs := []*ast.Glob{
{Glob: "./*"},
}
files, err := Globs(dir, globs, true)
require.NoError(t, err)
assert.Len(t, files, 1)
}