Skip to content

Commit 31fc1ed

Browse files
committed
Merge pull request #2 from JakeGinnivan/cache-merge-base
Additional refactoring for caching.
2 parents 1aa99a1 + 179c849 commit 31fc1ed

12 files changed

+281
-233
lines changed

src/GitVersionCore.Tests/IntegrationTests/FeatureBranchScenarios.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -514,9 +514,8 @@ public void PickUpVersionFromMasterMarkedWithIsTracksReleaseBranches()
514514
fixture.AssertFullSemver(config, "0.10.1-pre.1+1");
515515

516516
// create a feature branch from master and verify the version
517-
// TODO this will pass once default becomes inherit
518-
//fixture.BranchTo("MyFeatureD");
519-
//fixture.AssertFullSemver(config, "0.10.1-MyFeatureD.1+1");
517+
fixture.BranchTo("MyFeatureD");
518+
fixture.AssertFullSemver(config, "0.10.1-MyFeatureD.1+1");
520519
}
521520
}
522521
}

src/GitVersionCore/BranchConfigurationCalculator.cs

Lines changed: 22 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ public class BranchConfigurationCalculator
1212
/// <summary>
1313
/// Gets the <see cref="BranchConfig"/> for the current commit.
1414
/// </summary>
15-
public static BranchConfig GetBranchConfiguration(Commit currentCommit, IRepository repository, bool onlyEvaluateTrackedBranches, Config config, Branch currentBranch, IList<Branch> excludedInheritBranches = null)
15+
public static BranchConfig GetBranchConfiguration(GitVersionContext context, Branch targetBranch, IList<Branch> excludedInheritBranches = null)
1616
{
17-
var matchingBranches = LookupBranchConfiguration(config, currentBranch).ToArray();
17+
var matchingBranches = LookupBranchConfiguration(context.FullConfiguration, targetBranch).ToArray();
1818

1919
BranchConfig branchConfiguration;
2020
if (matchingBranches.Length > 0)
@@ -25,7 +25,7 @@ public static BranchConfig GetBranchConfiguration(Commit currentCommit, IReposit
2525
{
2626
Logger.WriteWarning(string.Format(
2727
"Multiple branch configurations match the current branch branchName of '{0}'. Using the first matching configuration, '{1}'. Matching configurations include: '{2}'",
28-
currentBranch.FriendlyName,
28+
targetBranch.FriendlyName,
2929
branchConfiguration.Name,
3030
string.Join("', '", matchingBranches.Select(b => b.Name))));
3131
}
@@ -34,14 +34,14 @@ public static BranchConfig GetBranchConfiguration(Commit currentCommit, IReposit
3434
{
3535
Logger.WriteInfo(string.Format(
3636
"No branch configuration found for branch {0}, falling back to default configuration",
37-
currentBranch.FriendlyName));
37+
targetBranch.FriendlyName));
3838

3939
branchConfiguration = new BranchConfig { Name = string.Empty };
40-
ConfigurationProvider.ApplyBranchDefaults(config, branchConfiguration, "");
40+
ConfigurationProvider.ApplyBranchDefaults(context.FullConfiguration, branchConfiguration, "");
4141
}
4242

4343
return branchConfiguration.Increment == IncrementStrategy.Inherit ?
44-
InheritBranchConfiguration(onlyEvaluateTrackedBranches, repository, currentCommit, currentBranch, branchConfiguration, config, excludedInheritBranches) :
44+
InheritBranchConfiguration(context, targetBranch, branchConfiguration, excludedInheritBranches) :
4545
branchConfiguration;
4646
}
4747

@@ -60,16 +60,18 @@ static IEnumerable<BranchConfig> LookupBranchConfiguration([NotNull] Config conf
6060
return config.Branches.Where(b => Regex.IsMatch(currentBranch.FriendlyName, "^" + b.Value.Regex, RegexOptions.IgnoreCase)).Select(kvp => kvp.Value);
6161
}
6262

