public string GetHash() { return(MatchedFile.GenerateHash(new [] { this })); }
public static Fingerprint EvaluateKeyToFingerprint( AgentTaskPluginExecutionContext context, string filePathRoot, IEnumerable <string> keySegments) { // Quickly validate all segments foreach (string keySegment in keySegments) { CheckKeySegment(keySegment); } string defaultWorkingDirectory = context.Variables.GetValueOrDefault( "system.defaultworkingdirectory" // Constants.Variables.System.DefaultWorkingDirectory )?.Value; var resolvedSegments = new List <string>(); var exceptions = new List <Exception>(); Action <string, KeySegmentType, Object> LogKeySegment = (segment, type, details) => { Func <string, int, string> FormatForDisplay = (value, displayLength) => { if (value.Length > displayLength) { value = value.Substring(0, displayLength - 3) + "..."; } return(value.PadRight(displayLength)); }; string formattedSegment = FormatForDisplay(segment, Math.Min(keySegments.Select(s => s.Length).Max(), 50)); if (type == KeySegmentType.String) { context.Output($" - {formattedSegment} [string]"); } else { var matches = (details as MatchedFile[]) ?? new MatchedFile[0]; if (type == KeySegmentType.FilePath) { string fileHash = matches.Length > 0 ? matches[0].Hash : null; context.Output($" - {formattedSegment} [file] {(!string.IsNullOrWhiteSpace(fileHash) ? $"--> {fileHash}" : "(not found)")}"); } else if (type == KeySegmentType.FilePattern) { context.Output($" - {formattedSegment} [file pattern; matches: {matches.Length}]"); if (matches.Any()) { int filePathDisplayLength = Math.Min(matches.Select(mf => mf.DisplayPath.Length).Max(), 70); foreach (var match in matches) { context.Output($" - {FormatForDisplay(match.DisplayPath, filePathDisplayLength)} --> {match.Hash}"); } } } } }; foreach (string keySegment in keySegments) { if (!IsPathyKeySegment(keySegment)) { LogKeySegment(keySegment, KeySegmentType.String, null); resolvedSegments.Add(keySegment); } else { string[] pathRules = keySegment.Split(new [] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray(); string[] includeRules = pathRules.Where(p => !p.StartsWith('!')).ToArray(); if (!includeRules.Any()) { throw new ArgumentException("No include rules specified."); } var enumerations = new Dictionary <Enumeration, List <string> >(); foreach (string includeRule in includeRules) { string absoluteRootRule = MakePathCanonical(defaultWorkingDirectory, includeRule); context.Verbose($"Expanded include rule is `{absoluteRootRule}`."); Enumeration enumeration = DetermineFileEnumerationFromGlob(absoluteRootRule); List <string> globs; if (!enumerations.TryGetValue(enumeration, out globs)) { enumerations[enumeration] = globs = new List <string>(); } globs.Add(absoluteRootRule); } string[] excludeRules = pathRules.Where(p => p.StartsWith('!')).ToArray(); string[] absoluteExcludeRules = excludeRules.Select(excludeRule => { excludeRule = excludeRule.Substring(1); return(MakePathCanonical(defaultWorkingDirectory, excludeRule)); }).ToArray(); var matchedFiles = new SortedDictionary <string, MatchedFile>(StringComparer.Ordinal); foreach (var kvp in enumerations) { Enumeration enumerate = kvp.Key; List <string> absoluteIncludeGlobs = kvp.Value; context.Verbose($"Enumerating starting at root `{enumerate.RootPath}` with pattern `{enumerate.Pattern}` and depth `{enumerate.Depth}`."); IEnumerable <string> files = Directory.EnumerateFiles(enumerate.RootPath, enumerate.Pattern, enumerate.Depth); Func <string, bool> filter = CreateFilter(context, absoluteIncludeGlobs, absoluteExcludeRules); files = files.Where(f => filter(f)).Distinct(); foreach (string path in files) { using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) { // Path.GetRelativePath returns 'The relative path, or path if the paths don't share the same root.' string displayPath = filePathRoot == null ? path : Path.GetRelativePath(filePathRoot, path); matchedFiles.Add(path, new MatchedFile(displayPath, fs)); } } } var patternSegment = keySegment.IndexOfAny(GlobChars) >= 0 || matchedFiles.Count() > 1; LogKeySegment(keySegment, patternSegment ? KeySegmentType.FilePattern : KeySegmentType.FilePath, matchedFiles.Values.ToArray()); if (!matchedFiles.Any()) { if (patternSegment) { exceptions.Add(new FileNotFoundException($"No matching files found for pattern: {keySegment}")); } else { exceptions.Add(new FileNotFoundException($"File not found: {keySegment}")); } } resolvedSegments.Add(MatchedFile.GenerateHash(matchedFiles.Values)); } } if (exceptions.Any()) { throw new AggregateException(exceptions); } return(new Fingerprint() { Segments = resolvedSegments.ToArray() }); }
public static Fingerprint EvaluateToFingerprint( AgentTaskPluginExecutionContext context, string filePathRoot, IEnumerable <string> segments, FingerprintType fingerprintType) { // Quickly validate all segments foreach (string segment in segments) { CheckSegment(segment, fingerprintType.ToString()); } string defaultWorkingDirectory = context.Variables.GetValueOrDefault( "system.defaultworkingdirectory" // Constants.Variables.System.DefaultWorkingDirectory )?.Value ?? filePathRoot; var resolvedSegments = new List <string>(); var exceptions = new List <Exception>(); var hasPatternSegments = false; foreach (string segment in segments) { if (!IsPathySegment(segment)) { LogSegment(context, segments, segment, KeySegmentType.String, null); resolvedSegments.Add(segment); } else { string[] pathRules = segment.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries).Select(s => s.Trim()).ToArray(); string[] includeRules = pathRules.Where(p => !p.StartsWith('!')).ToArray(); if (!includeRules.Any()) { throw new ArgumentException("No include rules specified."); } var enumerations = new Dictionary <Enumeration, List <string> >(); foreach (string includeRule in includeRules) { string absoluteRootRule = MakePathCanonical(defaultWorkingDirectory, includeRule); context.Verbose($"Expanded include rule is `{absoluteRootRule}`."); Enumeration enumeration = DetermineFileEnumerationFromGlob(absoluteRootRule); List <string> globs; if (!enumerations.TryGetValue(enumeration, out globs)) { enumerations[enumeration] = globs = new List <string>(); } globs.Add(absoluteRootRule); } string[] excludeRules = pathRules.Where(p => p.StartsWith('!')).ToArray(); string[] absoluteExcludeRules = excludeRules.Select(excludeRule => { excludeRule = excludeRule.Substring(1); return(MakePathCanonical(defaultWorkingDirectory, excludeRule)); }).ToArray(); var matchedFiles = new SortedDictionary <string, MatchedFile>(StringComparer.Ordinal); var matchedDirectories = new SortedDictionary <string, string>(StringComparer.Ordinal); foreach (var kvp in enumerations) { Enumeration enumerate = kvp.Key; List <string> absoluteIncludeGlobs = kvp.Value; context.Verbose($"Enumerating starting at root `{enumerate.RootPath}` with pattern `{enumerate.Pattern}` and depth `{enumerate.Depth}`."); IEnumerable <string> files = fingerprintType == FingerprintType.Key ? Directory.EnumerateFiles(enumerate.RootPath, enumerate.Pattern, enumerate.Depth) : Directory.EnumerateDirectories(enumerate.RootPath, enumerate.Pattern, enumerate.Depth); Func <string, bool> filter = CreateFilter(context, absoluteIncludeGlobs, absoluteExcludeRules); files = files.Where(f => filter(f)).Distinct(); foreach (string path in files) { // Path.GetRelativePath returns 'The relative path, or path if the paths don't share the same root.' string displayPath = filePathRoot == null ? path : Path.GetRelativePath(filePathRoot, path); if (fingerprintType == FingerprintType.Key) { using (var fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) { matchedFiles.Add(path, new MatchedFile(displayPath, fs)); } } else { matchedDirectories.Add(path, displayPath); } } } var patternSegment = segment.IndexOfAny(GlobChars) >= 0 || matchedFiles.Count > 1; hasPatternSegments |= patternSegment; var displaySegment = segment; if (context.Container != null) { displaySegment = context.Container.TranslateToContainerPath(displaySegment); } KeySegmentType segmentType; object details; if (fingerprintType == FingerprintType.Key) { segmentType = patternSegment ? KeySegmentType.FilePattern : KeySegmentType.FilePath; details = matchedFiles.Values.ToArray(); resolvedSegments.Add(MatchedFile.GenerateHash(matchedFiles.Values)); if (!matchedFiles.Any()) { var message = patternSegment ? $"No matching files found for pattern: {displaySegment}" : $"File not found: {displaySegment}"; exceptions.Add(new FileNotFoundException(message)); } } else { segmentType = patternSegment ? KeySegmentType.DirectoryPattern : KeySegmentType.Directory; details = matchedDirectories.Values.ToArray(); resolvedSegments.AddRange(matchedDirectories.Values); if (!matchedDirectories.Any()) { var message = patternSegment ? $"No matching directories found for pattern: {displaySegment}" : $"Directory not found: {displaySegment}"; exceptions.Add(new DirectoryNotFoundException(message)); } } LogSegment( context, segments, displaySegment, segmentType, details ); } } if (fingerprintType == FingerprintType.Path) { // If there are segments or contains a glob pattern, all resolved segments must be rooted within filePathRoot (e.g. Pipeline.Workspace) // This limitation is mainly due to 7z not extracting backtraced paths if (resolvedSegments.Count() > 1 || hasPatternSegments) { foreach (var backtracedPath in resolvedSegments.Where(x => x.StartsWith(".."))) { exceptions.Add(new ArgumentException($"Resolved path is not within `Pipeline.Workspace`: {backtracedPath}")); } } } if (exceptions.Any()) { throw new AggregateException(exceptions); } return(new Fingerprint() { Segments = resolvedSegments.ToArray() }); }