Backport pull request #15501 from jellyfin/release-10.11.z

Fix .ignore handling for directories

Original-merge: e8150428b6

Merged-by: crobibero <cody@robibe.ro>

Backported-by: Bond_009 <bond.009@outlook.com>
This commit is contained in:
theguymadmax 2025-11-17 14:09:09 -05:00 committed by Bond_009
parent c869b5b884
commit 5b3f29946b

View file

@ -1,6 +1,5 @@
using System; using System;
using System.IO; using System.IO;
using System.Runtime.InteropServices;
using MediaBrowser.Controller.Entities; using MediaBrowser.Controller.Entities;
using MediaBrowser.Controller.IO; using MediaBrowser.Controller.IO;
using MediaBrowser.Controller.Resolvers; using MediaBrowser.Controller.Resolvers;
@ -13,28 +12,24 @@ namespace Emby.Server.Implementations.Library;
/// </summary> /// </summary>
public class DotIgnoreIgnoreRule : IResolverIgnoreRule public class DotIgnoreIgnoreRule : IResolverIgnoreRule
{ {
private static readonly bool IsWindows = OperatingSystem.IsWindows();
private static FileInfo? FindIgnoreFile(DirectoryInfo directory) private static FileInfo? FindIgnoreFile(DirectoryInfo directory)
{ {
var ignoreFile = new FileInfo(Path.Join(directory.FullName, ".ignore")); for (var current = directory; current is not null; current = current.Parent)
if (ignoreFile.Exists)
{ {
return ignoreFile; var ignorePath = Path.Join(current.FullName, ".ignore");
if (File.Exists(ignorePath))
{
return new FileInfo(ignorePath);
}
} }
var parentDir = directory.Parent; return null;
if (parentDir is null)
{
return null;
}
return FindIgnoreFile(parentDir);
} }
/// <inheritdoc /> /// <inheritdoc />
public bool ShouldIgnore(FileSystemMetadata fileInfo, BaseItem? parent) public bool ShouldIgnore(FileSystemMetadata fileInfo, BaseItem? parent) => IsIgnored(fileInfo, parent);
{
return IsIgnored(fileInfo, parent);
}
/// <summary> /// <summary>
/// Checks whether or not the file is ignored. /// Checks whether or not the file is ignored.
@ -44,72 +39,58 @@ public class DotIgnoreIgnoreRule : IResolverIgnoreRule
/// <returns>True if the file should be ignored.</returns> /// <returns>True if the file should be ignored.</returns>
public static bool IsIgnored(FileSystemMetadata fileInfo, BaseItem? parent) public static bool IsIgnored(FileSystemMetadata fileInfo, BaseItem? parent)
{ {
if (fileInfo.IsDirectory) var searchDirectory = fileInfo.IsDirectory
{ ? new DirectoryInfo(fileInfo.FullName)
var dirIgnoreFile = FindIgnoreFile(new DirectoryInfo(fileInfo.FullName)); : new DirectoryInfo(Path.GetDirectoryName(fileInfo.FullName) ?? string.Empty);
if (dirIgnoreFile is null)
{
return false;
}
// Fast path in case the ignore files isn't a symlink and is empty if (string.IsNullOrEmpty(searchDirectory.FullName))
if (dirIgnoreFile.LinkTarget is null && dirIgnoreFile.Length == 0)
{
return true;
}
// ignore the directory only if the .ignore file is empty
// evaluate individual files otherwise
return string.IsNullOrWhiteSpace(GetFileContent(dirIgnoreFile));
}
var parentDirPath = Path.GetDirectoryName(fileInfo.FullName);
if (string.IsNullOrEmpty(parentDirPath))
{ {
return false; return false;
} }
var folder = new DirectoryInfo(parentDirPath); var ignoreFile = FindIgnoreFile(searchDirectory);
var ignoreFile = FindIgnoreFile(folder);
if (ignoreFile is null) if (ignoreFile is null)
{ {
return false; return false;
} }
string ignoreFileString = GetFileContent(ignoreFile); // Fast path in case the ignore files isn't a symlink and is empty
if (ignoreFile.LinkTarget is null && ignoreFile.Length == 0)
if (string.IsNullOrWhiteSpace(ignoreFileString))
{ {
// Ignore directory if we just have the file // Ignore directory if we just have the file
return true; return true;
} }
// If file has content, base ignoring off the content .gitignore-style rules var content = GetFileContent(ignoreFile);
var ignoreRules = ignoreFileString.Split('\n', StringSplitOptions.RemoveEmptyEntries); return string.IsNullOrWhiteSpace(content)
var ignore = new Ignore.Ignore(); || CheckIgnoreRules(fileInfo.FullName, content, fileInfo.IsDirectory);
ignore.Add(ignoreRules);
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
// Mitigate the problem of the Ignore library not handling Windows paths correctly.
// See https://github.com/jellyfin/jellyfin/issues/15484
return ignore.IsIgnored(fileInfo.FullName.NormalizePath('/'));
}
return ignore.IsIgnored(fileInfo.FullName);
} }
private static string GetFileContent(FileInfo dirIgnoreFile) private static bool CheckIgnoreRules(string path, string ignoreFileContent, bool isDirectory)
{ {
dirIgnoreFile = FileSystemHelper.ResolveLinkTarget(dirIgnoreFile, returnFinalTarget: true) ?? dirIgnoreFile; // If file has content, base ignoring off the content .gitignore-style rules
if (!dirIgnoreFile.Exists) var rules = ignoreFileContent.Split('\n', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
var ignore = new Ignore.Ignore();
ignore.Add(rules);
// Mitigate the problem of the Ignore library not handling Windows paths correctly.
// See https://github.com/jellyfin/jellyfin/issues/15484
var pathToCheck = IsWindows ? path.NormalizePath('/') : path;
// Add trailing slash for directories to match "folder/"
if (isDirectory)
{ {
return string.Empty; pathToCheck = string.Concat(pathToCheck.AsSpan().TrimEnd('/'), "/");
} }
using (var reader = dirIgnoreFile.OpenText()) return ignore.IsIgnored(pathToCheck);
{ }
return reader.ReadToEnd();
} private static string GetFileContent(FileInfo ignoreFile)
{
ignoreFile = FileSystemHelper.ResolveLinkTarget(ignoreFile, returnFinalTarget: true) ?? ignoreFile;
return ignoreFile.Exists
? File.ReadAllText(ignoreFile.FullName)
: string.Empty;
} }
} }