mirror of
https://github.com/stashapp/stash.git
synced 2026-05-04 18:41:22 +02:00
Feedback changes
This commit is contained in:
parent
152bdb22cd
commit
cc3431dd04
5 changed files with 162 additions and 84 deletions
|
|
@ -6,6 +6,7 @@ package manager
|
|||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"sort"
|
||||
|
|
@ -42,6 +43,17 @@ func (m *mockProgressReporter) Increment() {}
|
|||
func (m *mockProgressReporter) Definite() {}
|
||||
func (m *mockProgressReporter) ExecuteTask(description string, fn func()) { fn() }
|
||||
|
||||
// stashIgnorePathFilter wraps StashIgnoreFilter to implement PathFilter for testing.
|
||||
// It provides a fixed library root for the filter.
|
||||
type stashIgnorePathFilter struct {
|
||||
filter *file.StashIgnoreFilter
|
||||
libraryRoot string
|
||||
}
|
||||
|
||||
func (f *stashIgnorePathFilter) Accept(ctx context.Context, path string, info fs.FileInfo) bool {
|
||||
return f.filter.Accept(ctx, path, info, f.libraryRoot)
|
||||
}
|
||||
|
||||
// createTestFileOnDisk creates a file with some content.
|
||||
func createTestFileOnDisk(t *testing.T, dir, name string) string {
|
||||
t.Helper()
|
||||
|
|
@ -134,8 +146,11 @@ temp/
|
|||
FingerprintCalculator: &mockFingerprintCalculator{},
|
||||
}
|
||||
|
||||
// Create stashignore filter.
|
||||
stashIgnoreFilter := file.NewStashIgnoreFilter(tmpDir)
|
||||
// Create stashignore filter with library root.
|
||||
stashIgnoreFilter := &stashIgnorePathFilter{
|
||||
filter: file.NewStashIgnoreFilter(),
|
||||
libraryRoot: tmpDir,
|
||||
}
|
||||
|
||||
// Run scan.
|
||||
ctx := context.Background()
|
||||
|
|
@ -250,8 +265,11 @@ func TestScannerWithNestedStashIgnore(t *testing.T) {
|
|||
FingerprintCalculator: &mockFingerprintCalculator{},
|
||||
}
|
||||
|
||||
// Create stashignore filter.
|
||||
stashIgnoreFilter := file.NewStashIgnoreFilter(tmpDir)
|
||||
// Create stashignore filter with library root.
|
||||
stashIgnoreFilter := &stashIgnorePathFilter{
|
||||
filter: file.NewStashIgnoreFilter(),
|
||||
libraryRoot: tmpDir,
|
||||
}
|
||||
|
||||
// Run scan.
|
||||
ctx := context.Background()
|
||||
|
|
@ -335,8 +353,11 @@ func TestScannerWithoutStashIgnore(t *testing.T) {
|
|||
FingerprintCalculator: &mockFingerprintCalculator{},
|
||||
}
|
||||
|
||||
// Create stashignore filter (but no .stashignore file exists).
|
||||
stashIgnoreFilter := file.NewStashIgnoreFilter(tmpDir)
|
||||
// Create stashignore filter with library root (but no .stashignore file exists).
|
||||
stashIgnoreFilter := &stashIgnorePathFilter{
|
||||
filter: file.NewStashIgnoreFilter(),
|
||||
libraryRoot: tmpDir,
|
||||
}
|
||||
|
||||
// Run scan.
|
||||
ctx := context.Background()
|
||||
|
|
@ -425,8 +446,11 @@ func TestScannerWithNegationPattern(t *testing.T) {
|
|||
FingerprintCalculator: &mockFingerprintCalculator{},
|
||||
}
|
||||
|
||||
// Create stashignore filter.
|
||||
stashIgnoreFilter := file.NewStashIgnoreFilter(tmpDir)
|
||||
// Create stashignore filter with library root.
|
||||
stashIgnoreFilter := &stashIgnorePathFilter{
|
||||
filter: file.NewStashIgnoreFilter(),
|
||||
libraryRoot: tmpDir,
|
||||
}
|
||||
|
||||
// Run scan.
|
||||
ctx := context.Background()
|
||||
|
|
|
|||
|
|
@ -254,6 +254,7 @@ type scanFilter struct {
|
|||
videoExcludeRegex []*regexp.Regexp
|
||||
imageExcludeRegex []*regexp.Regexp
|
||||
minModTime time.Time
|
||||
stashIgnoreFilter *file.StashIgnoreFilter
|
||||
}
|
||||
|
||||
func newScanFilter(c *config.Config, repo models.Repository, minModTime time.Time) *scanFilter {
|
||||
|
|
@ -267,6 +268,7 @@ func newScanFilter(c *config.Config, repo models.Repository, minModTime time.Tim
|
|||
videoExcludeRegex: generateRegexps(c.GetExcludes()),
|
||||
imageExcludeRegex: generateRegexps(c.GetImageExcludes()),
|
||||
minModTime: minModTime,
|
||||
stashIgnoreFilter: file.NewStashIgnoreFilter(),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -287,6 +289,12 @@ func (f *scanFilter) Accept(ctx context.Context, path string, info fs.FileInfo)
|
|||
return false
|
||||
}
|
||||
|
||||
// Check .stashignore files, bounded to the library root.
|
||||
if !f.stashIgnoreFilter.Accept(ctx, path, info, s.Path) {
|
||||
logger.Debugf("Skipping %s due to .stashignore", path)
|
||||
return false
|
||||
}
|
||||
|
||||
isVideoFile := useAsVideo(path)
|
||||
isImageFile := useAsImage(path)
|
||||
isZipFile := fsutil.MatchExtension(path, f.zipExt)
|
||||
|
|
|
|||
|
|
@ -9,6 +9,7 @@ import (
|
|||
"sync"
|
||||
|
||||
ignore "github.com/sabhiram/go-gitignore"
|
||||
"github.com/stashapp/stash/pkg/logger"
|
||||
)
|
||||
|
||||
const stashIgnoreFilename = ".stashignore"
|
||||
|
|
@ -16,9 +17,6 @@ const stashIgnoreFilename = ".stashignore"
|
|||
// StashIgnoreFilter implements PathFilter to exclude files/directories
|
||||
// based on .stashignore files with gitignore-style patterns.
|
||||
type StashIgnoreFilter struct {
|
||||
// root is the root directory being scanned.
|
||||
root string
|
||||
|
||||
// cache stores compiled ignore patterns per directory.
|
||||
cache sync.Map // map[string]*ignoreEntry
|
||||
}
|
||||
|
|
@ -31,46 +29,36 @@ type ignoreEntry struct {
|
|||
dir string
|
||||
}
|
||||
|
||||
// NewStashIgnoreFilter creates a new StashIgnoreFilter for the given root directory.
|
||||
func NewStashIgnoreFilter(root string) *StashIgnoreFilter {
|
||||
return &StashIgnoreFilter{
|
||||
root: root,
|
||||
}
|
||||
// NewStashIgnoreFilter creates a new StashIgnoreFilter.
|
||||
func NewStashIgnoreFilter() *StashIgnoreFilter {
|
||||
return &StashIgnoreFilter{}
|
||||
}
|
||||
|
||||
// Accept returns true if the path should be included in the scan.
|
||||
// It checks for .stashignore files in the directory hierarchy and
|
||||
// applies gitignore-style pattern matching.
|
||||
func (f *StashIgnoreFilter) Accept(ctx context.Context, path string, info fs.FileInfo) bool {
|
||||
// The libraryRoot parameter bounds the search for .stashignore files -
|
||||
// only directories within the library root are checked.
|
||||
func (f *StashIgnoreFilter) Accept(ctx context.Context, path string, info fs.FileInfo, libraryRoot string) bool {
|
||||
// Always accept .stashignore files themselves so they can be read.
|
||||
if filepath.Base(path) == stashIgnoreFilename {
|
||||
return true
|
||||
}
|
||||
|
||||
// Get the directory containing this path.
|
||||
var dir string
|
||||
if info.IsDir() {
|
||||
dir = filepath.Dir(path)
|
||||
} else {
|
||||
dir = filepath.Dir(path)
|
||||
}
|
||||
|
||||
// Collect all applicable ignore entries from root to this directory.
|
||||
entries := f.collectIgnoreEntries(dir)
|
||||
|
||||
// Check if any pattern matches (and isn't negated).
|
||||
relPath, err := filepath.Rel(f.root, path)
|
||||
if err != nil {
|
||||
// If we can't get relative path, accept the file.
|
||||
// If no library root provided, accept the file (safety fallback).
|
||||
if libraryRoot == "" {
|
||||
return true
|
||||
}
|
||||
|
||||
// Normalise to forward slashes for consistent matching.
|
||||
relPath = filepath.ToSlash(relPath)
|
||||
// Get the directory containing this path.
|
||||
dir := filepath.Dir(path)
|
||||
|
||||
// For directories, also check with trailing slash.
|
||||
if info.IsDir() {
|
||||
relPath = relPath + "/"
|
||||
// Collect all applicable ignore entries from library root to this directory.
|
||||
entries := f.collectIgnoreEntries(dir, libraryRoot)
|
||||
|
||||
// If no .stashignore files found, accept the file.
|
||||
if len(entries) == 0 {
|
||||
return true
|
||||
}
|
||||
|
||||
// Check each ignore entry in order (from root to most specific).
|
||||
|
|
@ -90,43 +78,65 @@ func (f *StashIgnoreFilter) Accept(ctx context.Context, path string, info fs.Fil
|
|||
if entry.patterns.MatchesPath(entryRelPath) {
|
||||
ignored = true
|
||||
}
|
||||
// Check negation by testing without the directory suffix.
|
||||
// The library handles negation internally.
|
||||
}
|
||||
|
||||
return !ignored
|
||||
}
|
||||
|
||||
// collectIgnoreEntries gathers all ignore entries from root to the given directory.
|
||||
func (f *StashIgnoreFilter) collectIgnoreEntries(dir string) []*ignoreEntry {
|
||||
// collectIgnoreEntries gathers all ignore entries from library root to the given directory.
|
||||
// It walks up the directory tree from dir to libraryRoot and returns entries in order
|
||||
// from root to most specific.
|
||||
func (f *StashIgnoreFilter) collectIgnoreEntries(dir string, libraryRoot string) []*ignoreEntry {
|
||||
// Collect directories from library root down to current dir.
|
||||
var dirs []string
|
||||
|
||||
// Clean paths for consistent comparison.
|
||||
dir = filepath.Clean(dir)
|
||||
libraryRoot = filepath.Clean(libraryRoot)
|
||||
|
||||
// Walk up from dir to library root, collecting directories.
|
||||
current := dir
|
||||
for {
|
||||
// Check if we're still within the library root.
|
||||
if !isPathInOrEqual(libraryRoot, current) {
|
||||
break
|
||||
}
|
||||
|
||||
dirs = append([]string{current}, dirs...) // Prepend to maintain root-to-leaf order.
|
||||
|
||||
// Stop if we've reached the library root.
|
||||
if current == libraryRoot {
|
||||
break
|
||||
}
|
||||
|
||||
parent := filepath.Dir(current)
|
||||
if parent == current {
|
||||
// Reached filesystem root without finding library root.
|
||||
break
|
||||
}
|
||||
current = parent
|
||||
}
|
||||
|
||||
// Check each directory for .stashignore files.
|
||||
var entries []*ignoreEntry
|
||||
|
||||
// Walk from root to current directory.
|
||||
current := f.root
|
||||
relDir, err := filepath.Rel(f.root, dir)
|
||||
if err != nil {
|
||||
return entries
|
||||
}
|
||||
|
||||
// Check root directory first.
|
||||
if entry := f.getOrLoadIgnoreEntry(current); entry != nil {
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
|
||||
// Then check each subdirectory.
|
||||
if relDir != "." {
|
||||
parts := strings.Split(filepath.ToSlash(relDir), "/")
|
||||
for _, part := range parts {
|
||||
current = filepath.Join(current, part)
|
||||
if entry := f.getOrLoadIgnoreEntry(current); entry != nil {
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
for _, d := range dirs {
|
||||
if entry := f.getOrLoadIgnoreEntry(d); entry != nil {
|
||||
entries = append(entries, entry)
|
||||
}
|
||||
}
|
||||
|
||||
return entries
|
||||
}
|
||||
|
||||
// isPathInOrEqual checks if path is equal to or inside root.
|
||||
func isPathInOrEqual(root, path string) bool {
|
||||
if path == root {
|
||||
return true
|
||||
}
|
||||
// Check if path starts with root + separator.
|
||||
return strings.HasPrefix(path, root+string(filepath.Separator))
|
||||
}
|
||||
|
||||
// getOrLoadIgnoreEntry returns the cached ignore entry for a directory, or loads it.
|
||||
func (f *StashIgnoreFilter) getOrLoadIgnoreEntry(dir string) *ignoreEntry {
|
||||
// Check cache first.
|
||||
|
|
@ -140,13 +150,15 @@ func (f *StashIgnoreFilter) getOrLoadIgnoreEntry(dir string) *ignoreEntry {
|
|||
|
||||
// Try to load .stashignore from this directory.
|
||||
stashIgnorePath := filepath.Join(dir, stashIgnoreFilename)
|
||||
patterns, err := f.loadIgnoreFile(stashIgnorePath, dir)
|
||||
if err != nil {
|
||||
// Cache negative result.
|
||||
patterns, err := f.loadIgnoreFile(stashIgnorePath)
|
||||
if err != nil || patterns == nil {
|
||||
// Cache negative result (file doesn't exist or has no patterns).
|
||||
f.cache.Store(dir, &ignoreEntry{patterns: nil, dir: dir})
|
||||
return nil
|
||||
}
|
||||
|
||||
logger.Debugf("Loaded .stashignore from %s", dir)
|
||||
|
||||
entry := &ignoreEntry{
|
||||
patterns: patterns,
|
||||
dir: dir,
|
||||
|
|
@ -156,7 +168,7 @@ func (f *StashIgnoreFilter) getOrLoadIgnoreEntry(dir string) *ignoreEntry {
|
|||
}
|
||||
|
||||
// loadIgnoreFile loads and compiles a .stashignore file.
|
||||
func (f *StashIgnoreFilter) loadIgnoreFile(path string, dir string) (*ignore.GitIgnore, error) {
|
||||
func (f *StashIgnoreFilter) loadIgnoreFile(path string) (*ignore.GitIgnore, error) {
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
@ -183,7 +195,8 @@ func (f *StashIgnoreFilter) loadIgnoreFile(path string, dir string) (*ignore.Git
|
|||
}
|
||||
|
||||
if len(patterns) == 0 {
|
||||
return nil, os.ErrNotExist
|
||||
// File exists but has no patterns (e.g., only comments).
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
return ignore.CompileIgnoreLines(patterns...), nil
|
||||
|
|
|
|||
|
|
@ -64,7 +64,7 @@ func walkAndFilter(t *testing.T, root string, filter *StashIgnoreFilter) []strin
|
|||
return err
|
||||
}
|
||||
|
||||
if filter.Accept(ctx, path, info) {
|
||||
if filter.Accept(ctx, path, info, root) {
|
||||
relPath, _ := filepath.Rel(root, path)
|
||||
accepted = append(accepted, relPath)
|
||||
} else if info.IsDir() {
|
||||
|
|
@ -111,7 +111,7 @@ func TestStashIgnore_ExactFilename(t *testing.T) {
|
|||
// Create .stashignore that excludes exact filename.
|
||||
createTestFileWithContent(t, tmpDir, ".stashignore", "ignore_me.mp4\n")
|
||||
|
||||
filter := NewStashIgnoreFilter(tmpDir)
|
||||
filter := NewStashIgnoreFilter()
|
||||
accepted := walkAndFilter(t, tmpDir, filter)
|
||||
|
||||
expected := []string{
|
||||
|
|
@ -136,7 +136,7 @@ func TestStashIgnore_WildcardPattern(t *testing.T) {
|
|||
// Create .stashignore that excludes by extension.
|
||||
createTestFileWithContent(t, tmpDir, ".stashignore", "*.tmp\n*.log\n")
|
||||
|
||||
filter := NewStashIgnoreFilter(tmpDir)
|
||||
filter := NewStashIgnoreFilter()
|
||||
accepted := walkAndFilter(t, tmpDir, filter)
|
||||
|
||||
expected := []string{
|
||||
|
|
@ -162,7 +162,7 @@ func TestStashIgnore_DirectoryExclusion(t *testing.T) {
|
|||
// Create .stashignore that excludes a directory.
|
||||
createTestFileWithContent(t, tmpDir, ".stashignore", "excluded_dir/\n")
|
||||
|
||||
filter := NewStashIgnoreFilter(tmpDir)
|
||||
filter := NewStashIgnoreFilter()
|
||||
accepted := walkAndFilter(t, tmpDir, filter)
|
||||
|
||||
expected := []string{
|
||||
|
|
@ -186,7 +186,7 @@ func TestStashIgnore_NegationPattern(t *testing.T) {
|
|||
// Create .stashignore that excludes *.tmp but keeps one.
|
||||
createTestFileWithContent(t, tmpDir, ".stashignore", "*.tmp\n!keep_this.tmp\n")
|
||||
|
||||
filter := NewStashIgnoreFilter(tmpDir)
|
||||
filter := NewStashIgnoreFilter()
|
||||
accepted := walkAndFilter(t, tmpDir, filter)
|
||||
|
||||
expected := []string{
|
||||
|
|
@ -213,7 +213,7 @@ ignore_me.mp4
|
|||
`
|
||||
createTestFileWithContent(t, tmpDir, ".stashignore", stashignore)
|
||||
|
||||
filter := NewStashIgnoreFilter(tmpDir)
|
||||
filter := NewStashIgnoreFilter()
|
||||
accepted := walkAndFilter(t, tmpDir, filter)
|
||||
|
||||
expected := []string{
|
||||
|
|
@ -241,7 +241,7 @@ func TestStashIgnore_NestedStashIgnoreFiles(t *testing.T) {
|
|||
// Subdir .stashignore excludes *.log.
|
||||
createTestFileWithContent(t, tmpDir, "subdir/.stashignore", "*.log\n")
|
||||
|
||||
filter := NewStashIgnoreFilter(tmpDir)
|
||||
filter := NewStashIgnoreFilter()
|
||||
accepted := walkAndFilter(t, tmpDir, filter)
|
||||
|
||||
// *.tmp from root should apply everywhere.
|
||||
|
|
@ -269,7 +269,7 @@ func TestStashIgnore_PathPattern(t *testing.T) {
|
|||
// Create .stashignore that excludes a specific path.
|
||||
createTestFileWithContent(t, tmpDir, ".stashignore", "subdir/skip_this.mp4\n")
|
||||
|
||||
filter := NewStashIgnoreFilter(tmpDir)
|
||||
filter := NewStashIgnoreFilter()
|
||||
accepted := walkAndFilter(t, tmpDir, filter)
|
||||
|
||||
expected := []string{
|
||||
|
|
@ -298,7 +298,7 @@ func TestStashIgnore_DoubleStarPattern(t *testing.T) {
|
|||
// Create .stashignore that excludes temp directories at any level.
|
||||
createTestFileWithContent(t, tmpDir, ".stashignore", "**/temp/\n")
|
||||
|
||||
filter := NewStashIgnoreFilter(tmpDir)
|
||||
filter := NewStashIgnoreFilter()
|
||||
accepted := walkAndFilter(t, tmpDir, filter)
|
||||
|
||||
expected := []string{
|
||||
|
|
@ -323,7 +323,7 @@ func TestStashIgnore_LeadingSlashPattern(t *testing.T) {
|
|||
// Create .stashignore that excludes only at root level.
|
||||
createTestFileWithContent(t, tmpDir, ".stashignore", "/ignore.mp4\n")
|
||||
|
||||
filter := NewStashIgnoreFilter(tmpDir)
|
||||
filter := NewStashIgnoreFilter()
|
||||
accepted := walkAndFilter(t, tmpDir, filter)
|
||||
|
||||
// Only root ignore.mp4 should be excluded.
|
||||
|
|
@ -345,7 +345,7 @@ func TestStashIgnore_NoStashIgnoreFile(t *testing.T) {
|
|||
createTestDir(t, tmpDir, "subdir")
|
||||
createTestFile(t, tmpDir, "subdir/video3.mp4")
|
||||
|
||||
filter := NewStashIgnoreFilter(tmpDir)
|
||||
filter := NewStashIgnoreFilter()
|
||||
accepted := walkAndFilter(t, tmpDir, filter)
|
||||
|
||||
// All files should be accepted.
|
||||
|
|
@ -370,7 +370,7 @@ func TestStashIgnore_HiddenDirectories(t *testing.T) {
|
|||
// Create .stashignore that excludes hidden directories.
|
||||
createTestFileWithContent(t, tmpDir, ".stashignore", ".*\n!.stashignore\n")
|
||||
|
||||
filter := NewStashIgnoreFilter(tmpDir)
|
||||
filter := NewStashIgnoreFilter()
|
||||
accepted := walkAndFilter(t, tmpDir, filter)
|
||||
|
||||
expected := []string{
|
||||
|
|
@ -393,7 +393,7 @@ func TestStashIgnore_MultiplePatternsSameLine(t *testing.T) {
|
|||
// Each pattern should be on its own line.
|
||||
createTestFileWithContent(t, tmpDir, ".stashignore", "*.tmp\n*.log\n*.bak\n")
|
||||
|
||||
filter := NewStashIgnoreFilter(tmpDir)
|
||||
filter := NewStashIgnoreFilter()
|
||||
accepted := walkAndFilter(t, tmpDir, filter)
|
||||
|
||||
expected := []string{
|
||||
|
|
@ -414,7 +414,7 @@ func TestStashIgnore_TrailingSpaces(t *testing.T) {
|
|||
// Pattern with trailing spaces (should be trimmed).
|
||||
createTestFileWithContent(t, tmpDir, ".stashignore", "ignore_me.mp4 \n")
|
||||
|
||||
filter := NewStashIgnoreFilter(tmpDir)
|
||||
filter := NewStashIgnoreFilter()
|
||||
accepted := walkAndFilter(t, tmpDir, filter)
|
||||
|
||||
expected := []string{
|
||||
|
|
@ -435,7 +435,7 @@ func TestStashIgnore_EscapedHash(t *testing.T) {
|
|||
// Escaped hash should match literal # character.
|
||||
createTestFileWithContent(t, tmpDir, ".stashignore", "\\#filename.mp4\n")
|
||||
|
||||
filter := NewStashIgnoreFilter(tmpDir)
|
||||
filter := NewStashIgnoreFilter()
|
||||
accepted := walkAndFilter(t, tmpDir, filter)
|
||||
|
||||
expected := []string{
|
||||
|
|
@ -457,7 +457,7 @@ func TestStashIgnore_CaseSensitiveMatching(t *testing.T) {
|
|||
// Pattern should match exactly (case-sensitive).
|
||||
createTestFileWithContent(t, tmpDir, ".stashignore", "video_lower.mp4\n")
|
||||
|
||||
filter := NewStashIgnoreFilter(tmpDir)
|
||||
filter := NewStashIgnoreFilter()
|
||||
accepted := walkAndFilter(t, tmpDir, filter)
|
||||
|
||||
// Only exact match is excluded.
|
||||
|
|
@ -505,7 +505,7 @@ backup/
|
|||
`
|
||||
createTestFileWithContent(t, tmpDir, ".stashignore", stashignore)
|
||||
|
||||
filter := NewStashIgnoreFilter(tmpDir)
|
||||
filter := NewStashIgnoreFilter()
|
||||
accepted := walkAndFilter(t, tmpDir, filter)
|
||||
|
||||
expected := []string{
|
||||
|
|
|
|||
|
|
@ -10,6 +10,39 @@ Stash currently identifies files by performing a quick file hash. This means tha
|
|||
|
||||
Stash currently ignores duplicate files. If two files contain identical content, only the first one it comes across is used.
|
||||
|
||||
### Ignoring Files with .stashignore
|
||||
|
||||
You can create `.stashignore` files to exclude specific files or directories from being scanned. These files use gitignore-style pattern matching syntax.
|
||||
|
||||
Place a `.stashignore` file in any directory within your library. The patterns in that file will apply to all files and subdirectories within that directory. You can have multiple `.stashignore` files at different levels of your directory hierarchy - patterns from parent directories cascade down to child directories.
|
||||
|
||||
**Supported patterns:**
|
||||
|
||||
| Pattern | Description |
|
||||
|---------|-------------|
|
||||
| `filename.mp4` | Ignore a specific file. |
|
||||
| `*.tmp` | Ignore all files with a specific extension. |
|
||||
| `temp/` | Ignore a directory and all its contents. |
|
||||
| `**/cache/` | Ignore directories named "cache" at any level. |
|
||||
| `!important.mp4` | Negation - do not ignore this file even if it matches a previous pattern. |
|
||||
| `# comment` | Lines starting with # are comments. |
|
||||
| `\#filename` | Use backslash to match a literal # character. |
|
||||
|
||||
**Example .stashignore file:**
|
||||
|
||||
```
|
||||
# Ignore temporary files
|
||||
*.tmp
|
||||
*.log
|
||||
|
||||
# Ignore specific directories
|
||||
temp/
|
||||
.thumbnails/
|
||||
|
||||
# But keep this specific file
|
||||
!important.tmp
|
||||
```
|
||||
|
||||
The scan task accepts the following options:
|
||||
|
||||
| Option | Description |
|
||||
|
|
|
|||
Loading…
Reference in a new issue