Skip to content

Commit 9049ba4

Browse files
Merge pull request #588 Allow --files-list option for prefetch
This PR addresses #580 and adds --files-list option to prefetch verb. It also adds a magic file name, to allow passing in both --files-list and --folders-list via stdin.
2 parents 0459b28 + f8aea64 commit 9049ba4

6 files changed

Lines changed: 225 additions & 90 deletions

File tree

GVFS/FastFetch/FastFetchVerb.cs

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -105,8 +105,8 @@ public class FastFetchVerb
105105
Default = "",
106106
Required = false,
107107
HelpText = "Sets the path and filename for git.exe if it isn't expected to be on %PATH%.")]
108-
public string GitBinPath { get; set; }
109-
108+
public string GitBinPath { get; set; }
109+
110110
[Option(
111111
"folders",
112112
Required = false,
@@ -171,8 +171,8 @@ private int ExecuteWithExitCode()
171171
{
172172
Console.WriteLine("Cannot use --force-checkout option without --checkout option.");
173173
return ExitFailure;
174-
}
175-
174+
}
175+
176176
this.SearchThreadCount = this.SearchThreadCount > 0 ? this.SearchThreadCount : Environment.ProcessorCount;
177177
this.DownloadThreadCount = this.DownloadThreadCount > 0 ? this.DownloadThreadCount : Math.Min(Environment.ProcessorCount, MaxDefaultDownloadThreads);
178178
this.IndexThreadCount = this.IndexThreadCount > 0 ? this.IndexThreadCount : Environment.ProcessorCount;
@@ -185,8 +185,8 @@ private int ExecuteWithExitCode()
185185
{
186186
Console.WriteLine("Must be run within a git repo");
187187
return ExitFailure;
188-
}
189-
188+
}
189+
190190
string commitish = this.Commit ?? this.Branch;
191191
if (string.IsNullOrWhiteSpace(commitish))
192192
{
@@ -238,11 +238,11 @@ private int ExecuteWithExitCode()
238238
tracer.RelatedError(error);
239239
Console.WriteLine(error);
240240
return ExitFailure;
241-
}
242-
241+
}
242+
243243
RetryConfig retryConfig = new RetryConfig(this.MaxAttempts, TimeSpan.FromMinutes(RetryConfig.FetchAndCloneTimeoutMinutes));
244244
BlobPrefetcher prefetcher = this.GetFolderPrefetcher(tracer, enlistment, cacheServer, retryConfig);
245-
if (!BlobPrefetcher.TryLoadFolderList(enlistment, this.FolderList, this.FolderListFile, prefetcher.FolderList, out error))
245+
if (!BlobPrefetcher.TryLoadFolderList(enlistment, this.FolderList, this.FolderListFile, prefetcher.FolderList, readListFromStdIn: false, error: out error))
246246
{
247247
tracer.RelatedError(error);
248248
Console.WriteLine(error);

GVFS/GVFS.Common/Prefetch/BlobPrefetcher.cs

Lines changed: 108 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -83,70 +83,47 @@ public BlobPrefetcher(
8383

8484
public List<string> FolderList { get; }
8585

86-
public static bool TryLoadFolderList(Enlistment enlistment, string foldersInput, string folderListFile, List<string> folderListOutput, out string error)
86+
public static bool TryLoadFolderList(Enlistment enlistment, string foldersInput, string folderListFile, List<string> folderListOutput, bool readListFromStdIn, out string error)
8787
{
88-
folderListOutput.AddRange(
89-
foldersInput.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
90-
.Select(path => BlobPrefetcher.ToAbsolutePath(enlistment, path, isFolder: true)));
91-
92-
if (!string.IsNullOrWhiteSpace(folderListFile))
93-
{
94-
if (File.Exists(folderListFile))
95-
{
96-
IEnumerable<string> allLines = File.ReadAllLines(folderListFile)
97-
.Select(line => line.Trim())
98-
.Where(line => !string.IsNullOrEmpty(line))
99-
.Where(line => !line.StartsWith(GVFSConstants.GitCommentSign.ToString()))
100-
.Select(path => BlobPrefetcher.ToAbsolutePath(enlistment, path, isFolder: true));
101-
102-
folderListOutput.AddRange(allLines);
103-
}
104-
else
105-
{
106-
error = string.Format("Could not find '{0}' for folder list.", folderListFile);
107-
return false;
108-
}
109-
}
110-
111-
folderListOutput.RemoveAll(string.IsNullOrWhiteSpace);
112-
113-
foreach (string folder in folderListOutput)
114-
{
115-
if (folder.Contains("*"))
116-
{
117-
error = "Wildcards are not supported for folders. Invalid entry: " + folder;
118-
return false;
119-
}
120-
}
121-
122-
error = null;
123-
return true;
88+
return TryLoadFileOrFolderList(
89+
enlistment,
90+
foldersInput,
91+
folderListFile,
92+
isFolder: true,
93+
readListFromStdIn: readListFromStdIn,
94+
output: folderListOutput,
95+
elementValidationFunction: s =>
96+
s.Contains("*") ?
97+
"Wildcards are not supported for folders. Invalid entry: " + s :
98+
null,
99+
error: out error);
124100
}
125101

126-
public static bool TryLoadFileList(Enlistment enlistment, string filesInput, List<string> fileListOutput, out string error)
102+
public static bool TryLoadFileList(Enlistment enlistment, string filesInput, string filesListFile, List<string> fileListOutput, bool readListFromStdIn, out string error)
127103
{
128-
fileListOutput.AddRange(
129-
filesInput.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries)
130-
.Select(path => BlobPrefetcher.ToAbsolutePath(enlistment, path, isFolder: false)));
131-
132-
foreach (string file in fileListOutput)
133-
{
134-
if (file.IndexOf('*', 1) != -1)
104+
return TryLoadFileOrFolderList(
105+
enlistment,
106+
filesInput,
107+
filesListFile,
108+
readListFromStdIn: readListFromStdIn,
109+
isFolder: false,
110+
output: fileListOutput,
111+
elementValidationFunction: s =>
135112
{
136-
error = "Only prefix wildcards are supported. Invalid entry: " + file;
137-
return false;
138-
}
113+
if (s.IndexOf('*', 1) != -1)
114+
{
115+
return "Only prefix wildcards are supported. Invalid entry: " + s;
116+
}
139117

140-
if (file.EndsWith(GVFSConstants.GitPathSeparatorString) ||
141-
file.EndsWith(pathSeparatorString))
142-
{
143-
error = "Folders are not allowed in the file list. Invalid entry: " + file;
144-
return false;
145-
}
146-
}
118+
if (s.EndsWith(GVFSConstants.GitPathSeparatorString) ||
119+
s.EndsWith(pathSeparatorString))
120+
{
121+
return "Folders are not allowed in the file list. Invalid entry: " + s;
122+
}
147123

148-
error = null;
149-
return true;
124+
return null;
125+
},
126+
error: out error);
150127
}
151128

