Skip to content

Commit

Permalink
RevDiff: Compare selected items
Browse files Browse the repository at this point in the history
 * If two items in the RevDiff list are selected, add menu item to compare them
 * If one item is selected, menu item to save the file to later
 * If one item is selected and one item saved, menu item to compare them

This allows to compare refactored files, between commits
  • Loading branch information
gerhardol committed Jun 25, 2020
1 parent 1ec7cef commit fe5fbeb
Show file tree
Hide file tree
Showing 8 changed files with 513 additions and 4 deletions.
25 changes: 25 additions & 0 deletions GitCommands/Git/GitModule.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3648,6 +3648,31 @@ public string OpenWithDifftool(string filename, string oldFileName = "", string
return "";
}

/// <summary>
/// Compare two Git commitish; blob or rev:path
/// </summary>
/// <param name="firstGitCommit">commitish</param>
/// <param name="secondGitCommit">commitish</param>
/// <returns>empty string</returns>
public string OpenFilesWithDifftool(string firstGitCommit, string secondGitCommit)
{
if (string.IsNullOrWhiteSpace(firstGitCommit) || string.IsNullOrWhiteSpace(secondGitCommit))
{
return null;
}

_gitCommandRunner.RunDetached(new GitArgumentBuilder("difftool")
{
"--gui",
"--no-prompt",
"-M -C",
firstGitCommit.QuoteNE(),
secondGitCommit.QuoteNE()
});

return "";
}

[CanBeNull]
public ObjectId RevParse(string revisionExpression)
{
Expand Down
1 change: 1 addition & 0 deletions GitUI/CommandsDialogs/FormBrowse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -648,6 +648,7 @@ private void UICommands_PostRepositoryChanged(object sender, GitUIEventArgs e)
this.InvokeAsync(RefreshRevisions).FileAndForget();
UpdateSubmodulesStructure();
UpdateStashCount();
revisionDiff.UICommands_PostRepositoryChanged(sender, e);
}

private void RefreshRevisions()
Expand Down
51 changes: 49 additions & 2 deletions GitUI/CommandsDialogs/RevisionDiffControl.Designer.cs

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

98 changes: 97 additions & 1 deletion GitUI/CommandsDialogs/RevisionDiffControl.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
using GitCommands;
using GitCommands.Git;
using GitUI.CommandsDialogs.BrowseDialog;
using GitUI.HelperDialogs;
using GitUI.Hotkey;
using GitUI.UserControls;
using GitUI.UserControls.RevisionGrid;
Expand All @@ -32,6 +31,7 @@ public partial class RevisionDiffControl : GitModuleControl
private readonly TranslationString _multipleDescription = new TranslationString("<multiple>");
private readonly TranslationString _selectedRevision = new TranslationString("Second: b/");
private readonly TranslationString _firstRevision = new TranslationString("First: a/");
private readonly TranslationString _diffSelectedWithSavedFile = new TranslationString("Compare selected file to \"{0}\"");

private RevisionGridControl _revisionGrid;
private RevisionFileTreeControl _revisionFileTree;
Expand All @@ -40,6 +40,7 @@ public partial class RevisionDiffControl : GitModuleControl
private readonly IFullPathResolver _fullPathResolver;
private readonly IFindFilePredicateProvider _findFilePredicateProvider;
private readonly IGitRevisionTester _gitRevisionTester;
private readonly SaveForLaterContextMenuController _saveForLaterContextMenuController;

public RevisionDiffControl()
{
Expand All @@ -51,6 +52,7 @@ public RevisionDiffControl()
_findFilePredicateProvider = new FindFilePredicateProvider();
_gitRevisionTester = new GitRevisionTester(_fullPathResolver);
_revisionDiffContextMenuController = new FileStatusListContextMenuController();
_saveForLaterContextMenuController = new SaveForLaterContextMenuController();
DiffText.TopScrollReached += FileViewer_TopScrollReached;
DiffText.BottomScrollReached += FileViewer_BottomScrollReached;
}
Expand All @@ -67,6 +69,11 @@ private void FileViewer_BottomScrollReached(object sender, EventArgs e)
DiffText.ScrollToTop();
}

public void UICommands_PostRepositoryChanged(object sender, GitUIEventArgs e)
{
_saveForLaterContextMenuController.SavedDiffFileItem = null;
}

public void RefreshArtificial()
{
if (!Visible)
Expand Down Expand Up @@ -630,6 +637,65 @@ private void openWithDifftoolToolStripMenuItem_Click(object sender, EventArgs e)
}
}

