diff --git a/src/Models/RepositoryUIStates.cs b/src/Models/RepositoryUIStates.cs
index 0b35f8d618..693e9d9f5a 100644
--- a/src/Models/RepositoryUIStates.cs
+++ b/src/Models/RepositoryUIStates.cs
@@ -117,12 +117,30 @@ public bool PreferRebaseInsteadOfMerge
set;
} = true;
+ public DealWithLocalChanges? DealWithLocalChangesOnPull
+ {
+ get;
+ set;
+ } = null;
+
public bool CheckSubmodulesOnPush
{
get;
set;
} = true;
+ public bool SetTrackingOnPush
+ {
+ get;
+ set;
+ } = false;
+
+ public bool NoVerifyOnPush
+ {
+ get;
+ set;
+ } = false;
+
public bool PushAllTags
{
get;
diff --git a/src/Resources/Icons.axaml b/src/Resources/Icons.axaml
index a410aef45f..ab784000e1 100644
--- a/src/Resources/Icons.axaml
+++ b/src/Resources/Icons.axaml
@@ -122,6 +122,7 @@
M853 256h-43v512h43c47 0 85-38 85-85v-341c0-47-38-85-85-85zM725 768V171h128V85h-341v85H640v85H171c-47 0-85 38-85 85v341c0 47 38 85 85 85h469V853h-128v85h341v-85H725v-86zm-469-171v-171h384v171H256z
M960 146v91C960 318 759 384 512 384S64 318 64 238V146C64 66 265 0 512 0s448 66 448 146zM960 352v206C960 638 759 704 512 704S64 638 64 558V352c96 66 272 97 448 97S864 418 960 352zm0 320v206C960 958 759 1024 512 1024S64 958 64 878V672c96 66 272 97 448 97S864 738 960 672z
M883 567l-128-128c-17-17-43-17-60 0l-128 128c-17 17-17 43 0 60 17 17 43 17 60 0l55-55V683c0 21-21 43-43 43H418c-13-38-43-64-77-77V375c51-17 85-64 85-119 0-73-60-128-128-128-73 0-128 55-128 128 0 55 34 102 85 119v269c-51 17-85 64-85 119 0 73 55 128 128 128 55 0 102-34 119-85H640c73 0 128-55 128-128v-111l55 55c9 9 17 13 30 13 13 0 21-4 30-13 17-13 17-43 0-55zM299 213c26 0 43 17 43 43 0 21-21 43-43 43-26 0-43-21-43-43 0-26 17-43 43-43zm0 597c-26 0-43-21-43-43 0-26 17-43 43-43s43 17 43 43c0 21-17 43-43 43zM725 384c-73 0-128-60-128-128 0-73 55-128 128-128s128 55 128 128c0 68-55 128-128 128zm0-171c-26 0-43 17-43 43s17 43 43 43 43-17 43-43-17-43-43-43z
+ M512 112A400 400 0 1 1 112 512H192A320 320 0 1 0 512 192V272L352 152L512 32Z
M293 122v244h439V146l171 175V829a73 73 0 01-73 73h-98V536H293v366H195a73 73 0 01-73-73V195a73 73 0 0173-73h98zm366 512v268H366V634h293zm-49 49h-195v73h195v-73zm49-561v171H366V122h293z
M0 551V472c0-11 9-19 19-19h984c11 0 19 9 19 19v79c0 11-9 19-19 19H19c-11 0-19-9-19-19zM114 154v240c0 11-9 19-19 19H19C9 413 0 404 0 394V79C0 35 35 0 79 0h315c11 0 19 9 19 19v75c0 11-9 19-19 19H154c-21 0-39 18-39 39zm795 0c0-22-17-39-39-39h-240c-11 0-19-9-19-19V19c0-11 9-19 19-19h315c43 0 79 35 79 79v315c0 11-9 19-19 19h-75c-11 0-19-9-19-19l-1-240zm0 716v-240c0-11 9-19 19-19h75c11 0 19 9 19 19v315c0 43-35 79-79 79h-315c-11 0-19-9-19-19v-75c0-11 9-19 19-19H870c21-1 39-18 39-40zm-795 0c0 21 18 39 39 39h240c11 0 19 9 19 19v75c0 11-9 19-19 19H79C35 1023 0 988 0 945v-315c0-11 9-19 19-19h75c11 0 19 9 19 19V870z
M155 143h715v81H155V143zm358 94 358 369H662l1 278H363V605H155l358-369z
diff --git a/src/Resources/Locales/en_US.axaml b/src/Resources/Locales/en_US.axaml
index 76ac9e4dcb..1522250dba 100644
--- a/src/Resources/Locales/en_US.axaml
+++ b/src/Resources/Locales/en_US.axaml
@@ -662,6 +662,8 @@
{0} months ago
{0} years ago
Yesterday
+ Reset defaults
+ Save as default
Preferences
AI
Additional Prompt (Use `-` to list your requirements)
@@ -850,6 +852,7 @@
First-parent only
SHOW FLAGS
Show lost commits
+ Show options...
Show Submodules as Tree
Show Tags as Tree
SKIP
diff --git a/src/ViewModels/Fetch.cs b/src/ViewModels/Fetch.cs
index 330dd5d763..c790f1836e 100644
--- a/src/ViewModels/Fetch.cs
+++ b/src/ViewModels/Fetch.cs
@@ -19,11 +19,7 @@ public bool IsFetchAllRemoteVisible
public bool FetchAllRemotes
{
get => _fetchAllRemotes;
- set
- {
- if (SetProperty(ref _fetchAllRemotes, value) && IsFetchAllRemoteVisible)
- _repo.UIStates.FetchAllRemotes = value;
- }
+ set => SetProperty(ref _fetchAllRemotes, value);
}
public Models.Remote SelectedRemote
@@ -34,14 +30,19 @@ public Models.Remote SelectedRemote
public bool NoTags
{
- get => _repo.UIStates.FetchWithoutTags;
- set => _repo.UIStates.FetchWithoutTags = value;
+ get => _noTags;
+ set => SetProperty(ref _noTags, value);
}
public bool Force
{
- get => _repo.UIStates.EnableForceOnFetch;
- set => _repo.UIStates.EnableForceOnFetch = value;
+ get => _force;
+ set => SetProperty(ref _force, value);
+ }
+
+ public override bool HasDefaultOptions
+ {
+ get => true;
}
public Fetch(Repository repo, Models.Remote preferredRemote = null)
@@ -49,6 +50,8 @@ public Fetch(Repository repo, Models.Remote preferredRemote = null)
_repo = repo;
IsFetchAllRemoteVisible = repo.Remotes.Count > 1 && preferredRemote == null;
_fetchAllRemotes = IsFetchAllRemoteVisible && _repo.UIStates.FetchAllRemotes;
+ _noTags = _repo.UIStates.FetchWithoutTags;
+ _force = _repo.UIStates.EnableForceOnFetch;
CanTerminate = true;
if (preferredRemote != null)
@@ -66,6 +69,29 @@ public Fetch(Repository repo, Models.Remote preferredRemote = null)
}
}
+ public override void SaveOptionsAsDefault()
+ {
+ if (IsFetchAllRemoteVisible)
+ _repo.UIStates.FetchAllRemotes = _fetchAllRemotes;
+
+ _repo.UIStates.FetchWithoutTags = _noTags;
+ _repo.UIStates.EnableForceOnFetch = _force;
+ _repo.UIStates.Save();
+ }
+
+ public override void ResetDefaultOptions()
+ {
+ var defaults = new Models.RepositoryUIStates();
+ _repo.UIStates.FetchAllRemotes = defaults.FetchAllRemotes;
+ _repo.UIStates.FetchWithoutTags = defaults.FetchWithoutTags;
+ _repo.UIStates.EnableForceOnFetch = defaults.EnableForceOnFetch;
+ _repo.UIStates.Save();
+
+ FetchAllRemotes = IsFetchAllRemoteVisible && defaults.FetchAllRemotes;
+ NoTags = defaults.FetchWithoutTags;
+ Force = defaults.EnableForceOnFetch;
+ }
+
public override async Task Sure()
{
using var lockWatcher = _repo.LockWatcher();
@@ -74,19 +100,20 @@ public override async Task Sure()
_repo.Histories.SelectedCommits.Count == 1 &&
_repo.Histories.SelectedCommits[0].IsCurrentHead;
- var notags = _repo.UIStates.FetchWithoutTags;
- var force = _repo.UIStates.EnableForceOnFetch;
+ var notags = _noTags;
+ var force = _force;
var log = _repo.CreateLog("Fetch");
Use(log);
_cancellation = new CancellationTokenSource();
var token = _cancellation.Token;
+ var succ = true;
if (FetchAllRemotes)
{
foreach (var remote in _repo.Remotes)
{
- await new Commands.Fetch(_repo.FullPath, remote, notags, force)
+ succ &= await new Commands.Fetch(_repo.FullPath, remote, notags, force)
.WithCancellation(token)
.Use(log)
.ExecAsync();
@@ -97,7 +124,7 @@ public override async Task Sure()
}
else
{
- await new Commands.Fetch(_repo.FullPath, SelectedRemote, notags, force)
+ succ = await new Commands.Fetch(_repo.FullPath, SelectedRemote, notags, force)
.WithCancellation(token)
.Use(log)
.ExecAsync();
@@ -119,7 +146,7 @@ public override async Task Sure()
_repo.MarkFetched();
_cancellation = null;
- return true;
+ return succ;
}
public override void Terminate()
@@ -130,6 +157,8 @@ public override void Terminate()
private readonly Repository _repo = null;
private bool _fetchAllRemotes = false;
+ private bool _noTags = false;
+ private bool _force = false;
private CancellationTokenSource _cancellation = null;
}
}
diff --git a/src/ViewModels/Popup.cs b/src/ViewModels/Popup.cs
index 10bc44fe1b..ab3d055503 100644
--- a/src/ViewModels/Popup.cs
+++ b/src/ViewModels/Popup.cs
@@ -50,11 +50,24 @@ public void Cleanup()
_log?.Unsubscribe(this);
}
+ public virtual bool HasDefaultOptions
+ {
+ get => false;
+ }
+
public virtual bool CanStartDirectly()
{
return true;
}
+ public virtual void SaveOptionsAsDefault()
+ {
+ }
+
+ public virtual void ResetDefaultOptions()
+ {
+ }
+
public virtual Task Sure()
{
return null;
diff --git a/src/ViewModels/Pull.cs b/src/ViewModels/Pull.cs
index 7aaf1be4f4..ffe259716a 100644
--- a/src/ViewModels/Pull.cs
+++ b/src/ViewModels/Pull.cs
@@ -47,14 +47,19 @@ public bool HasLocalChanges
public Models.DealWithLocalChanges DealWithLocalChanges
{
- get;
- set;
+ get => _dealWithLocalChanges;
+ set => SetProperty(ref _dealWithLocalChanges, value);
}
public bool UseRebase
{
- get => _repo.UIStates.PreferRebaseInsteadOfMerge;
- set => _repo.UIStates.PreferRebaseInsteadOfMerge = value;
+ get => _useRebase;
+ set => SetProperty(ref _useRebase, value);
+ }
+
+ public override bool HasDefaultOptions
+ {
+ get => true;
}
public Pull(Repository repo, Models.Branch specifiedRemoteBranch)
@@ -63,9 +68,8 @@ public Pull(Repository repo, Models.Branch specifiedRemoteBranch)
Current = repo.CurrentBranch;
CanTerminate = true;
- DealWithLocalChanges = Preferences.Instance.UseStashAndReapplyByDefault ?
- Models.DealWithLocalChanges.StashAndReapply :
- Models.DealWithLocalChanges.DoNothing;
+ _dealWithLocalChanges = repo.UIStates.DealWithLocalChangesOnPull ?? GetPreferredDealWithLocalChanges();
+ _useRebase = repo.UIStates.PreferRebaseInsteadOfMerge;
if (specifiedRemoteBranch != null)
{
@@ -112,6 +116,24 @@ public Pull(Repository repo, Models.Branch specifiedRemoteBranch)
}
}
+ public override void SaveOptionsAsDefault()
+ {
+ _repo.UIStates.DealWithLocalChangesOnPull = _dealWithLocalChanges;
+ _repo.UIStates.PreferRebaseInsteadOfMerge = _useRebase;
+ _repo.UIStates.Save();
+ }
+
+ public override void ResetDefaultOptions()
+ {
+ var defaults = new Models.RepositoryUIStates();
+ _repo.UIStates.DealWithLocalChangesOnPull = defaults.DealWithLocalChangesOnPull;
+ _repo.UIStates.PreferRebaseInsteadOfMerge = defaults.PreferRebaseInsteadOfMerge;
+ _repo.UIStates.Save();
+
+ DealWithLocalChanges = GetPreferredDealWithLocalChanges();
+ UseRebase = defaults.PreferRebaseInsteadOfMerge;
+ }
+
public override async Task Sure()
{
using var lockWatcher = _repo.LockWatcher();
@@ -189,6 +211,13 @@ public override void Terminate()
var _ = _cancellation?.CancelAsync();
}
+ private static Models.DealWithLocalChanges GetPreferredDealWithLocalChanges()
+ {
+ return Preferences.Instance.UseStashAndReapplyByDefault ?
+ Models.DealWithLocalChanges.StashAndReapply :
+ Models.DealWithLocalChanges.DoNothing;
+ }
+
private void PostRemoteSelected()
{
var remoteName = _selectedRemote.Name;
@@ -214,6 +243,8 @@ private void PostRemoteSelected()
private Models.Remote _selectedRemote = null;
private List _remoteBranches = null;
private Models.Branch _selectedBranch = null;
+ private Models.DealWithLocalChanges _dealWithLocalChanges = Models.DealWithLocalChanges.DoNothing;
+ private bool _useRebase = true;
private CancellationTokenSource _cancellation = null;
}
}
diff --git a/src/ViewModels/Push.cs b/src/ViewModels/Push.cs
index ee7b660ac4..0274529d7d 100644
--- a/src/ViewModels/Push.cs
+++ b/src/ViewModels/Push.cs
@@ -87,14 +87,14 @@ public bool IsCheckSubmodulesVisible
public bool CheckSubmodules
{
- get;
- set;
- } = true;
+ get => _checkSubmodules;
+ set => SetProperty(ref _checkSubmodules, value);
+ }
public bool PushAllTags
{
- get => _repo.UIStates.PushAllTags;
- set => _repo.UIStates.PushAllTags = value;
+ get => _pushAllTags;
+ set => SetProperty(ref _pushAllTags, value);
}
public bool ForcePush
@@ -105,13 +105,22 @@ public bool ForcePush
public bool NoVerify
{
- get;
- set;
+ get => _noVerify;
+ set => SetProperty(ref _noVerify, value);
+ }
+
+ public override bool HasDefaultOptions
+ {
+ get => true;
}
public Push(Repository repo, Models.Branch localBranch)
{
_repo = repo;
+ _tracking = repo.UIStates.SetTrackingOnPush;
+ _checkSubmodules = repo.UIStates.CheckSubmodulesOnPush;
+ _pushAllTags = repo.UIStates.PushAllTags;
+ _noVerify = repo.UIStates.NoVerifyOnPush;
CanTerminate = true;
// Gather all local branches and find current branch.
@@ -162,6 +171,30 @@ public void PushToNewBranch(string name)
SelectedRemoteBranch = fake;
}
+ public override void SaveOptionsAsDefault()
+ {
+ _repo.UIStates.SetTrackingOnPush = _tracking;
+ _repo.UIStates.CheckSubmodulesOnPush = _checkSubmodules;
+ _repo.UIStates.PushAllTags = _pushAllTags;
+ _repo.UIStates.NoVerifyOnPush = _noVerify;
+ _repo.UIStates.Save();
+ }
+
+ public override void ResetDefaultOptions()
+ {
+ var defaults = new Models.RepositoryUIStates();
+ _repo.UIStates.SetTrackingOnPush = defaults.SetTrackingOnPush;
+ _repo.UIStates.CheckSubmodulesOnPush = defaults.CheckSubmodulesOnPush;
+ _repo.UIStates.PushAllTags = defaults.PushAllTags;
+ _repo.UIStates.NoVerifyOnPush = defaults.NoVerifyOnPush;
+ _repo.UIStates.Save();
+
+ Tracking = defaults.SetTrackingOnPush;
+ CheckSubmodules = defaults.CheckSubmodulesOnPush;
+ PushAllTags = defaults.PushAllTags;
+ NoVerify = defaults.NoVerifyOnPush;
+ }
+
public override bool CanStartDirectly()
{
return !string.IsNullOrEmpty(_selectedRemoteBranch?.Head);
@@ -357,6 +390,9 @@ private bool IsPreferredPushRemote(Models.Remote remote)
private Models.Branch _selectedRemoteBranch = null;
private bool _isSetTrackOptionVisible = false;
private bool _tracking = false;
+ private bool _checkSubmodules = true;
+ private bool _pushAllTags = false;
+ private bool _noVerify = false;
private CancellationTokenSource _cancellation = null;
}
}
diff --git a/src/ViewModels/Repository.cs b/src/ViewModels/Repository.cs
index aa58226d97..90c098932f 100644
--- a/src/ViewModels/Repository.cs
+++ b/src/ViewModels/Repository.cs
@@ -399,6 +399,42 @@ public bool IsAutoFetching
private set => SetProperty(ref _isAutoFetching, value);
}
+ public bool IsFetching
+ {
+ get => _isFetching;
+ private set => SetProperty(ref _isFetching, value);
+ }
+
+ public bool IsFetchSucceeded
+ {
+ get => _isFetchSucceeded;
+ private set => SetProperty(ref _isFetchSucceeded, value);
+ }
+
+ public bool IsPulling
+ {
+ get => _isPulling;
+ private set => SetProperty(ref _isPulling, value);
+ }
+
+ public bool IsPullSucceeded
+ {
+ get => _isPullSucceeded;
+ private set => SetProperty(ref _isPullSucceeded, value);
+ }
+
+ public bool IsPushing
+ {
+ get => _isPushing;
+ private set => SetProperty(ref _isPushing, value);
+ }
+
+ public bool IsPushSucceeded
+ {
+ get => _isPushSucceeded;
+ private set => SetProperty(ref _isPushSucceeded, value);
+ }
+
public AvaloniaList IssueTrackers
{
get;
@@ -494,7 +530,7 @@ public bool CanCreatePopup()
if (page == null)
return false;
- return !_isAutoFetching && page.CanCreatePopup();
+ return !_isAutoFetching && !_isFetching && !_isPulling && !_isPushing && page.CanCreatePopup();
}
public void ShowPopup(Popup popup)
@@ -662,7 +698,7 @@ public async Task FetchAsync(bool autoStart)
}
if (autoStart)
- await ShowAndStartPopupAsync(new Fetch(this));
+ await RunPopupInBackgroundAsync(new Fetch(this), v => IsFetching = v, v => IsFetchSucceeded = v);
else
ShowPopup(new Fetch(this));
}
@@ -685,8 +721,8 @@ public async Task PullAsync(bool autoStart)
}
var pull = new Pull(this, null);
- if (autoStart && pull.SelectedBranch != null)
- await ShowAndStartPopupAsync(pull);
+ if (autoStart)
+ await RunPopupInBackgroundAsync(pull, v => IsPulling = v, v => IsPullSucceeded = v);
else
ShowPopup(pull);
}
@@ -709,7 +745,7 @@ public async Task PushAsync(bool autoStart)
}
if (autoStart)
- await ShowAndStartPopupAsync(new Push(this, null));
+ await RunPopupInBackgroundAsync(new Push(this, null), v => IsPushing = v, v => IsPushSucceeded = v);
else
ShowPopup(new Push(this, null));
}
@@ -1888,6 +1924,46 @@ private void AutoFetchByTimer(object sender)
}
}
+ private async Task RunPopupInBackgroundAsync(Popup popup, Action setRunning, Action setSucceeded)
+ {
+ // Falls back to the popup when the options need user input.
+ if (!popup.CanStartDirectly() || !popup.Check())
+ {
+ ShowPopup(popup);
+ return;
+ }
+
+ setSucceeded(false);
+ setRunning(true);
+
+ var succ = false;
+ try
+ {
+ succ = await popup.Sure();
+ }
+ catch (Exception e)
+ {
+ Native.OS.LogException(e);
+ SendNotification(e.Message, true);
+ }
+
+ popup.Cleanup();
+ setRunning(false);
+
+ if (succ)
+ {
+ // Briefly show the success badge on the toolbar button.
+ var key = popup.GetType();
+ var version = _backgroundSuccessVersions.GetValueOrDefault(key) + 1;
+ _backgroundSuccessVersions[key] = version;
+
+ setSucceeded(true);
+ await Task.Delay(3000);
+ if (_backgroundSuccessVersions[key] == version)
+ setSucceeded(false);
+ }
+ }
+
private async Task AutoFetchOnUIThread()
{
if (IsAutoFetching)
@@ -1970,6 +2046,13 @@ private async Task AutoFetchOnUIThread()
private string _navigateToCommitDelayed = string.Empty;
private bool _isAutoFetching = false;
+ private bool _isFetching = false;
+ private bool _isPulling = false;
+ private bool _isPushing = false;
+ private bool _isFetchSucceeded = false;
+ private bool _isPullSucceeded = false;
+ private bool _isPushSucceeded = false;
+ private readonly Dictionary _backgroundSuccessVersions = new();
private Timer _autoFetchTimer = null;
private DateTime _lastFetchTime = DateTime.MinValue;
diff --git a/src/Views/LauncherPage.axaml b/src/Views/LauncherPage.axaml
index 08107bfa90..359eadda3a 100644
--- a/src/Views/LauncherPage.axaml
+++ b/src/Views/LauncherPage.axaml
@@ -102,33 +102,60 @@
-
-
-
-
-
+
+
+
+
+
+
+
+
+
+
+
+
-