Skip to content

Commit 555f78b

Browse files
JakeGinnivanDanielRose
authored andcommitted
Introduced a new class which builds cachable metadata about a git repository
This makes the cache non-static and hopefully easier to maintain in the long wrong
1 parent 1aa99a1 commit 555f78b

9 files changed

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

0 commit comments

Comments
 (0)