Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
9ab464e
Include executable extension in core.virtualfilesystem path
tyrielv Jul 1, 2026
b7827d1
Merge pull request #2038 from tyrielv/tyrielv/virtualfs-config-extension
tyrielv Jul 1, 2026
e14475c
fix: tighten FT slicer grouping regex to class-level
tyrielv Jun 30, 2026
dd63497
Merge pull request #2037 from tyrielv/tyrielv/ft-slice-balance
tyrielv Jul 1, 2026
6ee0ba2
Update default Microsoft Git version to v2.54.0.vfs.0.4
github-actions[bot] Jul 3, 2026
e51a34f
Merge pull request #2040 from microsoft/automation/gitrelease-v2.54.0…
mjcheetham Jul 3, 2026
3cbc75f
Fix clone ownership under Windows Administrator Protection
tyrielv Jul 3, 2026
776aea6
Re-sync libgit2 overlay admin-owner patch with merged upstream
tyrielv Jul 6, 2026
4a17c98
Merge pull request #2039 from tyrielv/tyrielv/fix-ap-clone-ownership
tyrielv Jul 6, 2026
2fa5b26
Merge pull request #2041 from tyrielv/tyrielv/resync-libgit2-admin-ow…
tyrielv Jul 6, 2026
d9d0a14
Bound git stdout/stderr capture to prevent GVFS.Mount OOM
tyrielv Jul 9, 2026
9aa675b
Narrow IsTransientError to BUSY+LOCKED only; exclude IOERR
tyrielv Jul 9, 2026
c01248d
ProjFS: only tolerate FilterAttach ACCESS_DENIED for unelevated callers
tyrielv Jul 9, 2026
e1dc57a
Mount: rethrow after logging in HandleRequest exception catch
tyrielv Jul 9, 2026
34666c4
Gate CLI mount-progress display behind gvfs.mount-progress config
tyrielv Jul 9, 2026
75adf9c
docs(agents): document release-shipping scope and feature-flag conven…
tyrielv Jul 9, 2026
c8eb49c
Bound the leaked ActiveEnumeration collection behind an off-by-defaul…
tyrielv Jul 9, 2026
0dcc2e8
Remove dead org auto-upgrade code
tyrielv Jul 9, 2026
6d92f79
Merge pull request #2048 from tyrielv/tyrielv/bound-git-output-buffer
tyrielv Jul 9, 2026
446303a
Merge pull request #2049 from tyrielv/tyrielv/narrow-ioerr-retry
tyrielv Jul 9, 2026
62c879f
Harden GVFS.Mount against native ProjFS failures under memory pressur…
tyrielv Jul 7, 2026
646346c
Split ServiceVerb repo-state check into intent-specific predicates
tyrielv Jul 10, 2026
b84ed52
Harden orphan-lock detection against false-release on transient failures
tyrielv Jul 10, 2026
f034ec7
test(functional): scope ServiceVerbTests to fixture service
tyrielv Jul 10, 2026
63223f8
HealthTests: wait for background ops before gvfs health
tyrielv Jul 10, 2026
52e7edd
Mount: don't crash when auth is used before background init completes
tyrielv Jul 13, 2026
067b106
Mount: gate background cache-server auth behind gvfs.background-cache…
tyrielv Jul 13, 2026
ba70b76
Merge pull request #2064 from tyrielv/tyrielv/flag-background-cache-auth
tyrielv Jul 13, 2026
2e74f61
Merge pull request #2063 from tyrielv/tyrielv/fix-mount-cache-auth-race
tyrielv Jul 14, 2026
a010c24
Merge pull request #2061 from tyrielv/tyrielv/fix-flaky-health-test
tyrielv Jul 14, 2026
301e73a
Merge pull request #2060 from tyrielv/tyrielv/orphan-lock-riskreduction
tyrielv Jul 14, 2026
a9a4fe2
Merge pull request #2053 from tyrielv/tyrielv/bound-active-enumeratio…
tyrielv Jul 14, 2026
e02c281
Merge pull request #2058 from tyrielv/tyrielv/fix-service-verb-mount-…
tyrielv Jul 14, 2026
27c13a5
Merge pull request #2057 from tyrielv/tyrielv/remove-dead-org-upgrader
tyrielv Jul 14, 2026
1f34e9a
Gate prefetch offload behind config flag
tyrielv Jul 9, 2026
e642590
Remove redundant heartbeat FeatureFlags telemetry
tyrielv Jul 13, 2026
ee65930
Potential fix for pull request finding
tyrielv Jul 14, 2026
c0a95bc
Retrigger CI: NuGet feed pull-through completed
tyrielv Jul 14, 2026
2e50a46
Merge pull request #2059 from microsoft/tyrielv/test-registry-scoping
tyrielv Jul 15, 2026
7c23da0
Merge pull request #2054 from tyrielv/tyrielv/flag-prefetch-offload
tyrielv Jul 15, 2026
f04016a
Merge pull request #2042 from tyrielv/tyrielv/fix-projfs-mount-oom-crash
tyrielv Jul 15, 2026
d505b3e
Merge pull request #2056 from tyrielv/tyrielv/agents-md-release-facts
tyrielv Jul 15, 2026
032f781
Merge pull request #2051 from tyrielv/tyrielv/filter-attach-elevation…
tyrielv Jul 15, 2026
cf7a17a
Merge pull request #2050 from tyrielv/tyrielv/narrow-handlerequest-catch
tyrielv Jul 15, 2026
7fc3913
Merge pull request #2055 from tyrielv/tyrielv/mount-progress-flag
tyrielv Jul 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ permissions:
checks: read

