diff --git a/GitCommands/StringExtensions.cs b/GitCommands/StringExtensions.cs
index 38dde28ff5b..2950f41e417 100644
--- a/GitCommands/StringExtensions.cs
+++ b/GitCommands/StringExtensions.cs
@@ -1,6 +1,7 @@
using System.Diagnostics.CodeAnalysis;
using System.Diagnostics.Contracts;
using System.Text;
+using GitCommands.Utils;
using GitExtUtils;
// ReSharper disable once CheckNamespace
@@ -189,6 +190,18 @@ public static string Quote(this string? s, string q = "\"")
return string.IsNullOrEmpty(s) ? s : s.Quote();
}
+ ///
+ /// Quotes and escapes this string for use as a command line argument.
+ ///
+ [Pure]
+ public static string QuoteForCommandLine(this string s, bool? forWindows = null)
+ {
+ return $"\"{((forWindows ?? EnvUtils.RunningOnWindows()) ? EscapeForWindowsCommandLine(s) : EscapeForPosixCommandLine(s))}\"";
+
+ static string EscapeForWindowsCommandLine(string s) => s.Replace("\"", "\"\"");
+ static string EscapeForPosixCommandLine(string s) => s.Replace(@"\", @"\\").Replace("\"", "\\\"").Replace("'", @"\'");
+ }
+
///
/// Adds parentheses if string is not null and not empty.
///
diff --git a/GitUI/CommandsDialogs/FormCommit.Designer.cs b/GitUI/CommandsDialogs/FormCommit.Designer.cs
index f2e3bec615d..8c505a9758b 100644
--- a/GitUI/CommandsDialogs/FormCommit.Designer.cs
+++ b/GitUI/CommandsDialogs/FormCommit.Designer.cs
@@ -46,6 +46,8 @@ private void InitializeComponent()
assumeUnchangedToolStripMenuItem = new ToolStripMenuItem();
doNotAssumeUnchangedToolStripMenuItem = new ToolStripMenuItem();
interactiveAddToolStripMenuItem = new ToolStripMenuItem();
+ toolStripSeparatorScript = new ToolStripSeparator();
+ runScriptToolStripMenuItem = new ToolStripMenuItem();
fileTooltip = new ToolTip(components);
StageInSuperproject = new CheckBox();
StagedFileContext = new ContextMenuStrip(components);
@@ -61,6 +63,8 @@ private void InitializeComponent()
stagedCopyPathToolStripMenuItem14 = new ToolStripMenuItem();
stagedOpenFolderToolStripMenuItem10 = new ToolStripMenuItem();
stagedEditFileToolStripMenuItem11 = new ToolStripMenuItem();
+ stagedToolStripSeparatorScript = new ToolStripSeparator();
+ stagedRunScriptToolStripMenuItem = new ToolStripMenuItem();
UnstagedSubmoduleContext = new ContextMenuStrip(components);
commitSubmoduleChanges = new ToolStripMenuItem();
resetSubmoduleChanges = new ToolStripMenuItem();
@@ -211,7 +215,9 @@ private void InitializeComponent()
skipWorktreeToolStripMenuItem,
doNotSkipWorktreeToolStripMenuItem,
assumeUnchangedToolStripMenuItem,
- doNotAssumeUnchangedToolStripMenuItem});
+ doNotAssumeUnchangedToolStripMenuItem,
+ toolStripSeparatorScript,
+ runScriptToolStripMenuItem});
UnstagedFileContext.Name = "UnstagedFileContext";
UnstagedFileContext.Size = new Size(233, 414);
UnstagedFileContext.Opening += UnstagedFileContext_Opening;
@@ -374,6 +380,18 @@ private void InitializeComponent()
interactiveAddToolStripMenuItem.Text = "Interactive Add";
interactiveAddToolStripMenuItem.Click += interactiveAddToolStripMenuItem_Click;
//
+ // toolStripSeparatorScript
+ //
+ toolStripSeparatorScript.Name = "toolStripSeparatorScript";
+ toolStripSeparatorScript.Size = new Size(259, 6);
+ //
+ // runScriptToolStripMenuItem
+ //
+ runScriptToolStripMenuItem.Image = Properties.Images.Console;
+ runScriptToolStripMenuItem.Name = "runScriptToolStripMenuItem";
+ runScriptToolStripMenuItem.Size = new Size(262, 22);
+ runScriptToolStripMenuItem.Text = "Run script";
+ //
// StageInSuperproject
//
StageInSuperproject.AutoSize = true;
@@ -401,7 +419,9 @@ private void InitializeComponent()
stagedCopyPathToolStripMenuItem14,
stagedOpenFolderToolStripMenuItem10,
stagedFileHistoryToolStripSeparator,
- stagedFileHistoryToolStripMenuItem6});
+ stagedFileHistoryToolStripMenuItem6,
+ stagedToolStripSeparatorScript,
+ stagedRunScriptToolStripMenuItem});
StagedFileContext.Name = "StagedFileContext";
StagedFileContext.Size = new Size(233, 198);
StagedFileContext.Opening += StagedFileContext_Opening;
@@ -491,6 +511,18 @@ private void InitializeComponent()
stagedEditFileToolStripMenuItem11.Text = "Edit file";
stagedEditFileToolStripMenuItem11.Click += editFileToolStripMenuItem_Click;
//
+ // stagedToolStripSeparatorScript
+ //
+ stagedToolStripSeparatorScript.Name = "stagedToolStripSeparatorScript";
+ stagedToolStripSeparatorScript.Size = new Size(259, 6);
+ //
+ // stagedRunScriptToolStripMenuItem
+ //
+ stagedRunScriptToolStripMenuItem.Image = Properties.Images.Console;
+ stagedRunScriptToolStripMenuItem.Name = "stagedRunScriptToolStripMenuItem";
+ stagedRunScriptToolStripMenuItem.Size = new Size(262, 22);
+ stagedRunScriptToolStripMenuItem.Text = "Run script";
+ //
// UnstagedSubmoduleContext
//
UnstagedSubmoduleContext.Items.AddRange(new ToolStripItem[] {
@@ -1660,6 +1692,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 85368375920..7b8e4a84c34 100644
--- a/GitUI/CommandsDialogs/FormCommit.cs
+++ b/GitUI/CommandsDialogs/FormCommit.cs
@@ -404,6 +404,8 @@ void AddToSelectionFilter()
}
});
+ UICommands.PostRepositoryChanged += UICommands_PostRepositoryChanged;
+
return;
void ConfigureMessageBox()
@@ -430,6 +432,11 @@ protected override void Dispose(bool disposing)
{
if (disposing)
{
+ if (!IsDesignMode && !IsUnitTestActive)
+ {
+ UICommands.PostRepositoryChanged -= UICommands_PostRepositoryChanged;
+ }
+
_unstagedLoader.Dispose();
_customDiffToolsSequence.Dispose();
_interactiveAddSequence.Dispose();
@@ -601,6 +608,20 @@ protected override void OnKeyUp(KeyEventArgs e)
return;
}
+ protected override void OnUICommandsChanged(GitUICommandsChangedEventArgs e)
+ {
+ GitUICommands oldCommands = e.OldCommands;
+
+ if (oldCommands is not null)
+ {
+ oldCommands.PostRepositoryChanged -= UICommands_PostRepositoryChanged;
+ }
+
+ UICommands.PostRepositoryChanged += UICommands_PostRepositoryChanged;
+
+ base.OnUICommandsChanged(e);
+ }
+
public override bool ProcessHotkey(Keys keyData)
{
if (IsDesignMode || !HotkeysEnabled)
@@ -889,6 +910,11 @@ protected override bool ExecuteCommand(int cmd)
}
}
+ public override IScriptOptionsProvider? GetScriptOptionsProvider()
+ {
+ return new ScriptOptionsProvider(_currentFilesList, () => _fullPathResolver, () => SelectedDiff.CurrentFileLine);
+ }
+
#endregion
private void ComputeUnstagedFiles(Action> onComputed, bool doAsync)
@@ -1647,6 +1673,8 @@ private void UnstagedFileContext_Opening(object sender, System.ComponentModel.Ca
openWithToolStripMenuItem.Enabled = !isAnyDeleted;
deleteFileToolStripMenuItem.Enabled = !isAnyDeleted;
openContainingFolderToolStripMenuItem.Enabled = !isAnyDeleted;
+
+ UnstagedFileContext.AddUserScripts(runScriptToolStripMenuItem, ExecuteCommand, script => script.OnEvent == ScriptEvent.ShowInFileList, UICommands);
}
private void StagedFileContext_Opening(object sender, System.ComponentModel.CancelEventArgs e)
@@ -1666,6 +1694,8 @@ private void StagedFileContext_Opening(object sender, System.ComponentModel.Canc
stagedOpenToolStripMenuItem7.Enabled = !isAnyDeleted;
stagedOpenWithToolStripMenuItem8.Enabled = !isAnyDeleted;
stagedOpenFolderToolStripMenuItem10.Enabled = !isAnyDeleted;
+
+ StagedFileContext.AddUserScripts(stagedRunScriptToolStripMenuItem, ExecuteCommand, script => script.OnEvent == ScriptEvent.ShowInFileList, UICommands);
}
private void UnstagedSubmoduleContext_Opening(object sender, System.ComponentModel.CancelEventArgs e)
@@ -3463,6 +3493,18 @@ private void UpdateButtonStates()
: TranslatedStrings.ButtonPush;
}
+ private void UICommands_PostRepositoryChanged(object sender, GitUIEventArgs e)
+ {
+ if (!_skipUpdate && !_bypassActivatedEventHandler)
+ {
+ ThreadHelper.FileAndForget(async () =>
+ {
+ await this.SwitchToMainThreadAsync();
+ RescanChanges();
+ });
+ }
+ }
+
internal TestAccessor GetTestAccessor()
=> new(this);
diff --git a/GitUI/CommandsDialogs/RevisionDiffControl.Designer.cs b/GitUI/CommandsDialogs/RevisionDiffControl.Designer.cs
index 493b2ecd7e2..33215db837a 100644
--- a/GitUI/CommandsDialogs/RevisionDiffControl.Designer.cs
+++ b/GitUI/CommandsDialogs/RevisionDiffControl.Designer.cs
@@ -61,6 +61,8 @@ private void InitializeComponent()
blameToolStripMenuItem = new ToolStripMenuItem();
findInDiffToolStripMenuItem = new ToolStripMenuItem();
showSearchCommitToolStripMenuItem = new ToolStripMenuItem();
+ toolStripSeparatorScript = new ToolStripSeparator();
+ runScriptToolStripMenuItem = new ToolStripMenuItem();
DiffText = new GitUI.Editor.FileViewer();
BlameControl = new Blame.BlameControl();
saveToolStripMenuItem = new ToolStripMenuItem();
@@ -146,7 +148,9 @@ private void InitializeComponent()
fileHistoryDiffToolstripMenuItem,
blameToolStripMenuItem,
findInDiffToolStripMenuItem,
- showSearchCommitToolStripMenuItem});
+ showSearchCommitToolStripMenuItem,
+ toolStripSeparatorScript,
+ runScriptToolStripMenuItem});
DiffContextMenu.Name = "DiffContextMenu";
DiffContextMenu.Size = new Size(263, 534);
DiffContextMenu.Opening += DiffContextMenu_Opening;
@@ -444,6 +448,18 @@ private void InitializeComponent()
showSearchCommitToolStripMenuItem.Text = "Sear&ch files in commit...";
showSearchCommitToolStripMenuItem.Click += showSearchCommitToolStripMenuItem_Click;
//
+ // toolStripSeparatorScript
+ //
+ toolStripSeparatorScript.Name = "toolStripSeparatorScript";
+ toolStripSeparatorScript.Size = new Size(259, 6);
+ //
+ // runScriptToolStripMenuItem
+ //
+ runScriptToolStripMenuItem.Image = Properties.Images.Console;
+ runScriptToolStripMenuItem.Name = "runScriptToolStripMenuItem";
+ runScriptToolStripMenuItem.Size = new Size(262, 22);
+ runScriptToolStripMenuItem.Text = "Run script";
+ //
// DiffText
//
DiffText.Dock = DockStyle.Fill;
@@ -526,5 +542,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 709b5fd1d5c..8aca25dabe9 100644
--- a/GitUI/CommandsDialogs/RevisionDiffControl.cs
+++ b/GitUI/CommandsDialogs/RevisionDiffControl.cs
@@ -5,6 +5,7 @@
using GitUI.CommandDialogs;
using GitUI.CommandsDialogs.BrowseDialog;
using GitUI.HelperDialogs;
+using GitUI.ScriptsEngine;
using GitUI.UserControls;
using GitUI.UserControls.RevisionGrid;
using GitUIPluginInterfaces;
@@ -188,6 +189,11 @@ bool SelectFirstGroupChangesIfFileNotFocused()
}
}
+ protected override IScriptOptionsProvider? GetScriptOptionsProvider()
+ {
+ return new ScriptOptionsProvider(DiffFiles, () => _fullPathResolver, () => BlameControl.Visible ? BlameControl.CurrentFileLine : DiffText.CurrentFileLine);
+ }
+
public void ReloadHotkeys()
{
LoadHotkeys(HotkeySettingsName);
@@ -747,6 +753,8 @@ private void UpdateStatusOfMenuItems()
{
blameToolStripMenuItem.Checked = false;
}
+
+ DiffContextMenu.AddUserScripts(runScriptToolStripMenuItem, ExecuteCommand, script => script.OnEvent == ScriptEvent.ShowInFileList, UICommands);
}
private void DiffContextMenu_Opening(object sender, CancelEventArgs e)
diff --git a/GitUI/CommandsDialogs/RevisionFileTreeControl.Designer.cs b/GitUI/CommandsDialogs/RevisionFileTreeControl.Designer.cs
index 8331f8466da..9b95ddc07c3 100644
--- a/GitUI/CommandsDialogs/RevisionFileTreeControl.Designer.cs
+++ b/GitUI/CommandsDialogs/RevisionFileTreeControl.Designer.cs
@@ -48,6 +48,8 @@ private void InitializeComponent()
findToolStripMenuItem = new ToolStripMenuItem();
expandToolStripMenuItem = new ToolStripMenuItem();
collapseAllToolStripMenuItem = new ToolStripMenuItem();
+ toolStripSeparatorScript = new ToolStripSeparator();
+ runScriptToolStripMenuItem = new ToolStripMenuItem();
FileText = new GitUI.Editor.FileViewer();
BlameControl = new Blame.BlameControl();
((System.ComponentModel.ISupportInitialize)(FileTreeSplitContainer)).BeginInit();
@@ -125,7 +127,9 @@ private void InitializeComponent()
toolStripSeparatorGitTrackingActions,
findToolStripMenuItem,
expandToolStripMenuItem,
- collapseAllToolStripMenuItem});
+ collapseAllToolStripMenuItem,
+ toolStripSeparatorScript,
+ runScriptToolStripMenuItem});
FileTreeContextMenu.Name = "FileTreeContextMenu";
FileTreeContextMenu.Size = new Size(326, 474);
FileTreeContextMenu.Opening += FileTreeContextMenu_Opening;
@@ -323,6 +327,18 @@ private void InitializeComponent()
collapseAllToolStripMenuItem.Text = "Co&llapse all";
collapseAllToolStripMenuItem.Click += collapseAllToolStripMenuItem_Click;
//
+ // toolStripSeparatorScript
+ //
+ toolStripSeparatorScript.Name = "toolStripSeparatorScript";
+ toolStripSeparatorScript.Size = new Size(259, 6);
+ //
+ // runScriptToolStripMenuItem
+ //
+ runScriptToolStripMenuItem.Image = Properties.Images.Console;
+ runScriptToolStripMenuItem.Name = "runScriptToolStripMenuItem";
+ runScriptToolStripMenuItem.Size = new Size(262, 22);
+ runScriptToolStripMenuItem.Text = "Run script";
+ //
// FileText
//
FileText.Dock = DockStyle.Fill;
@@ -391,5 +407,7 @@ private void InitializeComponent()
private ToolStripSeparator toolStripSeparatorGitActions;
private ToolStripMenuItem stopTrackingThisFileToolStripMenuItem;
private ToolStripMenuItem expandToolStripMenuItem;
+ private ToolStripSeparator toolStripSeparatorScript;
+ private ToolStripMenuItem runScriptToolStripMenuItem;
}
}
diff --git a/GitUI/CommandsDialogs/RevisionFileTreeControl.cs b/GitUI/CommandsDialogs/RevisionFileTreeControl.cs
index c7e19f2f361..eb1de4af366 100644
--- a/GitUI/CommandsDialogs/RevisionFileTreeControl.cs
+++ b/GitUI/CommandsDialogs/RevisionFileTreeControl.cs
@@ -7,6 +7,7 @@
using GitUI.CommandDialogs;
using GitUI.CommandsDialogs.BrowseDialog;
using GitUI.Properties;
+using GitUI.ScriptsEngine;
using GitUI.UserControls;
using GitUIPluginInterfaces;
using Microsoft;
@@ -290,6 +291,21 @@ protected override bool ExecuteCommand(int cmd)
return true;
}
+ protected override IScriptOptionsProvider? GetScriptOptionsProvider()
+ {
+ return new ScriptOptionsProvider(() =>
+ {
+ if (tvGitTree.SelectedNode?.Tag is not GitItem gitItem || gitItem.ObjectType != GitObjectType.Blob)
+ {
+ return Array.Empty();
+ }
+
+ return new string[] { gitItem.FileName };
+ },
+ () => _fullPathResolver,
+ () => BlameControl.Visible ? BlameControl.CurrentFileLine : FileText.CurrentFileLine);
+ }
+
public override bool ProcessHotkey(Keys keyData)
{
return base.ProcessHotkey(keyData)
@@ -733,6 +749,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, ExecuteCommand, script => script.OnEvent == ScriptEvent.ShowInFileList, UICommands);
}
private void fileTreeOpenContainingFolderToolStripMenuItem_Click(object sender, EventArgs e)
diff --git a/GitUI/CommandsDialogs/ScriptOptionsProvider.cs b/GitUI/CommandsDialogs/ScriptOptionsProvider.cs
new file mode 100644
index 00000000000..e4e919ee854
--- /dev/null
+++ b/GitUI/CommandsDialogs/ScriptOptionsProvider.cs
@@ -0,0 +1,49 @@
+using GitUI.ScriptsEngine;
+using GitUIPluginInterfaces;
+
+namespace GitUI.CommandsDialogs;
+
+internal class ScriptOptionsProvider : IScriptOptionsProvider
+{
+ private const string _selectedFiles = "SelectedFiles";
+ private const string _lineNumber = "LineNumber";
+
+ private Func> _getSelectedFiles;
+ private Func _getFullPathResolver;
+ private Func _getCurrentLineNumber;
+
+ public ScriptOptionsProvider(Func> getSelectedFiles, Func getFullPathResolver, Func getCurrentLineNumber)
+ {
+ _getSelectedFiles = getSelectedFiles;
+ _getFullPathResolver = getFullPathResolver;
+ _getCurrentLineNumber = getCurrentLineNumber;
+ }
+
+ public ScriptOptionsProvider(FileStatusList fileStatusList, Func getFullPathResolver, Func getCurrentLineNumber)
+ : this(() => fileStatusList.SelectedItems.Select(item => item.Item.Name), getFullPathResolver, getCurrentLineNumber)
+ {
+ }
+
+ IReadOnlyList IScriptOptionsProvider.Options { get; } = new[] { _selectedFiles, _lineNumber };
+
+ string? IScriptOptionsProvider.GetValue(string option)
+ {
+ switch (option)
+ {
+ case _selectedFiles:
+ IEnumerable selectedFiles = _getSelectedFiles();
+ if (!selectedFiles.Any())
+ {
+ return null;
+ }
+
+ IFullPathResolver fullPathResolver = _getFullPathResolver();
+ return string.Join(" ", selectedFiles.Select(item => fullPathResolver.Resolve(item).QuoteForCommandLine()));
+ case _lineNumber:
+ int? line = _getCurrentLineNumber();
+ return line?.ToString();
+ default:
+ throw new NotImplementedException(option);
+ }
+ }
+}
diff --git a/GitUI/CommandsDialogs/SettingsDialog/Pages/ScriptsSettingsPage.cs b/GitUI/CommandsDialogs/SettingsDialog/Pages/ScriptsSettingsPage.cs
index 03fa42eb169..fddadc90344 100644
--- a/GitUI/CommandsDialogs/SettingsDialog/Pages/ScriptsSettingsPage.cs
+++ b/GitUI/CommandsDialogs/SettingsDialog/Pages/ScriptsSettingsPage.cs
@@ -67,7 +67,11 @@ public partial class ScriptsSettingsPage : SettingsPageWithHeader
{cCommitDate}
{cDefaultRemote}
{cDefaultRemoteUrl}
-{cDefaultRemotePathFromUrl}");
+{cDefaultRemotePathFromUrl}
+
+File(s):
+{SelectedFiles}
+{LineNumber}");
private static readonly string[] WatchedProxyProperties = new string[]
{
diff --git a/GitUI/CommandsDialogs/UserScriptContextMenuExtensions.cs b/GitUI/CommandsDialogs/UserScriptContextMenuExtensions.cs
index 79bd7ad6379..b66413cec1e 100644
--- a/GitUI/CommandsDialogs/UserScriptContextMenuExtensions.cs
+++ b/GitUI/CommandsDialogs/UserScriptContextMenuExtensions.cs
@@ -18,7 +18,7 @@ public static class UserScriptContextMenuExtensions
/// The menu item user scripts not marked as are added to.
/// The handler that handles user script invocation.
/// The DI service provider.
- public static void AddUserScripts(this ContextMenuStrip contextMenu, ToolStripMenuItem hostMenuItem, Func scriptInvoker, IServiceProvider serviceProvider)
+ public static void AddUserScripts(this ContextMenuStrip contextMenu, ToolStripMenuItem hostMenuItem, Func scriptInvoker, Func scriptFilterAddDirect, IServiceProvider serviceProvider)
{
ArgumentNullException.ThrowIfNull(contextMenu);
ArgumentNullException.ThrowIfNull(hostMenuItem);
@@ -51,7 +51,7 @@ public static void AddUserScripts(this ContextMenuStrip contextMenu, ToolStripMe
scriptInvoker(scriptId);
};
- if (script.AddToRevisionGridContextMenu)
+ if (scriptFilterAddDirect(script))
{
// insert items after hostMenuItem
contextMenu.Items.Insert(++lastScriptItemIndex, item);
diff --git a/GitUI/GitModuleControl.cs b/GitUI/GitModuleControl.cs
index 535a1237633..1b0ef47021a 100644
--- a/GitUI/GitModuleControl.cs
+++ b/GitUI/GitModuleControl.cs
@@ -134,11 +134,42 @@ protected override bool ExecuteCommand(int command)
bool ExecuteScriptCommand()
{
+ IScriptsManager scriptsManager = UICommands.GetRequiredService();
+ ScriptInfo? scriptInfo = scriptsManager.GetScript(command);
+ if (scriptInfo is null)
+ {
+ return false;
+ }
+
IScriptsRunner scriptsRunner = UICommands.GetRequiredService();
- return scriptsRunner.RunScript(command, owner: this, UICommands, this as IScriptOptionsProvider);
+ _ = scriptsRunner.RunScript(scriptInfo, owner: this, UICommands, FindScriptOptionsProvider());
+ return true;
+
+ IScriptOptionsProvider? FindScriptOptionsProvider()
+ {
+ for (Control control = this; control != null; control = control.Parent)
+ {
+ if (control is GitModuleControl gitModuleControl && gitModuleControl.GetScriptOptionsProvider() is IScriptOptionsProvider scriptOptionsProvider)
+ {
+ return scriptOptionsProvider;
+ }
+
+ if (control is GitModuleForm gitModuleForm)
+ {
+ return gitModuleForm.GetScriptOptionsProvider();
+ }
+ }
+
+ return null;
+ }
}
}
+ protected virtual IScriptOptionsProvider? GetScriptOptionsProvider()
+ {
+ return null;
+ }
+
/// Raises the event.
protected virtual void OnUICommandsSourceSet(IGitUICommandsSource source)
{
diff --git a/GitUI/GitModuleForm.cs b/GitUI/GitModuleForm.cs
index 4d602056bb5..125f69757c2 100644
--- a/GitUI/GitModuleForm.cs
+++ b/GitUI/GitModuleForm.cs
@@ -105,8 +105,26 @@ protected GitModuleForm([NotNull] GitUICommands commands)
protected override bool ExecuteCommand(int command)
{
- return ScriptsRunner.RunScript(command, owner: this, UICommands)
+ return ExecuteScriptCommand()
|| base.ExecuteCommand(command);
+
+ bool ExecuteScriptCommand()
+ {
+ IScriptsManager scriptsManager = UICommands.GetRequiredService();
+ ScriptInfo? scriptInfo = scriptsManager.GetScript(command);
+ if (scriptInfo is null)
+ {
+ return false;
+ }
+
+ _ = ScriptsRunner.RunScript(scriptInfo, owner: this, UICommands, GetScriptOptionsProvider());
+ return true;
+ }
+ }
+
+ public virtual IScriptOptionsProvider? GetScriptOptionsProvider()
+ {
+ return null;
}
protected virtual void OnUICommandsChanged(GitUICommandsChangedEventArgs e)
diff --git a/GitUI/LeftPanel/RepoObjectsTree.ContextActions.cs b/GitUI/LeftPanel/RepoObjectsTree.ContextActions.cs
index 88150050b8c..345751811b9 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, ExecuteCommand, UICommands);
+ contextMenu.AddUserScripts(runScriptToolStripMenuItem, ExecuteCommand, script => script.AddToRevisionGridContextMenu, UICommands);
}
else
{
diff --git a/GitUI/ScriptsEngine/IScriptOptionsProvider.cs b/GitUI/ScriptsEngine/IScriptOptionsProvider.cs
index 4b36bdaa51e..51fdaa5d2ac 100644
--- a/GitUI/ScriptsEngine/IScriptOptionsProvider.cs
+++ b/GitUI/ScriptsEngine/IScriptOptionsProvider.cs
@@ -12,5 +12,5 @@ public interface IScriptOptionsProvider
///
/// The option identifier which is to be replaced.
/// The value to be used for the script argument.
- string GetValue(string option);
+ string? GetValue(string option);
}
diff --git a/GitUI/ScriptsEngine/IScriptsRunner.cs b/GitUI/ScriptsEngine/IScriptsRunner.cs
index 413db650426..f40eaf2c0ad 100644
--- a/GitUI/ScriptsEngine/IScriptsRunner.cs
+++ b/GitUI/ScriptsEngine/IScriptsRunner.cs
@@ -8,6 +8,6 @@ public interface IScriptsRunner
bool RunEventScripts(ScriptEvent scriptEvent, THostForm form)
where THostForm : IGitModuleForm, IWin32Window;
- bool RunScript(int scriptId, IWin32Window owner, IGitUICommands commands, IScriptOptionsProvider? scriptOptionsProvider = null);
+ bool RunScript(ScriptInfo scriptInfo, IWin32Window owner, IGitUICommands commands, IScriptOptionsProvider? scriptOptionsProvider = null);
}
}
diff --git a/GitUI/ScriptsEngine/ScriptEvent.cs b/GitUI/ScriptsEngine/ScriptEvent.cs
index eef741bf048..55fa3457916 100644
--- a/GitUI/ScriptsEngine/ScriptEvent.cs
+++ b/GitUI/ScriptsEngine/ScriptEvent.cs
@@ -15,6 +15,7 @@ public enum ScriptEvent
BeforeMerge,
AfterMerge,
BeforeFetch,
- AfterFetch
+ AfterFetch,
+ ShowInFileList
}
}
diff --git a/GitUI/ScriptsEngine/ScriptsManager.cs b/GitUI/ScriptsEngine/ScriptsManager.cs
index 8f2cdef07a4..050785c9c31 100644
--- a/GitUI/ScriptsEngine/ScriptsManager.cs
+++ b/GitUI/ScriptsEngine/ScriptsManager.cs
@@ -68,15 +68,8 @@ public bool RunEventScripts(ScriptEvent scriptEvent, THostForm form)
return true;
}
- public bool RunScript(int scriptId, IWin32Window owner, IGitUICommands commands, IScriptOptionsProvider? scriptOptionsProvider = null)
+ public bool RunScript(ScriptInfo scriptInfo, IWin32Window owner, IGitUICommands commands, IScriptOptionsProvider? scriptOptionsProvider = null)
{
- ScriptInfo? scriptInfo = GetScript(scriptId);
- if (scriptInfo is null)
- {
- throw new UserExternalOperationException($"{TranslatedStrings.ScriptErrorCantFind}: '{scriptId}'",
- new ExternalOperationException(workingDirectory: commands.Module.WorkingDir));
- }
-
return ScriptRunner.RunScript(scriptInfo, owner, commands, scriptOptionsProvider);
}
diff --git a/GitUI/Translation/English.xlf b/GitUI/Translation/English.xlf
index eff2bb2b9db..e8a6e480453 100644
--- a/GitUI/Translation/English.xlf
+++ b/GitUI/Translation/English.xlf
@@ -3950,6 +3950,10 @@ You can unset the template:
+
+
+
+
diff --git a/GitUI/UserControls/RevisionGrid/RevisionGridControl.cs b/GitUI/UserControls/RevisionGrid/RevisionGridControl.cs
index 4aa5635f0ac..cf869e11f91 100644
--- a/GitUI/UserControls/RevisionGrid/RevisionGridControl.cs
+++ b/GitUI/UserControls/RevisionGrid/RevisionGridControl.cs
@@ -2138,7 +2138,7 @@ private void ContextMenuOpening(object sender, CancelEventArgs e)
SetEnabled(openPullRequestPageStripMenuItem, !string.IsNullOrWhiteSpace(revision.BuildStatus?.PullRequestUrl));
- mainContextMenu.AddUserScripts(runScriptToolStripMenuItem, ExecuteCommand, UICommands);
+ mainContextMenu.AddUserScripts(runScriptToolStripMenuItem, ExecuteCommand, script => script.AddToRevisionGridContextMenu, UICommands);
UpdateSeparators();
diff --git a/UnitTests/GitCommands.Tests/StringExtensionTests.cs b/UnitTests/GitCommands.Tests/StringExtensionTests.cs
index 929ea06c016..10d95316d0c 100644
--- a/UnitTests/GitCommands.Tests/StringExtensionTests.cs
+++ b/UnitTests/GitCommands.Tests/StringExtensionTests.cs
@@ -130,5 +130,23 @@ public void SubstringAfterLast_string_works_as_expected(string str, string s, st
{
Assert.AreEqual(expected, str.SubstringAfterLast(s));
}
+
+ [TestCase("/usr/bin", false, "\"/usr/bin\"")]
+ [TestCase("/usr/bin", true, "\"/usr/bin\"")]
+ [TestCase("\\usr\\bin", false, "\"\\\\usr\\\\bin\"")]
+ [TestCase("\\usr\\bin", true, "\"\\usr\\bin\"")]
+ [TestCase("C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\setup.exe", true, "\"C:\\Program Files (x86)\\Microsoft Visual Studio\\Installer\\setup.exe\"")]
+ [TestCase("echo \"Hello world\"", false, "\"echo \\\"Hello world\\\"\"")]
+ [TestCase("echo \"Hello world\"", true, "\"echo \"\"Hello world\"\"\"")]
+ [TestCase("echo \'Hello world\'", false, "\"echo \\\'Hello world\\\'\"")]
+ [TestCase("echo \'Hello world\'", true, "\"echo \'Hello world\'\"")]
+ [TestCase("cmd /c \"echo \\\"Hello world\\\"\"", false, "\"cmd /c \\\"echo \\\\\\\"Hello world\\\\\\\"\\\"\"")]
+ [TestCase("cmd /c \"echo \\\"Hello world\\\"\"", true, "\"cmd /c \"\"echo \\\"\"Hello world\\\"\"\"\"\"")]
+ [TestCase("cmd /c \"echo \"\"Hello world\"\"\"", false, "\"cmd /c \\\"echo \\\"\\\"Hello world\\\"\\\"\\\"\"")]
+ [TestCase("cmd /c \"echo \"\"Hello world\"\"\"", true, "\"cmd /c \"\"echo \"\"\"\"Hello world\"\"\"\"\"\"\"")]
+ public void QuoteForCommandLine_works_as_expected(string s, bool forWindows, string expected)
+ {
+ Assert.AreEqual(expected, s.QuoteForCommandLine(forWindows));
+ }
}
}