Skip to content
Open
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
5 changes: 4 additions & 1 deletion src/Commands/QueryCommits.cs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ public QueryCommits(string repo, string filter, Models.CommitSearchMethod method
_markMerged = false;
}

public async Task<List<Models.Commit>> GetResultAsync()
public async Task<List<Models.Commit>> GetResultAsync(int firstPageSize = 0, Action<List<Models.Commit>> onFirstPageLoaded = null)
{
var commits = new List<Models.Commit>();
try
Expand Down Expand Up @@ -78,6 +78,9 @@ public QueryCommits(string repo, string filter, Models.CommitSearchMethod method

if (!findHead && commit.IsMerged)
findHead = true;

if (commits.Count == firstPageSize)
onFirstPageLoaded?.Invoke(commits.GetRange(0, firstPageSize));
}

await proc.WaitForExitAsync().ConfigureAwait(false);
Expand Down
1 change: 1 addition & 0 deletions src/Resources/Locales/en_US.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -535,6 +535,7 @@
<x:String x:Key="Text.Histories.HighlightsInGraph.CurrentBranchAndSelectedCommits" xml:space="preserve">Current Branch &amp; Selected Commits</x:String>
<x:String x:Key="Text.Histories.HighlightsInGraph.SelectedCommitsOnly" xml:space="preserve">Selected Commits Only</x:String>
<x:String x:Key="Text.Histories.HighlightsInGraph.SelectedCommitsOnlyFirstParent" xml:space="preserve">Selected Commits (First-parent Only)</x:String>
<x:String x:Key="Text.Histories.LoadingMoreCommits" xml:space="preserve">Loading more commits...</x:String>
<x:String x:Key="Text.Histories.Selected" xml:space="preserve">SELECTED {0} COMMITS</x:String>
<x:String x:Key="Text.Histories.ShowColumns" xml:space="preserve">SHOW COLUMNS</x:String>
<x:String x:Key="Text.Histories.Tips" xml:space="preserve">Hold 'Ctrl' or 'Shift' to select multiple commits.</x:String>
Expand Down
57 changes: 56 additions & 1 deletion src/ViewModels/Histories.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,12 @@ public bool IsLoading
set => SetProperty(ref _isLoading, value);
}

public bool IsLoadingMoreCommits
{
get => _isLoadingMoreCommits;
set => SetProperty(ref _isLoadingMoreCommits, value);
}

public bool IsSearchingCommits
{
get => _isSearchingCommits;
Expand Down Expand Up @@ -113,7 +119,7 @@ public List<Models.Commit> Commits
set
{
GenerateGraph(value);
if (SetProperty(ref _commits, value))
if (SetProperty(ref _commits, value) && !NavigateToPendingCommit())
PostCommitsChanged();
}
}
Expand Down Expand Up @@ -299,6 +305,24 @@ public Models.BisectState UpdateBisectInfo()
return Models.BisectState.WaitingForMark;
}

public bool CanShowPartialCommits(List<Models.Commit> commits)
{
if (_selectedCommits.Count == 0)
return true;

var set = new HashSet<string>();
foreach (var c in commits)
set.Add(c.SHA);

foreach (var c in _selectedCommits)
{
if (!set.Contains(c.SHA))
return false;
}

return true;
}

public void NavigateTo(string commitSHA)
{
var commit = _commits.Find(x => x.SHA.StartsWith(commitSHA, StringComparison.Ordinal));
Expand All @@ -308,6 +332,9 @@ public void NavigateTo(string commitSHA)
return;
}

// The target commit may be in the part of history that is still loading. Select it once it's loaded.
_pendingNavigateTarget = (_isLoading || _isLoadingMoreCommits) ? commitSHA : null;

