Skip to content

Commit

Permalink
Feature: Lazy load Git properties for better performance (#13211)
Browse files Browse the repository at this point in the history
  • Loading branch information
hishitetsu authored Aug 20, 2023
1 parent b4c7421 commit c19ee12
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 56 deletions.
14 changes: 14 additions & 0 deletions src/Files.App/Data/Items/ListedItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,20 @@ public override string Name

public class GitItem : ListedItem
{
private volatile int statusPropertiesInitialized = 0;
public bool StatusPropertiesInitialized
{
get => statusPropertiesInitialized == 1;
set => Interlocked.Exchange(ref statusPropertiesInitialized, value ? 1 : 0);
}

private volatile int commitPropertiesInitialized = 0;
public bool CommitPropertiesInitialized
{
get => commitPropertiesInitialized == 1;
set => Interlocked.Exchange(ref commitPropertiesInitialized, value ? 1 : 0);
}

private Style? _UnmergedGitStatusIcon;
public Style? UnmergedGitStatusIcon
{
Expand Down
2 changes: 1 addition & 1 deletion src/Files.App/Data/Models/GitItemModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ internal class GitItemModel
/// <remarks>
/// This is often showed as A(Added), D(Deleted), M(Modified), U(Untracked) in VS Code.
/// </remarks>
public ChangeKind Status { get; init; }
public ChangeKind? Status { get; init; }

/// <summary>
/// Gets or initializes file change kind icon
Expand Down
124 changes: 91 additions & 33 deletions src/Files.App/Data/Models/ItemViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,33 @@ public string? SolutionFilePath
private set => SetProperty(ref _SolutionFilePath, value);
}

private GitProperties _EnabledGitProperties;
public GitProperties EnabledGitProperties
{
get => _EnabledGitProperties;
set
{
if (SetProperty(ref _EnabledGitProperties, value) && value is not GitProperties.None)
{
filesAndFolders.ToList().ForEach(async item => {
if (item is GitItem gitItem &&
(!gitItem.StatusPropertiesInitialized && value is GitProperties.All or GitProperties.Status
|| !gitItem.CommitPropertiesInitialized && value is GitProperties.All or GitProperties.Commit))
{
try
{
await Task.Run(async () => await LoadGitProperties(gitItem, loadPropsCTS));
}
catch (OperationCanceledException)
{
// Ignored
}
}
});
}
}
}

public CollectionViewSource viewSource;

private FileSystemWatcher watcher;
Expand Down Expand Up @@ -1203,39 +1230,8 @@ await SafetyExtensions.IgnoreExceptions(() =>
}));
}

if (item.IsGitItem &&
GitHelpers.IsRepositoryEx(item.ItemPath, out var repoPath) &&
!string.IsNullOrEmpty(repoPath))
{
cts.Token.ThrowIfCancellationRequested();
await SafetyExtensions.IgnoreExceptions(() =>
{
return dispatcherQueue.EnqueueOrInvokeAsync(() =>
{
var repo = new LibGit2Sharp.Repository(repoPath);
GitItemModel gitItemModel = GitHelpers.GetGitInformationForItem(repo, item.ItemPath);

var gitItem = item.AsGitItem;
gitItem.UnmergedGitStatusIcon = gitItemModel.Status switch
{
ChangeKind.Added => (Microsoft.UI.Xaml.Style)Microsoft.UI.Xaml.Application.Current.Resources["ColorIconGitAdded"],
ChangeKind.Deleted => (Microsoft.UI.Xaml.Style)Microsoft.UI.Xaml.Application.Current.Resources["ColorIconGitDeleted"],
ChangeKind.Modified => (Microsoft.UI.Xaml.Style)Microsoft.UI.Xaml.Application.Current.Resources["ColorIconGitModified"],
ChangeKind.Untracked => (Microsoft.UI.Xaml.Style)Microsoft.UI.Xaml.Application.Current.Resources["ColorIconGitUntracked"],
_ => null,
};
gitItem.UnmergedGitStatusName = gitItemModel.StatusHumanized;
gitItem.GitLastCommitDate = gitItemModel.LastCommit?.Author.When;
gitItem.GitLastCommitMessage = gitItemModel.LastCommit?.MessageShort;
gitItem.GitLastCommitAuthor = gitItemModel.LastCommit?.Author.Name;
gitItem.GitLastCommitSha = gitItemModel.LastCommit?.Sha.Substring(0, 7);
gitItem.GitLastCommitFullSha = gitItemModel.LastCommit?.Sha;

repo.Dispose();
},
Microsoft.UI.Dispatching.DispatcherQueuePriority.Low);
});
}
if (EnabledGitProperties != GitProperties.None && item is GitItem gitItem)
await LoadGitProperties(gitItem, cts);
}
}, cts.Token);
}
Expand All @@ -1249,6 +1245,60 @@ await SafetyExtensions.IgnoreExceptions(() =>
}
}

