From eeb970034802211e49cd130c87fe49410322c362 Mon Sep 17 00:00:00 2001 From: SlugFiller <5435495+SlugFiller@users.noreply.github.com> Date: Sun, 1 Oct 2023 14:17:05 +0300 Subject: [PATCH] Allow user scripts to operate on selected files --- GitUI/CommandsDialogs/FormBrowse.cs | 6 +- GitUI/CommandsDialogs/FormCommit.Designer.cs | 40 +++++++- GitUI/CommandsDialogs/FormCommit.cs | 77 ++++++++++++++- .../RevisionDiffControl.Designer.cs | 20 +++- GitUI/CommandsDialogs/RevisionDiffControl.cs | 31 +++++- .../RevisionFileTreeControl.Designer.cs | 20 +++- .../RevisionFileTreeControl.cs | 31 +++++- .../Pages/ScriptsSettingsPage.cs | 10 +- .../UserScriptContextMenuExtensions.cs | 4 +- GitUI/GitModuleControl.cs | 9 +- GitUI/GitModuleForm.cs | 2 +- .../RepoObjectsTree.ContextActions.cs | 2 +- GitUI/Script/IRunScript.cs | 9 ++ GitUI/Script/ScriptEvent.cs | 3 +- GitUI/Script/ScriptManager.cs | 2 +- GitUI/Script/ScriptOptionsParser.cs | 98 +++++++++++++++++-- GitUI/Script/ScriptRunner.cs | 12 +-- GitUI/Translation/English.xlf | 26 ++++- .../RevisionGrid/RevisionGridControl.cs | 12 ++- .../Script/ScriptRunnerTests.cs | 25 ++--- .../Script/ScriptOptionsParserTests.cs | 43 ++++---- 21 files changed, 417 insertions(+), 65 deletions(-) diff --git a/GitUI/CommandsDialogs/FormBrowse.cs b/GitUI/CommandsDialogs/FormBrowse.cs index ab9474fb0dd..90c24e0b4b8 100644 --- a/GitUI/CommandsDialogs/FormBrowse.cs +++ b/GitUI/CommandsDialogs/FormBrowse.cs @@ -325,10 +325,10 @@ internal FormBrowse(GitUICommands commands, BrowseArguments args, ISettingsSourc _aheadBehindDataProvider = new AheadBehindDataProvider(() => Module.GitExecutable); toolStripButtonPush.Initialize(_aheadBehindDataProvider); repoObjectsTree.Initialize(_aheadBehindDataProvider, filterRevisionGridBySpaceSeparatedRefs: ToolStripFilters.SetBranchFilter, refsSource: RevisionGrid, revisionGridInfo: RevisionGrid, scriptRunner: RevisionGrid); - revisionDiff.Bind(revisionGridInfo: RevisionGrid, revisionGridUpdate: RevisionGrid, revisionFileTree: fileTree, () => RevisionGrid.CurrentFilter.PathFilter, RefreshGitStatusMonitor); + revisionDiff.Bind(revisionGridInfo: RevisionGrid, revisionGridUpdate: RevisionGrid, revisionFileTree: fileTree, () => RevisionGrid.CurrentFilter.PathFilter, RefreshGitStatusMonitor, scriptRunner: RevisionGrid); // Show blame by default if not started from command line - fileTree.Bind(revisionGridInfo: RevisionGrid, revisionGridUpdate: RevisionGrid, RefreshGitStatusMonitor, _isFileBlameHistory); + fileTree.Bind(revisionGridInfo: RevisionGrid, revisionGridUpdate: RevisionGrid, RefreshGitStatusMonitor, _isFileBlameHistory, scriptRunner: RevisionGrid); RevisionGrid.ResumeRefreshRevisions(); // Application is init, the repo related operations are triggered in OnLoad() @@ -1025,7 +1025,7 @@ void LoadUserMenu() button.Click += delegate { - if (ScriptRunner.RunScript(this, Module, script.Name, UICommands, RevisionGrid).NeedsGridRefresh) + if (ScriptRunner.RunScript(this, Module, script.Name, UICommands, RevisionGrid, null).NeedsGridRefresh) { RefreshRevisions(); } diff --git a/GitUI/CommandsDialogs/FormCommit.Designer.cs b/GitUI/CommandsDialogs/FormCommit.Designer.cs index 4b0ea19117e..30478148d63 100644 --- a/GitUI/CommandsDialogs/FormCommit.Designer.cs +++ b/GitUI/CommandsDialogs/FormCommit.Designer.cs @@ -45,6 +45,8 @@ private void InitializeComponent() this.assumeUnchangedToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.doNotAssumeUnchangedToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.interactiveAddToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparatorScript = new System.Windows.Forms.ToolStripSeparator(); + this.runScriptToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.fileTooltip = new System.Windows.Forms.ToolTip(this.components); this.StageInSuperproject = new System.Windows.Forms.CheckBox(); this.StagedFileContext = new System.Windows.Forms.ContextMenuStrip(this.components); @@ -60,6 +62,8 @@ private void InitializeComponent() this.stagedCopyPathToolStripMenuItem14 = new System.Windows.Forms.ToolStripMenuItem(); this.stagedOpenFolderToolStripMenuItem10 = new System.Windows.Forms.ToolStripMenuItem(); this.stagedEditFileToolStripMenuItem11 = new System.Windows.Forms.ToolStripMenuItem(); + this.stagedToolStripSeparatorScript = new System.Windows.Forms.ToolStripSeparator(); + this.stagedRunScriptToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.UnstagedSubmoduleContext = new System.Windows.Forms.ContextMenuStrip(this.components); this.commitSubmoduleChanges = new System.Windows.Forms.ToolStripMenuItem(); this.resetSubmoduleChanges = new System.Windows.Forms.ToolStripMenuItem(); @@ -210,7 +214,9 @@ private void InitializeComponent() this.skipWorktreeToolStripMenuItem, this.doNotSkipWorktreeToolStripMenuItem, this.assumeUnchangedToolStripMenuItem, - this.doNotAssumeUnchangedToolStripMenuItem}); + this.doNotAssumeUnchangedToolStripMenuItem, + this.toolStripSeparatorScript, + this.runScriptToolStripMenuItem}); this.UnstagedFileContext.Name = "UnstagedFileContext"; this.UnstagedFileContext.Size = new System.Drawing.Size(233, 414); this.UnstagedFileContext.Opening += UnstagedFileContext_Opening; @@ -373,6 +379,18 @@ private void InitializeComponent() this.interactiveAddToolStripMenuItem.Text = "Interactive Add"; this.interactiveAddToolStripMenuItem.Click += new System.EventHandler(this.interactiveAddToolStripMenuItem_Click); // + // toolStripSeparatorScript + // + this.toolStripSeparatorScript.Name = "toolStripSeparatorScript"; + this.toolStripSeparatorScript.Size = new System.Drawing.Size(259, 6); + // + // runScriptToolStripMenuItem + // + this.runScriptToolStripMenuItem.Image = global::GitUI.Properties.Images.Console; + this.runScriptToolStripMenuItem.Name = "runScriptToolStripMenuItem"; + this.runScriptToolStripMenuItem.Size = new System.Drawing.Size(262, 22); + this.runScriptToolStripMenuItem.Text = "Run script"; + // // StageInSuperproject // this.StageInSuperproject.AutoSize = true; @@ -400,7 +418,9 @@ private void InitializeComponent() this.stagedCopyPathToolStripMenuItem14, this.stagedOpenFolderToolStripMenuItem10, this.stagedFileHistoryToolStripSeparator, - this.stagedFileHistoryToolStripMenuItem6}); + this.stagedFileHistoryToolStripMenuItem6, + this.stagedToolStripSeparatorScript, + this.stagedRunScriptToolStripMenuItem}); this.StagedFileContext.Name = "StagedFileContext"; this.StagedFileContext.Size = new System.Drawing.Size(233, 198); this.StagedFileContext.Opening += StagedFileContext_Opening; @@ -490,6 +510,18 @@ private void InitializeComponent() this.stagedEditFileToolStripMenuItem11.Text = "Edit file"; this.stagedEditFileToolStripMenuItem11.Click += new System.EventHandler(this.editFileToolStripMenuItem_Click); // + // stagedToolStripSeparatorScript + // + this.stagedToolStripSeparatorScript.Name = "stagedToolStripSeparatorScript"; + this.stagedToolStripSeparatorScript.Size = new System.Drawing.Size(259, 6); + // + // stagedRunScriptToolStripMenuItem + // + this.stagedRunScriptToolStripMenuItem.Image = global::GitUI.Properties.Images.Console; + this.stagedRunScriptToolStripMenuItem.Name = "stagedRunScriptToolStripMenuItem"; + this.stagedRunScriptToolStripMenuItem.Size = new System.Drawing.Size(262, 22); + this.stagedRunScriptToolStripMenuItem.Text = "Run script"; + // // UnstagedSubmoduleContext // this.UnstagedSubmoduleContext.Items.AddRange(new System.Windows.Forms.ToolStripItem[] { @@ -1657,6 +1689,10 @@ private void InitializeComponent() private ToolStripMenuItem stagedCopyPathToolStripMenuItem14; private ToolStripSeparator toolStripSeparator12; private ToolStripMenuItem interactiveAddToolStripMenuItem; + private ToolStripSeparator toolStripSeparatorScript; + private ToolStripMenuItem runScriptToolStripMenuItem; + private ToolStripSeparator stagedToolStripSeparatorScript; + private ToolStripMenuItem stagedRunScriptToolStripMenuItem; private TableLayoutPanel tableLayoutPanel1; private StatusStrip commitStatusStrip; private ToolStripStatusLabel commitAuthorStatus; diff --git a/GitUI/CommandsDialogs/FormCommit.cs b/GitUI/CommandsDialogs/FormCommit.cs index f99679d315b..c5ce8092372 100644 --- a/GitUI/CommandsDialogs/FormCommit.cs +++ b/GitUI/CommandsDialogs/FormCommit.cs @@ -28,7 +28,7 @@ namespace GitUI.CommandsDialogs { - public sealed partial class FormCommit : GitModuleForm + public sealed partial class FormCommit : GitModuleForm, IRunScript.IFileListSource { #region Translation @@ -184,6 +184,8 @@ public sealed partial class FormCommit : GitModuleForm private IReadOnlyList? _currentSelection; private int _alreadyLoadedTemplatesCount = -1; private EventHandler? _branchNameLabelOnClick; + private IRunScript.IFileListSource _unstagedFileListSource; + private IRunScript.IFileListSource _stagedFileListSource; private CommitKind CommitKind { @@ -220,6 +222,8 @@ public FormCommit(GitUICommands commands, CommitKind commitKind = CommitKind.Nor ThreadHelper.ThrowIfNotOnUIThread(); _editedCommit = editedCommit; + _unstagedFileListSource = new RunScriptFileListSource(this, false); + _stagedFileListSource = new RunScriptFileListSource(this, true); InitializeComponent(); @@ -1660,6 +1664,8 @@ private void UnstagedFileContext_Opening(object sender, System.ComponentModel.Ca openWithToolStripMenuItem.Enabled = !isAnyDeleted; deleteFileToolStripMenuItem.Enabled = !isAnyDeleted; openContainingFolderToolStripMenuItem.Enabled = !isAnyDeleted; + + UnstagedFileContext.AddUserScripts(runScriptToolStripMenuItem, ExecuteScriptUnstaged, (script) => script.OnEvent == ScriptEvent.ShowInFileList); } private void StagedFileContext_Opening(object sender, System.ComponentModel.CancelEventArgs e) @@ -1679,6 +1685,8 @@ private void StagedFileContext_Opening(object sender, System.ComponentModel.Canc stagedOpenToolStripMenuItem7.Enabled = !isAnyDeleted; stagedOpenWithToolStripMenuItem8.Enabled = !isAnyDeleted; stagedOpenFolderToolStripMenuItem10.Enabled = !isAnyDeleted; + + StagedFileContext.AddUserScripts(stagedRunScriptToolStripMenuItem, ExecuteScriptStaged, (script) => script.OnEvent == ScriptEvent.ShowInFileList); } private void UnstagedSubmoduleContext_Opening(object sender, System.ComponentModel.CancelEventArgs e) @@ -3317,6 +3325,73 @@ private void UpdateButtonStates() : TranslatedStrings.ButtonPush; } + private class RunScriptFileListSource : IRunScript.IFileListSource + { + private FormCommit _formCommit; + private bool _staged; + + public RunScriptFileListSource(FormCommit formCommit, bool staged) + { + _formCommit = formCommit; + _staged = staged; + } + + List IRunScript.IFileListSource.GetFiles() + { + IReadOnlyList selectedItems = (_staged ? _formCommit.Staged : _formCommit.Unstaged).SelectedItems.ToList(); + + if (!selectedItems.Any()) + { + return null; + } + + return selectedItems.Select(item => _formCommit._fullPathResolver.Resolve(item.Item.Name)).ToList(); + } + + int? IRunScript.IFileListSource.GetLineNumber() + { + return _formCommit.SelectedDiff.CurrentFileLine; + } + } + + private void ExecuteScriptUnstaged(string name) + { + if (ScriptRunner.RunScript(this, Module, name, UICommands, null, _unstagedFileListSource).NeedsGridRefresh) + { + RescanChanges(); + } + } + + private void ExecuteScriptStaged(string name) + { + if (ScriptRunner.RunScript(this, Module, name, UICommands, null, _stagedFileListSource).NeedsGridRefresh) + { + RescanChanges(); + } + } + + List IRunScript.IFileListSource.GetFiles() + { + if (_currentFilesList is null) + { + return null; + } + + IReadOnlyList selectedItems = _currentFilesList.SelectedItems.ToList(); + + if (!selectedItems.Any()) + { + return null; + } + + return selectedItems.Select(item => _fullPathResolver.Resolve(item.Item.Name)).ToList(); + } + + int? IRunScript.IFileListSource.GetLineNumber() + { + return SelectedDiff.CurrentFileLine; + } + internal TestAccessor GetTestAccessor() => new(this); diff --git a/GitUI/CommandsDialogs/RevisionDiffControl.Designer.cs b/GitUI/CommandsDialogs/RevisionDiffControl.Designer.cs index 8ed2c72b162..ce6cde90794 100644 --- a/GitUI/CommandsDialogs/RevisionDiffControl.Designer.cs +++ b/GitUI/CommandsDialogs/RevisionDiffControl.Designer.cs @@ -60,6 +60,8 @@ private void InitializeComponent() this.fileHistoryDiffToolstripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.blameToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.findInDiffToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparatorScript = new System.Windows.Forms.ToolStripSeparator(); + this.runScriptToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.DiffText = new GitUI.Editor.FileViewer(); this.BlameControl = new Blame.BlameControl(); this.saveToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); @@ -145,7 +147,9 @@ private void InitializeComponent() this.diffFilterFileInGridToolStripMenuItem, this.fileHistoryDiffToolstripMenuItem, this.blameToolStripMenuItem, - this.findInDiffToolStripMenuItem}); + this.findInDiffToolStripMenuItem, + this.toolStripSeparatorScript, + this.runScriptToolStripMenuItem}); this.DiffContextMenu.Name = "DiffContextMenu"; this.DiffContextMenu.Size = new System.Drawing.Size(263, 534); this.DiffContextMenu.Opening += new System.ComponentModel.CancelEventHandler(this.DiffContextMenu_Opening); @@ -436,6 +440,18 @@ private void InitializeComponent() this.findInDiffToolStripMenuItem.Text = "&Find"; this.findInDiffToolStripMenuItem.Click += new System.EventHandler(this.findInDiffToolStripMenuItem_Click); // + // toolStripSeparatorScript + // + this.toolStripSeparatorScript.Name = "toolStripSeparatorScript"; + this.toolStripSeparatorScript.Size = new System.Drawing.Size(259, 6); + // + // runScriptToolStripMenuItem + // + this.runScriptToolStripMenuItem.Image = global::GitUI.Properties.Images.Console; + this.runScriptToolStripMenuItem.Name = "runScriptToolStripMenuItem"; + this.runScriptToolStripMenuItem.Size = new System.Drawing.Size(262, 22); + this.runScriptToolStripMenuItem.Text = "Run script"; + // // DiffText // this.DiffText.Dock = System.Windows.Forms.DockStyle.Fill; @@ -517,5 +533,7 @@ private void InitializeComponent() private ToolStripMenuItem diffOpenWorkingDirectoryFileWithToolStripMenuItem; private ToolStripMenuItem diffOpenRevisionFileToolStripMenuItem; private ToolStripMenuItem diffOpenRevisionFileWithToolStripMenuItem; + private ToolStripSeparator toolStripSeparatorScript; + private ToolStripMenuItem runScriptToolStripMenuItem; } } diff --git a/GitUI/CommandsDialogs/RevisionDiffControl.cs b/GitUI/CommandsDialogs/RevisionDiffControl.cs index 75332fa6054..0b7a4965f39 100644 --- a/GitUI/CommandsDialogs/RevisionDiffControl.cs +++ b/GitUI/CommandsDialogs/RevisionDiffControl.cs @@ -7,6 +7,7 @@ using GitUI.CommandsDialogs.BrowseDialog; using GitUI.HelperDialogs; using GitUI.Hotkey; +using GitUI.Script; using GitUI.UserControls; using GitUI.UserControls.RevisionGrid; using GitUIPluginInterfaces; @@ -16,7 +17,7 @@ namespace GitUI.CommandsDialogs { - public partial class RevisionDiffControl : GitModuleControl + public partial class RevisionDiffControl : GitModuleControl, IRunScript.IFileListSource { private readonly TranslationString _saveFileFilterCurrentFormat = new("Current format"); private readonly TranslationString _saveFileFilterAllFiles = new("All files"); @@ -50,6 +51,7 @@ public partial class RevisionDiffControl : GitModuleControl private string? _fallbackFollowedFile; private FileStatusItem? _lastExplicitlySelectedItem; private bool _isImplicitListSelection = false; + private IRunScript _scriptRunner; public RevisionDiffControl() { @@ -306,13 +308,14 @@ private async Task SetDiffsAsync(IReadOnlyList revisions) } } - public void Bind(IRevisionGridInfo revisionGridInfo, IRevisionGridUpdate revisionGridUpdate, RevisionFileTreeControl revisionFileTree, Func? pathFilter, Action? refreshGitStatus) + public void Bind(IRevisionGridInfo revisionGridInfo, IRevisionGridUpdate revisionGridUpdate, RevisionFileTreeControl revisionFileTree, Func? pathFilter, Action? refreshGitStatus, IRunScript scriptRunner) { _revisionGridInfo = revisionGridInfo; _revisionGridUpdate = revisionGridUpdate; _revisionFileTree = revisionFileTree; _pathFilter = pathFilter; _refreshGitStatus = refreshGitStatus; + _scriptRunner = scriptRunner; DiffFiles.Bind(objectId => DescribeRevision(objectId), _revisionGridInfo.GetActualRevision); } @@ -748,6 +751,8 @@ private void UpdateStatusOfMenuItems() { blameToolStripMenuItem.Checked = false; } + + DiffContextMenu.AddUserScripts(runScriptToolStripMenuItem, Execute, (script) => script.OnEvent == ScriptEvent.ShowInFileList); } private void DiffContextMenu_Opening(object sender, CancelEventArgs e) @@ -1453,6 +1458,28 @@ private void ResetSelectedItemsWithConfirmation(bool resetToParent) private static bool RenamedIndexItem(FileStatusItem item) => item.Item.IsRenamed && item.Item.Staged == StagedStatus.Index; + List IRunScript.IFileListSource.GetFiles() + { + IReadOnlyList selectedItems = DiffFiles.SelectedItems.ToList(); + + if (!selectedItems.Any()) + { + return null; + } + + return selectedItems.Select(item => _fullPathResolver.Resolve(item.Item.Name)).ToList(); + } + + int? IRunScript.IFileListSource.GetLineNumber() + { + return BlameControl.Visible ? BlameControl.CurrentFileLine : DiffText.CurrentFileLine; + } + + private void Execute(string name) + { + _scriptRunner.Execute(name, this); + } + internal void RegisterGitHostingPluginInBlameControl() { BlameControl.ConfigureRepositoryHostPlugin(PluginRegistry.TryGetGitHosterForModule(Module)); diff --git a/GitUI/CommandsDialogs/RevisionFileTreeControl.Designer.cs b/GitUI/CommandsDialogs/RevisionFileTreeControl.Designer.cs index 5bc853c8a24..c79f49caf28 100644 --- a/GitUI/CommandsDialogs/RevisionFileTreeControl.Designer.cs +++ b/GitUI/CommandsDialogs/RevisionFileTreeControl.Designer.cs @@ -48,6 +48,8 @@ private void InitializeComponent() this.findToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.expandToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.collapseAllToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); + this.toolStripSeparatorScript = new System.Windows.Forms.ToolStripSeparator(); + this.runScriptToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem(); this.FileText = new GitUI.Editor.FileViewer(); this.BlameControl = new Blame.BlameControl(); ((System.ComponentModel.ISupportInitialize)(this.FileTreeSplitContainer)).BeginInit(); @@ -125,7 +127,9 @@ private void InitializeComponent() this.toolStripSeparatorGitTrackingActions, this.findToolStripMenuItem, this.expandToolStripMenuItem, - this.collapseAllToolStripMenuItem}); + this.collapseAllToolStripMenuItem, + this.toolStripSeparatorScript, + this.runScriptToolStripMenuItem}); this.FileTreeContextMenu.Name = "FileTreeContextMenu"; this.FileTreeContextMenu.Size = new System.Drawing.Size(326, 474); this.FileTreeContextMenu.Opening += new System.ComponentModel.CancelEventHandler(this.FileTreeContextMenu_Opening); @@ -323,6 +327,18 @@ private void InitializeComponent() this.collapseAllToolStripMenuItem.Text = "Collapse All"; this.collapseAllToolStripMenuItem.Click += new System.EventHandler(this.collapseAllToolStripMenuItem_Click); // + // toolStripSeparatorScript + // + this.toolStripSeparatorScript.Name = "toolStripSeparatorScript"; + this.toolStripSeparatorScript.Size = new System.Drawing.Size(259, 6); + // + // runScriptToolStripMenuItem + // + this.runScriptToolStripMenuItem.Image = global::GitUI.Properties.Images.Console; + this.runScriptToolStripMenuItem.Name = "runScriptToolStripMenuItem"; + this.runScriptToolStripMenuItem.Size = new System.Drawing.Size(262, 22); + this.runScriptToolStripMenuItem.Text = "Run script"; + // // FileText // this.FileText.Dock = System.Windows.Forms.DockStyle.Fill; @@ -391,5 +407,7 @@ private void InitializeComponent() private System.Windows.Forms.ToolStripSeparator toolStripSeparatorGitActions; private System.Windows.Forms.ToolStripMenuItem stopTrackingThisFileToolStripMenuItem; private System.Windows.Forms.ToolStripMenuItem expandToolStripMenuItem; + private System.Windows.Forms.ToolStripSeparator toolStripSeparatorScript; + private System.Windows.Forms.ToolStripMenuItem runScriptToolStripMenuItem; } } diff --git a/GitUI/CommandsDialogs/RevisionFileTreeControl.cs b/GitUI/CommandsDialogs/RevisionFileTreeControl.cs index 2e173728a36..ff0be555372 100644 --- a/GitUI/CommandsDialogs/RevisionFileTreeControl.cs +++ b/GitUI/CommandsDialogs/RevisionFileTreeControl.cs @@ -8,6 +8,7 @@ using GitUI.CommandsDialogs.BrowseDialog; using GitUI.Hotkey; using GitUI.Properties; +using GitUI.Script; using GitUI.UserControls; using GitUIPluginInterfaces; using Microsoft; @@ -15,7 +16,7 @@ namespace GitUI.CommandsDialogs { - public partial class RevisionFileTreeControl : GitModuleControl + public partial class RevisionFileTreeControl : GitModuleControl, IRunScript.IFileListSource { private readonly TranslationString _resetFileCaption = new("Reset"); private readonly TranslationString _resetFileText = new("Are you sure you want to reset this file or directory?"); @@ -53,6 +54,7 @@ public partial class RevisionFileTreeControl : GitModuleControl private IRevisionGridInfo? _revisionGridInfo; private IRevisionGridUpdate? _revisionGridUpdate; private readonly CancellationTokenSequence _viewBlameSequence = new(); + private IRunScript _scriptRunner; public RevisionFileTreeControl() { @@ -69,11 +71,12 @@ public RevisionFileTreeControl() copyPathsToolStripMenuItem.Initialize(() => UICommands, () => new string[] { (tvGitTree.SelectedNode?.Tag as GitItem)?.FileName }); } - public void Bind(IRevisionGridInfo revisionGridInfo, IRevisionGridUpdate revisionGridUpdate, Action? refreshGitStatus, bool isBlame) + public void Bind(IRevisionGridInfo revisionGridInfo, IRevisionGridUpdate revisionGridUpdate, Action? refreshGitStatus, bool isBlame, IRunScript scriptRunner) { _revisionGridInfo = revisionGridInfo; _revisionGridUpdate = revisionGridUpdate; _refreshGitStatus = refreshGitStatus; + _scriptRunner = scriptRunner; blameToolStripMenuItem1.Checked = isBlame; } @@ -740,6 +743,8 @@ private void FileTreeContextMenu_Opening(object sender, System.ComponentModel.Ca findToolStripMenuItem.Enabled = tvGitTree.Nodes.Count > 0; expandToolStripMenuItem.Visible = isFolder; collapseAllToolStripMenuItem.Visible = isFolder; + + FileTreeContextMenu.AddUserScripts(runScriptToolStripMenuItem, Execute, (script) => script.OnEvent == ScriptEvent.ShowInFileList); } private void fileTreeOpenContainingFolderToolStripMenuItem_Click(object sender, EventArgs e) @@ -966,6 +971,28 @@ public bool SelectFileOrFolder(string filePath) return _revisionFileTreeController.SelectFileOrFolder(tvGitTree, filePath[Module.WorkingDir.Length..]); } + List IRunScript.IFileListSource.GetFiles() + { + if (tvGitTree.SelectedNode?.Tag is not GitItem gitItem || gitItem.ObjectType != GitObjectType.Blob) + { + return null; + } + + var fileName = _fullPathResolver.Resolve(gitItem.FileName); + Validates.NotNull(fileName); + return new List() { fileName }; + } + + int? IRunScript.IFileListSource.GetLineNumber() + { + return BlameControl.Visible ? BlameControl.CurrentFileLine : FileText.CurrentFileLine; + } + + private void Execute(string name) + { + _scriptRunner.Execute(name, this); + } + internal void RegisterGitHostingPluginInBlameControl() { BlameControl.ConfigureRepositoryHostPlugin(PluginRegistry.TryGetGitHosterForModule(Module)); diff --git a/GitUI/CommandsDialogs/SettingsDialog/Pages/ScriptsSettingsPage.cs b/GitUI/CommandsDialogs/SettingsDialog/Pages/ScriptsSettingsPage.cs index 4e88155c83f..99379267465 100644 --- a/GitUI/CommandsDialogs/SettingsDialog/Pages/ScriptsSettingsPage.cs +++ b/GitUI/CommandsDialogs/SettingsDialog/Pages/ScriptsSettingsPage.cs @@ -21,6 +21,10 @@ public partial class ScriptsSettingsPage : SettingsPageWithHeader {UserInput} {UserFiles} +Conditional: +{if:option}{/if} +{ifnot:option}{/ifnot} + Working Dir: {WorkingDir} @@ -62,7 +66,11 @@ public partial class ScriptsSettingsPage : SettingsPageWithHeader {cCommitDate} {cDefaultRemote} {cDefaultRemoteUrl} -{cDefaultRemotePathFromUrl}"); +{cDefaultRemotePathFromUrl} + +File List: +{fSelectedFiles} +{fLineNumber}"); private static readonly string[] WatchedProxyProperties = new string[] { diff --git a/GitUI/CommandsDialogs/UserScriptContextMenuExtensions.cs b/GitUI/CommandsDialogs/UserScriptContextMenuExtensions.cs index 40e1d3dfb3b..1b4e178a948 100644 --- a/GitUI/CommandsDialogs/UserScriptContextMenuExtensions.cs +++ b/GitUI/CommandsDialogs/UserScriptContextMenuExtensions.cs @@ -19,7 +19,7 @@ public static class UserScriptContextMenuExtensions /// The context menu to add user scripts to. /// The menu item user scripts not marked as are added to. /// The handler that handles user script invocation. - public static void AddUserScripts(this ContextMenuStrip contextMenu, ToolStripMenuItem hostMenuItem, Action scriptInvoker) + public static void AddUserScripts(this ContextMenuStrip contextMenu, ToolStripMenuItem hostMenuItem, Action scriptInvoker, Func filterAddDirect) { contextMenu = contextMenu ?? throw new ArgumentNullException(nameof(contextMenu)); hostMenuItem = hostMenuItem ?? throw new ArgumentNullException(nameof(hostMenuItem)); @@ -49,7 +49,7 @@ public static void AddUserScripts(this ContextMenuStrip contextMenu, ToolStripMe } }; - if (script.AddToRevisionGridContextMenu) + if (filterAddDirect(script)) { // insert items after hostMenuItem contextMenu.Items.Insert(++lastScriptItemIndex, item); diff --git a/GitUI/GitModuleControl.cs b/GitUI/GitModuleControl.cs index 9b35b7123f5..d94887bc3b9 100644 --- a/GitUI/GitModuleControl.cs +++ b/GitUI/GitModuleControl.cs @@ -1,6 +1,7 @@ using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using GitCommands; +using GitUI.Script; using ResourceManager; namespace GitUI @@ -149,7 +150,13 @@ CommandStatus ExecuteScriptCommand() revisionGridControl = (FindForm() as GitModuleForm)?.RevisionGridControl; } - return Script.ScriptRunner.ExecuteScriptCommand(this, Module, command, UICommands, revisionGridControl); + Control files = this; + while (files is not null && files is not IRunScript.IFileListSource && files is not GitModuleForm) + { + files = files.Parent; + } + + return Script.ScriptRunner.ExecuteScriptCommand(this, Module, command, UICommands, revisionGridControl, files as IRunScript.IFileListSource); } } diff --git a/GitUI/GitModuleForm.cs b/GitUI/GitModuleForm.cs index b4e6751485e..8202f9773a4 100644 --- a/GitUI/GitModuleForm.cs +++ b/GitUI/GitModuleForm.cs @@ -81,7 +81,7 @@ protected GitModuleForm([NotNull] GitUICommands commands) protected override CommandStatus ExecuteCommand(int command) { - CommandStatus result = ScriptRunner.ExecuteScriptCommand(this, Module, command, UICommands, RevisionGridControl); + CommandStatus result = ScriptRunner.ExecuteScriptCommand(this, Module, command, UICommands, RevisionGridControl, this as IRunScript.IFileListSource); if (!result.Executed) { result = base.ExecuteCommand(command); diff --git a/GitUI/LeftPanel/RepoObjectsTree.ContextActions.cs b/GitUI/LeftPanel/RepoObjectsTree.ContextActions.cs index 3881592a806..487ae011589 100644 --- a/GitUI/LeftPanel/RepoObjectsTree.ContextActions.cs +++ b/GitUI/LeftPanel/RepoObjectsTree.ContextActions.cs @@ -245,7 +245,7 @@ private void contextMenu_Opening(object sender, CancelEventArgs e) if (hasSingleSelection && selectedLocalBranch?.Visible == true) { - contextMenu.AddUserScripts(runScriptToolStripMenuItem, _scriptRunner.Execute); + contextMenu.AddUserScripts(runScriptToolStripMenuItem, _scriptRunner.Execute, (script) => script.AddToRevisionGridContextMenu); } else { diff --git a/GitUI/Script/IRunScript.cs b/GitUI/Script/IRunScript.cs index 84f946cecd0..f69a6afb77b 100644 --- a/GitUI/Script/IRunScript.cs +++ b/GitUI/Script/IRunScript.cs @@ -3,5 +3,14 @@ public interface IRunScript { void Execute(string name); + + void Execute(string name, IFileListSource files); + + public interface IFileListSource + { + List GetFiles(); + + int? GetLineNumber(); + } } } diff --git a/GitUI/Script/ScriptEvent.cs b/GitUI/Script/ScriptEvent.cs index 222f6cd839a..c0236f9586d 100644 --- a/GitUI/Script/ScriptEvent.cs +++ b/GitUI/Script/ScriptEvent.cs @@ -15,6 +15,7 @@ public enum ScriptEvent BeforeMerge, AfterMerge, BeforeFetch, - AfterFetch + AfterFetch, + ShowInFileList } } diff --git a/GitUI/Script/ScriptManager.cs b/GitUI/Script/ScriptManager.cs index 0c5c184166c..b23499cef88 100644 --- a/GitUI/Script/ScriptManager.cs +++ b/GitUI/Script/ScriptManager.cs @@ -53,7 +53,7 @@ public static bool RunEventScripts(GitModuleForm form, ScriptEvent scriptEvent) { foreach (var script in GetScripts().Where(scriptInfo => scriptInfo.Enabled && scriptInfo.OnEvent == scriptEvent)) { - var result = ScriptRunner.RunScript(form, form.Module, script.Name, form.UICommands, revisionGrid: null); + var result = ScriptRunner.RunScript(form, form.Module, script.Name, form.UICommands, revisionGrid: null, files: null); if (!result.Executed) { return false; diff --git a/GitUI/Script/ScriptOptionsParser.cs b/GitUI/Script/ScriptOptionsParser.cs index 4192d20d307..f9e0d1f958b 100644 --- a/GitUI/Script/ScriptOptionsParser.cs +++ b/GitUI/Script/ScriptOptionsParser.cs @@ -1,6 +1,8 @@ -using System.Text.RegularExpressions; +using System.Text; +using System.Text.RegularExpressions; using GitCommands.Config; using GitCommands.UserRepositoryHistory; +using GitCommands.Utils; using GitUI.UserControls.RevisionGrid; using GitUIPluginInterfaces; @@ -18,6 +20,8 @@ public sealed class ScriptOptionsParser /// public static readonly IReadOnlyList Options = new[] { + "fSelectedFiles", + "fLineNumber", "sHashes", "sTag", "sBranch", @@ -81,7 +85,7 @@ public static bool DependsOnSelectedRevision(string option) return option.StartsWith("s"); } - public static (string? arguments, bool abort) Parse(string? arguments, IGitUICommands uiCommands, IWin32Window owner, IScriptHostControl? scriptHostControl) + public static (string? arguments, bool abort) Parse(string? arguments, IGitUICommands uiCommands, IWin32Window owner, IScriptHostControl? scriptHostControl, IRunScript.IFileListSource fileListSource) { if (string.IsNullOrWhiteSpace(arguments)) { @@ -105,10 +109,13 @@ public static (string? arguments, bool abort) Parse(string? arguments, IGitUICom var currentRemote = ""; List currentBranches = new(); List currentTags = new(); + bool parsedSelectedFiles = false; + List selectedFiles = null; + int? lineNumber = null; foreach (string option in Options) { - if (!Contains(arguments, option)) + if (!Contains(arguments, option) && !Contains(arguments, "{if:" + option + "}") && !Contains(arguments, "{ifnot:" + option + "}")) { continue; } @@ -145,8 +152,18 @@ public static (string? arguments, bool abort) Parse(string? arguments, IGitUICom return (arguments: null, abort: true); } } + else if (!parsedSelectedFiles && option.StartsWith("f")) + { + if (fileListSource is not null) + { + selectedFiles = fileListSource.GetFiles(); + lineNumber = fileListSource.GetLineNumber(); + } + + parsedSelectedFiles = true; + } - arguments = ParseScriptArguments(arguments, option, owner, scriptHostControl, uiCommands, allSelectedRevisions, selectedTags, selectedBranches, selectedLocalBranches, selectedRemoteBranches, selectedRemotes, selectedRevision!, currentTags, currentBranches, currentLocalBranches, currentRemoteBranches, currentRevision!, currentRemote); + arguments = ParseScriptArguments(arguments, option, owner, scriptHostControl, uiCommands, allSelectedRevisions, selectedTags, selectedBranches, selectedLocalBranches, selectedRemoteBranches, selectedRemotes, selectedRevision!, currentTags, currentBranches, currentLocalBranches, currentRemoteBranches, currentRevision!, currentRemote, selectedFiles, lineNumber); if (arguments is null) { return (arguments: null, abort: true); @@ -282,13 +299,22 @@ private static string GetRemotePath(string url) in IList selectedTags, in IList selectedBranches, in IList selectedLocalBranches, in IList selectedRemoteBranches, in IList selectedRemotes, GitRevision selectedRevision, in IList currentTags, in IList currentBranches, in IList currentLocalBranches, - in IList currentRemoteBranches, GitRevision currentRevision, string currentRemote) + in IList currentRemoteBranches, GitRevision currentRevision, string currentRemote, + in IList selectedFiles, int? lineNumber) { string? newString = null; string remote; string url; switch (option) { + case "fSelectedFiles": + newString = selectedFiles is not null ? string.Join(" ", selectedFiles.Select(file => "\"" + (EnvUtils.RunningOnWindows() ? file.Replace("\"", "\"\"") : file.Replace("\\", "\\\\").Replace("\"", "\\\"").Replace("\'", "\\\'")) + "\"").ToArray()) : null; + break; + + case "fLineNumber": + newString = lineNumber is not null ? $"{lineNumber}" : null; + break; + case "sHashes": newString = string.Join(" ", allSelectedRevisions.Select(revision => revision.Guid).ToArray()); break; @@ -495,6 +521,14 @@ private static string GetRemotePath(string url) arguments = arguments.Replace(CreateOption(option, quoted: true), newStringQuoted); arguments = arguments.Replace(CreateOption(option, quoted: false), newString); + + arguments = FilterConditionals(arguments, option, true, true); + arguments = FilterConditionals(arguments, option, false, false); + } + else + { + arguments = FilterConditionals(arguments, option, true, false); + arguments = FilterConditionals(arguments, option, false, true); } return arguments; @@ -542,6 +576,56 @@ private static string StripRemoteName(string remoteBranchName) return remoteBranchName; } + private static string FilterConditionals(string source, string option, bool positiveCondition, bool keep) + { + string exactMatch = positiveCondition ? ("{if:" + option + "}") : ("{ifnot:" + option + "}"); + int prevOffset = 0; + int depth = 0; + List depthStack = new(); + StringBuilder sb = new(); + foreach (Match match in Regex.Matches(source, positiveCondition ? @"(\{if:[A-Za-z]*\})|(\{/if\})" : @"(\{ifnot:[A-Za-z]*\})|(\{/ifnot\})")) + { + bool write = keep || depthStack.Count <= 0; + bool skip = false; + if (match.Value.StartsWith("{/")) + { + if (depth > 0) + { + depth--; + if (depthStack.Count > 0 && depthStack[depthStack.Count - 1] == depth) + { + skip = true; + depthStack.RemoveAt(depthStack.Count - 1); + } + } + } + else + { + if (match.Value == exactMatch) + { + skip = true; + depthStack.Add(depth); + } + + depth++; + } + + if (write) + { + sb.Append(source.Substring(prevOffset, match.Index + (skip ? 0 : match.Length) - prevOffset)); + } + + prevOffset = match.Index + match.Length; + } + + if (keep || depthStack.Count <= 0) + { + sb.Append(source.Substring(prevOffset)); + } + + return sb.ToString(); + } + internal static TestAccessor GetTestAccessor() => new(); internal readonly struct TestAccessor @@ -557,10 +641,10 @@ public string CreateOption(string option, bool quoted) IGitUICommands uiCommands, IReadOnlyList allSelectedRevisions, List selectedTags, List selectedBranches, List selectedLocalBranches, List selectedRemoteBranches, List selectedRemotes, GitRevision selectedRevision, List currentTags, List currentBranches, List currentLocalBranches, List currentRemoteBranches, - GitRevision currentRevision, string currentRemote) + GitRevision currentRevision, string currentRemote, in IList selectedFiles, int? lineNumber) => ScriptOptionsParser.ParseScriptArguments(arguments, option, owner, scriptHostControl, uiCommands, allSelectedRevisions, selectedTags, selectedBranches, selectedLocalBranches, selectedRemoteBranches, selectedRemotes, selectedRevision, - currentTags, currentBranches, currentLocalBranches, currentRemoteBranches, currentRevision, currentRemote); + currentTags, currentBranches, currentLocalBranches, currentRemoteBranches, currentRevision, currentRemote, selectedFiles, lineNumber); } } } diff --git a/GitUI/Script/ScriptRunner.cs b/GitUI/Script/ScriptRunner.cs index 866edb6f422..cbd218414ed 100644 --- a/GitUI/Script/ScriptRunner.cs +++ b/GitUI/Script/ScriptRunner.cs @@ -10,7 +10,7 @@ namespace GitUI.Script public static class ScriptRunner { /// Tries to run scripts identified by a . - public static CommandStatus ExecuteScriptCommand(IWin32Window owner, GitModule module, int command, IGitUICommands uiCommands, RevisionGridControl? revisionGrid) + public static CommandStatus ExecuteScriptCommand(IWin32Window owner, GitModule module, int command, IGitUICommands uiCommands, RevisionGridControl? revisionGrid, IRunScript.IFileListSource files) { bool anyScriptExecuted = false; bool needsGridRefresh = false; @@ -19,7 +19,7 @@ public static CommandStatus ExecuteScriptCommand(IWin32Window owner, GitModule m { if (script.HotkeyCommandIdentifier == command) { - CommandStatus result = RunScript(owner, module, script.Name, uiCommands, revisionGrid); + CommandStatus result = RunScript(owner, module, script.Name, uiCommands, revisionGrid, files); anyScriptExecuted = true; needsGridRefresh |= result.NeedsGridRefresh; } @@ -28,11 +28,11 @@ public static CommandStatus ExecuteScriptCommand(IWin32Window owner, GitModule m return new CommandStatus(anyScriptExecuted, needsGridRefresh); } - public static CommandStatus RunScript(IWin32Window owner, IGitModule module, string? scriptKey, IGitUICommands uiCommands, RevisionGridControl? revisionGrid) + public static CommandStatus RunScript(IWin32Window owner, IGitModule module, string? scriptKey, IGitUICommands uiCommands, RevisionGridControl? revisionGrid, IRunScript.IFileListSource files) { try { - return RunScriptInternal(owner, module, scriptKey, uiCommands, revisionGrid); + return RunScriptInternal(owner, module, scriptKey, uiCommands, revisionGrid, files); } catch (ExternalOperationException ex) when (ex is not UserExternalOperationException) { @@ -40,7 +40,7 @@ public static CommandStatus RunScript(IWin32Window owner, IGitModule module, str } } - private static CommandStatus RunScriptInternal(IWin32Window owner, IGitModule module, string? scriptKey, IGitUICommands uiCommands, RevisionGridControl? revisionGrid) + private static CommandStatus RunScriptInternal(IWin32Window owner, IGitModule module, string? scriptKey, IGitUICommands uiCommands, RevisionGridControl? revisionGrid, IRunScript.IFileListSource files) { if (string.IsNullOrEmpty(scriptKey)) { @@ -80,7 +80,7 @@ private static CommandStatus RunScriptInternal(IWin32Window owner, IGitModule mo } string? originalCommand = scriptInfo.Command; - (string? argument, bool abort) = ScriptOptionsParser.Parse(scriptInfo.Arguments, uiCommands, owner, revisionGrid); + (string? argument, bool abort) = ScriptOptionsParser.Parse(scriptInfo.Arguments, uiCommands, owner, revisionGrid, files); if (abort) { throw new UserExternalOperationException($"{TranslatedStrings.ScriptText}: '{scriptKey}'{Environment.NewLine}{TranslatedStrings.ScriptErrorOptionWithoutRevisionText}", diff --git a/GitUI/Translation/English.xlf b/GitUI/Translation/English.xlf index dbe5f753ca7..36661a72ae8 100644 --- a/GitUI/Translation/English.xlf +++ b/GitUI/Translation/English.xlf @@ -3916,6 +3916,10 @@ You can unset the template: Reset submodule changes + + Run script + + Selection filter @@ -3976,6 +3980,10 @@ You can unset the template: Reset file or directory changes + + Run script + + Stash submodule changes @@ -8839,6 +8847,10 @@ Reset the filter via View > Show all branches. &Reset file(s) to + + Run script + + S&ave selected as... @@ -9005,6 +9017,10 @@ See the changes in the commit form. Reset to selected revision + + Run script + + Save as... @@ -9577,6 +9593,10 @@ User Input: {UserInput} {UserFiles} +Conditional: +{if:option}{/if} +{ifnot:option}{/ifnot} + Working Dir: {WorkingDir} @@ -9618,7 +9638,11 @@ Current Branch: {cCommitDate} {cDefaultRemote} {cDefaultRemoteUrl} -{cDefaultRemotePathFromUrl} +{cDefaultRemotePathFromUrl} + +File List: +{fSelectedFiles} +{fLineNumber} diff --git a/GitUI/UserControls/RevisionGrid/RevisionGridControl.cs b/GitUI/UserControls/RevisionGrid/RevisionGridControl.cs index 6bb5984394c..bfd2a5f8ad2 100644 --- a/GitUI/UserControls/RevisionGrid/RevisionGridControl.cs +++ b/GitUI/UserControls/RevisionGrid/RevisionGridControl.cs @@ -2020,7 +2020,7 @@ private void ContextMenuOpening(object sender, CancelEventArgs e) SetEnabled(openPullRequestPageStripMenuItem, !string.IsNullOrWhiteSpace(revision.BuildStatus?.PullRequestUrl)); - mainContextMenu.AddUserScripts(runScriptToolStripMenuItem, ((IRunScript)this).Execute); + mainContextMenu.AddUserScripts(runScriptToolStripMenuItem, ((IRunScript)this).Execute, (script) => script.AddToRevisionGridContextMenu); UpdateSeparators(); @@ -3120,7 +3120,15 @@ IReadOnlyList IRevisionGridInfo.GetSelectedRevisions() void IRunScript.Execute(string name) { - if (ScriptRunner.RunScript(this, Module, name, UICommands, this).NeedsGridRefresh) + if (ScriptRunner.RunScript(this, Module, name, UICommands, this, null).NeedsGridRefresh) + { + PerformRefreshRevisions(); + } + } + + void IRunScript.Execute(string name, IRunScript.IFileListSource files) + { + if (ScriptRunner.RunScript(this, Module, name, UICommands, this, files).NeedsGridRefresh) { PerformRefreshRevisions(); } diff --git a/IntegrationTests/UI.IntegrationTests/Script/ScriptRunnerTests.cs b/IntegrationTests/UI.IntegrationTests/Script/ScriptRunnerTests.cs index 80741163ca4..c8f0cfa18b2 100644 --- a/IntegrationTests/UI.IntegrationTests/Script/ScriptRunnerTests.cs +++ b/IntegrationTests/UI.IntegrationTests/Script/ScriptRunnerTests.cs @@ -68,7 +68,7 @@ public void OneTimeTearDown() [Test] public void RunScript_without_scriptKey_shall_return_false([Values(null, "")] string scriptKey) { - var result = ScriptRunner.RunScript(null, _module, scriptKey, uiCommands: null, revisionGrid: null); + var result = ScriptRunner.RunScript(null, _module, scriptKey, uiCommands: null, revisionGrid: null, files: null); result.Executed.Should().BeFalse(); } @@ -78,7 +78,7 @@ public void RunScript_with_invalid_scriptKey_shall_display_error_and_return_fals { const string invalidScriptKey = "InVaLid ScRiPt KeY"; - var ex = ((Action)(() => ExecuteRunScript(null, _module, invalidScriptKey, uiCommands: null, revisionGrid: null))).Should() + var ex = ((Action)(() => ExecuteRunScript(null, _module, invalidScriptKey, uiCommands: null, revisionGrid: null, files: null))).Should() .Throw(); ex.And.Context.Should().Be($"Unable to find script: '{invalidScriptKey}'"); ex.And.Command.Should().BeNull(); @@ -91,7 +91,7 @@ public void RunScript_without_command_shall_return_false([Values(null, "")] stri { _exampleScript.Command = command; - var result = ScriptRunner.RunScript(null, _module, _keyOfExampleScript, uiCommands: null, revisionGrid: null); + var result = ScriptRunner.RunScript(null, _module, _keyOfExampleScript, uiCommands: null, revisionGrid: null, files: null); result.Executed.Should().BeFalse(); } @@ -102,7 +102,7 @@ public void RunScript_without_arguments_shall_succeed() _exampleScript.Command = "{git}"; _exampleScript.Arguments = ""; - var result = ScriptRunner.RunScript(null, _module, _keyOfExampleScript, uiCommands: null, revisionGrid: null); + var result = ScriptRunner.RunScript(null, _module, _keyOfExampleScript, uiCommands: null, revisionGrid: null, files: null); result.Should().BeEquivalentTo(new CommandStatus(true, needsGridRefresh: false)); } @@ -113,7 +113,7 @@ public void RunScript_with_arguments_without_options_shall_succeed() _exampleScript.Command = "{git}"; _exampleScript.Arguments = "--version"; - var result = ScriptRunner.RunScript(null, _module, _keyOfExampleScript, _uiCommands, revisionGrid: null); + var result = ScriptRunner.RunScript(null, _module, _keyOfExampleScript, _uiCommands, revisionGrid: null, files: null); result.Should().BeEquivalentTo(new CommandStatus(true, needsGridRefresh: false)); } @@ -127,7 +127,7 @@ public void RunScript_with_arguments_with_c_option_shall_succeed() GitRevision revision = new(ObjectId.IndexId); _module.GetRevision(shortFormat: true, loadRefs: true).Returns(x => revision); - var result = ScriptRunner.RunScript(null, _module, _keyOfExampleScript, _uiCommands, revisionGrid: null); + var result = ScriptRunner.RunScript(null, _module, _keyOfExampleScript, _uiCommands, revisionGrid: null, files: null); result.Should().BeEquivalentTo(new CommandStatus(true, needsGridRefresh: false)); } @@ -141,7 +141,7 @@ public void RunScript_with_arguments_with_c_option_without_revision_shall_displa _module.GetCurrentCheckout().Returns((ObjectId)null); - var ex = ((Action)(() => ExecuteRunScript(null, _module, _keyOfExampleScript, uiCommands: null, revisionGrid: null))).Should() + var ex = ((Action)(() => ExecuteRunScript(null, _module, _keyOfExampleScript, uiCommands: null, revisionGrid: null, files: null))).Should() .Throw(); ex.And.Context.Should().Be($"Script: '{_keyOfExampleScript}'\r\nA valid revision is required to substitute the argument options"); ex.And.Command.Should().Be(_exampleScript.Command); @@ -155,7 +155,7 @@ public void RunScript_with_arguments_with_s_option_without_RevisionGrid_shall_di _exampleScript.Command = "cmd"; _exampleScript.Arguments = "/c echo {sHash}"; - var ex = ((Action)(() => ExecuteRunScript(null, _module, _keyOfExampleScript, uiCommands: null, revisionGrid: null))).Should() + var ex = ((Action)(() => ExecuteRunScript(null, _module, _keyOfExampleScript, uiCommands: null, revisionGrid: null, files: null))).Should() .Throw(); ex.And.Context.Should().Be($"Script: '{_keyOfExampleScript}'\r\n'sHash' option is only supported when invoked from the revision grid"); ex.And.Command.Should().Be(_exampleScript.Command); @@ -179,7 +179,7 @@ public void RunScript_with_arguments_with_s_option_with_RevisionGrid_without_sel Assert.AreEqual(0, formBrowse.RevisionGridControl.GetSelectedRevisions().Count); formBrowse.RevisionGridControl.LatestSelectedRevision.Should().BeNull(); - var ex = ((Action)(() => ExecuteRunScript(null, _module, _keyOfExampleScript, _uiCommands, formBrowse.RevisionGridControl))).Should() + var ex = ((Action)(() => ExecuteRunScript(null, _module, _keyOfExampleScript, _uiCommands, formBrowse.RevisionGridControl, files: null))).Should() .Throw(); ex.And.Context.Should().Be($"Script: '{_keyOfExampleScript}'\r\nA valid revision is required to substitute the argument options"); ex.And.Command.Should().Be(_exampleScript.Command); @@ -204,7 +204,7 @@ public void RunScript_with_arguments_with_s_option_with_RevisionGrid_with_select string errorMessage = null; var result = ExecuteRunScript(formBrowse, _referenceRepository.Module, _keyOfExampleScript, _uiCommands, - formBrowse.RevisionGridControl); + formBrowse.RevisionGridControl, files: null); errorMessage.Should().BeNull(); result.Should().BeEquivalentTo(new CommandStatus(executed: true, needsGridRefresh: false)); @@ -212,7 +212,7 @@ public void RunScript_with_arguments_with_s_option_with_RevisionGrid_with_select } private static CommandStatus ExecuteRunScript(IWin32Window owner, IGitModule module, string scriptKey, IGitUICommands uiCommands, - RevisionGridControl revisionGrid) + RevisionGridControl revisionGrid, IRunScript.IFileListSource files) { try { @@ -223,7 +223,8 @@ public void RunScript_with_arguments_with_s_option_with_RevisionGrid_with_select module, scriptKey, uiCommands, - revisionGrid + revisionGrid, + files }); return result; } diff --git a/UnitTests/GitUI.Tests/Script/ScriptOptionsParserTests.cs b/UnitTests/GitUI.Tests/Script/ScriptOptionsParserTests.cs index 52bbd09bcad..3695a0a554c 100644 --- a/UnitTests/GitUI.Tests/Script/ScriptOptionsParserTests.cs +++ b/UnitTests/GitUI.Tests/Script/ScriptOptionsParserTests.cs @@ -125,7 +125,7 @@ public void GetCurrentRevision_should_return_expected_if_current_revision_has_no [Test] public void Parse_should_throw_if_module_null() { - ((Action)(() => ScriptOptionsParser.Parse(arguments: "bla", uiCommands: null, owner: null, scriptHostControl: null))).Should() + ((Action)(() => ScriptOptionsParser.Parse(arguments: "bla", uiCommands: null, owner: null, scriptHostControl: null, fileListSource: null))).Should() .Throw() .WithMessage("Value cannot be null. (Parameter 'uiCommands')"); } @@ -136,7 +136,7 @@ public void Parse_should_throw_if_module_null() [TestCase("\t")] public void Parse_should_return_without_process_if_arguments_unset(string arguments) { - var result = ScriptOptionsParser.Parse(arguments, uiCommands: null, owner: null, scriptHostControl: null); + var result = ScriptOptionsParser.Parse(arguments, uiCommands: null, owner: null, scriptHostControl: null, fileListSource: null); result.arguments.Should().Be(arguments); result.abort.Should().Be(false); @@ -147,7 +147,7 @@ public void Parse_should_return_unmodified_arguments_if_no_options_matched() { const string arguments = "{openUrl} https://gitlab.com{zeDefaultRemotePathFromUrl}/tree/{zeBranch}"; - var result = ScriptOptionsParser.Parse(arguments: arguments, _commands, owner: null, scriptHostControl: null); + var result = ScriptOptionsParser.Parse(arguments: arguments, _commands, owner: null, scriptHostControl: null, fileListSource: null); result.arguments.Should().Be(arguments); result.abort.Should().Be(false); @@ -166,7 +166,7 @@ public void Parse_should_parse_c_arguments() string expectedMessage = $"{Subject}\\n\\nline3"; - var result = ScriptOptionsParser.Parse("echo {{cSubject}} {{cMessage}}", _commands, owner: null, scriptHostControl: null); + var result = ScriptOptionsParser.Parse("echo {{cSubject}} {{cMessage}}", _commands, owner: null, scriptHostControl: null, fileListSource: null); result.arguments.Should().Be($"echo \"{revision.Subject}\" \"{expectedMessage}\""); result.abort.Should().Be(false); @@ -185,7 +185,7 @@ public void Parse_should_parse_s_arguments() string expectedMessage = $"{Subject}\\n\\nline3"; - var result = ScriptOptionsParser.Parse("echo {{sSubject}} {{sMessage}}", _commands, owner: null, _scriptHostControl); + var result = ScriptOptionsParser.Parse("echo {{sSubject}} {{sMessage}}", _commands, owner: null, _scriptHostControl, fileListSource: null); result.arguments.Should().Be($"echo \"{revision.Subject}\" \"{expectedMessage}\""); result.abort.Should().Be(false); @@ -199,7 +199,8 @@ public void ParseScriptArguments_resolves_cDefaultRemotePathFromUrl_currentRemot owner: null, scriptHostControl: null, uiCommands: null, allSelectedRevisions: null, selectedTags: null, selectedBranches: null, selectedLocalBranches: null, selectedRemoteBranches: null, selectedRemotes: null, selectedRevision: null, currentTags: null, - currentBranches: null, currentLocalBranches: null, currentRemoteBranches: null, currentRevision: null, currentRemote: null); + currentBranches: null, currentLocalBranches: null, currentRemoteBranches: null, currentRevision: null, currentRemote: null, + selectedFiles: null, lineNumber: null); result.Should().Be("{openUrl} https://gitlab.com/tree/{sBranch}"); } @@ -215,7 +216,8 @@ public void ParseScriptArguments_resolve_cDefaultRemotePathFromUrl_currentRemote owner: null, scriptHostControl: null, _commands, allSelectedRevisions: null, selectedTags: null, selectedBranches: null, selectedLocalBranches: null, selectedRemoteBranches: null, selectedRemotes: null, selectedRevision: null, currentTags: null, - currentBranches: null, currentLocalBranches: null, currentRemoteBranches: null, currentRevision: null, currentRemote); + currentBranches: null, currentLocalBranches: null, currentRemoteBranches: null, currentRevision: null, currentRemote, + selectedFiles: null, lineNumber: null); result.Should().Be("{openUrl} https://gitlab.com/gitlabhq/gitlabhq/tree/{sBranch}"); } @@ -230,7 +232,8 @@ public void ParseScriptArguments_resolve_sRemotePathFromUrl_selectedRemotes_empt owner: null, scriptHostControl: null, uiCommands: null, allSelectedRevisions: null, selectedTags: null, selectedBranches: null, selectedLocalBranches: null, selectedRemoteBranches: null, noSelectedRemotes, selectedRevision: null, currentTags: null, - currentBranches: null, currentLocalBranches: null, currentRemoteBranches: null, currentRevision: null, currentRemote: null); + currentBranches: null, currentLocalBranches: null, currentRemoteBranches: null, currentRevision: null, currentRemote: null, + selectedFiles: null, lineNumber: null); result.Should().Be("{openUrl} https://gitlab.com/tree/{sBranch}"); } @@ -247,7 +250,8 @@ public void ParseScriptArguments_resolve_sRemotePathFromUrl_currentRemote_set() owner: null, scriptHostControl: null, _commands, allSelectedRevisions: null, selectedTags: null, selectedBranches: null, selectedLocalBranches: null, selectedRemoteBranches: null, selectedRemotes, selectedRevision: null, currentTags: null, - currentBranches: null, currentLocalBranches: null, currentRemoteBranches: null, currentRevision: null, currentRemote: null); + currentBranches: null, currentLocalBranches: null, currentRemoteBranches: null, currentRevision: null, currentRemote: null, + selectedFiles: null, lineNumber: null); result.Should().Be("{openUrl} https://gitlab.com/gitlabhq/gitlabhq/tree/{sBranch}"); } @@ -266,7 +270,8 @@ public void ParseScriptArguments_resolve_sRemotePathFromUrl_currentRemote_set() owner: null, scriptHostControl: null, uiCommands: null, allSelectedRevisions: null, selectedTags: null, selectedBranches: null, selectedLocalBranches: null, selectedRemoteBranches: remoteBranches, selectedRemotes: null, selectedRevision: null, currentTags: null, - currentBranches: null, currentLocalBranches: null, currentRemoteBranches: null, currentRevision: null, currentRemote: null); + currentBranches: null, currentLocalBranches: null, currentRemoteBranches: null, currentRevision: null, currentRemote: null, + selectedFiles: null, lineNumber: null); result.Should().Be(branch); } @@ -285,7 +290,8 @@ public void ParseScriptArguments_resolve_sRemotePathFromUrl_currentRemote_set() owner: null, scriptHostControl: null, uiCommands: null, allSelectedRevisions: null, selectedTags: null, selectedBranches: null, selectedLocalBranches: null, selectedRemoteBranches: remoteBranches, selectedRemotes: null, selectedRevision: null, currentTags: null, - currentBranches: null, currentLocalBranches: null, currentRemoteBranches: null, currentRevision: null, currentRemote: null); + currentBranches: null, currentLocalBranches: null, currentRemoteBranches: null, currentRevision: null, currentRemote: null, + selectedFiles: null, lineNumber: null); result.Should().Be(branchName); } @@ -304,7 +310,8 @@ public void ParseScriptArguments_resolve_sRemotePathFromUrl_currentRemote_set() owner: null, scriptHostControl: null, uiCommands: null, allSelectedRevisions: null, selectedTags: null, selectedBranches: null, selectedLocalBranches: null, selectedRemoteBranches: null, selectedRemotes: null, selectedRevision: null, currentTags: null, - currentBranches: null, currentLocalBranches: null, currentRemoteBranches: remoteBranches, currentRevision: null, currentRemote: null); + currentBranches: null, currentLocalBranches: null, currentRemoteBranches: remoteBranches, currentRevision: null, currentRemote: null, + selectedFiles: null, lineNumber: null); result.Should().Be(branch); } @@ -323,7 +330,8 @@ public void ParseScriptArguments_resolve_sRemotePathFromUrl_currentRemote_set() owner: null, scriptHostControl: null, uiCommands: null, allSelectedRevisions: null, selectedTags: null, selectedBranches: null, selectedLocalBranches: null, selectedRemoteBranches: null, selectedRemotes: null, selectedRevision: null, currentTags: null, - currentBranches: null, currentLocalBranches: null, currentRemoteBranches: remoteBranches, currentRevision: null, currentRemote: null); + currentBranches: null, currentLocalBranches: null, currentRemoteBranches: remoteBranches, currentRevision: null, currentRemote: null, + selectedFiles: null, lineNumber: null); result.Should().Be(branchName); } @@ -349,7 +357,8 @@ public void ParseScriptArguments_resolve_RepoName() owner: null, scriptHostControl: null, _commands, allSelectedRevisions: null, selectedTags: null, selectedBranches: null, selectedLocalBranches: null, selectedRemoteBranches: null, selectedRemotes: null, selectedRevision: null, currentTags: null, - currentBranches: null, currentLocalBranches: null, currentRemoteBranches: null, currentRevision: null, currentRemote: null); + currentBranches: null, currentLocalBranches: null, currentRemoteBranches: null, currentRevision: null, currentRemote: null, + selectedFiles: null, lineNumber: null); result.Should().Be(dirName); } @@ -359,7 +368,7 @@ public void ParseScriptArguments_resolve_QuotedWithBackslashAtEnd() { _module.WorkingDir.Returns("C:\\test path with whitespaces\\"); - var result = ScriptOptionsParser.GetTestAccessor().ParseScriptArguments("{{WorkingDir}} \"{WorkingDir}\"", "WorkingDir", null, null, _commands, null, null, null, null, null, null, null, null, null, null, null, null, null); + var result = ScriptOptionsParser.GetTestAccessor().ParseScriptArguments("{{WorkingDir}} \"{WorkingDir}\"", "WorkingDir", null, null, _commands, null, null, null, null, null, null, null, null, null, null, null, null, null, null, null); result.Should().Be("\"C:\\test path with whitespaces\\\\\" \"C:\\test path with whitespaces\\\""); } @@ -370,10 +379,10 @@ public void ParseScriptArguments_resolve_StringWithDoubleQuotes() GitRevision gitRevision = new(ObjectId.Random()); gitRevision.Subject = "test string with \"double qoutes\" and escaped \\\"double qoutes\\\""; - var result = ScriptOptionsParser.GetTestAccessor().ParseScriptArguments("{{sMessage}}", "sMessage", null, null, null, null, null, null, null, null, null, gitRevision, null, null, null, null, null, null); + var result = ScriptOptionsParser.GetTestAccessor().ParseScriptArguments("{{sMessage}}", "sMessage", null, null, null, null, null, null, null, null, null, gitRevision, null, null, null, null, null, null, null, null); result.Should().Be("\"test string with \\\"double qoutes\\\" and escaped \\\"double qoutes\\\"\""); - result = ScriptOptionsParser.GetTestAccessor().ParseScriptArguments("{sMessage}", "sMessage", null, null, null, null, null, null, null, null, null, gitRevision, null, null, null, null, null, null); + result = ScriptOptionsParser.GetTestAccessor().ParseScriptArguments("{sMessage}", "sMessage", null, null, null, null, null, null, null, null, null, gitRevision, null, null, null, null, null, null, null, null); result.Should().Be("test string with \"double qoutes\" and escaped \\\"double qoutes\\\""); } }