Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
31 changes: 31 additions & 0 deletions src/dnvm/PruneCommand.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,12 +48,25 @@ public static async Task<int> Run(DnvmEnv env, Logger logger, Options options)
}
else
{
// Check if the SDK is actually in InstalledSdks. It may not be if the manifest
// was corrupted by a bug fixed in https://github.com/dn-vm/dnvm/pull/274.
// In that case, we just need to clean up the stale entry from RegisteredChannels.
if (!manifest.IsSdkInstalled(sdk.Version, sdk.Dir))
{
env.Console.Warn($"SDK {sdk.Version} was not found in installed SDKs, cleaning up stale manifest entry.");
manifest = RemoveSdkFromChannels(manifest, sdk.Version);
await @lock.WriteManifest(env, manifest);
continue;
}

Console.WriteLine($"Removing {sdk}");
int result = await UninstallCommand.Run(@lock, env, logger, sdk.Version, sdk.Dir);
if (result != 0)
{
return result;
}
// Re-read manifest after uninstall since it was modified
manifest = await @lock.ReadManifest(env);
}
}
return 0;
Expand Down Expand Up @@ -99,4 +112,22 @@ public static async Task<int> Run(DnvmEnv env, Logger logger, Options options)

return sdksToRemove;
}

/// <summary>
/// Removes an SDK version from all RegisteredChannels.InstalledSdkVersions.
/// This is used to clean up stale entries left by a bug fixed in
/// https://github.com/dn-vm/dnvm/pull/274.
/// </summary>
private static Manifest RemoveSdkFromChannels(Manifest manifest, SemVersion sdkVersion)
{
var updatedChannels = manifest.RegisteredChannels.Select(channel =>
{
var updatedInstalledVersions = channel.InstalledSdkVersions
.Where(version => version != sdkVersion)
.ToEq();
return channel with { InstalledSdkVersions = updatedInstalledVersions };
}).ToEq();

return manifest with { RegisteredChannels = updatedChannels };
}
}
61 changes: 61 additions & 0 deletions test/UnitTests/PruneTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -177,4 +177,65 @@ public Task MissingDirectoriesHandled() => RunWithServer(async (server, env) =>
Assert.Single(finalManifest.InstalledSdks);
Assert.Equal(upgradeVersion, finalManifest.InstalledSdks[0].SdkVersion);
});

/// <summary>
/// Tests the scenario from https://github.com/dn-vm/dnvm/issues/311:
/// Before PR #274, uninstalling an SDK removed it from InstalledSdks but not from
/// RegisteredChannels.InstalledSdkVersions. Prune should handle this gracefully by
/// logging a warning and cleaning up the stale manifest entry.
/// </summary>
[Fact]
public Task PruneHandlesCorruptedManifestFromPre274Uninstall() => RunWithServer(async (server, env) =>
{
var oldVersion = SemVersion.Parse("8.0.100", SemVersionStyles.Strict);
var newVersion = SemVersion.Parse("8.0.101", SemVersionStyles.Strict);
Channel channel = new Channel.Latest();

// Create a corrupted manifest that simulates the state left by a pre-PR#274 uninstall:
// - The old SDK version is in RegisteredChannels.InstalledSdkVersions (was not removed)
// - But the old SDK is NOT in InstalledSdks (was removed)
// - The new SDK is properly tracked in both places
var corruptedManifest = Manifest.Empty with
{
InstalledSdks = [
// Only the new version is in InstalledSdks (old version was uninstalled)
new InstalledSdk
{
SdkVersion = newVersion,
RuntimeVersion = newVersion,
AspNetVersion = newVersion,
ReleaseVersion = newVersion,
SdkDirName = DnvmEnv.DefaultSdkDirName
}
],
RegisteredChannels = [
new RegisteredChannel
{
ChannelName = channel,
SdkDirName = DnvmEnv.DefaultSdkDirName,
// Bug: old version is still in InstalledSdkVersions even though it was uninstalled
InstalledSdkVersions = [oldVersion, newVersion]
}
]
};

await Manifest.WriteManifestUnsafe(env, corruptedManifest);

var console = (TestConsole)env.Console;
var initialOutput = console.Output;

// Prune should succeed and clean up the stale manifest entry
var pruneResult = await PruneCommand.Run(env, _logger, new PruneCommand.Options());

var output = console.Output[initialOutput.Length..];

Assert.Equal(0, pruneResult);
Assert.Contains("was not found in installed SDKs", output);

// Verify the manifest was cleaned up - old version should be removed from channel
var finalManifest = await Manifest.ReadManifestUnsafe(env);
var registeredChannel = Assert.Single(finalManifest.RegisteredChannels);
Assert.Single(registeredChannel.InstalledSdkVersions);
Assert.Equal(newVersion, registeredChannel.InstalledSdkVersions[0]);
});
}