private void diffTwoSelectedDiffToolToolStripMenuItem_Click(object sender, EventArgs e)
{
var diffFiles = DiffFiles.SelectedItems.ToList();
if (diffFiles.Count != 2)
{
return;
}

// The order is always the order in the list, not clicked order, but the (last) selected is known
var firstIndex = DiffFiles.SelectedItem == diffFiles[0] ? 1 : 0;

// Fallback to first revision if second revision cannot be used
var isFirstItemSecondRev = _saveForLaterContextMenuController.ShouldEnableFirstItemDiff(diffFiles[firstIndex], isSecondRevision: true);
var first = _saveForLaterContextMenuController.GetGitCommit(Module.GetFileBlobHash, diffFiles[firstIndex], isSecondRevision: isFirstItemSecondRev);
var isSecondItemSecondRev = _saveForLaterContextMenuController.ShouldEnableSecondItemDiff(DiffFiles.SelectedItem, isSecondRevision: true);
var second = _saveForLaterContextMenuController.GetGitCommit(Module.GetFileBlobHash, DiffFiles.SelectedItem, isSecondRevision: isSecondItemSecondRev);

Module.OpenFilesWithDifftool(first, second);
}

private void diffWithSavedDiffToolToolStripMenuItem_Click(object sender, EventArgs e)
{
// For first item, the second revision is explicitly saved
var first = _saveForLaterContextMenuController.GetGitCommit(Module.GetFileBlobHash,
_saveForLaterContextMenuController.SavedDiffFileItem, isSecondRevision: true);

// Fallback to first revision if second cannot be used
var isSecond = _saveForLaterContextMenuController.ShouldEnableSecondItemDiff(DiffFiles.SelectedItem, isSecondRevision: true);
var second = _saveForLaterContextMenuController.GetGitCommit(Module.GetFileBlobHash, DiffFiles.SelectedItem, isSecondRevision: isSecond);

Module.OpenFilesWithDifftool(first, second);
}

private void saveSecondForLaterDiffToolToolStripMenuItem_Click(object sender, EventArgs e)
{
_saveForLaterContextMenuController.SavedDiffFileItem = DiffFiles.SelectedItem;
}

private void saveFirstForLaterDiffToolToolStripMenuItem_Click(object sender, EventArgs e)
{
if (DiffFiles.SelectedItem?.FirstRevision == null)
{
return;
}

var item = new FileStatusItem(
firstRev: DiffFiles.SelectedItem.SecondRevision,
secondRev: DiffFiles.SelectedItem.FirstRevision,
item: DiffFiles.SelectedItem.Item);
if (!string.IsNullOrWhiteSpace(DiffFiles.SelectedItem.Item.OldName))
{
var name = DiffFiles.SelectedItem.Item.OldName;
DiffFiles.SelectedItem.Item.OldName = DiffFiles.SelectedItem.Item.Name;
DiffFiles.SelectedItem.Item.Name = name;
}

_saveForLaterContextMenuController.SavedDiffFileItem = item;
}

