Skip to content

Commit

Permalink
gitextensions#5125 RevisionGrid Graph: Nearest branch in tooltip
Browse files Browse the repository at this point in the history
  • Loading branch information
mstv authored and gerhardol committed Sep 26, 2018
1 parent 9712f9c commit afebab9
Show file tree
Hide file tree
Showing 2 changed files with 186 additions and 12 deletions.
189 changes: 177 additions & 12 deletions GitUI/UserControls/RevisionGrid/Columns/GraphColumnProvider.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Windows.Forms;
using GitCommands;
using GitExtUtils.GitUI;
Expand Down Expand Up @@ -508,14 +510,8 @@ private void ClearDrawCache()

public override bool TryGetToolTip(DataGridViewCellMouseEventArgs e, GitRevision revision, out string toolTip)
{
if (!revision.IsArtificial)
{
toolTip = GetLaneInfo(e.X - ColumnLeftMargin, e.RowIndex);
return true;
}

toolTip = default;
return false;
toolTip = GetLaneInfo(e.X - ColumnLeftMargin, e.RowIndex);
return true;

string GetLaneInfo(int x, int rowIndex)
{
Expand All @@ -532,7 +528,7 @@ string GetLaneInfo(int x, int rowIndex)
node = laneRow.Node;
if (!node.Revision.IsArtificial)
{
laneInfoText.AppendLine(node.Revision.Guid);
laneInfoText.Append("* ");
}
}
else if (lane >= 0 && lane < laneRow.Count)
Expand All @@ -556,18 +552,187 @@ string GetLaneInfo(int x, int rowIndex)

if (node != null)
{
if (laneInfoText.Length > 0)
if (!node.Revision.IsArtificial)
{
laneInfoText.AppendLine();
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.Append(references.DebugInfo);

laneInfoText.AppendLine().AppendLine();
}

laneInfoText.Append(node.Revision.Body ?? node.Revision.Subject);
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 HashSet<Node> _visitedNodes = new HashSet<Node>();
internal string CommittedTo { get; private set; }
internal string MergedWith { get; private set; }
internal StringBuilder DebugInfo = new StringBuilder();

internal References([NotNull] Node node)
{
AddReferencesOf(node, previousDescJunction: null);
}

private bool AddReferencesOf([NotNull] Node node, [CanBeNull] Junction previousDescJunction)
{
if (_visitedNodes.Add(node))
{
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
bool nodeFound = false;
for (int nodeIndex = descJunction.NodeCount - 1; nodeIndex > 0; --nodeIndex)
{
Node 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;
}
else 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)
{
bool isTheFirstBranch = descJunction == null || node.Ancestors.Count == 0 || node.Ancestors.First() == descJunction;
string mergedInto;
string mergedWith;
(mergedInto, mergedWith) = ParseMergeMessage(node, appendPullRequest: isTheFirstBranch);

if (mergedInto != null)
{
CommittedTo = isTheFirstBranch ? mergedInto : mergedWith;
}

if (MergedWith == null)
{
MergedWith = mergedWith ?? string.Empty;
}

if (CommittedTo != null)
{
DebugInfo.AppendFormat(" node: {0}, branch: {1}, descJunction {2} isTheFirstBranch {3}\n",
node.Revision.Guid.Substring(0, 8), CommittedTo, descJunction?.ToString() ?? "(null)", isTheFirstBranch);
foreach (var ancJunction in node.Ancestors)
{
DebugInfo.AppendFormat(" {2}anc {0} -> {1} {3}\n",
ancJunction.Oldest.Revision.Guid.Substring(0, 8),
ancJunction.Youngest.Revision.Guid.Substring(0, 8),
ancJunction == descJunction ? ">\x200A" : " ",
ancJunction == descJunction ? CommittedTo + " " + MergedWith : "");
}
}

return CommittedTo != null;
}

private static readonly Regex _merge = new Regex("(?i)^merged? (pull request (.*) from )?(.*branch )?'?([^ ']+)'?( into (.*))?\\.?$");

private static (string into, string with) ParseMergeMessage([NotNull] Node node, bool appendPullRequest = false)
{
string into = null;
string with = null;
Match match = _merge.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);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ string GetToolTipText()
provider.TryGetToolTip(e, revision, out var toolTip) &&
!string.IsNullOrWhiteSpace(toolTip))
{
int lineCount = 0;
for (int pos = 0, length = toolTip.Length; pos < length; ++pos)
{
if (toolTip[pos] == '\n' && ++lineCount == 30)
{
return toolTip.Substring(0, pos + 1) + ">>> TOOLTIP TRUNCATED <<<";
}
}

return toolTip;
}

Expand Down

0 comments on commit afebab9

Please sign in to comment.