-
Notifications
You must be signed in to change notification settings - Fork 68
Expand file tree
/
Copy pathConfigStep.cs
More file actions
290 lines (253 loc) · 12.8 KB
/
ConfigStep.cs
File metadata and controls
290 lines (253 loc) · 12.8 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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
using Scalar.Common.Git;
using Scalar.Common.Tracing;
using System;
using System.Collections.Generic;
using System.IO;
using System.Runtime.InteropServices;
namespace Scalar.Common.Maintenance
{
public class ConfigStep : GitMaintenanceStep
{
public override string Area => nameof(ConfigStep);
[Flags]
private enum GitCoreGVFSFlags
{
// GVFS_SKIP_SHA_ON_INDEX
// Disables the calculation of the sha when writing the index
SkipShaOnIndex = 1 << 0,
// GVFS_BLOCK_COMMANDS
// Blocks git commands that are not allowed in a GVFS/Scalar repo
BlockCommands = 1 << 1,
// GVFS_MISSING_OK
// Normally git write-tree ensures that the objects referenced by the
// directory exist in the object database.This option disables this check.
MissingOk = 1 << 2,
// GVFS_NO_DELETE_OUTSIDE_SPARSECHECKOUT
// When marking entries to remove from the index and the working
// directory this option will take into account what the
// skip-worktree bit was set to so that if the entry has the
// skip-worktree bit set it will not be removed from the working
// directory. This will allow virtualized working directories to
// detect the change to HEAD and use the new commit tree to show
// the files that are in the working directory.
NoDeleteOutsideSparseCheckout = 1 << 3,
// GVFS_FETCH_SKIP_REACHABILITY_AND_UPLOADPACK
// While performing a fetch with a virtual file system we know
// that there will be missing objects and we don't want to download
// them just because of the reachability of the commits. We also
// don't want to download a pack file with commits, trees, and blobs
// since these will be downloaded on demand. This flag will skip the
// checks on the reachability of objects during a fetch as well as
// the upload pack so that extraneous objects don't get downloaded.
FetchSkipReachabilityAndUploadPack = 1 << 4,
// 1 << 5 has been deprecated
// GVFS_BLOCK_FILTERS_AND_EOL_CONVERSIONS
// With a virtual file system we only know the file size before any
// CRLF or smudge/clean filters processing is done on the client.
// To prevent file corruption due to truncation or expansion with
// garbage at the end, these filters must not run when the file
// is first accessed and brought down to the client. Git.exe can't
// currently tell the first access vs subsequent accesses so this
// flag just blocks them from occurring at all.
BlockFiltersAndEolConversions = 1 << 6,
// GVFS_PREFETCH_DURING_FETCH
// While performing a `git fetch` command, use the gvfs-helper to
// perform a "prefetch" of commits and trees.
PrefetchDuringFetch = 1 << 7,
}
private bool? UseGvfsProtocol = true;
public ConfigStep(ScalarContext context, bool? useGvfsProtocol = null) : base(context, requireObjectCacheLock: false)
{
this.UseGvfsProtocol = useGvfsProtocol;
}
public override string ProgressMessage => "Setting recommended config settings";
public bool TrySetConfig(out string error)
{
string coreGVFSFlags = Convert.ToInt32(
GitCoreGVFSFlags.BlockCommands |
GitCoreGVFSFlags.MissingOk |
GitCoreGVFSFlags.FetchSkipReachabilityAndUploadPack |
GitCoreGVFSFlags.PrefetchDuringFetch)
.ToString();
string expectedHooksPath = Path.Combine(this.Context.Enlistment.WorkingDirectoryRoot, ScalarConstants.DotGit.Hooks.Root);
expectedHooksPath = Paths.ConvertPathToGitFormat(expectedHooksPath);
if (!this.UseGvfsProtocol.HasValue)
{
this.UseGvfsProtocol = this.Context.Enlistment.UsesGvfsProtocol;
}
// These settings are required for normal Scalar functionality.
// They will override any existing local configuration values.
//
// IMPORTANT! These must parallel the settings in ControlGitRepo:Initialize
//
Dictionary<string, string> requiredSettings = new Dictionary<string, string>
{
{ "am.keepcr", "true" },
{ "core.fscache", "true" },
{ "core.multiPackIndex", "true" },
{ "core.preloadIndex", "true" },
{ "core.untrackedCache", ScalarPlatform.Instance.FileSystem.SupportsUntrackedCache ? "true" : "false" },
{ "core.filemode", ScalarPlatform.Instance.FileSystem.SupportsFileMode ? "true" : "false" },
{ "core.bare", "false" },
{ "core.logallrefupdates", "true" },
{ "core.hookspath", expectedHooksPath },
{ GitConfigSetting.CredentialUseHttpPath, "true" },
{ "credential.validate", "false" },
{ "gc.auto", "0" },
{ "gui.gcwarning", "false" },
{ "index.threads", "true" },
{ "index.version", "4" },
{ "log.excludeDecoration", "refs/scalar/*" },
{ "merge.stat", "false" },
{ "merge.renames", "false" },
{ "pack.useBitmaps", "false" },
{ "pack.useSparse", "true" },
{ "receive.autogc", "false" },
{ "reset.quiet", "true" },
{ "feature.manyFiles", "false" },
{ "feature.experimental", "false" },
{ "fetch.unpackLimit", "1" },
{ "fetch.writeCommitGraph", "false" },
};
if (this.UseGvfsProtocol.Value)
{
requiredSettings.Add("core.gvfs", coreGVFSFlags);
requiredSettings.Add(ScalarConstants.GitConfig.UseGvfsHelper, "true");
requiredSettings.Add("http.version", "HTTP/1.1");
}
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
{
requiredSettings.Add("http.sslBackend", "schannel");
}
// If we do not use the GVFS protocol, then these config settings
// are in fact optional.
if (!this.TrySetConfig(requiredSettings, isRequired: this.UseGvfsProtocol.Value, out error))
{
error = $"Failed to set some required settings: {error}";
this.Context.Tracer.RelatedError(error);
return false;
}
// These settings are optional, because they impact performance but not functionality of Scalar.
// These settings should only be set by the clone or repair verbs, so that they do not
// overwrite the values set by the user in their local config.
Dictionary<string, string> optionalSettings = new Dictionary<string, string>
{
{ "status.aheadbehind", "false" },
{ "core.autocrlf", "false" },
{ "core.safecrlf", "false" },
};
if (this.UseGvfsProtocol.Value)
{
// If a user's global config has "status.submoduleSummary=true", then
// that could slow "git status" significantly here, even though the
// GVFS protocol forbids submodules. Still, disable it optionally in
// case the user really wants it in their local config.
optionalSettings.Add("status.submoduleSummary", "false");
}
if (!this.TrySetConfig(optionalSettings, isRequired: false, out error))
{
error = $"Failed to set some optional settings: {error}";
this.Context.Tracer.RelatedError(error);
return false;
}
return this.ConfigureWatchmanIntegration(out error);
}
protected override void PerformMaintenance()
{
this.TrySetConfig(out _);
}
private bool TrySetConfig(Dictionary<string, string> configSettings, bool isRequired, out string error)
{
Dictionary<string, GitConfigSetting> existingConfigSettings = null;
GitProcess.Result getConfigResult = this.RunGitCommand(
process => process.TryGetAllConfig(localOnly: isRequired, configSettings: out existingConfigSettings),
nameof(GitProcess.TryGetAllConfig));
// If the settings are required, then only check local config settings, because we don't want to depend on
// global settings that can then change independent of this repo.
if (getConfigResult.ExitCodeIsFailure)
{
error = "Failed to get all config entries";
return false;
}
foreach (KeyValuePair<string, string> setting in configSettings)
{
GitConfigSetting existingSetting;
if (setting.Value != null)
{
if (!existingConfigSettings.TryGetValue(setting.Key, out existingSetting) ||
(isRequired && !existingSetting.HasValue(setting.Value)))
{
this.Context.Tracer.RelatedInfo($"Setting config value {setting.Key}={setting.Value}");
GitProcess.Result setConfigResult = this.RunGitCommand(
process => process.SetInLocalConfig(setting.Key, setting.Value),
nameof(GitProcess.SetInLocalConfig));
if (setConfigResult.ExitCodeIsFailure)
{
error = setConfigResult.Errors;
return false;
}
}
}
else
{
if (existingConfigSettings.TryGetValue(setting.Key, out _))
{
this.RunGitCommand(
process => process.DeleteFromLocalConfig(setting.Key),
nameof(GitProcess.DeleteFromLocalConfig));
}
}
}
error = null;
return true;
}
private bool ConfigureWatchmanIntegration(out string error)
{
string watchmanLocation = ProcessHelper.GetProgramLocation(ScalarPlatform.Instance.Constants.ProgramLocaterCommand, "watchman");
if (string.IsNullOrEmpty(watchmanLocation))
{
this.Context.Tracer.RelatedWarning("Watchman is not installed - skipping Watchman configuration.");
error = null;
return true;
}
try
{
string hooksDir = ScalarPlatform.Instance.GetTemplateHooksDirectory();
if (string.IsNullOrEmpty(hooksDir))
{
error = "Could not find hook templates directory. Skipping watchman integration.";
this.Context.Tracer.RelatedError(error);
return false;
}
// Install query-watchman hook from latest Git path
string fsMonitorWatchmanSampleHookPath = Path.Combine(
hooksDir,
ScalarConstants.DotGit.Hooks.FsMonitorWatchmanSampleName);
string queryWatchmanPath = Path.Combine(
this.Context.Enlistment.WorkingDirectoryRoot,
ScalarConstants.DotGit.Hooks.QueryWatchmanPath);
this.Context.FileSystem.CopyFile(
fsMonitorWatchmanSampleHookPath,
queryWatchmanPath,
overwrite: true);
string dotGitRoot = this.Context.Enlistment.DotGitRoot.Replace(Path.DirectorySeparatorChar, ScalarConstants.GitPathSeparator);
this.RunGitCommand(
process => process.SetInLocalConfig("core.fsmonitor", dotGitRoot + "/hooks/query-watchman"),
"config");
this.RunGitCommand(
process => process.SetInLocalConfig("core.fsmonitorHookVersion", "2"),
"config");
this.Context.Tracer.RelatedInfo("Watchman configured!");
error = null;
return true;
}
catch (IOException ex)
{
EventMetadata metadata = this.CreateEventMetadata(ex);
error = $"Failed to configure Watchman integration: {ex.Message}";
this.Context.Tracer.RelatedError(metadata, error);
return false;
}
}
}
}