Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rename detection for Advanced filter #9413

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 10 additions & 10 deletions GitUI/CommandsDialogs/FormFileHistory.Designer.cs

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

173 changes: 39 additions & 134 deletions GitUI/CommandsDialogs/FormFileHistory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ public sealed partial class FormFileHistory : GitModuleForm
private readonly CancellationTokenSequence _viewChangesSequence = new();

private BuildReportTabPageExtension? _buildReportTabPageExtension;
private readonly Dictionary<ObjectId, string> _filePathByObjectId = new();

private string FileName { get; set; }

Expand All @@ -60,13 +59,13 @@ public FormFileHistory(GitUICommands commands, string fileName, GitRevision? rev
InitializeComponent();
ConfigureTabControl();

_filterBranchHelper = new FilterBranchHelper(toolStripBranchFilterComboBox, toolStripBranchFilterDropDownButton, FileChanges);
_filterRevisionsHelper = new FilterRevisionsHelper(toolStripRevisionFilterTextBox, toolStripRevisionFilterDropDownButton, FileChanges, toolStripRevisionFilterLabel, ShowFirstParent, form: this);
_filterBranchHelper = new FilterBranchHelper(toolStripBranchFilterComboBox, toolStripBranchFilterDropDownButton, RevisionGrid);
_filterRevisionsHelper = new FilterRevisionsHelper(toolStripRevisionFilterTextBox, toolStripRevisionFilterDropDownButton, RevisionGrid, toolStripRevisionFilterLabel, ShowFirstParent, form: this);

_formBrowseMenus = new FormBrowseMenus(FileHistoryContextMenu);
_formBrowseMenus.ResetMenuCommandSets();
_formBrowseMenus.AddMenuCommandSet(MainMenuItem.NavigateMenu, FileChanges.MenuCommands.NavigateMenuCommands);
_formBrowseMenus.AddMenuCommandSet(MainMenuItem.ViewMenu, FileChanges.MenuCommands.ViewMenuCommands);
_formBrowseMenus.AddMenuCommandSet(MainMenuItem.NavigateMenu, RevisionGrid.MenuCommands.NavigateMenuCommands);
_formBrowseMenus.AddMenuCommandSet(MainMenuItem.ViewMenu, RevisionGrid.MenuCommands.ViewMenuCommands);
_formBrowseMenus.InsertRevisionGridMainMenuItems(toolStripSeparator4);

_commitDataManager = new CommitDataManager(() => Module);
Expand All @@ -77,14 +76,15 @@ public FormFileHistory(GitUICommands commands, string fileName, GitRevision? rev
Diff.EscapePressed += Close;
Blame.EscapePressed += Close;

copyToClipboardToolStripMenuItem.SetRevisionFunc(() => FileChanges.GetSelectedRevisions());
copyToClipboardToolStripMenuItem.SetRevisionFunc(() => RevisionGrid.GetSelectedRevisions());

InitializeComplete();

Blame.ConfigureRepositoryHostPlugin(PluginRegistry.TryGetGitHosterForModule(Module));

FileChanges.SelectedId = revision?.ObjectId;
FileChanges.ShowBuildServerInfo = true;
RevisionGrid.SelectedId = revision?.ObjectId;
RevisionGrid.ShowBuildServerInfo = true;
RevisionGrid.FilePathByObjectId = new();

FileName = fileName;
SetTitle();
Expand All @@ -98,8 +98,8 @@ public FormFileHistory(GitUICommands commands, string fileName, GitRevision? rev
tabControl1.RemoveIfExists(BlameTab);
}

FileChanges.SelectionChanged += FileChangesSelectionChanged;
FileChanges.DisableContextMenu();
RevisionGrid.SelectionChanged += FileChangesSelectionChanged;
RevisionGrid.DisableContextMenu();

bool blameTabExists = tabControl1.Contains(BlameTab);

Expand Down Expand Up @@ -195,7 +195,7 @@ protected override void OnRuntimeLoad(EventArgs e)
}
else
{
FileChanges.Visible = false;
RevisionGrid.Visible = false;
}

LoadCustomDifftools();
Expand All @@ -215,129 +215,34 @@ public void LoadCustomDifftools()

private void LoadFileHistory()
{
FileChanges.Visible = true;
RevisionGrid.Visible = true;

if (string.IsNullOrEmpty(FileName))
{
return;
}

_asyncLoader.LoadAsync(
() => BuildFilter(),
filter =>
{
FileChanges.SetFilters(filter);
FileChanges.Load();
});

return;

(string? revision, string path) BuildFilter()
{
var fileName = FileName;
// Replace windows path separator to Linux path separator.
// This is needed to keep the file history working when started from file tree in
// browse dialog.
FileName = FileName.ToPosixPath();

// Replace windows path separator to Linux path separator.
// This is needed to keep the file history working when started from file tree in
// browse dialog.
FileName = fileName.ToPosixPath();
RevisionGrid.SetPathFilters(FileName.QuoteNE());
RevisionGrid.Load();

var fullFilePath = _fullPathResolver.Resolve(fileName);
_filePathByObjectId.Clear();

if (!AppSettings.FollowRenamesInFileHistory)
{
return (revision: (string?)null, path: $" \"{fileName}\"");
}

// git log --follow is not working as expected (see http://kerneltrap.org/mailarchive/git/2009/1/30/4856404/thread)
//
// But we can take a more complicated path to get reasonable results:
// 1. use git log --follow to get all previous filenames of the file we are interested in
// 2. use git log "list of files names" to get the history graph
//
// note: This implementation is quite a quick hack (by someone who does not speak C# fluently).
//

const string startOfObjectId = "????";
GitArgumentBuilder args = new("log")
{
// --name-only will list each filename on a separate line, ending with a an empty line
// Find start of a new commit with a sequence impossible in a filename
$"--format=\"{startOfObjectId}%H\"",
"--name-only",
"--follow",
FindRenamesAndCopiesOpts(),
"--",
fileName.Quote()
};

HashSet<string?> setOfFileNames = new();
var lines = Module.GitExecutable.GetOutputLines(args, outputEncoding: GitModule.LosslessEncoding);

ObjectId currentObjectId = null;
foreach (var line in lines.Select(GitModule.ReEncodeFileNameFromLossless))
{
if (string.IsNullOrEmpty(line))
{
// empty filename after sha
continue;
}

if (line.StartsWith(startOfObjectId))
{
if (line.Length < ObjectId.Sha1CharCount + startOfObjectId.Length
|| !ObjectId.TryParse(line, offset: startOfObjectId.Length, out currentObjectId))
{
// Parse error, ignore
currentObjectId = null;
}

continue;
}

if (currentObjectId == null)
{
// Parsing has failed, ignore
continue;
}

// Add only the first file to the dictionary
_filePathByObjectId.TryAdd(currentObjectId, line);
setOfFileNames.Add(line);
}

if (FileName.EndsWith("/"))
{
// For object type tree (folders), the filename for a commit is "random" and is not used
_filePathByObjectId.Clear();
}

return (revision: FindRenamesAndCopiesOpts(), path: string.Join("", setOfFileNames.Select(s => @$" ""{s}""")));
}
return;
}

private string? GetFileNameForRevision(GitRevision rev)
{
ObjectId objectId = rev.IsArtificial ? FileChanges.CurrentCheckout : rev.ObjectId;

return _filePathByObjectId.TryGetValue(objectId, out string? path) ? path : null;
}
if (RevisionGrid.FilePathByObjectId is null)
{
return null;
}

// returns " --find-renames=..." according to app settings
private static ArgumentString FindRenamesOpt()
{
return AppSettings.FollowRenamesInFileHistoryExactOnly
? " --find-renames=\"100%\""
: " --find-renames";
}
ObjectId objectId = rev.IsArtificial ? RevisionGrid.CurrentCheckout : rev.ObjectId;

// returns " --find-renames=... --find-copies=..." according to app settings
private static ArgumentString FindRenamesAndCopiesOpts()
{
var findCopies = AppSettings.FollowRenamesInFileHistoryExactOnly
? " --find-copies=\"100%\""
: " --find-copies";
return FindRenamesOpt() + findCopies;
return RevisionGrid.FilePathByObjectId.TryGetValue(objectId, out string? path) ? path : null;
}

private void FileChangesSelectionChanged(object sender, EventArgs e)
Expand All @@ -363,15 +268,15 @@ private void SetTitle(string? alternativeFileName = null)

private void UpdateSelectedFileViewers(bool force = false)
{
var selectedRevisions = FileChanges.GetSelectedRevisions();
var selectedRevisions = RevisionGrid.GetSelectedRevisions();

if (selectedRevisions.Count == 0)
{
return;
}

GitRevision revision = selectedRevisions[0];
var children = FileChanges.GetRevisionChildren(revision.ObjectId);
var children = RevisionGrid.GetRevisionChildren(revision.ObjectId);
string fileName = GetFileNameForRevision(revision) ?? FileName;

SetTitle(fileName);
Expand Down Expand Up @@ -436,7 +341,7 @@ private void UpdateSelectedFileViewers(bool force = false)

if (tabControl1.SelectedTab == BlameTab)
{
Blame.LoadBlame(revision, children, fileName, FileChanges, BlameTab, Diff.Encoding, force: force);
Blame.LoadBlame(revision, children, fileName, RevisionGrid, BlameTab, Diff.Encoding, force: force);
}
else if (tabControl1.SelectedTab == ViewTab)
{
Expand All @@ -457,7 +362,7 @@ private void UpdateSelectedFileViewers(bool force = false)
IsTracked = true,
IsSubmodule = GitModule.IsValidGitWorkingDir(_fullPathResolver.Resolve(fileName))
};
var revisions = FileChanges.GetSelectedRevisions();
var revisions = RevisionGrid.GetSelectedRevisions();
FileStatusItem item = new(firstRev: revisions.Skip(1).LastOrDefault(), secondRev: revisions.FirstOrDefault(), file);
_ = Diff.ViewChangesAsync(item, defaultText: "You need to select at least one revision to view diff.",
cancellationToken: _viewChangesSequence.Next());
Expand All @@ -479,7 +384,7 @@ private void TabControl1SelectedIndexChanged(object sender, EventArgs e)

private void FileChangesDoubleClick(object sender, EventArgs e)
{
FileChanges.ViewSelectedRevisions();
RevisionGrid.ViewSelectedRevisions();
}

private void OpenWithDifftoolToolStripMenuItem_Click(object sender, EventArgs e)
Expand All @@ -498,7 +403,7 @@ private void OpenFilesWithDiffTool(RevisionDiffKind diffKind, object sender)
}

var toolName = item?.Tag as string;
var selectedRevisions = FileChanges.GetSelectedRevisions();
var selectedRevisions = RevisionGrid.GetSelectedRevisions();
var orgFileName = selectedRevisions.Count != 0
? GetFileNameForRevision(selectedRevisions[0])
: null;
Expand All @@ -508,7 +413,7 @@ private void OpenFilesWithDiffTool(RevisionDiffKind diffKind, object sender)

private void saveAsToolStripMenuItem_Click(object sender, EventArgs e)
{
var selectedRows = FileChanges.GetSelectedRevisions();
var selectedRows = RevisionGrid.GetSelectedRevisions();

if (selectedRows.Count > 0)
{
Expand Down Expand Up @@ -601,7 +506,7 @@ private void ToggleFullHistoryFlag()

private void cherryPickThisCommitToolStripMenuItem_Click(object sender, EventArgs e)
{
var selectedRevisions = FileChanges.GetSelectedRevisions();
var selectedRevisions = RevisionGrid.GetSelectedRevisions();
if (selectedRevisions.Count == 1)
{
UICommands.StartCherryPickDialog(this, selectedRevisions[0]);
Expand All @@ -610,7 +515,7 @@ private void cherryPickThisCommitToolStripMenuItem_Click(object sender, EventArg

private void revertCommitToolStripMenuItem_Click(object sender, EventArgs e)
{
var selectedRevisions = FileChanges.GetSelectedRevisions();
var selectedRevisions = RevisionGrid.GetSelectedRevisions();
if (selectedRevisions.Count == 1)
{
UICommands.StartRevertCommitDialog(this, selectedRevisions[0]);
Expand All @@ -619,7 +524,7 @@ private void revertCommitToolStripMenuItem_Click(object sender, EventArgs e)

private void FileHistoryContextMenuOpening(object sender, CancelEventArgs e)
{
var selectedRevisions = FileChanges.GetSelectedRevisions();
var selectedRevisions = RevisionGrid.GetSelectedRevisions();

diffToolRemoteLocalStripMenuItem.Enabled =
selectedRevisions.Count == 1 && selectedRevisions[0].ObjectId != ObjectId.WorkTreeId &&
Expand Down Expand Up @@ -662,7 +567,7 @@ private void Blame_CommandClick(object sender, ResourceManager.CommandEventArgs
Validates.NotNull(e.Data);
if (Module.TryResolvePartialCommitId(e.Data, out var objectId))
{
FileChanges.SetSelectedRevision(objectId);
RevisionGrid.SetSelectedRevision(objectId);
}
}
else if (e.Command == "gotobranch" || e.Command == "gototag")
Expand All @@ -671,16 +576,16 @@ private void Blame_CommandClick(object sender, ResourceManager.CommandEventArgs
CommitData? commit = _commitDataManager.GetCommitData(e.Data, out _);
if (commit is not null)
{
FileChanges.SetSelectedRevision(commit.ObjectId);
RevisionGrid.SetSelectedRevision(commit.ObjectId);
}
}
else if (e.Command == "navigatebackward")
{
FileChanges.NavigateBackward();
RevisionGrid.NavigateBackward();
}
else if (e.Command == "navigateforward")
{
FileChanges.NavigateForward();
RevisionGrid.NavigateForward();
}
}

Expand Down
8 changes: 7 additions & 1 deletion GitUI/UserControls/RevisionGrid/FormRevisionFilter.cs
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,7 @@ public int GetMaxCount()

public string GetPathFilter()
{
return FileFilterCheck.Checked ? $" {FileFilter.Text.ToPosixPath()}" : "";
return FileFilterCheck.Checked ? FileFilter.Text : "";
}

public string GetInMemAuthorFilter()
Expand Down Expand Up @@ -149,6 +149,12 @@ public void SetBranchFilter(string filter)
BranchFilter.Text = filter?.Trim();
}

public void SetPathFilter(string filter)
{
FileFilter.Text = filter?.Trim();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you considered filenames with spaces at start/end?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GE added filenames (for FileHistory and a later PR for a context menu for RevDiff etc) are quouted when adding.
User added paths in Advanced filter must be correctly quoted by the user.
(There is a helper for quoting paths without spaces and quotes, likely the most common case).

A separate commit with the renaming would ease the review a little.

This was separated in several commits until I squashed it preparing for merge.
But commits are still separate in branch gerhardol/feature/i7598-replace-formfilehistory

FileFilterCheck.Checked = !string.IsNullOrWhiteSpace(filter);
}

private void OkClick(object sender, EventArgs e)
{
Close();
Expand Down
Loading