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
2 changes: 2 additions & 0 deletions src/Languages/lang_en.json
Original file line number Diff line number Diff line change
Expand Up @@ -142,6 +142,8 @@
"Skip hash check": "Skip hash check",
"Uninstall previous versions when updated": "Uninstall previous versions when updated",
"Skip minor updates for this package": "Skip minor updates for this package",
"Ignore changes after the first": "Ignore changes after the first",
"version numbers": "version numbers",
"Automatically update this package": "Automatically update this package",
"{0} installation options": "{0} installation options",
"Latest": "Latest",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ public partial class InstallOptionsViewModel : ObservableObject
public string SkipHashCheckBox_Content { get; } = CoreTools.Translate("Skip hash check");
public string UninstallPrevCheckBox_Content { get; } = CoreTools.Translate("Uninstall previous versions when updated");
public string SkipMinorCheckBox_Content { get; } = CoreTools.Translate("Skip minor updates for this package");
public string SkipMinorLevelPrefix_Content { get; } = CoreTools.Translate("Ignore changes after the first");
public string SkipMinorLevelSuffix_Content { get; } = CoreTools.Translate("version numbers");
public string AutoUpdateCheckBox_Content { get; } = CoreTools.Translate("Automatically update this package");
public string IgnoreUpdatesCheckBox_Content { get; } = CoreTools.Translate("Ignore future updates for this package");

Expand Down Expand Up @@ -152,6 +154,9 @@ partial void OnSelectedProfileChanged(string? value)
[ObservableProperty] private string? _selectedVersion;

[ObservableProperty] private bool _skipMinorChecked;
// Index 0 => keep first 1 number significant (level 2), 1 => first 2 (level 3), 2 => first 3 (level 4).
public ObservableCollection<string> SkipMinorLevelOptions { get; } = ["1", "2", "3"];
[ObservableProperty] private int _skipMinorLevelIndex;
[ObservableProperty] private bool _autoUpdateChecked;
[ObservableProperty] private bool _ignoreUpdatesChecked;

