From fc061b86359d7d3ce6f750e560e8234489eb4537 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Tue, 28 Apr 2026 22:35:59 -0500 Subject: [PATCH 1/4] Reset sources without dropping them --- src/AppInstallerCLITests/Sources.cpp | 65 +++++++++++++++++++ .../RepositorySource.cpp | 13 +++- src/AppInstallerRepositoryCore/SourceList.cpp | 57 ++++++++++++++++ src/AppInstallerRepositoryCore/SourceList.h | 4 ++ 4 files changed, 138 insertions(+), 1 deletion(-) diff --git a/src/AppInstallerCLITests/Sources.cpp b/src/AppInstallerCLITests/Sources.cpp index a92683ce71..eba811deec 100644 --- a/src/AppInstallerCLITests/Sources.cpp +++ b/src/AppInstallerCLITests/Sources.cpp @@ -78,6 +78,18 @@ constexpr std::string_view s_SingleSourceOverride = R"( Priority: 12 )"sv; +constexpr std::string_view s_WingetDefaultMetadata = R"( +Sources: + - Name: winget + LastUpdate: 100 +)"sv; + +constexpr std::string_view s_WingetFontOverrideMetadata = R"( +Sources: + - Name: winget-font + LastUpdate: 100 +)"sv; + constexpr std::string_view s_SingleSourceMetadata = R"( Sources: - Name: testName @@ -755,6 +767,59 @@ TEST_CASE("RepoSources_DropAllSources", "[sources]") REQUIRE(sources[0].Origin == SourceOrigin::Default); } + +TEST_CASE("RepoSources_DropDefaultSourceByName", "[sources]") +{ + RemoveSetting(Stream::UserSources); + SetSetting(Stream::SourcesMetadata, s_WingetDefaultMetadata); + + std::vector sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount); + + // Verify the winget source has non-zero metadata before reset + auto wingetBefore = std::find_if(sources.begin(), sources.end(), [](const SourceDetails& sd) { return sd.Name == "winget"; }); + REQUIRE(wingetBefore != sources.end()); + REQUIRE(wingetBefore->LastUpdateTime == ConvertUnixEpochToSystemClock(100)); + + DropSource("winget"); + + // Source should still be present as a Default source + sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount); + + auto wingetAfter = std::find_if(sources.begin(), sources.end(), [](const SourceDetails& sd) { return sd.Name == "winget"; }); + REQUIRE(wingetAfter != sources.end()); + REQUIRE(wingetAfter->Origin == SourceOrigin::Default); + // Metadata should be cleared + REQUIRE(wingetAfter->LastUpdateTime == ConvertUnixEpochToSystemClock(0)); +} + +TEST_CASE("RepoSources_DropDefaultSourceOverrideByName", "[sources]") +{ + SetSetting(Stream::UserSources, s_SingleSourceOverride); + SetSetting(Stream::SourcesMetadata, s_WingetFontOverrideMetadata); + + std::vector sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount); + + // winget-font should be present as a User (override) source with metadata + auto fontBefore = std::find_if(sources.begin(), sources.end(), [](const SourceDetails& sd) { return sd.Name == "winget-font"; }); + REQUIRE(fontBefore != sources.end()); + REQUIRE(fontBefore->Origin == SourceOrigin::User); + REQUIRE(fontBefore->LastUpdateTime == ConvertUnixEpochToSystemClock(100)); + + DropSource("winget-font"); + + // winget-font should be restored to Default with cleared metadata + sources = GetSources(); + REQUIRE(sources.size() == c_DefaultSourceCount); + + auto fontAfter = std::find_if(sources.begin(), sources.end(), [](const SourceDetails& sd) { return sd.Name == "winget-font"; }); + REQUIRE(fontAfter != sources.end()); + REQUIRE(fontAfter->Origin == SourceOrigin::Default); + REQUIRE(fontAfter->LastUpdateTime == ConvertUnixEpochToSystemClock(0)); +} + TEST_CASE("RepoSources_SearchAcrossMultipleSources", "[sources]") { TestHook_ClearSourceFactoryOverrides(); diff --git a/src/AppInstallerRepositoryCore/RepositorySource.cpp b/src/AppInstallerRepositoryCore/RepositorySource.cpp index 5fdcccea0b..801595ed2c 100644 --- a/src/AppInstallerRepositoryCore/RepositorySource.cpp +++ b/src/AppInstallerRepositoryCore/RepositorySource.cpp @@ -1105,7 +1105,18 @@ namespace AppInstaller::Repository AICLI_LOG(Repo, Info, << "Named source to be dropped, found: " << source->Name); EnsureSourceIsRemovable(*source); - sourceList.RemoveSource(*source); + + // For default sources and overrides of default sources, reset to clean state + // (remove user customizations and clear metadata) instead of tombstoning. + if (source->Origin == SourceOrigin::Default || + (source->Origin == SourceOrigin::User && source->IsOverride)) + { + sourceList.ResetSource(*source); + } + else + { + sourceList.RemoveSource(*source); + } return true; } diff --git a/src/AppInstallerRepositoryCore/SourceList.cpp b/src/AppInstallerRepositoryCore/SourceList.cpp index 6cfd224764..c16af558ab 100644 --- a/src/AppInstallerRepositoryCore/SourceList.cpp +++ b/src/AppInstallerRepositoryCore/SourceList.cpp @@ -661,6 +661,63 @@ namespace AppInstaller::Repository SaveMetadataInternal(details, true); } + void SourceList::ResetSource(const SourceDetailsInternal& detailsRef) + { + // Copy the incoming details because we might destroy the referenced structure + // when reloading the source details from settings. + SourceDetailsInternal details = detailsRef; + bool sourcesSet = false; + + for (size_t i = 0; !sourcesSet && i < 10; ++i) + { + // Remove any user-level entry for this source (tombstone or override). + auto itr = FindSource(details.Name, true); + if (itr != m_sourceList.end() && itr->Origin == SourceOrigin::User) + { + m_sourceList.erase(itr); + } + + sourcesSet = SetSourcesByOrigin(SourceOrigin::User, m_sourceList); + + if (!sourcesSet) + { + OverwriteSourceList(); + OverwriteMetadata(); + } + } + + THROW_HR_IF_MSG(E_UNEXPECTED, !sourcesSet, "Too many attempts at SetSourcesByOrigin"); + + // Reload to bring back the Default source (now that the user entry is removed) + // and to get fresh metadata for all other sources. + OverwriteSourceList(); + OverwriteMetadata(); + + // Clear the metadata for the reset source and save. + bool metadataSet = false; + for (size_t i = 0; !metadataSet && i < 10; ++i) + { + auto target = FindSource(details.Name, true); + if (target != m_sourceList.end()) + { + target->LastUpdateTime = {}; + target->DoNotUpdateBefore = {}; + target->AcceptedAgreementFields = 0; + target->AcceptedAgreementsIdentifier = {}; + } + + metadataSet = SetMetadata(m_sourceList); + + if (!metadataSet) + { + OverwriteSourceList(); + OverwriteMetadata(); + } + } + + THROW_HR_IF_MSG(E_UNEXPECTED, !metadataSet, "Too many attempts at SetMetadata"); + } + void SourceList::SaveMetadata(const SourceDetailsInternal& details) { SaveMetadataInternal(details); diff --git a/src/AppInstallerRepositoryCore/SourceList.h b/src/AppInstallerRepositoryCore/SourceList.h index a2bd862169..c7a00e6781 100644 --- a/src/AppInstallerRepositoryCore/SourceList.h +++ b/src/AppInstallerRepositoryCore/SourceList.h @@ -70,6 +70,10 @@ namespace AppInstaller::Repository void RemoveSource(const SourceDetailsInternal& details); void EditSource(const SourceDetailsInternal& details); + // Reset a default source: removes any user-level entry (tombstone or override) + // and clears the source's metadata, restoring it to clean-install state. + void ResetSource(const SourceDetailsInternal& details); + // Save source metadata; the particular source with the metadata update is given. // The given source must already be in the internal source list. void SaveMetadata(const SourceDetailsInternal& details); From cf6552d05a45beb46a2f03ef94aa8efe1db2d150 Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Tue, 28 Apr 2026 22:50:18 -0500 Subject: [PATCH 2/4] Release Notes --- doc/ReleaseNotes.md | 1 + 1 file changed, 1 insertion(+) diff --git a/doc/ReleaseNotes.md b/doc/ReleaseNotes.md index ad5dd4dc9a..e91ce5cb4e 100644 --- a/doc/ReleaseNotes.md +++ b/doc/ReleaseNotes.md @@ -81,3 +81,4 @@ Added a user setting (`logging.fileNameStrategy`) for controlling the default na * File and directory paths passed to `signtool.exe` and `makeappx.exe` are now quoted, fixing failures when paths contain spaces. * DSC export now correctly exports WinGet Admin Settings * `winget validate` now performs case-insensitive comparison for file extensions where applicable +* `winget source reset` now properly resets default sources instead of removing them From e8567e3108cc746dbc5623f4537bf7541520faac Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Tue, 28 Apr 2026 23:04:04 -0500 Subject: [PATCH 3/4] Spelling --- .github/actions/spelling/expect.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/spelling/expect.txt b/.github/actions/spelling/expect.txt index 53175f0afa..2c6906108d 100644 --- a/.github/actions/spelling/expect.txt +++ b/.github/actions/spelling/expect.txt @@ -584,6 +584,7 @@ Tlg tlhelp TLSCAs tombstoned +tombstoning Toolhelp transitioning trimstart From 5c52fe4edb5100e60d66ecde79922b702c32f8be Mon Sep 17 00:00:00 2001 From: Kaleb Luedtke Date: Wed, 29 Apr 2026 13:21:38 -0500 Subject: [PATCH 4/4] Create functions for resetting metadata --- src/AppInstallerRepositoryCore/SourceList.cpp | 62 +++++++++++-------- src/AppInstallerRepositoryCore/SourceList.h | 6 ++ 2 files changed, 42 insertions(+), 26 deletions(-) diff --git a/src/AppInstallerRepositoryCore/SourceList.cpp b/src/AppInstallerRepositoryCore/SourceList.cpp index c16af558ab..de26a6944b 100644 --- a/src/AppInstallerRepositoryCore/SourceList.cpp +++ b/src/AppInstallerRepositoryCore/SourceList.cpp @@ -240,6 +240,14 @@ namespace AppInstaller::Repository DoNotUpdateBefore = source.DoNotUpdateBefore; } + void SourceDetailsInternal::ResetMetadataFields() + { + LastUpdateTime = {}; + DoNotUpdateBefore = {}; + AcceptedAgreementFields = 0; + AcceptedAgreementsIdentifier = {}; + } + void SourceDetailsInternal::CopyOverrideFieldsFrom(const SourceDetails& overrideSource) { // These are the supported Override fields. @@ -688,34 +696,10 @@ namespace AppInstaller::Repository THROW_HR_IF_MSG(E_UNEXPECTED, !sourcesSet, "Too many attempts at SetSourcesByOrigin"); - // Reload to bring back the Default source (now that the user entry is removed) - // and to get fresh metadata for all other sources. + // Reload to bring back the Default source (now that the user entry is removed). OverwriteSourceList(); - OverwriteMetadata(); - - // Clear the metadata for the reset source and save. - bool metadataSet = false; - for (size_t i = 0; !metadataSet && i < 10; ++i) - { - auto target = FindSource(details.Name, true); - if (target != m_sourceList.end()) - { - target->LastUpdateTime = {}; - target->DoNotUpdateBefore = {}; - target->AcceptedAgreementFields = 0; - target->AcceptedAgreementsIdentifier = {}; - } - - metadataSet = SetMetadata(m_sourceList); - if (!metadataSet) - { - OverwriteSourceList(); - OverwriteMetadata(); - } - } - - THROW_HR_IF_MSG(E_UNEXPECTED, !metadataSet, "Too many attempts at SetMetadata"); + ResetMetadataInternal(details); } void SourceList::SaveMetadata(const SourceDetailsInternal& details) @@ -1063,4 +1047,30 @@ namespace AppInstaller::Repository THROW_HR_IF_MSG(E_UNEXPECTED, !metadataSet, "Too many attempts at SetMetadata"); } + + void SourceList::ResetMetadataInternal(const SourceDetailsInternal& details) + { + bool metadataSet = false; + + for (size_t i = 0; !metadataSet && i < 10; ++i) + { + // Load fresh metadata from storage so that other sources' metadata is preserved. + OverwriteMetadata(); + + auto target = FindSource(details.Name, true); + if (target != m_sourceList.end()) + { + target->ResetMetadataFields(); + } + + metadataSet = SetMetadata(m_sourceList); + + if (!metadataSet) + { + OverwriteSourceList(); + } + } + + THROW_HR_IF_MSG(E_UNEXPECTED, !metadataSet, "Too many attempts at SetMetadata"); + } } diff --git a/src/AppInstallerRepositoryCore/SourceList.h b/src/AppInstallerRepositoryCore/SourceList.h index c7a00e6781..d5f87aff40 100644 --- a/src/AppInstallerRepositoryCore/SourceList.h +++ b/src/AppInstallerRepositoryCore/SourceList.h @@ -25,6 +25,9 @@ namespace AppInstaller::Repository // Copies the metadata fields from this source. This only include partial metadata. void CopyMetadataFieldsFrom(const SourceDetails& source); + // Resets all metadata fields to their default (clean-install) state. + void ResetMetadataFields(); + // Copies the overridden fields from the target source to this source. This is only the supported override fields. void CopyOverrideFieldsFrom(const SourceDetails& overrideSource); @@ -112,6 +115,9 @@ namespace AppInstaller::Repository // If remove is true, the given source is being removed. void SaveMetadataInternal(const SourceDetailsInternal& details, bool remove = false); + // Clears all metadata for the named source and persists the change. + void ResetMetadataInternal(const SourceDetailsInternal& details); + std::vector m_sourceList; Settings::Stream m_userSourcesStream; Settings::Stream m_metadataStream;