Skip to content

Commit c9b4e25

Browse files
committed
Add and use IExecutable and IProcess abstractions
This change introduces an interface for the use and substitution of executables and the processes they create. GitModule accepts an instance of IExecutable that represents git.exe, allowing injection of MockExecutable for testing purposes. See GitModuleTest. ExecutableExtensions provide convenient access to the most common operations required from executables. Notably all executions that read process output now do so using async/await, rather than spawning one-off threads to read output streams. This gives better utilisation of the thread pool. The lightweight type ArgumentString is used as a union type between string and ArgumentBuilder (and potential future types) for passing arguments. It has implicit conversions to string, and from both string and ArgumentBuilder. All process invocations are now tracked in CommandLog. CommandCache has been converted from a static class into an instance class so that it may be used for any execution that returns output, whether for git.exe or other processes. It has been changed to reuse the MruCache class that was recently added for in-memory avatar caching. GitCommandHelpers.VersionInUse has been moved to GitVersion.Current where it has a clearer relationship.
1 parent e494e93 commit c9b4e25

File tree

91 files changed

+2779
-2182
lines changed

Some content is hidden

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

91 files changed

+2779
-2182
lines changed

GitCommands/BoolExtensions.cs

-16
This file was deleted.

GitCommands/CommitHelper.cs

+1-2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
1-
using System;
2-
using System.IO;
1+
using System.IO;
32

43
namespace GitCommands
54
{

GitCommands/Git/CommandCache.cs

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using GitExtUtils;
4+
using JetBrains.Annotations;
5+
6+
namespace GitCommands
7+
{
8+
/// <summary>
9+
/// Caches a set of command output/error bytes, for the purpose of avoiding repeated
10+
/// process invocations when the results are known to be identical across operations.
11+
/// </summary>
12+
/// <remarks>
13+
/// <para>
14+
/// A bounded number of commands are cached. When that number is exceeded, least
15+
/// recently used commands are removed.
16+
/// </para>
17+
/// <para>
18+
/// Not all commands may be cached.
19+
/// <list type="bullet">
20+
/// <item>Refs are moveable and cannot be cached.</item>
21+
/// <item>Commit notes can change, and cannot be cached.</item>
22+
/// <item>Commit data, queried by commit ID, will not change over time and can be cached.</item>
23+
/// <item>Diffs between commit IDs can be cached.</item>
24+
/// </list>
25+
/// </para>
26+
/// </remarks>
27+
public sealed class CommandCache
28+
{
29+
/// <summary>
30+
/// Raised whenever the contents of the cache is changed.
31+
/// </summary>
32+
public event EventHandler Changed;
33+
34+
private readonly MruCache<string, (byte[] output, byte[] error)> _cache;
35+
36+
/// <summary>
37+
/// Initialises a new instance of <see cref="CommandCache"/> with specified <paramref name="capacity"/>.
38+
/// </summary>
39+
/// <param name="capacity">The maximum number of commands to cache.</param>
40+
public CommandCache(int capacity = 40)
41+
{
42+
_cache = new MruCache<string, (byte[] output, byte[] error)>(capacity: capacity);
43+
}
44+
45+
/// <summary>
46+
/// Gets the list of commands stored within the cache.
47+
/// </summary>
48+
public IReadOnlyList<string> GetCachedCommands()
49+
{
50+
lock (_cache)
51+
{
52+
return _cache.Keys;
53+
}
54+
}
55+
56+
/// <summary>
57+
/// Looks up a command's output in the cache.
58+
/// </summary>
59+
/// <param name="cmd">The command to look for.</param>
60+
/// <param name="output">Stored output bytes of the command, if found.</param>
61+
/// <param name="error">Stored error bytes of the command, if found.</param>
62+
/// <returns><c>true</c> if the command was found, otherwise <c>false</c>.</returns>
63+
[ContractAnnotation("=>false,output:null,error:null")]
64+
[ContractAnnotation("=>true,output:notnull,error:notnull")]
65+
public bool TryGet([NotNull] string cmd, out byte[] output, out byte[] error)
66+
{
67+
// Never cache empty commands
68+
if (!string.IsNullOrEmpty(cmd))
69+
{
70+
lock (_cache)
71+
{
72+
if (_cache.TryGetValue(cmd, out var item))
73+
{
74+
(output, error) = item;
75+
return true;
76+
}
77+
}
78+
}
79+
80+
output = null;
81+
error = null;
82+
return false;
83+
}
84+
85+
/// <summary>
86+
/// Adds output and error bytes for a command.
87+
/// </summary>
88+
/// <param name="cmd">The command to add to the cache.</param>
89+
/// <param name="output">Output bytes of the command.</param>
90+
/// <param name="error">Error bytes of the command.</param>
91+
public void Add([NotNull] string cmd, byte[] output, byte[] error)
92+
{
93+
// Never cache empty commands
94+
if (string.IsNullOrEmpty(cmd))
95+
{
96+
return;
97+
}
98+
99+
lock (_cache)
100+
{
101+
_cache.Add(cmd, (output, error));
102+
}
103+
104+
Changed?.Invoke(this, EventArgs.Empty);
105+
}
106+
}
107+
}

0 commit comments

Comments
 (0)