Repository navigation
Improve missing disk layout metadata error - #2118
Tyrie Vella (tyrielv) wants to merge 4 commits into
Conversation
When the disk layout version is missing from .gvfs metadata, report that the metadata is incomplete and tell the user to reclone the enlistment. Return a distinct mount exit code for that case so automation can identify it. Keep generic upgrade failures on the existing error path. Cover both a missing layout key and a missing repo metadata file in mount functional tests. Assisted-by: GitHub Copilot Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
069a28c to
f6fbfc4
Compare
Nathan Baird (ShiningMassXAcc)
left a comment
There was a problem hiding this comment.
The user-facing goal is right — the old "check if a breaking change has been made to GVFS since cloning" text is genuinely misleading, and a distinct exit code is the correct fix for the automation problem. CI is green across the full matrix. But the central mechanism has a hole.
Seven findings, ranked.
1. High — GVFS/GVFS.Common/DiskLayoutUpgrades/DiskLayoutUpgrade.cs:139-148
TryCheckDiskLayoutVersion calls the 3-arg TryGetDiskLayoutVersion, throwing away the returnCode this PR just added, and then re-derives the decision with string equality:
if (error == RepoMetadata.MissingDiskLayoutVersionMessage)
{
returnCode = ReturnCode.MissingDiskLayoutVersion;
}That string comparison is load-bearing for the new exit code, and nothing tests it. Append the log path to the message, localize it, add a trailing period — and exit code 12 silently degrades to 3, which is exactly the automation problem this PR exists to fix.
It is also internally inconsistent: TryRunAllUpgrades -> TryFindUpgrade -> TryGetDiskLayoutVersion all plumb returnCode properly. Only the one method MountVerb depends on for the new code does not.
Fix should be small: call the 5-arg TryGetDiskLayoutVersion overload and use what it returns. It already sets GenericError at the top and MissingDiskLayoutVersion in exactly the right branch, so the whole string check can be deleted. returnCode is assigned at the top of the method, so definite assignment across the try/finally is fine.
2. Medium — UTF-8 BOM added to four files
GVFS.Common/RepoMetadata.cs, GVFS/CommandLine/MountVerb.cs, GVFS.FunctionalTests/Tests/EnlistmentPerFixture/MountTests.cs and GVFS.FunctionalTests/Tools/GVFSHelpers.cs each gained a UTF-8 BOM (EF BB BF) — verified by byte-comparing the blobs against the merge base. That is the only change to line 1 of each file, it is unrelated to the fix, and it permanently dirties git blame on the first line. Please strip it.
3. Medium — DiskLayoutUpgrade.cs:19, dead overload
The new 1-arg TryRunAllUpgrades(string enlistmentRoot) has zero callers — MountVerb was the only one and it moved to the 3-arg form. Dead code on day one; delete it.
(The 3-arg TryCheckDiskLayoutVersion overload is a different story — DehydrateVerb.cs:226 and RepairVerb.cs:95 still use it, so that one earns its keep.)
4. Medium — RepairVerb.cs:95 and DehydrateVerb.cs:226
Both still call the overload that discards the return code. gvfs repair is the command a user runs after hitting this failure, and gvfs dehydrate hits the same condition — so automation still cannot distinguish it from either. Is it intentional that only mount reports code 12?
5. Low — MountTests.cs:103
private const int GVFSMissingDiskLayoutVersion = 12; duplicates the enum value. The functional test project already references GVFS.Common (see the using GVFS.Common; in GVFSHelpers.cs), so (int)ReturnCode.MissingDiskLayoutVersion keeps them in lockstep. Worth doing for the neighbouring GVFSGenericError = 3 while you are there.
6. Low — GVFSHelpers.DeletePersistedValue
Near-verbatim copy of SavePersistedValue: identical FileStream/StreamReader loop, identical json.Substring(0, 2).ShouldEqual("A ") assertion, identical rewrite loop. The only difference is repoMetadata.Add(kvp.Key, kvp.Value) versus the key filter. Factor the read into a shared helper and have both write from the resulting dictionary.
7. Low — assert against the const, not the prose
Both functional tests assert on a substring of the message text. The message is now a public const, so RepoMetadata.MissingDiskLayoutVersionMessage would stop it drifting — and would also directly guard finding 1 if you would rather not restructure that method.
No AI-cruft or stray .github artifacts in this diff.
|
Cross-cutting note on base branches (posting here because this PR is one of the The eight PRs tagged in
The branches have genuinely diverged — 22 commits in But |
Use structured return codes throughout disk layout version checks instead of deriving the missing-layout case from message text. Propagate the new return code through repair and dehydrate as well as mount. Keep functional tests tied to the production return code and message constants, and share repo metadata read/write helper logic in test utilities. Assisted-by: GitHub Copilot Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
|
Note 🤖 Machine-drafted reply — reviewed & approved by Tyrie before posting Thanks, confirmed these targets are intentional. #2018 and #2117 are test infrastructure changes, so they do not affect shipped runtime behavior and are fine for #2118 is a small improvement to an existing error condition: clearer guidance and a distinct exit code when disk layout metadata is missing. I want that on the shippable line, so #2116, #2121, and the related Git 3.0 preparation work are different. Those preemptively fix issues that are rare today but will become common when Git 3.0 ships. |
|
Note 🤖 Machine-drafted reply — reviewed & approved by Tyrie before posting Thanks — addressed the review feedback in
CI is green across the matrix. |
Nathan Baird (ShiningMassXAcc)
left a comment
There was a problem hiding this comment.
Review pass over this PR. 6 comment(s).
These findings concern code the diff does not touch directly, so GitHub cannot anchor them inline:
GVFS/GVFS/CommandLine/MountVerb.cs (around line 142) - LOW
This reads as though both ReportErrorAndExit calls could run; it is only correct because the first one throws VerbAbortedException. An if/else would make the exclusivity explicit and survive someone later changing how ReportErrorAndExit terminates.
(PR description) - LOW
Small description nit: the bullet about propagating "structured disk-layout failure details … instead of re-reading metadata after failure" does not match the baseline — MountVerb.PreCreateEnlistment on master never re-read metadata after a failure. The change is real; the "instead of" framing describes something that was not there. Worth rewording so the description matches the diff.
| { | ||
| public class RepoMetadata | ||
| { | ||
| public const string MissingDiskLayoutVersionMessage = "Enlistment disk layout version not found. The .gvfs metadata is incomplete. Recloning this enlistment is the recommended fix."; |
There was a problem hiding this comment.
Two things about the new message:
-
It drops the log pointer. Every other
TryCheckDiskLayoutVersionfailure ends withRun 'gvfs log <enlistmentRoot>' for more details.(seeDiskLayoutUpgrade.cs:134). We do write a log on this path, so omitting the pointer means the user has the one piece of diagnostic information we produced and no idea it exists. -
"Recloning this enlistment is the recommended fix" is unqualified. That is advice to destroy an enlistment that may be very large and may hold unpushed commits or uncommitted work, offered at the exact moment we have told the user we cannot read its metadata.
GVFS.Hooks/Program.Unstage.cs:102is a good model for how we caveat destructive guidance elsewhere.
Suggest adding the log pointer, and a short "make sure your work is pushed or backed up first" ahead of the reclone recommendation.
| { | ||
| private const int GVFSGenericError = 3; | ||
| private const int GVFSGenericError = (int)ReturnCode.GenericError; | ||
| private const int GVFSMissingDiskLayoutVersion = (int)ReturnCode.MissingDiskLayoutVersion; |
There was a problem hiding this comment.
Using (int)ReturnCode.MissingDiskLayoutVersion and RepoMetadata.MissingDiskLayoutVersionMessage makes the assertion move in lockstep with the product: if someone renumbers the enum or rewrites the message, the shipped contract changes and the test stays green.
That contract is the whole point of this PR — the exit code and message are what scripts and users key off. CONTRIBUTING.md asks functional tests to treat the product as a black box for exactly this reason.
Suggest pinning the literal 12 and a stable phrase from the message instead, with a comment noting these are a shipped contract.
(I realize this cuts against what the previous review asked for — flagging it so you two can settle on one answer rather than me quietly reversing it.)
| { | ||
| ReturnCode returnCode; | ||
| return TryCheckDiskLayoutVersion(tracer, enlistmentRoot, out error, out returnCode); | ||
| } |
There was a problem hiding this comment.
After this change the 3-argument TryCheckDiskLayoutVersion overload and the new private 3-argument TryFindUpgrade have no callers left in the tree. Same dead-overload cleanup the prior review asked for on the other pair — suggest deleting both so there is only one shape of each to maintain.
| if (!DiskLayoutUpgrade.TryCheckDiskLayoutVersion(tracer: null, enlistmentRoot: enlistment.PrimaryEnlistmentRoot, error: out error, returnCode: out returnCode)) | ||
| { | ||
| this.ReportErrorAndExit(error); | ||
| this.ReportErrorAndExit(tracer: null, exitCode: returnCode, error: error); |
There was a problem hiding this comment.
The new functional tests cover gvfs mount only, but this PR also changes the return-code plumbing in repair (here) and dehydrate (DehydrateVerb.cs:228), plus the returnCode != MissingDiskLayoutVersion fall-through at DiskLayoutUpgrade.cs:131-135. Those three branches are what decides whether a user gets the new actionable message or the old generic one, and none of them are exercised.
A functional test per verb would be expensive. A unit test over RepoMetadata.TryGetOnDiskLayoutVersion using MockFileSystem and a mock:\ root would cover the core cheaply — GVFS.UnitTests/Virtual/TestsWithCommonRepo.cs has the pattern.
Qualify the missing disk layout recovery guidance and keep the log pointer on that path. Remove unused disk layout helper overloads, make the mount upgrade failure branch explicit, and pin the functional test to the shipped exit-code contract. Add RepoMetadata unit coverage for missing and invalid disk layout values. Assisted-by: GitHub Copilot Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
Nathan Baird (ShiningMassXAcc)
left a comment
There was a problem hiding this comment.
Re-review. Commit 0ccec54 addresses all four of my earlier threads — the log pointer and backup caveat are in the message, the dead overloads are gone, the functional test pins the literal 12, and RepoMetadataTests.cs is new coverage. I'm satisfied on all of those.
Three remaining observations below, none of them blocking. CI is green across every leg.
Also worth calling out as a positive that the description doesn't mention: the GVFSHelpers change from repoMetadata.Add(k, v) to an indexer assignment when replaying the append-only log quietly fixes a latent ArgumentException on duplicated keys.
Medium — GVFS/GVFS.Mount/InProcessMount.cs ~L1538 (not in this diff)
This call site still uses the 3-argument TryGetOnDiskLayoutVersion, so the mount process prints the new "reclone" text but exits with GenericError (3) rather than the new 12, and without the gvfs log pointer.
That leaves the same user-visible message reachable under two different exit codes, which undercuts the automation story the new code is justified by. Worth routing this one through the new overload too.
| if (!DiskLayoutUpgrade.TryRunAllUpgrades(enlistmentRoot)) | ||
| string diskLayoutError; | ||
| ReturnCode diskLayoutReturnCode; | ||
| if (!DiskLayoutUpgrade.TryRunAllUpgrades(enlistmentRoot, out diskLayoutError, out diskLayoutReturnCode)) |
There was a problem hiding this comment.
Medium — The new out string error from TryRunAllUpgrades is discarded for every return code except MissingDiskLayoutVersion — including the catch path, where it carries the exception detail and is the only place that detail exists.
Either surface it on the other failure codes, or drop the out-parameter and keep the specific message construction local to the one branch that uses it. As written it reads like an oversight rather than a decision.
| { | ||
| GVFSHelpers.DeletePersistedDiskLayoutMajorVersion(this.Enlistment.DotGVFSRoot); | ||
|
|
||
| this.MountShouldFail( |
There was a problem hiding this comment.
Medium — Both tests assert only on MissingDiskLayoutVersionMessage. Nothing pins the gvfs log pointer that was added in response to the earlier review, so removing that append would leave CI fully green.
This is the same regression shape I flagged last pass — the valuable half of the message is the half that isn't asserted. A ShouldContain on the log-pointer fragment would close it.
Surface disk layout upgrade error details on non-missing-layout failures instead of discarding them. Extend the functional tests for missing disk layout metadata to assert that the mount output includes the gvfs log pointer. Assisted-by: GitHub Copilot Signed-off-by: Tyrie Vella <tyrielv@gmail.com>
|
Note 🤖 Machine-drafted reply — reviewed & approved by Tyrie before posting Thanks — addressed the two active follow-up comments in
Focused local validation passed for the two missing-layout mount functional tests. GitHub checks are running on the new head. |
Problem and Context
When an enlistment is missing its persisted disk layout version,
gvfs mountreports that a breaking disk-layout change can have occurred since clone. That guidance is misleading for current users because the more likely cause is incomplete.gvfsmetadata.Automation also cannot reliably distinguish this condition from other generic mount failures because the command exits with the generic error code.
Changes
MissingDiskLayoutVersionreturn code for incomplete disk-layout metadata.gvfs mount.