Skip to content

Commit 9c51e78

Browse files
committed
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 5939345 commit 9c51e78

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,14 +12,14 @@ 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
if (matchingBranches.Length > 1)
2020
{
2121
const string format = "Multiple branch configurations match the current branch branchName of '{0}'. Matching configurations: '{1}'";
22-
throw new Exception(string.Format(format, currentBranch.FriendlyName, string.Join(", ", matchingBranches.Select(b => b.Name))));
22+
throw new Exception(string.Format(format, targetBranch.FriendlyName, string.Join(", ", matchingBranches.Select(b => b.Name))));
2323
}
2424

2525
BranchConfig branchConfiguration;
@@ -31,14 +31,14 @@ public static BranchConfig GetBranchConfiguration(Commit currentCommit, IReposit
3131
{
3232
Logger.WriteInfo(string.Format(
3333
"No branch configuration found for branch {0}, falling back to default configuration",
34-
currentBranch.FriendlyName));
34+
targetBranch.FriendlyName));
3535

3636
branchConfiguration = new BranchConfig { Name = string.Empty };
37-
ConfigurationProvider.ApplyBranchDefaults(config, branchConfiguration, "");
37+
ConfigurationProvider.ApplyBranchDefaults(context.FullConfiguration, branchConfiguration, "");
3838
}
3939

4040
return branchConfiguration.Increment == IncrementStrategy.Inherit ?
41-
InheritBranchConfiguration(onlyEvaluateTrackedBranches, repository, currentCommit, currentBranch, branchConfiguration, config, excludedInheritBranches) :
41+
InheritBranchConfiguration(context, targetBranch, branchConfiguration, excludedInheritBranches) :
4242
branchConfiguration;
4343
}
4444

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

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

7274
if (excludedInheritBranches == null)
@@ -87,22 +89,25 @@ static BranchConfig InheritBranchConfiguration(bool onlyEvaluateTrackedBranches,
8789
}
8890
var branchesToEvaluate = repository.Branches.Except(excludedInheritBranches).ToList();
8991

90-
var branchPoint = currentBranch.FindCommitBranchWasBranchedFrom(repository, excludedInheritBranches.ToArray());
92+
var branchPoint = context.RepostioryMetadataProvider
93+
.FindCommitBranchWasBranchedFrom(targetBranch, repository, excludedInheritBranches.ToArray());
9194
List<Branch> possibleParents;
9295
if (branchPoint == BranchCommit.Empty)
9396
{
94-
possibleParents = currentCommit.GetBranchesContainingCommit(repository, branchesToEvaluate, true)
97+
possibleParents = context.RepostioryMetadataProvider.GetBranchesContainingCommit(context.CurrentCommit, repository, branchesToEvaluate, true)
9598
// It fails to inherit Increment branch configuration if more than 1 parent;
9699
// therefore no point to get more than 2 parents
97100
.Take(2)
98101
.ToList();
99102
}
100103
else
101104
{
102-
var branches = branchPoint.Commit.GetBranchesContainingCommit(repository, branchesToEvaluate, true).ToList();
105+
var branches = context.RepostioryMetadataProvider
106+
.GetBranchesContainingCommit(branchPoint.Commit, repository, branchesToEvaluate, true).ToList();
103107
if (branches.Count > 1)
104108
{
105-
var currentTipBranches = currentCommit.GetBranchesContainingCommit(repository, branchesToEvaluate, true).ToList();
109+
var currentTipBranches = context.RepostioryMetadataProvider
110+
.GetBranchesContainingCommit(context.CurrentCommit, repository, branchesToEvaluate, true).ToList();
106111
possibleParents = branches.Except(currentTipBranches).ToList();
107112
}
108113
else
@@ -115,7 +120,7 @@ static BranchConfig InheritBranchConfiguration(bool onlyEvaluateTrackedBranches,
115120

116121
if (possibleParents.Count == 1)
117122
{
118-
var branchConfig = GetBranchConfiguration(currentCommit, repository, onlyEvaluateTrackedBranches, config, possibleParents[0], excludedInheritBranches);
123+
var branchConfig = GetBranchConfiguration(context, possibleParents[0], excludedInheritBranches);
119124
return new BranchConfig(branchConfiguration)
120125
{
121126
Increment = branchConfig.Increment,
@@ -146,7 +151,7 @@ static BranchConfig InheritBranchConfiguration(bool onlyEvaluateTrackedBranches,
146151
Logger.WriteWarning(errorMessage + Environment.NewLine + Environment.NewLine + "Falling back to " + branchName + " branch config");
147152

148153
// To prevent infinite loops, make sure that a new branch was chosen.
149-
if (LibGitExtensions.IsSameBranch(currentBranch, chosenBranch))
154+
if (LibGitExtensions.IsSameBranch(targetBranch, chosenBranch))
150155
{
151156
Logger.WriteWarning("Fallback branch wants to inherit Increment branch configuration from itself. Using patch increment instead.");
152157
return new BranchConfig(branchConfiguration)
@@ -155,7 +160,7 @@ static BranchConfig InheritBranchConfiguration(bool onlyEvaluateTrackedBranches,
155160
};
156161
}
157162

158-
var inheritingBranchConfig = GetBranchConfiguration(currentCommit, repository, onlyEvaluateTrackedBranches, config, chosenBranch, excludedInheritBranches);
163+
var inheritingBranchConfig = GetBranchConfiguration(context, chosenBranch, excludedInheritBranches);
159164
return new BranchConfig(branchConfiguration)
160165
{
161166
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)