Skip to content

Commit bd47a66

Browse files
author
Pascal van Buijtene
committed
Reduce memory usage by caching git objects
1 parent 9939c56 commit bd47a66

File tree

7 files changed

+58
-21
lines changed

7 files changed

+58
-21
lines changed

src/GitVersion.LibGit2Sharp/Git/Branch.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,16 +10,18 @@ internal sealed class Branch : IBranch
1010

1111
private readonly LibGit2Sharp.Branch innerBranch;
1212

13-
internal Branch(LibGit2Sharp.Branch branch, LibGit2Sharp.Diff diff)
13+
internal Branch(LibGit2Sharp.Branch branch, LibGit2Sharp.Diff diff, GitRepository repo)
1414
{
15+
diff.NotNull();
16+
repo.NotNull();
1517
this.innerBranch = branch.NotNull();
1618
Name = new(branch.CanonicalName);
1719

1820
var commit = this.innerBranch.Tip;
19-
Tip = commit is null ? null : new Commit(commit, diff);
21+
Tip = commit is null ? null : repo.GetOrCreate(commit, diff);
2022

2123
var commits = this.innerBranch.Commits;
22-
Commits = new CommitCollection(commits, diff);
24+
Commits = new CommitCollection(commits, diff, repo);
2325
}
2426

2527
public ReferenceName Name { get; }

src/GitVersion.LibGit2Sharp/Git/BranchCollection.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ internal sealed class BranchCollection : IBranchCollection
88
private readonly LibGit2Sharp.BranchCollection innerCollection;
99
private readonly Lazy<IReadOnlyCollection<IBranch>> branches;
1010
private readonly Diff diff;
11+
private readonly GitRepository repo;
1112

12-
internal BranchCollection(LibGit2Sharp.BranchCollection collection, Diff diff)
13+
internal BranchCollection(LibGit2Sharp.BranchCollection collection, Diff diff, GitRepository repo)
1314
{
1415
this.innerCollection = collection.NotNull();
15-
this.branches = new Lazy<IReadOnlyCollection<IBranch>>(() => [.. this.innerCollection.Select(branch => new Branch(branch, diff))]);
16+
this.branches = new Lazy<IReadOnlyCollection<IBranch>>(() => [.. this.innerCollection.Select(branch => repo.GetOrCreate(branch, diff))]);
1617
this.diff = diff.NotNull();
18+
this.repo = repo.NotNull();
1719
}
1820

1921
public IEnumerator<IBranch> GetEnumerator()
@@ -27,7 +29,7 @@ public IBranch? this[string name]
2729
{
2830
name = name.NotNull();
2931
var branch = this.innerCollection[name];
30-
return branch is null ? null : new Branch(branch, this.diff);
32+
return branch is null ? null : this.repo.GetOrCreate(branch, this.diff);
3133
}
3234
}
3335

src/GitVersion.LibGit2Sharp/Git/Commit.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,12 @@ internal sealed class Commit : GitObject, ICommit
1414
private readonly LibGit2Sharp.Commit innerCommit;
1515
private readonly LibGit2Sharp.Diff repoDiff;
1616

17-
internal Commit(LibGit2Sharp.Commit innerCommit, LibGit2Sharp.Diff repoDiff) : base(innerCommit)
17+
internal Commit(LibGit2Sharp.Commit innerCommit, LibGit2Sharp.Diff repoDiff, GitRepository repo) : base(innerCommit)
1818
{
19+
repoDiff.NotNull();
20+
repo.NotNull();
1921
this.innerCommit = innerCommit.NotNull();
20-
this.parentsLazy = new(() => innerCommit.Parents.Select(parent => new Commit(parent, repoDiff)).ToList());
22+
this.parentsLazy = new(() => innerCommit.Parents.Select(parent => repo.GetOrCreate(parent, repoDiff)).ToList());
2123
When = innerCommit.Committer.When;
2224
this.repoDiff = repoDiff;
2325
}