152129
public static bool IsNoopPrefetch(
@@ -173,7 +150,7 @@ public static bool IsNoopPrefetch(
173150

174151
tracer.RelatedEvent(
175152
EventLevel.Informational,
176-
"BlobPrefetcher.IsNoopPrefetch",
153+
"BlobPrefetcher.IsNoopPrefetch",
177154
new EventMetadata
178155
{
179156
{ "Last" + PrefetchArgs.CommitId, lastCommitId },
@@ -231,7 +208,7 @@ public virtual void Prefetch(string branchOrCommit, bool isBranch)
231208
int matchedBlobCount;
232209
int downloadedBlobCount;
233210
int hydratedFileCount;
234-
211+
235212
this.PrefetchWithStats(branchOrCommit, isBranch, false, out matchedBlobCount, out downloadedBlobCount, out hydratedFileCount);
236213
}
237214

@@ -290,7 +267,7 @@ public void PrefetchWithStats(
290267
return;
291268
}
292269
}
293-
270+
294271
BlockingCollection<string> availableBlobs = new BlockingCollection<string>();
295272

296273
////
@@ -300,7 +277,7 @@ public void PrefetchWithStats(
300277
// | | | |
301278
// ------------------------------------------------------> fileHydrator
302279
////
303-
280+
304281
// diff
305282
// Inputs:
306283
// * files/folders
@@ -310,7 +287,7 @@ public void PrefetchWithStats(
310287
// * FileAddOperations (property): Repo-relative paths corresponding to those blob ids
311288
DiffHelper diff = new DiffHelper(this.Tracer, this.Enlistment, this.FileList, this.FolderList, includeSymLinks: false);
312289

313-
// blobFinder
290+
// blobFinder
314291
// Inputs:
315292
// * requiredBlobs (in param): Blob ids from output of `diff`
316293
// Outputs:
@@ -513,9 +490,77 @@ protected void DownloadMissingCommit(string commitSha, GitObjects gitObjects)
513490
}
514491
}
515492

493+
private static IEnumerable<string> GetFilesFromVerbParameter(string valueString)
494+
{
495+
return valueString.Split(new[] { ';' }, StringSplitOptions.RemoveEmptyEntries);
496+
}
497+
498+
private static IEnumerable<string> GetFilesFromFile(string fileName, out string error)
499+
{
500+
error = null;
501+
if (string.IsNullOrWhiteSpace(fileName))
502+
{
503+
return Enumerable.Empty<string>();
504+
}
505+
506+
if (!File.Exists(fileName))
507+
{
508+
error = string.Format("Could not find '{0}' list file.", fileName);
509+
return Enumerable.Empty<string>();
510+
}
511+
512+
return File.ReadAllLines(fileName)
513+
.Select(line => line.Trim());
514+
}
515+
516+
private static IEnumerable<string> GetFilesFromStdin(bool shouldRead)
517+
{
518+
if (!shouldRead)
519+
{
520+
yield break;
521+
}
522+
523+
string line;
524+
while ((line = Console.In.ReadLine()) != null)
525+
{
526+
yield return line.Trim();
527+
}
528+
}
529+
530+
private static bool TryLoadFileOrFolderList(Enlistment enlistment, string valueString, string listFileName, bool readListFromStdIn, bool isFolder, List<string> output, Func<string, string> elementValidationFunction, out string error)
531+
{
532+
output.AddRange(
533+
GetFilesFromVerbParameter(valueString)
534+
.Union(GetFilesFromFile(listFileName, out string fileReadError))
535+
.Union(GetFilesFromStdin(readListFromStdIn))
536+
.Where(path => !path.StartsWith(GVFSConstants.GitCommentSign.ToString()))
537+
.Where(path => !string.IsNullOrWhiteSpace(path))
538+
.Select(path => BlobPrefetcher.ToAbsolutePath(enlistment, path, isFolder: isFolder)));
539+
540+
if (!string.IsNullOrWhiteSpace(fileReadError))
541+
{
542+
error = fileReadError;
543+
return false;
544+
}
545+
546+
string[] errorArray = output
547+
.Select(elementValidationFunction)
548+
.Where(s => !string.IsNullOrWhiteSpace(s))
549+
.ToArray();
550+
551+
if (errorArray != null && errorArray.Length > 0)
552+
{
553+
error = string.Join("\n", errorArray);
554+
return false;
555+
}
556+
557+
error = null;
558+
return true;
559+
}
560+
516561
private static string ToAbsolutePath(Enlistment enlistment, string path, bool isFolder)
517562
{
518-
string absolute =
563+
string absolute =
519564
path.StartsWith("*")
520565
? path
521566
: Path.Combine(enlistment.WorkingDirectoryRoot, path.Replace(GVFSConstants.GitPathSeparator, Path.DirectorySeparatorChar).TrimStart(Path.DirectorySeparatorChar));

GVFS/GVFS.FunctionalTests/Tests/EnlistmentPerFixture/PrefetchVerbTests.cs

Lines changed: 55 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using GVFS.FunctionalTests.Tools;
44
using GVFS.Tests.Should;
55
using NUnit.Framework;
6+
using System;
67
using System.IO;
78
using System.Threading;
89

@@ -93,7 +94,7 @@ public void PrefetchFolderListFromFile()
9394
"gvfs/"
9495
});
9596

96-
this.ExpectBlobCount(this.Enlistment.Prefetch("--folders-list " + tempFilePath), 279);
97+
this.ExpectBlobCount(this.Enlistment.Prefetch("--folders-list \"" + tempFilePath + "\""), 279);
9798
File.Delete(tempFilePath);
9899
}
99100

