Skip to content

Commit cd80a55

Browse files
committed
refactor: file-system watcher
- Use `System.Threading.Interlocked` to read/write data - Use `SourceGit.Models.Watcher.LockContext` to release watcher's lock - New way to detect file changes in submodules - Manually update stashes after `git stash` command complete (#1760) Signed-off-by: leo <[email protected]>
1 parent 2c96a13 commit cd80a55

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

62 files changed

+212
-249
lines changed

src/Models/Watcher.cs

Lines changed: 114 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,26 @@ namespace SourceGit.Models
77
{
88
public class Watcher : IDisposable
99
{
10+
public class LockContext : IDisposable
11+
{
12+
public LockContext(Watcher target)
13+
{
14+
_target = target;
15+
Interlocked.Increment(ref _target._lockCount);
16+
}
17+
18+
public void Dispose()
19+
{
20+
Interlocked.Decrement(ref _target._lockCount);
21+
}
22+
23+
private Watcher _target;
24+
}
25+
1026
public Watcher(IRepository repo, string fullpath, string gitDir)
1127
{
1228
_repo = repo;
29+
_root = new DirectoryInfo(fullpath).FullName;
1330

1431
var testGitDir = new DirectoryInfo(Path.Combine(fullpath, ".git")).FullName;
1532
var desiredDir = new DirectoryInfo(gitDir).FullName;
@@ -59,42 +76,29 @@ public Watcher(IRepository repo, string fullpath, string gitDir)
5976
_timer = new Timer(Tick, null, 100, 100);
6077
}
6178

62-
public void SetEnabled(bool enabled)
63-
{
64-
if (enabled)
65-
{
66-
if (_lockCount > 0)
67-
_lockCount--;
68-
}
69-
else
70-
{
71-
_lockCount++;
72-
}
73-
}
74-
75-
public void SetSubmodules(List<Submodule> submodules)
79+
public IDisposable Lock()
7680
{
77-
lock (_lockSubmodule)
78-
{
79-
_submodules.Clear();
80-
foreach (var submodule in submodules)
81-
_submodules.Add(submodule.Path);
82-
}
81+
return new LockContext(this);
8382
}
8483

8584
public void MarkBranchUpdated()
8685
{
87-
_updateBranch = 0;
86+
Interlocked.Exchange(ref _updateBranch, 0);
8887
}
8988

9089
public void MarkTagUpdated()
9190
{
92-
_updateTags = 0;
91+
Interlocked.Exchange(ref _updateTags, 0);
9392
}
9493

9594
public void MarkWorkingCopyUpdated()
9695
{
97-
_updateWC = 0;
96+
Interlocked.Exchange(ref _updateWC, 0);
97+
}
98+
99+
public void MarkStashUpdated()
100+
{
101+
Interlocked.Exchange(ref _updateStashes, 0);
98102
}
99103

100104
public void Dispose()
@@ -112,57 +116,81 @@ public void Dispose()
112116

113117
private void Tick(object sender)
114118
{
115-
if (_lockCount > 0)
119+
if (Interlocked.Read(ref _lockCount) > 0)
116120
return;
117121

118122
var now = DateTime.Now.ToFileTime();
119-
if (_updateBranch > 0 && now > _updateBranch)
120-
{
121-
_updateBranch = 0;
122-
_updateWC = 0;
123+
var refreshCommits = false;
124+
var refreshSubmodules = false;
123125

124-
if (_updateTags > 0)
126+
var oldUpdateBranch = Interlocked.Exchange(ref _updateBranch, -1);
127+
if (oldUpdateBranch > 0)
128+
{
129+
if (now > oldUpdateBranch)
125130
{
126-
_updateTags = 0;
127-
_repo.RefreshTags();
128-
}
131+
refreshCommits = true;
132+
refreshSubmodules = _repo.MayHaveSubmodules();
129133

130-
if (_updateSubmodules > 0 || _repo.MayHaveSubmodules())
134+
_repo.RefreshBranches();
135+
_repo.RefreshWorktrees();
136+
}
137+
else
131138
{
132-
_updateSubmodules = 0;
133-
_repo.RefreshSubmodules();
139+
Interlocked.CompareExchange(ref _updateBranch, oldUpdateBranch, -1);
134140
}
135-
136-
_repo.RefreshBranches();
137-
_repo.RefreshCommits();
138-
_repo.RefreshWorkingCopyChanges();
139-
_repo.RefreshWorktrees();
140141
}
141142

142-
if (_updateWC > 0 && now > _updateWC)
143+
var oldUpdateWC = Interlocked.Exchange(ref _updateWC, -1);
144+
if (oldUpdateWC > 0)
143145
{
144-
_updateWC = 0;
145-
_repo.RefreshWorkingCopyChanges();
146+
if (now > oldUpdateWC)
147+
_repo.RefreshWorkingCopyChanges();
148+
else
149+
Interlocked.CompareExchange(ref _updateWC, oldUpdateWC, -1);
146150
}
147151

148-
if (_updateSubmodules > 0 && now > _updateSubmodules)
152+
if (refreshSubmodules)
149153
{
150-
_updateSubmodules = 0;
154+
Interlocked.Exchange(ref _updateSubmodules, -1);
151155
_repo.RefreshSubmodules();
152156
}
157+
else
158+
{
159+
var oldUpdateSubmodule = Interlocked.Exchange(ref _updateSubmodules, -1);
160+
if (oldUpdateSubmodule > 0)
161+
{
162+
if (now > oldUpdateSubmodule)
163+
_repo.RefreshSubmodules();
164+
else
165+
Interlocked.CompareExchange(ref _updateSubmodules, oldUpdateSubmodule, -1);
166+
}
167+
}
153168

154-
if (_updateStashes > 0 && now > _updateStashes)
169+
var oldUpdateStashes = Interlocked.Exchange(ref _updateStashes, -1);
170+
if (oldUpdateStashes > 0)
155171
{
156-
_updateStashes = 0;
157-
_repo.RefreshStashes();
172+
if (now > oldUpdateStashes)
173+
_repo.RefreshStashes();
174+
else
175+
Interlocked.CompareExchange(ref _updateStashes, oldUpdateStashes, -1);
158176
}
159177

160-
if (_updateTags > 0 && now > _updateTags)
178+
var oldUpdateTags = Interlocked.Exchange(ref _updateTags, -1);
179+
if (oldUpdateTags > 0)
161180
{
162-
_updateTags = 0;
163-
_repo.RefreshTags();
164-
_repo.RefreshCommits();
181+
if (now > oldUpdateTags)
182+
{
183+
refreshCommits = true;
184+
_repo.RefreshTags();
185+
}
186+
else
187+
{
188+
Interlocked.CompareExchange(ref _updateTags, oldUpdateTags, -1);
189+
}
165190
}
191+
192+
if (refreshCommits)
193+
_repo.RefreshCommits();
166194
}
167195

168196
private void OnRepositoryChanged(object o, FileSystemEventArgs e)
@@ -177,7 +205,7 @@ private void OnRepositoryChanged(object o, FileSystemEventArgs e)
177205
if (name.StartsWith(".git/", StringComparison.Ordinal))
178206
HandleGitDirFileChanged(name.Substring(5));
179207
else
180-
HandleWorkingCopyFileChanged(name);
208+
HandleWorkingCopyFileChanged(name, e.FullPath);
181209
}
182210

183211
private void OnGitDirChanged(object o, FileSystemEventArgs e)
@@ -200,7 +228,7 @@ private void OnWorkingCopyChanged(object o, FileSystemEventArgs e)
200228
name.EndsWith("/.git", StringComparison.Ordinal))
201229
return;
202230