src/GitVersion.LibGit2Sharp/Git/CommitCollection.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,14 @@ internal sealed class CommitCollection : ICommitCollection
88
private readonly ICommitLog innerCollection;
99
private readonly Lazy<IReadOnlyCollection<ICommit>> commits;
1010
private readonly Diff diff;
11+
private readonly GitRepository repo;
1112

12-
internal CommitCollection(ICommitLog collection, Diff diff)
13+
internal CommitCollection(ICommitLog collection, Diff diff, GitRepository repo)
1314
{
1415
this.innerCollection = collection.NotNull();
15-
this.commits = new Lazy<IReadOnlyCollection<ICommit>>(() => [.. this.innerCollection.Select(commit => new Commit(commit, diff))]);
16+
this.commits = new Lazy<IReadOnlyCollection<ICommit>>(() => [.. this.innerCollection.Select(commit => repo.GetOrCreate(commit, diff))]);
1617
this.diff = diff.NotNull();
18+
this.repo = repo.NotNull();
1719
}
1820

1921
public IEnumerator<ICommit> GetEnumerator()
@@ -36,7 +38,7 @@ public IEnumerable<ICommit> QueryBy(CommitFilter commitFilter)
3638
SortBy = (LibGit2Sharp.CommitSortStrategies)commitFilter.SortBy
3739
};
3840
var commitLog = ((IQueryableCommitLog)this.innerCollection).QueryBy(filter);
39-
return new CommitCollection(commitLog, this.diff);
41+
return new CommitCollection(commitLog, this.diff, this.repo);
4042

4143
static object? GetReacheableFrom(object? item) =>
4244
item switch

src/GitVersion.LibGit2Sharp/Git/GitRepository.cs

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
using System.Collections.Concurrent;
12
using GitVersion.Extensions;
23
using GitVersion.Helpers;
34
using LibGit2Sharp;
@@ -8,6 +9,10 @@ internal sealed partial class GitRepository
89
{
910
private Lazy<IRepository>? repositoryLazy;
1011

12+
private readonly ConcurrentDictionary<string, Branch> cachedBranches = new();
13+
private readonly ConcurrentDictionary<string, Commit> cachedCommits = new();
14+
private readonly ConcurrentDictionary<string, Tag> cachedTags = new();
15+
1116
private IRepository RepositoryInstance
1217
{
1318
get
@@ -20,12 +25,12 @@ private IRepository RepositoryInstance
2025
public string WorkingDirectory => RepositoryInstance.Info.WorkingDirectory;
2126
public bool IsHeadDetached => RepositoryInstance.Info.IsHeadDetached;
2227
public bool IsShallow => RepositoryInstance.Info.IsShallow;
23-
public IBranch Head => new Branch(RepositoryInstance.Head, RepositoryInstance.Diff);
28+
public IBranch Head => GetOrCreate(RepositoryInstance.Head, RepositoryInstance.Diff);
2429

25-
public ITagCollection Tags => new TagCollection(RepositoryInstance.Tags, RepositoryInstance.Diff);
30+
public ITagCollection Tags => new TagCollection(RepositoryInstance.Tags, RepositoryInstance.Diff, this);
2631
public IReferenceCollection Refs => new ReferenceCollection(RepositoryInstance.Refs);
27-
public IBranchCollection Branches => new BranchCollection(RepositoryInstance.Branches, RepositoryInstance.Diff);
28-
public ICommitCollection Commits => new CommitCollection(RepositoryInstance.Commits, RepositoryInstance.Diff);
32+
public IBranchCollection Branches => new BranchCollection(RepositoryInstance.Branches, RepositoryInstance.Diff, this);
33+
public ICommitCollection Commits => new CommitCollection(RepositoryInstance.Commits, RepositoryInstance.Diff, this);
2934
public IRemoteCollection Remotes => new RemoteCollection(RepositoryInstance.Network.Remotes);
3035

3136
public void DiscoverRepository(string? gitDirectory)
@@ -48,7 +53,7 @@ public void DiscoverRepository(string? gitDirectory)
4853
var first = (Commit)commit;
4954
var second = (Commit)otherCommit;
5055
var mergeBase = RepositoryInstance.ObjectDatabase.FindMergeBase(first, second);
51-
return mergeBase == null ? null : new Commit(mergeBase, RepositoryInstance.Diff);
56+
return mergeBase == null ? null : GetOrCreate(mergeBase, RepositoryInstance.Diff);
5257
});
5358
}
5459