@@ -133,7 +134,7 @@ public void PrefetchCleansUpStalePrefetchLock()
133134
[Category(Categories.MacTODO.M4)]
134135
public void PrefetchCleansUpPackDir()
135136
{
136-
string multiPackIndexLockFile = Path.Combine(this.Enlistment.GetPackRoot(this.fileSystem), MultiPackIndexLock);
137+
string multiPackIndexLockFile = Path.Combine(this.Enlistment.GetPackRoot(this.fileSystem), MultiPackIndexLock);
137138
string oldGitTempFile = Path.Combine(this.Enlistment.GetPackRoot(this.fileSystem), "tmp_midx_XXXX");
138139

139140
this.fileSystem.WriteAllText(multiPackIndexLockFile, this.Enlistment.EnlistmentRoot);
@@ -147,6 +148,58 @@ public void PrefetchCleansUpPackDir()
147148
}
148149

149150
[TestCase, Order(12)]
151+
public void PrefetchFilesFromFileListFile()
152+
{
153+
string tempFilePath = Path.Combine(Path.GetTempPath(), "temp.file");
154+
try
155+
{
156+
File.WriteAllLines(
157+
tempFilePath,
158+
new[]
159+
{
160+
Path.Combine("GVFS", "GVFS", "Program.cs"),
161+
Path.Combine("GVFS", "GVFS.FunctionalTests", "GVFS.FunctionalTests.csproj")
162+
});
163+
164+
this.ExpectBlobCount(this.Enlistment.Prefetch($"--files-list \"{tempFilePath}\""), 2);
165+
}
166+
finally
167+
{
168+
File.Delete(tempFilePath);
169+
}
170+
}
171+
172+
[TestCase, Order(13)]
173+
public void PrefetchFilesFromFileListStdIn()
174+
{
175+
string input = string.Join(
176+
Environment.NewLine,
177+
new[]
178+
{
179+
Path.Combine("GVFS", "GVFS", "packages.config"),
180+
Path.Combine("GVFS", "GVFS.FunctionalTests", "App.config")
181+
});
182+
183+
this.ExpectBlobCount(this.Enlistment.Prefetch("--stdin-files-list", standardInput: input), 2);
184+
}
185+
186+
[TestCase, Order(14)]
187+
public void PrefetchFolderListFromStdin()
188+
{
189+
string input = string.Join(
190+
Environment.NewLine,
191+
new[]
192+
{
193+
"# A comment",
194+
" ",
195+
"gvfs/",
196+
"gvfs/gvfs",
197+
"gvfs/"
198+
});
199+
200+
this.ExpectBlobCount(this.Enlistment.Prefetch("--stdin-folders-list", standardInput: input), 279);
201+
}
202+
150203
public void PrefetchPathsWithLsTreeTypeInPath()
151204
{
152205
ProcessResult checkoutResult = GitProcess.InvokeProcess(this.Enlistment.RepoRoot, "checkout " + LsTreeTypeInPathBranchName);

GVFS/GVFS.FunctionalTests/Tools/GVFSFunctionalTestEnlistment.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -197,9 +197,9 @@ public bool TryMountGVFS(out string output)
197197
return this.gvfsProcess.TryMount(out output);
198198
}
199199

200-
public string Prefetch(string args, bool failOnError = true)
200+
public string Prefetch(string args, bool failOnError = true, string standardInput = null)
201201
{
202-
return this.gvfsProcess.Prefetch(args, failOnError);
202+
return this.gvfsProcess.Prefetch(args, failOnError, standardInput);
203203
}
204204

205205
public void Repair(bool confirm)

0 commit comments

Comments
 (0)