Skip to content

Commit adb620b

Browse files
committed
Replace GVFS.Service with machine-wide logon task and LocalRepoRegistry
Replace the GVFS.Service Windows service with a simpler architecture: Infrastructure: - LocalRepoRegistry: file-based repo tracking, wire-compatible with old service format. SYSTEM uses ProgramData; per-user uses platform default. Seed-on-first-use copies accessible entries from system registry. - LogonTaskRegistration: machine-wide \GVFS\AutoMount task fires for all interactive users (GroupId S-1-5-4) at logon, runs gvfs service --mount-all. Each user's repos loaded from their own LocalRepoRegistry. - ProjFS boot task: enable-projfs-on-all-drives.ps1 enables ProjFS and attaches PrjFlt on all volumes. Embedded in task XML via build-task-xml.ps1 with SHA-256 hash marker for drift detection. - CLI verb fallbacks: mount/unmount/service verbs fall back to LocalRepoRegistry when the service named pipe is unavailable. - GVFSVerb: silent-success fallback for PrjFlt FilterAttach. - InProcessMount: restore exception safety net in HandleRequest. Installer: - Stop and delete GVFS.Service on upgrade from older versions. - Register \GVFS\AutoMount logon task. - Remove service deployment, install, start from [Files]/[Run]. - Remove PendingUpgrade staging logic and ShowMountChoiceDialog. - Exclude GVFS.Service.exe from payload (layout.bat). Functional tests: - Remove service install/uninstall (no service to test against). - Settings.cs auto-detects user-mode gvfs at %LocalAppData%\VFSForGit\Current. 926 unit tests pass. Assisted-by: Claude Sonnet 4.5 Assisted-by: Claude Opus 4.6 Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
1 parent 410f0d2 commit adb620b

21 files changed

Lines changed: 2304 additions & 342 deletions
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
using System.Collections.Generic;
2+
3+
namespace GVFS.Common
4+
{
5+
/// <summary>
6+
/// Abstracts the Windows Task Scheduler operations needed by
7+
/// <see cref="LogonTaskRegistration"/>. Production callers use
8+
/// <see cref="SchTasksScheduledTaskInvoker"/>; tests pass a mock so
9+
/// they can exercise <see cref="LogonTaskRegistration"/>'s logic
10+
/// without actually touching the Task Scheduler on the test machine.
11+
/// </summary>
12+
public interface IScheduledTaskInvoker
13+
{
14+
/// <summary>
15+
/// Register the task at <paramref name="taskPath"/> from the given
16+
/// XML, overwriting any existing task at that path. Returns
17+
/// <c>true</c> on success.
18+
/// </summary>
19+
bool TryRegisterFromXml(string taskPath, string xml, out string errorMessage);
20+
21+
/// <summary>
22+
/// Read back the registered XML for the task at
23+
/// <paramref name="taskPath"/>. Returns <c>true</c> with the XML
24+
/// when the task exists; returns <c>false</c> with a populated
25+
/// <paramref name="errorMessage"/> when it does not.
26+
/// </summary>
27+
bool TryQueryXml(string taskPath, out string xml, out string errorMessage);
28+
29+
/// <summary>
30+
/// Unregister the task at <paramref name="taskPath"/>. Returns
31+
/// <c>true</c> if the task was unregistered OR was not registered
32+
/// to begin with (idempotent). Returns <c>false</c> only on a hard
33+
/// failure (e.g., permission denied).
34+
/// </summary>
35+
bool TryUnregister(string taskPath, out string errorMessage);
36+
}
37+
}
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
using System.Text.Json;
2+
using System.Text.Json.Serialization;
3+
4+
namespace GVFS.Common
5+
{
6+
/// <summary>
7+
/// One entry in the user-level repo registry on disk. Field set and
8+
/// JSON shape MUST match GVFS.Service.RepoRegistration so that the
9+
/// user-level registry file (written by <see cref="LocalRepoRegistry"/>)
10+
/// is wire-compatible with any registry the legacy service has written
11+
/// in the past. If a new field is added here, the same field must also
12+
/// be added to GVFS.Service.RepoRegistration (and vice versa) along
13+
/// with a registry-format-version bump.
14+
/// </summary>
15+
public class LocalRepoRegistration
16+
{
17+
public LocalRepoRegistration()
18+
{
19+
}
20+
21+
public LocalRepoRegistration(string enlistmentRoot, string ownerSID)
22+
{
23+
this.EnlistmentRoot = enlistmentRoot;
24+
this.OwnerSID = ownerSID;
25+
this.IsActive = true;
26+
}
27+
28+
public string EnlistmentRoot { get; set; }
29+
public string OwnerSID { get; set; }
30+
public bool IsActive { get; set; }
31+
32+
// Uses LocalRepoRegistrationJsonContext (assembly-local source generator)
33+
// rather than GVFSJsonContext. The service-side RepoRegistration uses
34+
// its own ServiceJsonContext for the same reason — neither type can be
35+
// registered in GVFSJsonContext because GVFSJsonContext lives in
36+
// GVFS.Common and the service-side type lives in GVFS.Service (wrong
37+
// dependency direction). Keeping symmetric local contexts here means
38+
// the on-disk JSON shape is governed by identical source-gen behavior
39+
// on both sides.
40+
public static LocalRepoRegistration FromJson(string json)
41+
{
42+
return JsonSerializer.Deserialize(json, LocalRepoRegistrationJsonContext.Default.LocalRepoRegistration);
43+
}
44+
45+
public string ToJson()
46+
{
47+
return JsonSerializer.Serialize(this, LocalRepoRegistrationJsonContext.Default.LocalRepoRegistration);
48+
}
49+
50+
public override string ToString()
51+
{
52+
return string.Format(
53+
"({0} - {1}) {2}",
54+
this.IsActive ? "Active" : "Inactive",
55+
this.OwnerSID,
56+
this.EnlistmentRoot);
57+
}
58+
}
59+
60+
[JsonSerializable(typeof(LocalRepoRegistration))]
61+
internal partial class LocalRepoRegistrationJsonContext : JsonSerializerContext
62+
{
63+
}
64+
}

0 commit comments

Comments
 (0)