forked from microsoft/VFSForGit
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathEnlistmentHydrationSummary.cs
More file actions
196 lines (177 loc) · 7.74 KB
/
EnlistmentHydrationSummary.cs
File metadata and controls
196 lines (177 loc) · 7.74 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
using GVFS.Common.FileSystem;
using GVFS.Common.Git;
using System;
using System.IO;
using System.Linq;
using System.Threading;
namespace GVFS.Common
{
public class EnlistmentHydrationSummary
{
public int HydratedFileCount { get; private set; }
public int TotalFileCount { get; private set; }
public int HydratedFolderCount { get; private set; }
public int TotalFolderCount { get; private set; }
public Exception Error { get; private set; } = null;
public bool IsValid
{
get
{
return HydratedFileCount >= 0
&& HydratedFolderCount >= 0
&& TotalFileCount >= HydratedFileCount
&& TotalFolderCount >= HydratedFolderCount;
}
}
public string ToMessage()
{
if (!IsValid)
{
return "Error calculating hydration summary. Run 'gvfs health' at the repository root for hydration status details.";
}
int fileHydrationPercent = TotalFileCount == 0 ? 0 : (100 * HydratedFileCount) / TotalFileCount;
int folderHydrationPercent = TotalFolderCount == 0 ? 0 : ((100 * HydratedFolderCount) / TotalFolderCount);
return $"{fileHydrationPercent}% of files and {folderHydrationPercent}% of folders hydrated. Run 'gvfs health' at the repository root for details.";
}
public static EnlistmentHydrationSummary CreateSummary(
GVFSEnlistment enlistment,
PhysicalFileSystem fileSystem,
CancellationToken cancellationToken = default)
{
try
{
/* Getting all the file paths from git index is slow and we only need the total count,
* so we read the index file header instead of calling GetPathsFromGitIndex */
int totalFileCount = GetIndexFileCount(enlistment, fileSystem);
/* Getting all the directories is also slow, but not as slow as reading the entire index,
* GetTotalPathCount caches the count so this is only slow occasionally,
* and the GitStatusCache manager also calls this to ensure it is updated frequently. */
cancellationToken.ThrowIfCancellationRequested();
int totalFolderCount = GetHeadTreeCount(enlistment, fileSystem);
EnlistmentPathData pathData = new EnlistmentPathData();
/* FUTURE: These could be optimized to only deal with counts instead of full path lists */
pathData.LoadPlaceholdersFromDatabase(enlistment);
cancellationToken.ThrowIfCancellationRequested();
pathData.LoadModifiedPaths(enlistment);
cancellationToken.ThrowIfCancellationRequested();
int hydratedFileCount = pathData.ModifiedFilePaths.Count + pathData.PlaceholderFilePaths.Count;
int hydratedFolderCount = pathData.ModifiedFolderPaths.Count + pathData.PlaceholderFolderPaths.Count;
return new EnlistmentHydrationSummary()
{
HydratedFileCount = hydratedFileCount,
HydratedFolderCount = hydratedFolderCount,
TotalFileCount = totalFileCount,
TotalFolderCount = totalFolderCount,
};
}
catch (OperationCanceledException)
{
return new EnlistmentHydrationSummary()
{
HydratedFileCount = -1,
HydratedFolderCount = -1,
TotalFileCount = -1,
TotalFolderCount = -1,
};
}
catch (Exception e)
{
return new EnlistmentHydrationSummary()
{
HydratedFileCount = -1,
HydratedFolderCount = -1,
TotalFileCount = -1,
TotalFolderCount = -1,
Error = e,
};
}
}
/// <summary>
/// Get the total number of files in the index.
/// </summary>
internal static int GetIndexFileCount(GVFSEnlistment enlistment, PhysicalFileSystem fileSystem)
{
string indexPath = Path.Combine(enlistment.WorkingDirectoryBackingRoot, GVFSConstants.DotGit.Index);
using (var indexFile = fileSystem.OpenFileStream(indexPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite, callFlushFileBuffers: false))
{
if (indexFile.Length < 12)
{
return -1;
}
/* The number of files in the index is a big-endian integer from
* the 4 bytes at offsets 8-11 of the index file. */
indexFile.Position = 8;
var bytes = new byte[4];
indexFile.Read(
bytes, // Destination buffer
offset: 0, // Offset in destination buffer, not in indexFile
count: 4);
if (BitConverter.IsLittleEndian)
{
Array.Reverse(bytes);
}
int count = BitConverter.ToInt32(bytes, 0);
return count;
}
}
/// <summary>
/// Get the total number of trees in the repo at HEAD.
/// </summary>
/// <remarks>
/// This is used as the denominator in displaying percentage of hydrated
/// directories as part of git status pre-command hook.
/// It can take several seconds to calculate, so we cache it near the git status cache.
/// </remarks>
/// <returns>
/// The number of subtrees at HEAD, which may be 0.
/// Will return 0 if unsuccessful.
/// </returns>
internal static int GetHeadTreeCount(GVFSEnlistment enlistment, PhysicalFileSystem fileSystem)
{
var gitProcess = enlistment.CreateGitProcess();
var headResult = gitProcess.GetHeadTreeId();
if (headResult.ExitCodeIsFailure)
{
return 0;
}
var headSha = headResult.Output.Trim();
var cacheFile = Path.Combine(
enlistment.DotGVFSRoot,
GVFSConstants.DotGVFS.GitStatusCache.TreeCount);
// Load from cache if cache matches current HEAD.
if (fileSystem.FileExists(cacheFile))
{
try
{
var lines = fileSystem.ReadLines(cacheFile).ToArray();
if (lines.Length == 2
&& lines[0] == headSha
&& int.TryParse(lines[1], out int cachedCount))
{
return cachedCount;
}
}
catch
{
// Ignore errors reading the cache
}
}
int totalPathCount = 0;
GitProcess.Result folderResult = gitProcess.LsTree(
GVFSConstants.DotGit.HeadName,
line => totalPathCount++,
recursive: true,
showDirectories: true);
try
{
fileSystem.CreateDirectory(Path.GetDirectoryName(cacheFile));
fileSystem.WriteAllText(cacheFile, $"{headSha}\n{totalPathCount}");
}
catch
{
// Ignore errors writing the cache
}
return totalPathCount;
}
}
}