Task.Run(async () =>
{
var c = await new Commands.QuerySingleCommit(_repo.FullPath, commitSHA)
Expand All @@ -316,6 +343,9 @@ public void NavigateTo(string commitSHA)

Dispatcher.UIThread.Post(() =>
{
if (_selectedCommits.Count == 1 && _selectedCommits[0].SHA.StartsWith(commitSHA, StringComparison.Ordinal))
return;

_ignoreSelectionChange = true;
SelectedCommits = [];

Expand Down Expand Up @@ -529,6 +559,9 @@ private void PostSelectedCommitsChanged()
if (_ignoreSelectionChange)
return;

if (_selectedCommits.Count > 0)
_pendingNavigateTarget = null;

if (_selectedCommits.Count == 0)
{
_searchCommitContext.Selected = null;
Expand Down Expand Up @@ -567,6 +600,26 @@ private void PostSelectedCommitsChanged()
GenerateGraph(_commits);
}

private bool NavigateToPendingCommit()
{
if (string.IsNullOrEmpty(_pendingNavigateTarget))
return false;

var commit = _commits.Find(x => x.SHA.StartsWith(_pendingNavigateTarget, StringComparison.Ordinal));
if (commit != null)
{
_pendingNavigateTarget = null;
SelectedCommits = [commit];
return true;
}

if (_isLoading || _isLoadingMoreCommits)
return true;

_pendingNavigateTarget = null;
return false;
}

private void GenerateGraph(List<Models.Commit> commits)
{
var firstParentOnly = _repo.UIStates.HistoryShowFlags.HasFlag(Models.HistoryShowFlags.FirstParentOnly);
Expand All @@ -587,6 +640,8 @@ private void GenerateGraph(List<Models.Commit> commits)
private bool _hasSingleRemote = false;
private CommitDetailSharedData _commitDetailSharedData = null;
private bool _isLoading = true;
private bool _isLoadingMoreCommits = false;
private string _pendingNavigateTarget = null;
private List<Models.Commit> _commits = [];
private Models.CommitGraph _graph = null;
private List<Models.Commit> _selectedCommits = [];
Expand Down
77 changes: 49 additions & 28 deletions src/ViewModels/Repository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1257,10 +1257,11 @@ public void RefreshCommits()
{
await Dispatcher.UIThread.InvokeAsync(() => _histories.IsLoading = true);

var maxHistoryCommits = Preferences.Instance.MaxHistoryCommits;
var builder = new StringBuilder();
builder
.Append('-')
.Append(Preferences.Instance.MaxHistoryCommits)
.Append(maxHistoryCommits)
.Append(' ');

if (_uiStates.FocusOnCurrentBranch)
Expand All @@ -1275,39 +1276,27 @@ public void RefreshCommits()
builder.Append(_uiStates.BuildHistoryParams_NormalMode(GitDir));
}

// Shows the first page of commits as soon as it's ready, and keeps loading the rest in background.
var firstPageSize = maxHistoryCommits > 1000 ? 1000 : 0;
var commits = await new Commands.QueryCommits(FullPath, builder.ToString())
.GetResultAsync()
.ConfigureAwait(false);
.GetResultAsync(firstPageSize, firstPage =>
{
MarkMergedCommits(firstPage);

var merged = new HashSet<string>();
foreach (var c in commits)
{
if (merged.Remove(c.SHA))
c.IsMerged = true;
Dispatcher.UIThread.Invoke(() =>
{
if (!token.IsCancellationRequested && _histories != null && _histories.CanShowPartialCommits(firstPage))
SetHistoryCommits(firstPage, true);
});
})
.ConfigureAwait(false);

if (c.IsMerged)
{
foreach (var p in c.Parents)
merged.Add(p);
}
}
MarkMergedCommits(commits);

Dispatcher.UIThread.Invoke(() =>
{
if (token.IsCancellationRequested)
return;

if (_histories != null)
{
_histories.IsLoading = false;
_histories.Commits = commits;
BisectState = _histories.UpdateBisectInfo();

if (!string.IsNullOrEmpty(_navigateToCommitDelayed))
NavigateToCommit(_navigateToCommitDelayed);
}

_navigateToCommitDelayed = string.Empty;
if (!token.IsCancellationRequested)
SetHistoryCommits(commits, false);
});
}, token);
}
Expand Down Expand Up @@ -1811,6 +1800,38 @@ private object BuildVisibleSubmodules()
return new SubmoduleCollectionAsList() { Submodules = visible };
}

private static void MarkMergedCommits(List<Models.Commit> commits)
{
var merged = new HashSet<string>();
foreach (var c in commits)
{
if (merged.Remove(c.SHA))
c.IsMerged = true;

if (c.IsMerged)
{
foreach (var p in c.Parents)
merged.Add(p);
}
}
}

private void SetHistoryCommits(List<Models.Commit> commits, bool hasMore)
{
if (_histories != null)
{
_histories.IsLoading = false;
_histories.IsLoadingMoreCommits = hasMore;
_histories.Commits = commits;
BisectState = _histories.UpdateBisectInfo();

if (!string.IsNullOrEmpty(_navigateToCommitDelayed))
NavigateToCommit(_navigateToCommitDelayed);
}

_navigateToCommitDelayed = string.Empty;
}

private void RefreshHistoryFilters(bool refresh)
{
HistoryFilterMode = _uiStates.GetHistoryFilterMode();
Expand Down
7 changes: 7 additions & 0 deletions src/Views/Histories.axaml
Original file line number Diff line number Diff line change
Expand Up @@ -272,6 +272,13 @@
<Path Width="12" Height="12"
Data="{StaticResource Icons.Settings2}"/>
</Button>

<v:LoadingIcon Width="12" Height="12"
VerticalAlignment="Center"
Background="Transparent"
IsHitTestVisible="True"
ToolTip.Tip="{DynamicResource Text.Histories.LoadingMoreCommits}"
IsVisible="{Binding IsLoadingMoreCommits}"/>
</StackPanel>
</Border>

Expand Down
56 changes: 54 additions & 2 deletions src/Views/Histories.axaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Platform.Storage;
using Avalonia.Threading;
using Avalonia.VisualTree;

namespace SourceGit.Views
Expand Down Expand Up @@ -135,11 +136,17 @@ protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs chang
{
base.OnPropertyChanged(change);

if (change.Property == SelectedCommitsProperty && IsLoaded && !_ignoreSelectionChanged)
if (change.Property == ItemsSourceProperty && IsLoaded)
{
KeepScrollPosition(change.OldValue as List<Models.Commit>, change.NewValue as List<Models.Commit>);
}
else if (change.Property == SelectedCommitsProperty && IsLoaded && !_ignoreSelectionChanged)
{
if (change.OldValue is List<Models.Commit> { Count: 1 } old &&
change.NewValue is List<Models.Commit> { Count: 1 } cur &&
old[0] == cur[0])
old[0] == cur[0] &&
SelectedItems is [Models.Commit selected] &&
selected == cur[0])
ScrollIntoView(old[0], null);
else
ApplySelection();
Expand Down Expand Up @@ -246,6 +253,51 @@ protected override async void OnKeyDown(KeyEventArgs e)
base.OnKeyDown(e);
}

private void KeepScrollPosition(List<Models.Commit> oldCommits, List<Models.Commit> newCommits)
{
if (newCommits == null)
return;

// Changing `ItemsSource` always resets the scroll offset. When the new list only appends commits to the
// old one (more history has been loaded), remember the last fully visible row and scroll back to it.
Models.Commit anchor = null;
if (oldCommits is { Count: > 0 } && newCommits.Count > oldCommits.Count && newCommits[oldCommits.Count - 1] == oldCommits[^1])
{
var rowsPresenter = this.FindDescendantOfType<DataGridRowsPresenter>();
if (rowsPresenter != null)
{
var lastIndex = -1;
foreach (var child in rowsPresenter.Children)
{
if (child is DataGridRow { IsVisible: true } row &&
row.Bounds.Top >= 0 &&
row.Bounds.Bottom <= rowsPresenter.Bounds.Height + 0.5 &&
row.Index > lastIndex)
lastIndex = row.Index;
}

if (lastIndex >= 0 && lastIndex < oldCommits.Count)
anchor = oldCommits[lastIndex];
}
}

var oldSelected = SelectedItems is [Models.Commit c] ? c.SHA : null;
Dispatcher.UIThread.Post(() =>
{
if (ItemsSource != newCommits)
return;

// Rows are not available until the next layout pass, so `ScrollIntoView` doesn't work before it.
UpdateLayout();

// A commit that is navigated to while loading should be visible after it is selected.
if (SelectedItems is [Models.Commit selected] && !selected.SHA.Equals(oldSelected, StringComparison.Ordinal))
ScrollIntoView(selected, null);
else if (anchor != null)
ScrollIntoView(anchor, null);
});
}

private void ApplySelection()
{
_ignoreSelectionChanged = true;
Expand Down