@@ -58,6 +63,26 @@ public int UncommittedChangesCount()
5863
return retryAction.Execute(GetUncommittedChangesCountInternal);
5964
}
6065

66+
public Branch GetOrCreate(LibGit2Sharp.Branch innerBranch, Diff repoDiff)
67+
{
68+
if (innerBranch.Tip is null)
69+
{
70+
return new Branch(innerBranch, repoDiff, this);
71+
}
72+
73+
var cacheKey = $"{innerBranch.CanonicalName}|{innerBranch.Tip.Sha}|{innerBranch.RemoteName}";
74+
return cachedBranches.GetOrAdd(cacheKey, new Branch(innerBranch, repoDiff, this));
75+
}
76+
77+
public Commit GetOrCreate(LibGit2Sharp.Commit innerCommit, Diff repoDiff) =>
78+
cachedCommits.GetOrAdd(innerCommit.Sha, new Commit(innerCommit, repoDiff, this));
79+
80+
public Tag GetOrCreate(LibGit2Sharp.Tag innerTag, Diff repoDiff)
81+
{
82+
var cacheKey = $"{innerTag.CanonicalName}|{innerTag.Target.Sha}";
83+
return cachedTags.GetOrAdd(cacheKey, new Tag(innerTag, repoDiff, this));
84+
}
85+
6186
public void Dispose()
6287
{
6388
if (this.repositoryLazy is { IsValueCreated: true }) RepositoryInstance.Dispose();

src/GitVersion.LibGit2Sharp/Git/Tag.cs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,14 @@ internal sealed class Tag : ITag
1111
private readonly LibGit2Sharp.Tag innerTag;
1212
private readonly Diff diff;
1313
private readonly Lazy<ICommit?> commitLazy;
14+
private readonly GitRepository repo;
1415

15-
internal Tag(LibGit2Sharp.Tag tag, Diff diff)
16+
internal Tag(LibGit2Sharp.Tag tag, Diff diff, GitRepository repo)
1617
{
1718
this.innerTag = tag.NotNull();
1819
this.commitLazy = new(PeeledTargetCommit);
1920
this.diff = diff.NotNull();
21+
this.repo = repo.NotNull();
2022
Name = new(this.innerTag.CanonicalName);
2123
}
2224

@@ -35,7 +37,7 @@ internal Tag(LibGit2Sharp.Tag tag, Diff diff)
3537
target = annotation.Target;
3638
}
3739

38-
return target is LibGit2Sharp.Commit commit ? new Commit(commit, this.diff) : null;
40+
return target is LibGit2Sharp.Commit commit ? this.repo.GetOrCreate(commit, this.diff) : null;
3941
}
4042

4143
public override bool Equals(object? obj) => Equals(obj as ITag);

src/GitVersion.LibGit2Sharp/Git/TagCollection.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,12 @@ internal sealed class TagCollection : ITagCollection
66
{
77
private readonly Lazy<IReadOnlyCollection<ITag>> tags;
88

9-
internal TagCollection(LibGit2Sharp.TagCollection collection, LibGit2Sharp.Diff diff)
9+
internal TagCollection(LibGit2Sharp.TagCollection collection, LibGit2Sharp.Diff diff, GitRepository repo)
1010
{
11-
collection = collection.NotNull();
12-
this.tags = new Lazy<IReadOnlyCollection<ITag>>(() => [.. collection.Select(tag => new Tag(tag, diff))]);
11+
collection.NotNull();
12+
diff.NotNull();
13+
repo.NotNull();
14+
this.tags = new Lazy<IReadOnlyCollection<ITag>>(() => [.. collection.Select(tag => repo.GetOrCreate(tag, diff))]);
1315
}
1416

1517
public IEnumerator<ITag> GetEnumerator()

0 commit comments

Comments
 (0)