private async Task LoadGitProperties(GitItem gitItem, CancellationTokenSource cts)
{
var getStatus = EnabledGitProperties is GitProperties.All or GitProperties.Status && !gitItem.StatusPropertiesInitialized;
var getCommit = EnabledGitProperties is GitProperties.All or GitProperties.Commit && !gitItem.CommitPropertiesInitialized;

if (!getStatus && !getCommit)
return;

if (GitHelpers.IsRepositoryEx(gitItem.ItemPath, out var repoPath) &&
!string.IsNullOrEmpty(repoPath))
{
cts.Token.ThrowIfCancellationRequested();

if (getStatus)
gitItem.StatusPropertiesInitialized = true;

if (getCommit)
gitItem.CommitPropertiesInitialized = true;

await SafetyExtensions.IgnoreExceptions(() =>
{
return dispatcherQueue.EnqueueOrInvokeAsync(() =>
{
var repo = new Repository(repoPath);
GitItemModel gitItemModel = GitHelpers.GetGitInformationForItem(repo, gitItem.ItemPath, getStatus, getCommit);

if (getStatus)
{
gitItem.UnmergedGitStatusIcon = gitItemModel.Status switch
{
ChangeKind.Added => (Microsoft.UI.Xaml.Style)Microsoft.UI.Xaml.Application.Current.Resources["ColorIconGitAdded"],
ChangeKind.Deleted => (Microsoft.UI.Xaml.Style)Microsoft.UI.Xaml.Application.Current.Resources["ColorIconGitDeleted"],
ChangeKind.Modified => (Microsoft.UI.Xaml.Style)Microsoft.UI.Xaml.Application.Current.Resources["ColorIconGitModified"],
ChangeKind.Untracked => (Microsoft.UI.Xaml.Style)Microsoft.UI.Xaml.Application.Current.Resources["ColorIconGitUntracked"],
_ => null,
};
gitItem.UnmergedGitStatusName = gitItemModel.StatusHumanized;
}
if (getCommit)
{
gitItem.GitLastCommitDate = gitItemModel.LastCommit?.Author.When;
gitItem.GitLastCommitMessage = gitItemModel.LastCommit?.MessageShort;
gitItem.GitLastCommitAuthor = gitItemModel.LastCommit?.Author.Name;
gitItem.GitLastCommitSha = gitItemModel.LastCommit?.Sha.Substring(0, 7);
gitItem.GitLastCommitFullSha = gitItemModel.LastCommit?.Sha;
}

repo.Dispose();
},
Microsoft.UI.Dispatching.DispatcherQueuePriority.Low);
});
}
}

private async Task<ImageSource?> GetItemTypeGroupIcon(ListedItem item, BaseStorageFile? matchingStorageItem = null)
{
ImageSource? groupImage = null;
Expand Down Expand Up @@ -2459,4 +2509,12 @@ public enum ItemLoadStatus
/// </summary>
public string? Path { get; set; }
}

public enum GitProperties
{
None,
Status,
Commit,
All,
}
}
2 changes: 1 addition & 1 deletion src/Files.App/Services/Settings/FoldersSettingsService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -215,7 +215,7 @@ public bool ShowGitLastCommitMessageColumn

public bool ShowGitCommitAuthorColumn
{
get => Get(true);
get => Get(false);
set => Set(value);
}

Expand Down
48 changes: 28 additions & 20 deletions src/Files.App/Utils/Git/GitHelpers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -507,36 +507,44 @@ public static bool IsRepositoryEx(string path, out string repoRootPath)
return false;
}

public static GitItemModel GetGitInformationForItem(Repository repository, string path)
public static GitItemModel GetGitInformationForItem(Repository repository, string path, bool getStatus = true, bool getCommit = true)
{
var rootRepoPath = repository.Info.WorkingDirectory;
var relativePath = path.Substring(rootRepoPath.Length).Replace('\\', '/');

var commit = GetLastCommitForFile(repository, relativePath);
//var commit = repository.Commits.QueryBy(relativePath).FirstOrDefault()?.Commit; // Considers renames but slow

var changeKind = ChangeKind.Unmodified;
//foreach (TreeEntryChanges c in repository.Diff.Compare<TreeChanges>())
foreach (TreeEntryChanges c in repository.Diff.Compare<TreeChanges>(repository.Commits.FirstOrDefault()?.Tree, DiffTargets.Index | DiffTargets.WorkingDirectory))
Commit? commit = null;
if (getCommit)
{
if (c.Path.StartsWith(relativePath))
{
changeKind = c.Status;
break;
}
commit = GetLastCommitForFile(repository, relativePath);
//var commit = repository.Commits.QueryBy(relativePath).FirstOrDefault()?.Commit; // Considers renames but slow
}

ChangeKind? changeKind = null;
string? changeKindHumanized = null;
if (changeKind is not ChangeKind.Ignored)
if (getStatus)
{
changeKindHumanized = changeKind switch
changeKind = ChangeKind.Unmodified;
//foreach (TreeEntryChanges c in repository.Diff.Compare<TreeChanges>())
foreach (TreeEntryChanges c in repository.Diff.Compare<TreeChanges>(repository.Commits.FirstOrDefault()?.Tree, DiffTargets.Index | DiffTargets.WorkingDirectory))
{
ChangeKind.Added => "Added".GetLocalizedResource(),
ChangeKind.Deleted => "Deleted".GetLocalizedResource(),
ChangeKind.Modified => "Modified".GetLocalizedResource(),
ChangeKind.Untracked => "Untracked".GetLocalizedResource(),
_ => null,
};
if (c.Path.StartsWith(relativePath))
{
changeKind = c.Status;
break;
}
}

if (changeKind is not ChangeKind.Ignored)
{
changeKindHumanized = changeKind switch
{
ChangeKind.Added => "Added".GetLocalizedResource(),
ChangeKind.Deleted => "Deleted".GetLocalizedResource(),
ChangeKind.Modified => "Modified".GetLocalizedResource(),
ChangeKind.Untracked => "Untracked".GetLocalizedResource(),
_ => null,
};
}
}