63-
static BranchConfig InheritBranchConfiguration(bool onlyEvaluateTrackedBranches, IRepository repository, Commit currentCommit, Branch currentBranch, BranchConfig branchConfiguration, Config config, IList<Branch> excludedInheritBranches)
63+
static BranchConfig InheritBranchConfiguration(GitVersionContext context, Branch targetBranch, BranchConfig branchConfiguration, IList<Branch> excludedInheritBranches)
6464
{
65+
var repository = context.Repository;
66+
var config = context.FullConfiguration;
6567
using (Logger.IndentLog("Attempting to inherit branch configuration from parent branch"))
6668
{
67-
var excludedBranches = new[] { currentBranch };
69+
var excludedBranches = new[] { targetBranch };
6870
// Check if we are a merge commit. If so likely we are a pull request
69-
var parentCount = currentCommit.Parents.Count();
71+
var parentCount = context.CurrentCommit.Parents.Count();
7072
if (parentCount == 2)
7173
{
72-
excludedBranches = CalculateWhenMultipleParents(repository, currentCommit, ref currentBranch, excludedBranches);
74+
excludedBranches = CalculateWhenMultipleParents(repository, context.CurrentCommit, ref targetBranch, excludedBranches);
7375
}
7476

7577
if (excludedInheritBranches == null)
@@ -90,22 +92,25 @@ static BranchConfig InheritBranchConfiguration(bool onlyEvaluateTrackedBranches,
9092
}
9193
var branchesToEvaluate = repository.Branches.Except(excludedInheritBranches).ToList();
9294

93-
var branchPoint = currentBranch.FindCommitBranchWasBranchedFrom(repository, excludedInheritBranches.ToArray());
95+
var branchPoint = context.RepostioryMetadataProvider
96+
.FindCommitBranchWasBranchedFrom(targetBranch, repository, excludedInheritBranches.ToArray());
9497
List<Branch> possibleParents;
9598
if (branchPoint == BranchCommit.Empty)
9699
{
97-
possibleParents = currentCommit.GetBranchesContainingCommit(repository, branchesToEvaluate, true)
100+
possibleParents = context.RepostioryMetadataProvider.GetBranchesContainingCommit(context.CurrentCommit, repository, branchesToEvaluate, true)
98101
// It fails to inherit Increment branch configuration if more than 1 parent;
99102
// therefore no point to get more than 2 parents
100103
.Take(2)
101104
.ToList();
102105
}
103106
else
104107
{
105-
var branches = branchPoint.Commit.GetBranchesContainingCommit(repository, branchesToEvaluate, true).ToList();
108+
var branches = context.RepostioryMetadataProvider
109+
.GetBranchesContainingCommit(branchPoint.Commit, repository, branchesToEvaluate, true).ToList();
106110
if (branches.Count > 1)
107111
{
108-
var currentTipBranches = currentCommit.GetBranchesContainingCommit(repository, branchesToEvaluate, true).ToList();
112+
var currentTipBranches = context.RepostioryMetadataProvider
113+
.GetBranchesContainingCommit(context.CurrentCommit, repository, branchesToEvaluate, true).ToList();
109114
possibleParents = branches.Except(currentTipBranches).ToList();
110115
}
111116
else
@@ -118,7 +123,7 @@ static BranchConfig InheritBranchConfiguration(bool onlyEvaluateTrackedBranches,
118123

119124
if (possibleParents.Count == 1)
120125
{
121-
var branchConfig = GetBranchConfiguration(currentCommit, repository, onlyEvaluateTrackedBranches, config, possibleParents[0], excludedInheritBranches);
126+
var branchConfig = GetBranchConfiguration(context, possibleParents[0], excludedInheritBranches);
122127
return new BranchConfig(branchConfiguration)
123128
{
124129
Increment = branchConfig.Increment,
@@ -149,7 +154,7 @@ static BranchConfig InheritBranchConfiguration(bool onlyEvaluateTrackedBranches,
149154
Logger.WriteWarning(errorMessage + Environment.NewLine + Environment.NewLine + "Falling back to " + branchName + " branch config");
150155

151156
// To prevent infinite loops, make sure that a new branch was chosen.
152-
if (LibGitExtensions.IsSameBranch(currentBranch, chosenBranch))
157+
if (LibGitExtensions.IsSameBranch(targetBranch, chosenBranch))
153158
{
154159
Logger.WriteWarning("Fallback branch wants to inherit Increment branch configuration from itself. Using patch increment instead.");
155160
return new BranchConfig(branchConfiguration)
@@ -158,7 +163,7 @@ static BranchConfig InheritBranchConfiguration(bool onlyEvaluateTrackedBranches,
158163
};
159164
}
160165

161-
var inheritingBranchConfig = GetBranchConfiguration(currentCommit, repository, onlyEvaluateTrackedBranches, config, chosenBranch, excludedInheritBranches);
166+
var inheritingBranchConfig = GetBranchConfiguration(context, chosenBranch, excludedInheritBranches);
162167
return new BranchConfig(branchConfiguration)
163168
{
164169
Increment = inheritingBranchConfig.Increment,
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
using JetBrains.Annotations;
2+
using LibGit2Sharp;
3+
using System;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
7+
namespace GitVersion
8+
{
9+
public class GitRepoMetadataProvider
10+
{
11+
private Dictionary<Branch, List<BranchCommit>> mergeBaseCommitsCache;
12+
private Dictionary<Tuple<Branch, Branch>, MergeBaseData> mergeBaseCache;
13+
private Dictionary<Branch, List<SemanticVersion>> semanticVersionTagsOnBranchCache;
14+
private IRepository repository;
15+
const string missingTipFormat = "{0} has no tip. Please see http://example.com/docs for information on how to fix this.";
16+
17+
18+
public GitRepoMetadataProvider(IRepository repository)
19+
{
20+
mergeBaseCache = new Dictionary<Tuple<Branch, Branch>, MergeBaseData>();
21+
mergeBaseCommitsCache = new Dictionary<Branch, List<BranchCommit>>();
22+
semanticVersionTagsOnBranchCache = new Dictionary<Branch, List<SemanticVersion>>();
23+
this.repository = repository;
24+
}
25+
26+
public IEnumerable<SemanticVersion> GetVersionTagsOnBranch(Branch branch, IRepository repository, string tagPrefixRegex)
27+
{
28+
if (semanticVersionTagsOnBranchCache.ContainsKey(branch))
29+
{
30+
Logger.WriteDebug(string.Format("Cache hit for version tags on branch '{0}", branch.CanonicalName));
31+
return semanticVersionTagsOnBranchCache[branch];
32+
}
33+
34+
using (Logger.IndentLog(string.Format("Getting version tags from branch '{0}'.", branch.CanonicalName)))
35+
{
36+
var tags = repository.Tags.Select(t => t).ToList();
37+
38+
var versionTags = repository.Commits.QueryBy(new CommitFilter
39+
{
40+
IncludeReachableFrom = branch.Tip
41+
})
42+
.SelectMany(c => tags.Where(t => c.Sha == t.Target.Sha).SelectMany(t =>
43+
{
44+
SemanticVersion semver;
45+
if (SemanticVersion.TryParse(t.FriendlyName, tagPrefixRegex, out semver))
46+
return new[] { semver };
47+
return new SemanticVersion[0];
48+
})).ToList();
49+
50+
semanticVersionTagsOnBranchCache.Add(branch, versionTags);
51+
return versionTags;
52+
}
53+
}
54+
55+
// TODO Should we cache this?
56+
public IEnumerable<Branch> GetBranchesContainingCommit([NotNull] Commit commit, IRepository repository, IList<Branch> branches, bool onlyTrackedBranches)
57+
{
58+
if (commit == null)
59+
{
60+
throw new ArgumentNullException("commit");
61+
}
62+
Logger.WriteDebug("Heh");
63+
using (Logger.IndentLog(string.Format("Getting branches containing the commit '{0}'.", commit.Id)))
64+
{
65+
var directBranchHasBeenFound = false;
66+
Logger.WriteInfo("Trying to find direct branches.");
67+
// TODO: It looks wasteful looping through the branches twice. Can't these loops be merged somehow? @asbjornu
68+
foreach (var branch in branches)
69+
{
70+
if (branch.Tip != null && branch.Tip.Sha != commit.Sha || (onlyTrackedBranches && !branch.IsTracking))
71+
{
72+
continue;
73+
}
74+
75+
directBranchHasBeenFound = true;
76+
Logger.WriteInfo(string.Format("Direct branch found: '{0}'.", branch.FriendlyName));
77+
yield return branch;
78+
}
79+
80+
if (directBranchHasBeenFound)
81+
{
82+
yield break;
83+
}
84+
85+
Logger.WriteInfo(string.Format("No direct branches found, searching through {0} branches.", onlyTrackedBranches ? "tracked" : "all"));
86+
foreach (var branch in branches.Where(b => onlyTrackedBranches && !b.IsTracking))
87+
{
88+
Logger.WriteInfo(string.Format("Searching for commits reachable from '{0}'.", branch.FriendlyName));
89+
90+
var commits = repository.Commits.QueryBy(new CommitFilter
91+
{
92+
IncludeReachableFrom = branch
93+
}).Where(c => c.Sha == commit.Sha);
94+
95+
if (!commits.Any())
96+
{
97+
Logger.WriteInfo(string.Format("The branch '{0}' has no matching commits.", branch.FriendlyName));
98+
continue;
99+
}
100+
101+
Logger.WriteInfo(string.Format("The branch '{0}' has a matching commit.", branch.FriendlyName));
102+
yield return branch;
103+
}
104+
}
105+
}
106+
107+
/// <summary>
108+
/// Find the merge base of the two branches, i.e. the best common ancestor of the two branches' tips.
109+
/// </summary>
110+
public Commit FindMergeBase(Branch branch, Branch otherBranch, IRepository repository)
111+
{
112+
var key = Tuple.Create(branch, otherBranch);
113+
114+
if (mergeBaseCache.ContainsKey(key))
115+
{
116+
Logger.WriteDebug(string.Format(
117+
"Cache hit for merge base between '{0}' and '{1}'.",
118+
branch.FriendlyName, otherBranch.FriendlyName));
119+
return mergeBaseCache[key].MergeBase;
120+
}
121+
122+
using (Logger.IndentLog(string.Format("Finding merge base between '{0}' and '{1}'.", branch.FriendlyName, otherBranch.FriendlyName)))
123+
{
124+
// Otherbranch tip is a forward merge
125+
var commitToFindCommonBase = otherBranch.Tip;
126+
var commit = branch.Tip;
127+
if (otherBranch.Tip.Parents.Contains(commit))
128+
{
129+
commitToFindCommonBase = otherBranch.Tip.Parents.First();
130+
}
131+
132+
var findMergeBase = repository.ObjectDatabase.FindMergeBase(commit, commitToFindCommonBase);
133+
if (findMergeBase != null)
134+
{
135+
Logger.WriteInfo(string.Format("Found merge base of {0}", findMergeBase.Sha));
136+
// We do not want to include merge base commits which got forward merged into the other branch
137+
bool mergeBaseWasForwardMerge;
138+
do
139+
{
140+
// Now make sure that the merge base is not a forward merge
141+
mergeBaseWasForwardMerge = otherBranch.Commits
142+
.SkipWhile(c => c != commitToFindCommonBase)
143+
.TakeWhile(c => c != findMergeBase)
144+
.Any(c => c.Parents.Contains(findMergeBase));
145+
if (mergeBaseWasForwardMerge)
146+
{
147+
var second = commitToFindCommonBase.Parents.First();
148+
var mergeBase = repository.ObjectDatabase.FindMergeBase(commit, second);
149+
if (mergeBase == findMergeBase)
150+
{
151+
break;
152+
}
153+
findMergeBase = mergeBase;
154+
Logger.WriteInfo(string.Format("Merge base was due to a forward merge, next merge base is {0}", findMergeBase));
155+
}
156+
} while (mergeBaseWasForwardMerge);
157+
}
158+
159+
// Store in cache.
160+
mergeBaseCache.Add(key, new MergeBaseData(branch, otherBranch, repository, findMergeBase));
161+
162+
return findMergeBase;
163+
}
164+
}
165+
166+
/// <summary>
167+
/// Find the commit where the given branch was branched from another branch.
168+
/// If there are multiple such commits and branches, returns the newest commit.
169+
/// </summary>
170+
public BranchCommit FindCommitBranchWasBranchedFrom([NotNull] Branch branch, IRepository repository, params Branch[] excludedBranches)
171+
{
172+
if (branch == null)
173+
{
174+
throw new ArgumentNullException("branch");
175+
}
176+
177+
using (Logger.IndentLog(string.Format("Finding branch source of '{0}'", branch.FriendlyName)))
178+
{
179+
if (branch.Tip == null)
180+
{
181+
Logger.WriteWarning(string.Format(missingTipFormat, branch.FriendlyName));
182+
return BranchCommit.Empty;
183+
}
184+
185+
return GetMergeCommitsForBranch(branch).ExcludingBranches(excludedBranches).FirstOrDefault(b => !branch.IsSameBranch(b.Branch));
186+
}
187+
}
188+
189+
190+
List<BranchCommit> GetMergeCommitsForBranch(Branch branch)
191+
{
192+
if (mergeBaseCommitsCache.ContainsKey(branch))
193+
{
194+
Logger.WriteDebug(string.Format(
195+
"Cache hit for getting merge commits for branch {0}.",
196+
branch.CanonicalName));
197+
return mergeBaseCommitsCache[branch];
198+
}
199+
200+
var branchMergeBases = repository.Branches.Select(otherBranch =>
201+
{
202+
if (otherBranch.Tip == null)
203+
{
204+
Logger.WriteWarning(string.Format(missingTipFormat, otherBranch.FriendlyName));
205+
return BranchCommit.Empty;
206+
}
207+
208+
var findMergeBase = FindMergeBase(branch, otherBranch, repository);
209+
return new BranchCommit(findMergeBase, otherBranch);
210+
}).Where(b => b.Commit != null).OrderByDescending(b => b.Commit.Committer.When).ToList();
211+
mergeBaseCommitsCache.Add(branch, branchMergeBases);
212+
213+
return branchMergeBases;
214+
}
215+
216+
private class MergeBaseData
217+
{
218+
public Branch Branch { get; private set; }
219+
public Branch OtherBranch { get; private set; }
220+
public IRepository Repository { get; private set; }
221+
222+
public Commit MergeBase { get; private set; }
223+
224+
public MergeBaseData(Branch branch, Branch otherBranch, IRepository repository, Commit mergeBase)
225+
{
226+
Branch = branch;
227+
OtherBranch = otherBranch;
228+
Repository = repository;
229+
MergeBase = mergeBase;
230+
}
231+
}
232+
}
233+
}

src/GitVersionCore/GitVersionCacheKeyFactory.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,15 @@ static List<string> CalculateDirectoryContents(string root)
123123

124124
private static string GetRepositorySnapshotHash(GitPreparer gitPreparer)
125125
{
126-
var repositorySnapshot = gitPreparer.WithRepository(repo => string.Join(":", repo.Head.CanonicalName, repo.Head.Tip.Sha));
126+
var repositorySnapshot = gitPreparer.WithRepository(repo => {
127+
var head = repo.Head;
128+
if (head.Tip == null)
129+
{
130+
return head.CanonicalName;
131+
}
132+
var hash = string.Join(":", head.CanonicalName, head.Tip.Sha);
133+
return hash;
134+
});
127135
return GetHash(repositorySnapshot);
128136
}
129137

0 commit comments

Comments
 (0)