private void diffEditWorkingDirectoryFileToolStripMenuItem_Click(object sender, EventArgs e)
{
if (DiffFiles.SelectedItem == null)
Expand Down Expand Up @@ -742,6 +808,36 @@ private void openWithDifftoolToolStripMenuItem_DropDownOpening(object sender, Ev
selectedParentToLocalToolStripMenuItem.Enabled = _revisionDiffContextMenuController.ShouldShowMenuSelectedParentToLocal(selectionInfo);
firstParentToLocalToolStripMenuItem.Visible = _revisionDiffContextMenuController.ShouldDisplayMenuFirstParentToLocal(selectionInfo);
selectedParentToLocalToolStripMenuItem.Visible = _revisionDiffContextMenuController.ShouldDisplayMenuSelectedParentToLocal(selectionInfo);

var diffFiles = DiffFiles.SelectedItems.ToList();
diffSavedForLaterStripSeparator.Visible = diffFiles.Count == 1 || diffFiles.Count == 2;

// The order is always the order in the list, not clicked order, but the (last) selected is known
var firstIndex = diffFiles.Count == 2 && DiffFiles.SelectedItem == diffFiles[0] ? 1 : 0;

diffTwoSelectedDifftoolToolStripMenuItem.Visible = diffFiles.Count == 2;
diffTwoSelectedDifftoolToolStripMenuItem.Enabled =
diffFiles.Count == 2
&& _saveForLaterContextMenuController.ShouldEnableFirstItemDiff(diffFiles[firstIndex])
&& _saveForLaterContextMenuController.ShouldEnableSecondItemDiff(DiffFiles.SelectedItem);

diffWithSavedDifftoolToolStripMenuItem.Visible = diffFiles.Count == 1 && _saveForLaterContextMenuController.SavedDiffFileItem != null;
diffWithSavedDifftoolToolStripMenuItem.Enabled =
diffFiles.Count == 1
&& diffFiles[0] != _saveForLaterContextMenuController.SavedDiffFileItem
&& _saveForLaterContextMenuController.ShouldEnableSecondItemDiff(diffFiles[0]);
diffWithSavedDifftoolToolStripMenuItem.Text =
_saveForLaterContextMenuController.SavedDiffFileItem != null
? string.Format(_diffSelectedWithSavedFile.Text, _saveForLaterContextMenuController.SavedDiffFileItem.Item.Name)
: string.Empty;

saveSecondRevForLaterDiffToolStripMenuItem.Visible = diffFiles.Count == 1;
saveSecondRevForLaterDiffToolStripMenuItem.Enabled = diffFiles.Count == 1
&& _saveForLaterContextMenuController.ShouldEnableFirstItemDiff(diffFiles[0], isSecondRevision: true);

saveFirstRevForLaterDiffToolStripMenuItem.Visible = diffFiles.Count == 1;
saveFirstRevForLaterDiffToolStripMenuItem.Enabled = diffFiles.Count == 1
&& _saveForLaterContextMenuController.ShouldEnableFirstItemDiff(diffFiles[0], isSecondRevision: false);
}

private void resetFileToolStripMenuItem_Click(object sender, EventArgs e)
Expand Down
93 changes: 93 additions & 0 deletions GitUI/CommandsDialogs/SaveForLaterContextMenuController.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
using System;
using GitUI.UserControls;
using GitUIPluginInterfaces;
using JetBrains.Annotations;

namespace GitUI.CommandsDialogs
{
public class SaveForLaterContextMenuController
{
/// <summary>
/// The saved file status item, to diff with other files and commits
/// </summary>
public FileStatusItem SavedDiffFileItem { get; set; }

// Note that the methods in this class are without side effects (static)

/// <summary>
/// Check if this file and commit can be used as the first item in a diff to another item
/// It must be possible to describe the item as a Git commitish
/// </summary>
/// <param name="item">The file status item</param>
/// <param name="isSecondRevision">true if second revision can be used as the first item</param>
/// <returns>If the item can be used</returns>
public bool ShouldEnableFirstItemDiff(FileStatusItem item, bool isSecondRevision)
{
// First item must be a git reference existing in the revision, i.e. other than work tree
return ShouldEnableSecondItemDiff(item, isSecondRevision: isSecondRevision)
&& (isSecondRevision ? item.SecondRevision : item.FirstRevision)?.ObjectId != ObjectId.WorkTreeId;
}

public bool ShouldEnableFirstItemDiff(FileStatusItem item)
=> ShouldEnableFirstItemDiff(item, isSecondRevision: false)
|| ShouldEnableFirstItemDiff(item, isSecondRevision: true);

/// <summary>
/// Check if this file and commit can be used as the second item in a diff to another item
/// </summary>
/// <param name="item">The file status item</param>
/// <param name="isSecondRevision">true if second revision can be used as the second item</param>
/// <returns>If the item can be used</returns>
public bool ShouldEnableSecondItemDiff(FileStatusItem item, bool isSecondRevision)
{
// Git reference in this revision or work tree file existing on the file system
return item != null
&& !item.Item.IsSubmodule
&& (isSecondRevision ? !item.Item.IsDeleted : !item.Item.IsNew);
}

public bool ShouldEnableSecondItemDiff(FileStatusItem item)
=> ShouldEnableSecondItemDiff(item, isSecondRevision: false)
|| ShouldEnableSecondItemDiff(item, isSecondRevision: true);

/// <summary>
/// A Git commitish representation of an object
/// https://git-scm.com/docs/gitrevisions#_specifying_revisions
/// </summary>
/// <param name="getFileBlobHash">the Git module function to get the blob</param>
/// <param name="item">the item</param>
/// <param name="isSecondRevision">true if second revision is used</param>
/// <returns>A Git commitish</returns>
public string GetGitCommit([CanBeNull] Func<string, ObjectId, ObjectId> getFileBlobHash, [CanBeNull] FileStatusItem item, bool isSecondRevision)
{
if (item == null)
{
return null;
}

var name = !isSecondRevision && !string.IsNullOrWhiteSpace(item.Item.OldName) ? item.Item.OldName : item.Item.Name;
var id = (isSecondRevision ? item.SecondRevision : item.FirstRevision)?.ObjectId;
if (string.IsNullOrWhiteSpace(name) || id == null)
{
return null;
}

if (id == ObjectId.WorkTreeId)
{
// A file system file
return name;
}

if (id == ObjectId.IndexId)
{
// Must be referenced by blob - no commit. File name presented in difftool will be blob or the other file
return item.Item.TreeGuid != null
? item.Item.TreeGuid.ToString()
: getFileBlobHash?.Invoke(name, id)?.ToString();
}

// commit:path
return $"{id}:{name}";
}
}
}

0 comments on commit fe5fbeb

Please sign in to comment.