env:
GIT_VERSION: ${{ github.event.inputs.git_version || 'v2.54.0.vfs.0.2' }}
GIT_VERSION: ${{ github.event.inputs.git_version || 'v2.54.0.vfs.0.4' }}

jobs:
validate:
Expand Down
33 changes: 33 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,39 @@ Do not manually delete or re-run vcpkg unless you've changed an overlay
port. If you've copied `out\` from another enlistment as a build-time
shortcut, vcpkg results come along with it.

## What ships in a public release

The signed release pipeline builds several artifacts, but the **public GitHub
release only carries the installer and symbols**: `SetupGVFS.<version>.exe`
(per arch) and `Symbols.zip`.

- **FastFetch is NOT shipped publicly.** `FastFetch.exe` is built and
ESRP-signed as a pipeline artifact, but it is not attached to the GitHub
release. Treat FastFetch-only changes as **non-shipping** when scoping a
release or writing changelog notes — they don't reach the installer end
users get.
- **Git is NOT bundled.** VFS for Git does not ship Microsoft Git. PRs titled
"update default Microsoft Git version" change only the CI `GIT_VERSION` in
`.github/workflows/build.yaml` (used to install Git for build + functional
test runs) — they are **non-shipping**. The only shipped Git constraint is
the compiled `MinimumGitVersion` constant in `Version.props`, which is
changed separately and rarely.

## Feature flags

Product feature flags are **git config** keys under the `gvfs.` prefix (not a
separate config system). To add one, mirror `gvfs.show-hydration-status`:

- Declare the key name and default in `GVFS.Common/GVFSConstants.cs` under
`GitConfig` (e.g. `ShowHydrationStatus = GVFSPrefix + "show-hydration-status"`
and `ShowHydrationStatusDefault = false`).
- Read it via `repo.GetConfigBoolOrDefault(name, default)` (or
`LibGit2RepoInvoker.GetConfigBoolOrDefault`) at the point of use.

Default new gates to `false` and gate the **runtime entry point** into a
feature, not its build, so the code still compiles and ships (and keeps
getting exercised) while its behavior stays off by default.

## Coding standards

See [CONTRIBUTING.md](CONTRIBUTING.md) for StyleCop rules, error-handling
Expand Down
51 changes: 0 additions & 51 deletions GVFS/GVFS.Common/AzDevOpsOrgFromNuGetFeed.cs

