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
30 changes: 30 additions & 0 deletions src/Commands/QueryHasLocalChanges.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System.Threading.Tasks;

namespace SourceGit.Commands
{
/// <summary>
/// Lightweight check used by repositories in inactive tabs: only tells whether there are local changes,
/// without parsing them, so the tab's dirty indicator stays up to date.
/// </summary>
public class QueryHasLocalChanges : Command
{
public QueryHasLocalChanges(string repo, bool includeUntracked)
{
WorkingDirectory = repo;
Context = repo;
RaiseError = false;
Args = includeUntracked
? "--no-optional-locks status -unormal --ignore-submodules=dirty --porcelain"
: "--no-optional-locks status -uno --ignore-submodules=dirty --porcelain";
}

public async Task<bool?> GetResultAsync()
{
var rs = await ReadToEndAsync().ConfigureAwait(false);
if (!rs.IsSuccess)
return null;

return !string.IsNullOrWhiteSpace(rs.StdOut);
}
}
}
1 change: 1 addition & 0 deletions src/Models/IRepository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public interface IRepository
void RefreshCommits();
void RefreshSubmodules();
void RefreshWorkingCopyChanges();
void RefreshDirtyState();
void RefreshStashes();
}
}
29 changes: 29 additions & 0 deletions src/Models/Watcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,15 @@ public IDisposable Lock()
return new LockContext(this);
}

/// <summary>
/// Inactive repositories (not shown in the active tab) still collect changes, but do not refresh
/// anything until they become active again.
/// </summary>
public void SetActive(bool active)
{
_isActive = active;
}

public void MarkBranchUpdated()
{
Interlocked.Exchange(ref _updateBranch, 0);
Expand Down Expand Up @@ -144,6 +153,12 @@ private void Tick(object sender)
if (Interlocked.Read(ref _lockCount) > 0)
return;

if (!_isActive)
{
CheckDirtyStateWhileInactive();
return;
}

var now = DateTime.Now.ToFileTime();
var refreshCommits = false;
var refreshSubmodules = false;
Expand Down Expand Up @@ -228,6 +243,18 @@ private void Tick(object sender)
_repo.RefreshCommits();
}

private void CheckDirtyStateWhileInactive()
{
// Pending refreshes are kept as-is so they run once the repository becomes active again. Only update
// the dirty indicator of the tab, once per batch of changes.
var pending = Math.Max(Interlocked.Read(ref _updateWC), Interlocked.Read(ref _updateBranch));
if (pending <= 0 || pending == _lastDirtyStateCheck || DateTime.Now.ToFileTime() <= pending)
return;

_lastDirtyStateCheck = pending;
_repo.RefreshDirtyState();
}

private void OnRepositoryChanged(object o, FileSystemEventArgs e)
{
if (string.IsNullOrEmpty(e.Name) || e.Name.Equals(".git", StringComparison.Ordinal))
Expand Down Expand Up @@ -377,6 +404,8 @@ private bool IsInSubmodule(string folder)
private readonly string _root;
private List<FileSystemWatcher> _watchers;
private Timer _timer;
private volatile bool _isActive = true;
private long _lastDirtyStateCheck = 0;

private long _lockCount;
private long _updateWC;
Expand Down
9 changes: 9 additions & 0 deletions src/ViewModels/Launcher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,17 @@ public LauncherPage ActivePage
get => _activePage;
set
{
var old = _activePage;
if (SetProperty(ref _activePage, value))
{
if (old is { Data: Repository oldRepo })
oldRepo.SetActive(false);

if (value is { Data: Repository newRepo })
newRepo.SetActive(true);

PostActivePageChanged();
}
}
}

Expand Down
22 changes: 22 additions & 0 deletions src/ViewModels/Repository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -504,6 +504,11 @@ public void Close()
_autoFetchTimer.Dispose();
}

public void SetActive(bool active)
{
_watcher?.SetActive(active);
}

public void SendNotification(string message, bool isError = false)
{
Models.Notification.Send(FullPath, message, isError);
Expand Down Expand Up @@ -1368,6 +1373,23 @@ public void RefreshSubmodules()
});
}

public void RefreshDirtyState()
{
if (IsBare)
return;

var includeUntracked = _uiStates.IncludeUntrackedInLocalChanges;
Task.Run(async () =>
{
var hasChanges = await new Commands.QueryHasLocalChanges(FullPath, includeUntracked)
.GetResultAsync()
.ConfigureAwait(false);

if (hasChanges is { } value)
Dispatcher.UIThread.Post(() => GetOwnerPage()?.ChangeDirtyState(Models.DirtyState.HasLocalChanges, !value));
});
}

public void RefreshWorkingCopyChanges()
{
if (IsBare)
Expand Down