Expand Down Expand Up @@ -267,6 +272,7 @@ public InstallOptionsViewModel(IPackage package, OperationType operation, Instal
SkipHashEnabled = caps.CanSkipIntegrityChecks;
UninstallPrevChecked = options.UninstallPreviousVersionsOnUpdate;
SkipMinorChecked = options.SkipMinorUpdates;
SkipMinorLevelIndex = options.SkipMinorUpdatesLevel - 2; // level is validated to 2..4 by the getter
AutoUpdateChecked = options.AutoUpdatePackage;

// Version
Expand Down Expand Up @@ -515,6 +521,7 @@ private InstallOptions SnapshotOptions()
o.UninstallPreviousVersionsOnUpdate = UninstallPrevChecked;
o.AutoUpdatePackage = AutoUpdateChecked;
o.SkipMinorUpdates = SkipMinorChecked;
o.SkipMinorUpdatesLevel = SkipMinorLevelIndex + 2;
o.OverridesNextLevelOpts = !FollowGlobal;

var ver = SelectedVersion ?? "";
Expand Down Expand Up @@ -550,6 +557,7 @@ private void ApplyToOptions()
_options.UninstallPreviousVersionsOnUpdate = s.UninstallPreviousVersionsOnUpdate;
_options.AutoUpdatePackage = s.AutoUpdatePackage;
_options.SkipMinorUpdates = s.SkipMinorUpdates;
_options.SkipMinorUpdatesLevel = s.SkipMinorUpdatesLevel;
_options.OverridesNextLevelOpts = s.OverridesNextLevelOpts;
_options.PreRelease = s.PreRelease;
_options.Version = s.Version;
Expand Down
28 changes: 18 additions & 10 deletions src/UniGetUI.Avalonia/Views/DialogPages/InstallOptionsControl.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -145,20 +145,28 @@
<Border Height="1" Background="{DynamicResource AppBorderBrush}"
automation:AutomationProperties.AccessibilityView="Raw"/>

<WrapPanel Orientation="Horizontal">
<StackPanel Orientation="Vertical" Spacing="4">
<CheckBox Content="{Binding IgnoreUpdatesCheckBox_Content, FallbackValue='Ignore future updates for this package'}"
IsChecked="{Binding IgnoreUpdatesChecked}"
automation:AutomationProperties.Name="{Binding IgnoreUpdatesCheckBox_Content}"
Margin="0,0,16,4"/>
<CheckBox Content="{Binding SkipMinorCheckBox_Content, FallbackValue='Skip minor updates'}"
IsChecked="{Binding SkipMinorChecked}"
automation:AutomationProperties.Name="{Binding SkipMinorCheckBox_Content}"
Margin="0,0,16,4"/>
automation:AutomationProperties.Name="{Binding IgnoreUpdatesCheckBox_Content}"/>
<CheckBox Content="{Binding AutoUpdateCheckBox_Content, FallbackValue='Auto-update this package'}"
IsChecked="{Binding AutoUpdateChecked}"
automation:AutomationProperties.Name="{Binding AutoUpdateCheckBox_Content}"
Margin="0,0,16,4"/>
</WrapPanel>
automation:AutomationProperties.Name="{Binding AutoUpdateCheckBox_Content}"/>
<CheckBox Content="{Binding SkipMinorCheckBox_Content, FallbackValue='Skip minor updates'}"
IsChecked="{Binding SkipMinorChecked}"
automation:AutomationProperties.Name="{Binding SkipMinorCheckBox_Content}"/>
<WrapPanel Orientation="Horizontal" Margin="28,0,0,0"
IsEnabled="{Binding SkipMinorChecked}">
<TextBlock Text="{Binding SkipMinorLevelPrefix_Content, FallbackValue='Ignore changes after the first'}"
VerticalAlignment="Center" Margin="0,0,8,0" TextWrapping="Wrap"/>
<ComboBox ItemsSource="{Binding SkipMinorLevelOptions}"
SelectedIndex="{Binding SkipMinorLevelIndex}"
automation:AutomationProperties.Name="{Binding SkipMinorLevelPrefix_Content}"
VerticalAlignment="Center" MinWidth="64"/>
<TextBlock Text="{Binding SkipMinorLevelSuffix_Content, FallbackValue='version numbers'}"
VerticalAlignment="Center" Margin="8,0,0,0" TextWrapping="Wrap"/>
</WrapPanel>
</StackPanel>

</StackPanel>
</Border>
Expand Down
8 changes: 5 additions & 3 deletions src/UniGetUI.PackageEngine.Interfaces/IPackage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,10 +151,12 @@ public interface IPackage : INotifyPropertyChanged, IEquatable<IPackage>
public Task<string?> GetInstallerFileName();

/// <summary>
/// Checks whether a new update of this package is a minor update or not (0.0.x)
/// Checks whether a new update of this package is a minor update or not, according to
/// <paramref name="level"/> — the 1-based version-number position (2=Minor, 3=Patch, 4=Remainder)
/// from which a change is still considered minor.
/// </summary>
/// <returns>False if the update is a major update or the update doesn't exist, true if it's a minor update</returns>
public bool IsUpdateMinor();
/// <returns>False if the most significant changed component is more significant than <paramref name="level"/> or the update doesn't exist, true otherwise</returns>
public bool IsUpdateMinor(int level = InstallOptions.DefaultSkipMinorLevel);

/// <summary>
/// Gets the applicable install options for this package
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,11 @@ protected override async Task<bool> IsPackageValid(IPackage package)
if (package.NewerVersionIsInstalled())
return false;

if (package.IsUpdateMinor() && (await package.GetInstallOptions()).SkipMinorUpdates)
var installOptions = await package.GetInstallOptions();
if (installOptions.SkipMinorUpdates && package.IsUpdateMinor(installOptions.SkipMinorUpdatesLevel))
{
Logger.Info(
$"Ignoring package {package.Id} because it is a minor update ({package.VersionString} -> {package.NewVersionString}) and SkipMinorUpdates is set to true."
$"Ignoring package {package.Id} because it is a minor update ({package.VersionString} -> {package.NewVersionString}) below skip level {installOptions.SkipMinorUpdatesLevel} and SkipMinorUpdates is set to true."
);
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ public bool NewerVersionIsInstalled()
return Task.FromResult<string?>("");
}

public bool IsUpdateMinor()
public bool IsUpdateMinor(int level = InstallOptions.DefaultSkipMinorLevel)
{
return false;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -324,17 +324,26 @@ public virtual bool NewerVersionIsInstalled()
}
}

public virtual bool IsUpdateMinor()
// 1-based position of the most significant version component that changed
// (1=Major, 2=Minor, 3=Patch, 4=Remainder), or 0 if identical/unparseable.
private int HighestChangedVersionComponent()
{
if (NormalizedVersion == CoreTools.Version.Null || NormalizedNewVersion == CoreTools.Version.Null)
return 0;
if (NormalizedVersion.Major != NormalizedNewVersion.Major) return 1;
if (NormalizedVersion.Minor != NormalizedNewVersion.Minor) return 2;
if (NormalizedVersion.Patch != NormalizedNewVersion.Patch) return 3;
if (NormalizedVersion.Remainder != NormalizedNewVersion.Remainder) return 4;
return 0;
}