This file was deleted.

16 changes: 12 additions & 4 deletions GVFS/GVFS.Common/Database/SqliteErrorCodes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,21 @@ public static class SqliteErrorCodes
public const int NotADatabase = 26;

/// <summary>
/// Returns true if the error code represents a transient condition
/// that may resolve on retry (I/O errors, locking contention).
/// Returns true if the error code represents a transient locking condition
/// that is safe to retry. Only BUSY and LOCKED qualify: both mean the operation
/// was blocked before executing and was never committed, so retrying is idempotent.
///
/// SQLITE_IOERR is intentionally excluded. An I/O error can fire during connection
/// disposal AFTER a write has already committed (e.g. WAL flush on close). Retrying
/// in that case would re-execute the read phase on an already-empty table and return
/// stale results — a risk that exists in ExecuteReadThenWrite (RemoveAllEntriesForFolder).
/// IOERR on a genuinely failed write will have been rolled back by SQLite atomically,
/// but we cannot distinguish that case at the catch site without inspecting whether
/// the write had already committed. The safe choice is to surface IOERR immediately.
/// </summary>
public static bool IsTransientError(int sqliteErrorCode)
{
return sqliteErrorCode == DiskIOError
|| sqliteErrorCode == Busy
return sqliteErrorCode == Busy
|| sqliteErrorCode == Locked;
}
}
Expand Down
35 changes: 19 additions & 16 deletions GVFS/GVFS.Common/GVFSConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public static class GitConfig
public const string MaxRetriesConfig = GVFSPrefix + "max-retries";
public const string TimeoutSecondsConfig = GVFSPrefix + "timeout-seconds";
public const string GitStatusCacheBackoffConfig = GVFSPrefix + "status-cache-backoff-seconds";
public const string MaxActiveEnumerationsConfig = GVFSPrefix + "max-active-enumerations";
public const string MountId = GVFSPrefix + "mount-id";
public const string EnlistmentId = GVFSPrefix + "enlistment-id";
public const string CacheServer = GVFSPrefix + "cache-server";
Expand All @@ -47,18 +48,32 @@ public static class GitConfig
public const string ShowHydrationStatus = GVFSPrefix + "show-hydration-status";
public const bool ShowHydrationStatusDefault = false;

/* Gates the CLI mount-progress display layer (progress phase strings surfaced
* over the named pipe during `gvfs mount`). Disabled by default so stabilization
* builds keep the reliability-relevant early-pipe infrastructure without shipping
* the newer progress-display behavior. The early pipe, HandleRequest Mounting
* guard, volatile currentState, and null-safe GetStatus are always on regardless. */
public const string MountProgress = GVFSPrefix + "mount-progress";
public const bool MountProgressDefault = false;

public const string MaxHttpConnectionsConfig = GVFSPrefix + "max-http-connections";

public const string PrefetchUseIdx = GVFSPrefix + "prefetch-use-idx";
public const bool PrefetchUseIdxDefault = false;

/* Gates the background (fire-and-forget) auth + /gvfs/config behavior on
* mount when a cache server is configured locally. When false (default),
* mount blocks on auth/config synchronously before starting virtualization.
* When true, auth runs in the background so mount is not delayed. */
public const string BackgroundCacheAuth = GVFSPrefix + "background-cache-auth";
public const bool BackgroundCacheAuthDefault = false;

public const string PrefetchOffload = GVFSPrefix + "prefetch-offload";
public const bool PrefetchOffloadDefault = false;
}

public static class LocalGVFSConfig
{
public const string UpgradeRing = "upgrade.ring";
public const string UpgradeFeedPackageName = "upgrade.feedpackagename";
public const string UpgradeFeedUrl = "upgrade.feedurl";
public const string OrgInfoServerUrl = "upgrade.orgInfoServerUrl";
public const string USNJournalUpdates = "usn.updateDirectories";
}