203-
HandleWorkingCopyFileChanged(name);
231+
HandleWorkingCopyFileChanged(name, e.FullPath);
204232
}
205233

206234
private void HandleGitDirFileChanged(string name)
@@ -215,76 +243,84 @@ private void HandleGitDirFileChanged(string name)
215243
if (name.EndsWith("/HEAD", StringComparison.Ordinal) ||
216244
name.EndsWith("/ORIG_HEAD", StringComparison.Ordinal))
217245
{
218-
_updateSubmodules = DateTime.Now.AddSeconds(1).ToFileTime();
219-
_updateWC = DateTime.Now.AddSeconds(1).ToFileTime();
246+
var desired = DateTime.Now.AddSeconds(1).ToFileTime();
247+
Interlocked.Exchange(ref _updateSubmodules, desired);
248+
Interlocked.Exchange(ref _updateWC, desired);
220249
}
221250
}
222251
else if (name.Equals("MERGE_HEAD", StringComparison.Ordinal) ||
223252
name.Equals("AUTO_MERGE", StringComparison.Ordinal))
224253
{
225254
if (_repo.MayHaveSubmodules())
226-
_updateSubmodules = DateTime.Now.AddSeconds(1).ToFileTime();
255+
Interlocked.Exchange(ref _updateSubmodules, DateTime.Now.AddSeconds(1).ToFileTime());
227256
}
228257
else if (name.StartsWith("refs/tags", StringComparison.Ordinal))
229258
{
230-
_updateTags = DateTime.Now.AddSeconds(.5).ToFileTime();
259+
Interlocked.Exchange(ref _updateTags, DateTime.Now.AddSeconds(.5).ToFileTime());
231260
}
232261
else if (name.StartsWith("refs/stash", StringComparison.Ordinal))
233262
{
234-
_updateStashes = DateTime.Now.AddSeconds(.5).ToFileTime();
263+
Interlocked.Exchange(ref _updateStashes, DateTime.Now.AddSeconds(.5).ToFileTime());
235264
}
236265
else if (name.Equals("HEAD", StringComparison.Ordinal) ||
237266
name.Equals("BISECT_START", StringComparison.Ordinal) ||
238267
name.StartsWith("refs/heads/", StringComparison.Ordinal) ||
239268
name.StartsWith("refs/remotes/", StringComparison.Ordinal) ||
240269
(name.StartsWith("worktrees/", StringComparison.Ordinal) && name.EndsWith("/HEAD", StringComparison.Ordinal)))
241270
{
242-
_updateBranch = DateTime.Now.AddSeconds(.5).ToFileTime();
271+
Interlocked.Exchange(ref _updateBranch, DateTime.Now.AddSeconds(.5).ToFileTime());
243272
}
244273
else if (name.StartsWith("objects/", StringComparison.Ordinal) || name.Equals("index", StringComparison.Ordinal))
245274
{
246-
_updateWC = DateTime.Now.AddSeconds(1).ToFileTime();
275+
Interlocked.Exchange(ref _updateWC, DateTime.Now.AddSeconds(1).ToFileTime());
247276
}
248277
}
249278