var gitItemModel = new GitItemModel()
Expand Down
3 changes: 3 additions & 0 deletions src/Files.App/Views/LayoutModes/BaseLayout.cs
Original file line number Diff line number Diff line change
Expand Up @@ -473,6 +473,9 @@ protected override async void OnNavigatedTo(NavigationEventArgs eventArgs)

ItemContextMenuFlyout.Opening += ItemContextFlyout_Opening;
BaseContextMenuFlyout.Opening += BaseContextFlyout_Opening;

// Git properties are not loaded by default
ParentShellPageInstance.FilesystemViewModel.EnabledGitProperties = GitProperties.None;
}

public void SetSelectedItemsOnNavigation()
Expand Down
20 changes: 19 additions & 1 deletion src/Files.App/Views/LayoutModes/DetailsLayoutBrowser.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
// Licensed under the MIT License. See the LICENSE.

using CommunityToolkit.WinUI.UI;
using Files.App.Data.Commands;
using Files.App.UserControls.Selection;
using Microsoft.UI.Input;
using Microsoft.UI.Xaml;
Expand Down Expand Up @@ -124,6 +123,8 @@ protected override void OnNavigatedTo(NavigationEventArgs eventArgs)
ColumnsViewModel.GitLastCommitShaColumn = FolderSettings.ColumnsViewModel.GitLastCommitShaColumn;
}

ParentShellPageInstance.FilesystemViewModel.EnabledGitProperties = GetEnabledGitProperties(ColumnsViewModel);

currentIconSize = FolderSettings.GetIconSize();
FolderSettings.LayoutModeChangeRequested += FolderSettings_LayoutModeChangeRequested;
FolderSettings.GridViewSizeChangeRequested += FolderSettings_GridViewSizeChangeRequested;
Expand Down Expand Up @@ -559,6 +560,7 @@ private void GridSplitter_Loaded(object sender, RoutedEventArgs e)
private void ToggleMenuFlyoutItem_Click(object sender, RoutedEventArgs e)
{
FolderSettings.ColumnsViewModel = ColumnsViewModel;
ParentShellPageInstance.FilesystemViewModel.EnabledGitProperties = GetEnabledGitProperties(ColumnsViewModel);
}

private void GridSplitter_DoubleTapped(object sender, DoubleTappedRoutedEventArgs e)
Expand Down Expand Up @@ -883,5 +885,21 @@ private void FileList_LosingFocus(UIElement sender, LosingFocusEventArgs args)
if (args.NewFocusedElement == FileList)
args.TryCancel();
}

private static GitProperties GetEnabledGitProperties(ColumnsViewModel columnsViewModel)
{
var enableStatus = !columnsViewModel.GitStatusColumn.IsHidden && !columnsViewModel.GitStatusColumn.UserCollapsed;
var enableCommit = !columnsViewModel.GitLastCommitDateColumn.IsHidden && !columnsViewModel.GitLastCommitDateColumn.UserCollapsed
|| !columnsViewModel.GitLastCommitMessageColumn.IsHidden && !columnsViewModel.GitLastCommitMessageColumn.UserCollapsed
|| !columnsViewModel.GitCommitAuthorColumn.IsHidden && !columnsViewModel.GitCommitAuthorColumn.UserCollapsed
|| !columnsViewModel.GitLastCommitShaColumn.IsHidden && !columnsViewModel.GitLastCommitShaColumn.UserCollapsed;
return (enableStatus, enableCommit) switch
{
(true, true) => GitProperties.All,
(true, false) => GitProperties.Status,
(false, true) => GitProperties.Commit,
(false, false) => GitProperties.None
};
}
}
}

0 comments on commit c19ee12

Please sign in to comment.