Expand Down Expand Up @@ -268,17 +283,5 @@ public static class Unmount
public const string SkipLock = "skip-wait-for-lock";
}
}

public static class UpgradeVerbMessages
{
public const string GVFSUpgrade = "`gvfs upgrade`";
public const string GVFSUpgradeDryRun = "`gvfs upgrade --dry-run`";
public const string NoUpgradeCheckPerformed = "No upgrade check was performed.";
public const string NoneRingConsoleAlert = "Upgrade ring set to \"None\". " + NoUpgradeCheckPerformed;
public const string NoRingConfigConsoleAlert = "Upgrade ring is not set. " + NoUpgradeCheckPerformed;
public const string InvalidRingConsoleAlert = "Upgrade ring set to unknown value. " + NoUpgradeCheckPerformed;
public const string SetUpgradeRingCommand = "To set or change upgrade ring, run `gvfs config " + LocalGVFSConfig.UpgradeRing + " [\"Fast\"|\"Slow\"|\"None\"]` from a command prompt.";
public const string UnmountRepoWarning = "Upgrade will unmount and remount gvfs repos, ensure you are at a stopping point.";
}
}
}
1 change: 0 additions & 1 deletion GVFS/GVFS.Common/GVFSJsonContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,6 @@ namespace GVFS.Common
[JsonSerializable(typeof(List<string>))]
[JsonSerializable(typeof(List<GitObjectsHttpRequestor.GitObjectSize>))]
[JsonSerializable(typeof(ServerGVFSConfig))]
[JsonSerializable(typeof(VersionResponse))]
[JsonSerializable(typeof(InternalVerbParameters))]
[JsonSerializable(typeof(CacheServerInfo))]
[JsonSerializable(typeof(NamedPipeMessages.GetStatus.Response), TypeInfoPropertyName = "GetStatusResponse")]
Expand Down
60 changes: 49 additions & 11 deletions GVFS/GVFS.Common/GVFSLock.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@ public bool TryAcquireLockForExternalRequestor(
// failure on OpenProcess for a different-integrity caller) we still accept the
// lock and fall back to the legacy PID-only orphan check; record the fallback in
// telemetry so we can spot if it becomes common.
long? requestorStartTime = GVFSPlatform.Instance.TryGetActiveProcessStartTime(requestor.PID, out long startTime)
long? requestorStartTime =
GVFSPlatform.Instance.TryGetActiveProcessStartTime(requestor.PID, out long startTime) == ProcessStartTimeResult.Success
? startTime
: (long?)null;
if (requestorStartTime == null)
Expand Down Expand Up @@ -215,6 +216,17 @@ private bool IsLockAvailable(bool checkExternalHolderOnly, out NamedPipeMessages
this.tracer.SetGitCommandSessionId(string.Empty);
existingExternalHolder = null;
}
else if (terminationReason == "StartTimeIndeterminate" && existingExternalHolder != null)
{
// We could not determine whether the holder is still alive, so we deliberately
// kept the lock rather than risk releasing one that is still held. Record it so
// we can see whether these transient failures ever happen in the field (and,
// if a holder ever gets stuck this way, why the lock was not released).
EventMetadata metadata = new EventMetadata();
metadata.Add("CurrentLockHolder", existingExternalHolder.ToString());
metadata.Add("Reason", terminationReason);
this.tracer.RelatedEvent(EventLevel.Verbose, "ExternalHolderLivenessIndeterminate", metadata);
}

return existingExternalHolder == null;
}
Expand Down Expand Up @@ -460,17 +472,43 @@ public NamedPipeMessages.LockData GetExternalHolder(out bool externalHolderTermi
{
// Identity check: confirm the same process still owns this PID by comparing
// the OS-supplied process start time we captured at acquisition with the
// current one. A mismatch means the original holder exited and Windows
// recycled the PID to a different process (the bug this code fixes).
if (!GVFSPlatform.Instance.TryGetActiveProcessStartTime(pid, out long currentStartTime))
// current one. We only conclude the holder is gone (and release its lock)
// when we have positive evidence, so that a transient failure to read the
// start time can never release a lock that is still legitimately held.
ProcessStartTimeResult result = GVFSPlatform.Instance.TryGetActiveProcessStartTime(pid, out long currentStartTime);
switch (result)
{
externalHolderTerminatedWithoutReleasingLock = true;
terminationReason = "ProcessNotActive";
}
else if (currentStartTime != capturedStartTime)
{
externalHolderTerminatedWithoutReleasingLock = true;
terminationReason = "PidRecycled";
case ProcessStartTimeResult.Success when currentStartTime == capturedStartTime:
// Same process, still alive. Keep the lock.
break;

case ProcessStartTimeResult.Success:
// A different, still-running process now owns this PID: the original
// holder exited and Windows recycled the PID (the bug this fixes).
externalHolderTerminatedWithoutReleasingLock = true;
terminationReason = "PidRecycled";
break;

case ProcessStartTimeResult.ProcessNotFound:
// Positive evidence the holder is gone (no such PID, or it exited).
externalHolderTerminatedWithoutReleasingLock = true;
terminationReason = "ProcessExited";
break;

case ProcessStartTimeResult.Inaccessible:
// We could open the holder at acquire time, and that access is stable
// for the process's lifetime, so an access-denied result now means the
// PID refers to a different process: the original holder is gone.
externalHolderTerminatedWithoutReleasingLock = true;
terminationReason = "HolderInaccessible";
break;

case ProcessStartTimeResult.Indeterminate:
default:
// We could not determine liveness (e.g. transient resource error).
// Do NOT release: keep the lock and let the next poll re-evaluate.
terminationReason = "StartTimeIndeterminate";
break;
}
}
else
Expand Down
23 changes: 14 additions & 9 deletions GVFS/GVFS.Common/GVFSPlatform.cs
Original file line number Diff line number Diff line change
Expand Up @@ -68,16 +68,21 @@ public static void Register(GVFSPlatform platform)
public abstract bool IsProcessActive(int processId);

/// <summary>
/// Returns true and writes an opaque, OS-supplied process-identity token (e.g. process
/// creation time on Windows) when the process with the given PID is currently active.
/// The token has no meaning beyond identity comparison: two calls for the same underlying
/// process yield equal tokens, and a call after the OS has recycled the PID to a different
/// process yields a different token. Returns false (and startTime = 0) if the process
/// is no longer running, or if it could not be identified for any reason (e.g. permission
/// failure). Callers must treat a false return as "no identity information available" and
/// fall back to <see cref="IsProcessActive(int)"/> if they still need a liveness check.
/// Attempts to read an opaque, OS-supplied process-identity token (e.g. process creation
/// time on Windows) for the process with the given PID. The token has no meaning beyond
/// identity comparison: two calls for the same underlying process yield equal tokens, and
/// a call after the OS has recycled the PID to a different process yields a different token.
/// </summary>
public abstract bool TryGetActiveProcessStartTime(int processId, out long startTime);
/// <param name="processId">The PID to inspect.</param>
/// <param name="startTime">The identity token, valid only when the result is <see cref="ProcessStartTimeResult.Success"/>.</param>
/// <returns>
/// A <see cref="ProcessStartTimeResult"/> describing the outcome. Callers making a
/// release-versus-hold decision about a lock holder must only treat
/// <see cref="ProcessStartTimeResult.ProcessNotFound"/> and
/// <see cref="ProcessStartTimeResult.Inaccessible"/> as evidence the original holder is gone;
/// <see cref="ProcessStartTimeResult.Indeterminate"/> means "unknown" and must not trigger a release.
/// </returns>
public abstract ProcessStartTimeResult TryGetActiveProcessStartTime(int processId, out long startTime);

public abstract void IsServiceInstalledAndRunning(string name, out bool installed, out bool running);
public abstract string GetNamedPipeName(string enlistmentRoot);
Expand Down
Loading
Loading