250-
private void HandleWorkingCopyFileChanged(string name)
279+
private void HandleWorkingCopyFileChanged(string name, string fullpath)
251280
{
252281
if (name.StartsWith(".vs/", StringComparison.Ordinal))
253282
return;
254283

255284
if (name.Equals(".gitmodules", StringComparison.Ordinal))
256285
{
257-
_updateSubmodules = DateTime.Now.AddSeconds(1).ToFileTime();
258-
_updateWC = DateTime.Now.AddSeconds(1).ToFileTime();
286+
var desired = DateTime.Now.AddSeconds(1).ToFileTime();
287+
Interlocked.Exchange(ref _updateSubmodules, desired);
288+
Interlocked.Exchange(ref _updateWC, desired);
259289
return;
260290
}
261291

262-
lock (_lockSubmodule)
292+
var dir = Directory.Exists(fullpath) ? fullpath : Path.GetDirectoryName(fullpath);
293+
if (IsInSubmodule(dir))
263294
{
264-
foreach (var submodule in _submodules)
265-
{
266-
if (name.StartsWith(submodule, StringComparison.Ordinal))
267-
{
268-
_updateSubmodules = DateTime.Now.AddSeconds(1).ToFileTime();
269-
return;
270-
}
271-
}
295+
Interlocked.Exchange(ref _updateSubmodules, DateTime.Now.AddSeconds(1).ToFileTime());
296+
return;
272297
}
273298

274-
_updateWC = DateTime.Now.AddSeconds(1).ToFileTime();
299+
Interlocked.Exchange(ref _updateWC, DateTime.Now.AddSeconds(1).ToFileTime());
300+
}
301+
302+
private bool IsInSubmodule(string folder)
303+
{
304+
if (File.Exists($"{folder}/.git"))
305+
return true;
306+
307+
var parent = Path.GetDirectoryName(folder);
308+
if (parent == null || parent.Equals(_root, StringComparison.Ordinal))
309+
return false;
310+
311+
return IsInSubmodule(parent);
275312
}
276313