public virtual bool IsUpdateMinor(int level = InstallOptions.DefaultSkipMinorLevel)
{
if (!IsUpgradable)
return false;

return NormalizedVersion.Major == NormalizedNewVersion.Major
&& NormalizedVersion.Minor == NormalizedNewVersion.Minor
&& (
NormalizedVersion.Patch != NormalizedNewVersion.Patch
|| NormalizedVersion.Remainder != NormalizedNewVersion.Remainder
);
int changed = HighestChangedVersionComponent();
return changed >= level;
}

public virtual Task<InstallOptions> GetInstallOptions() =>
Expand Down
11 changes: 11 additions & 0 deletions src/UniGetUI.PackageEngine.Serializable/InstallOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class InstallOptions : SerializableComponent<InstallOptions>
private const string AS_ADMIN = "RunAsAdministrator";
private const string PRERELEASE = "PreRelease";
private const string SKIP_MINOR = "SkipMinorUpdates";
private const string SKIP_MINOR_LEVEL = "SkipMinorUpdatesLevel";
private const string REMOVE_DATA_UNINST = "RemoveDataOnUninstall";
private const string CLEAR_PREV_VER = "UninstallPreviousVersionsOnUpdate";
private const string ABORT_PRE_INST_FAIL = "AbortOnPreInstallFail";
Expand Down Expand Up @@ -65,6 +66,7 @@ public class InstallOptions : SerializableComponent<InstallOptions>
POST_UPD_CMD,
PRE_UNINST_CMD,
POST_UNINST_CMD,
SKIP_MINOR_LEVEL,
];

public readonly IReadOnlyList<string> _listKeys =
Expand Down Expand Up @@ -104,6 +106,15 @@ public bool SkipMinorUpdates
get => _boolVal[SKIP_MINOR];
set => _boolVal[SKIP_MINOR] = value;
}

// The version-number position (1=Major, 2=Minor, 3=Patch, 4=Remainder) from which a change is
// still considered "minor" and therefore skippable. Default 3 reproduces the legacy behaviour.
public const int DefaultSkipMinorLevel = 3;
public int SkipMinorUpdatesLevel
{
get => int.TryParse(_strVal[SKIP_MINOR_LEVEL], out int v) && v is >= 2 and <= 4 ? v : DefaultSkipMinorLevel;
set => _strVal[SKIP_MINOR_LEVEL] = value == DefaultSkipMinorLevel ? "" : value.ToString();
}
public bool RemoveDataOnUninstall
{
get => _boolVal[REMOVE_DATA_UNINST];
Expand Down
41 changes: 41 additions & 0 deletions src/UniGetUI.PackageEngine.Tests/UpgradablePackagesLoaderTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,47 @@ public async Task EvaluatePackageAsync_SkipsMinorUpdatesWhenConfigured()
Assert.False(await loader.EvaluatePackageAsync(package));
}

[Fact]
public async Task EvaluatePackageAsync_RespectsSkipMinorLevel()
{
var manager = new PackageManagerBuilder().Build();
_ = new InstalledPackagesLoader([manager]);
_ = new DiscoverablePackagesLoader([manager]);
var loader = new TestUpgradablePackagesLoader([manager]);

// A 3rd-position (patch) change on a four-part version.
var package = new PackageBuilder()
.WithManager(manager)
.WithId("Contoso.FourPart")
.WithVersion("3.4.5.6")
.WithNewVersion("3.4.6.6")
.Build();

// Level 4 => only 4th-position changes count as minor, so this patch change must NOT be skipped.
InstallOptionsFactory.SaveForPackage(
new InstallOptions
{
OverridesNextLevelOpts = true,
SkipMinorUpdates = true,
SkipMinorUpdatesLevel = 4,
},
package
);
Assert.True(await loader.EvaluatePackageAsync(package));

// Default level (3) => a 3rd-position change is minor and must be skipped.
InstallOptionsFactory.SaveForPackage(
new InstallOptions
{
OverridesNextLevelOpts = true,
SkipMinorUpdates = true,
SkipMinorUpdatesLevel = InstallOptions.DefaultSkipMinorLevel,
},
package
);
Assert.False(await loader.EvaluatePackageAsync(package));
}

[Fact]
public async Task EvaluatePackageAsync_SkipsIgnoredAndSupersededPackages()
{
Expand Down
Loading