forked from gitextensions/gitextensions
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
WIP gitextensions#5125 RevisionGrid Graph: Nearest branch in tooltip
- Loading branch information
Showing
9 changed files
with
421 additions
and
77 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
193 changes: 193 additions & 0 deletions
193
GitUI/UserControls/RevisionGrid/Graph/LaneInfoProvider.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,193 @@ | ||
using System; | ||
using System.Collections.Generic; | ||
using System.Linq; | ||
using System.Text; | ||
using System.Text.RegularExpressions; | ||
using JetBrains.Annotations; | ||
|
||
namespace GitUI.UserControls.RevisionGrid.Graph | ||
{ | ||
internal sealed class LaneInfoProvider | ||
{ | ||
private readonly ILaneNodeLocator _nodeLocator; | ||
|
||
public LaneInfoProvider(ILaneNodeLocator nodeLocator) | ||
{ | ||
_nodeLocator = nodeLocator; | ||
} | ||
|
||
public string GetLaneInfo(int x, int rowIndex, int laneWidth) | ||
{ | ||
var node = _nodeLocator.FindNextNode(x, rowIndex, laneWidth); | ||
if (node == null) | ||
{ | ||
return string.Empty; | ||
} | ||
|
||
var laneInfoText = new StringBuilder(); | ||
if (!node.Revision.IsArtificial) | ||
{ | ||
laneInfoText.AppendLine(node.Revision.Guid); | ||
|
||
var references = new References(node); | ||
if (references.CommittedTo.IsNotNullOrWhitespace()) | ||
{ | ||
laneInfoText.AppendFormat("\nBranch: {0}", references.CommittedTo); | ||
if (references.MergedWith.IsNotNullOrWhitespace()) | ||
{ | ||
laneInfoText.AppendFormat(" (merged with {0})", references.MergedWith); | ||
} | ||
} | ||
|
||
laneInfoText.AppendLine(); | ||
} | ||
|
||
if (node.Revision.Body != null) | ||
{ | ||
laneInfoText.Append(node.Revision.Body.TrimEnd()); | ||
} | ||
else | ||
{ | ||
laneInfoText.Append(node.Revision.Subject); | ||
if (node.Revision.HasMultiLineMessage) | ||
{ | ||
laneInfoText.Append("\n\nFull message text is not present in older commits.\nSelect this commit to populate the full message."); | ||
} | ||
} | ||
|
||
return laneInfoText.ToString(); | ||
} | ||
|
||
private class References | ||
{ | ||
private static readonly Regex MergeRegex = new Regex("(?i)^merged? (pull request (.*) from )?(.*branch )?'?([^ ']+)'?( into (.*))?\\.?$", | ||
RegexOptions.Compiled | RegexOptions.CultureInvariant); | ||
private readonly HashSet<Node> _visitedNodes = new HashSet<Node>(); | ||
|
||
internal References([NotNull] Node node) | ||
{ | ||
AddReferencesOf(node, previousDescJunction: null); | ||
} | ||
|
||
internal string CommittedTo { get; private set; } | ||
internal string MergedWith { get; private set; } | ||
|
||
private bool AddReferencesOf([NotNull] Node node, [CanBeNull] Junction previousDescJunction) | ||
{ | ||
if (!_visitedNodes.Add(node)) | ||
{ | ||
return false; | ||
} | ||
|
||
if (CheckForMerge(node, previousDescJunction) || FindBranch(node, node, previousDescJunction)) | ||
{ | ||
return true; | ||
} | ||
|
||
foreach (var descJunction in node.Descendants) | ||
{ | ||
// iterate the inner nodes (i.e. excluding the youngest) beginning with the oldest | ||
var nodeFound = false; | ||
for (var nodeIndex = descJunction.NodeCount - 1; nodeIndex > 0; --nodeIndex) | ||
{ | ||
var innerNode = descJunction[nodeIndex]; | ||
if (nodeFound) | ||
{ | ||
if (FindBranch(innerNode, node, descJunction)) | ||
{ | ||
return true; | ||
} | ||
} | ||
else | ||
{ | ||
nodeFound = innerNode == node; | ||
} | ||
} | ||
|
||
// handle the youngest and its descendants | ||
if (AddReferencesOf(descJunction.Youngest, descJunction)) | ||
{ | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
private bool FindBranch([NotNull] Node descNode, [NotNull] Node node, [CanBeNull] Junction descJunction) | ||
{ | ||
foreach (var gitReference in descNode.Revision.Refs) | ||
{ | ||
if (gitReference.IsHead || gitReference.IsRemote) | ||
{ | ||
CheckForMerge(node, descJunction); | ||
CommittedTo = CommittedTo ?? gitReference.Name; | ||
return true; | ||
} | ||
|
||
if (gitReference.IsStash && CommittedTo == null) | ||
{ | ||
CommittedTo = gitReference.Name; | ||
return true; | ||
} | ||
} | ||
|
||
return false; | ||
} | ||
|
||
/// <summary> | ||
/// Checks whether the commit message is a merge message | ||
/// and then if its a merge message, sets CommittedTo and MergedWith. | ||
/// | ||
/// MergedWith is set if it is the current node, i.e. on the first call. | ||
/// MergedWith is set to string.Empty if it is no merge. | ||
/// First/second branch does not matter because it is the message of the current node. | ||
/// </summary> | ||
/// <param name="node">the node of the revision to evaluate</param> | ||
/// <param name="descJunction"> | ||
/// the descending junction the node is part of | ||
/// (used for the decision whether the node belongs the first or second branch of the merge) | ||
/// </param> | ||
private bool CheckForMerge([NotNull] Node node, [CanBeNull] Junction descJunction) | ||
{ | ||
var isTheFirstBranch = descJunction == null || node.Ancestors.Count == 0 || node.Ancestors.First() == descJunction; | ||
string mergedInto; | ||
string mergedWith; | ||
(mergedInto, mergedWith) = ParseMergeMessage(node, isTheFirstBranch); | ||
|
||
if (mergedInto != null) | ||
{ | ||
CommittedTo = isTheFirstBranch ? mergedInto : mergedWith; | ||
} | ||
|
||
if (MergedWith == null) | ||
{ | ||
MergedWith = mergedWith ?? string.Empty; | ||
} | ||
|
||
return CommittedTo != null; | ||
} | ||
|
||
private static (string into, string with) ParseMergeMessage([NotNull] Node node, bool appendPullRequest) | ||
{ | ||
string into = null; | ||
string with = null; | ||
var match = MergeRegex.Match(node.Revision.Subject); | ||
if (match.Success) | ||
{ | ||
var matchPullRequest = match.Groups[2]; | ||
var matchWith = match.Groups[4]; | ||
var matchInto = match.Groups[6]; | ||
into = matchInto.Success ? matchInto.Value : "master"; | ||
with = matchWith.Success ? matchWith.Value : "?"; | ||
if (appendPullRequest && matchPullRequest.Success) | ||
{ | ||
with += string.Format(" by pull request {0}", matchPullRequest); | ||
} | ||
} | ||
|
||
return (into, with); | ||
} | ||
} | ||
} | ||
} |
Oops, something went wrong.