277314
private readonly IRepository _repo = null;
315+
private readonly string _root = null;
278316
private List<FileSystemWatcher> _watchers = [];
279317
private Timer _timer = null;
280-
private int _lockCount = 0;
318+
319+
private long _lockCount = 0;
281320
private long _updateWC = 0;
282321
private long _updateBranch = 0;
283322
private long _updateSubmodules = 0;
284323
private long _updateStashes = 0;
285324
private long _updateTags = 0;
286-
287-
private readonly Lock _lockSubmodule = new();
288-
private List<string> _submodules = new List<string>();
289325
}
290326
}

src/ViewModels/AddRemote.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ public static ValidationResult ValidateSSHKey(string sshkey, ValidationContext c
8989

9090
public override async Task<bool> Sure()
9191
{
92-
_repo.SetWatcherEnabled(false);
92+
using var lockWatcher = _repo.LockWatcher();
9393
ProgressDescription = "Adding remote ...";
9494

9595
var log = _repo.CreateLog("Add Remote");
@@ -114,7 +114,6 @@ public override async Task<bool> Sure()
114114

115115
_repo.MarkFetched();
116116
_repo.MarkBranchesDirtyManually();
117-
_repo.SetWatcherEnabled(true);
118117
return succ;
119118
}
120119

src/ViewModels/AddSubmodule.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,7 @@ public static ValidationResult ValidateURL(string url, ValidationContext ctx)
4242

4343
public override async Task<bool> Sure()
4444
{
45-
_repo.SetWatcherEnabled(false);
45+
using var lockWatcher = _repo.LockWatcher();
4646
ProgressDescription = "Adding submodule...";
4747

4848
var log = _repo.CreateLog("Add Submodule");
@@ -64,7 +64,6 @@ public override async Task<bool> Sure()
6464
.AddAsync(_url, relativePath, Recursive);
6565

6666
log.Complete();
67-
_repo.SetWatcherEnabled(true);
6867
return succ;
6968
}
7069

src/ViewModels/AddToIgnore.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ public AddToIgnore(Repository repo, string pattern)
2929

3030
public override async Task<bool> Sure()
3131
{
32-
_repo.SetWatcherEnabled(false);
32+
using var lockWatcher = _repo.LockWatcher();
3333
ProgressDescription = "Adding Ignored File(s) ...";
3434

3535
var file = StorageFile.GetFullPath(_repo.FullPath, _repo.GitDir);
@@ -47,7 +47,6 @@ public override async Task<bool> Sure()
4747
}
4848

4949
_repo.MarkWorkingCopyDirtyManually();
50-
_repo.SetWatcherEnabled(true);
5150
return true;
5251
}
5352

src/ViewModels/AddWorktree.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ public static ValidationResult ValidateWorktreePath(string path, ValidationConte
106106

107107
public override async Task<bool> Sure()
108108
{
109-
_repo.SetWatcherEnabled(false);
109+
using var lockWatcher = _repo.LockWatcher();
110110
ProgressDescription = "Adding worktree ...";
111111

112112
var branchName = _selectedBranch;
@@ -120,7 +120,6 @@ public override async Task<bool> Sure()
120120
.AddAsync(_path, branchName, _createNewBranch, tracking);
121121

122122
log.Complete();
123-
_repo.SetWatcherEnabled(true);
124123
return succ;
125124
}
126125

0 commit comments

Comments
 (0)