/GitUI/Script/ScriptRunner.cs

https://github.com/XelaRellum/gitextensions · C# · 507 lines · 467 code · 35 blank · 5 comment · 111 complexity · 16db90a0fce3a57f4b799424d6990589 MD5 · raw file

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Linq;
  5. using System.Windows.Forms;
  6. using GitCommands;
  7. using GitCommands.Config;
  8. using GitUI.HelperDialogs;
  9. using GitUIPluginInterfaces;
  10. namespace GitUI.Script
  11. {
  12. /// <summary>Runs scripts.</summary>
  13. public static class ScriptRunner
  14. {
  15. /// <summary>Tries to run scripts identified by a <paramref name="command"/>
  16. /// and returns true if any executed.</summary>
  17. public static bool ExecuteScriptCommand(IWin32Window owner, GitModule aModule, int command, RevisionGrid revisionGrid = null)
  18. {
  19. var curScripts = ScriptManager.GetScripts();
  20. bool anyScriptExecuted = false;
  21. foreach (ScriptInfo s in curScripts)
  22. {
  23. if (s.HotkeyCommandIdentifier == command)
  24. {
  25. RunScript(owner, aModule, s.Name, revisionGrid);
  26. anyScriptExecuted = true;
  27. }
  28. }
  29. return anyScriptExecuted;
  30. }
  31. public static bool RunScript(IWin32Window owner, GitModule aModule, string script, RevisionGrid revisionGrid)
  32. {
  33. if (string.IsNullOrEmpty(script))
  34. return false;
  35. ScriptInfo scriptInfo = ScriptManager.GetScript(script);
  36. if (scriptInfo == null)
  37. {
  38. MessageBox.Show(owner, "Cannot find script: " + script, "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
  39. return false;
  40. }
  41. if (string.IsNullOrEmpty(scriptInfo.Command))
  42. return false;
  43. string argument = scriptInfo.Arguments;
  44. foreach (string option in Options)
  45. {
  46. if (string.IsNullOrEmpty(argument) || !argument.Contains(option))
  47. continue;
  48. if (!option.StartsWith("{s"))
  49. continue;
  50. if (revisionGrid != null)
  51. continue;
  52. MessageBox.Show(owner,
  53. string.Format("Option {0} is only supported when started from revision grid.", option),
  54. "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
  55. return false;
  56. }
  57. return RunScript(owner, aModule, scriptInfo, revisionGrid);
  58. }
  59. private static string GetRemotePath(string url)
  60. {
  61. Uri uri;
  62. string path = "";
  63. if (Uri.TryCreate(url, UriKind.Absolute, out uri))
  64. path = uri.LocalPath;
  65. else if (Uri.TryCreate("ssh://" + url.Replace(":", "/"), UriKind.Absolute, out uri))
  66. path = uri.LocalPath;
  67. int pos = path.LastIndexOf(".");
  68. if (pos >= 0)
  69. path = path.Substring(0, pos);
  70. return path;
  71. }
  72. internal static bool RunScript(IWin32Window owner, GitModule aModule, ScriptInfo scriptInfo, RevisionGrid revisionGrid)
  73. {
  74. string originalCommand = scriptInfo.Command;
  75. string argument = scriptInfo.Arguments;
  76. string command = OverrideCommandWhenNecessary(originalCommand);
  77. var allSelectedRevisions = new List<GitRevision>();
  78. GitRevision selectedRevision = null;
  79. GitRevision currentRevision = null;
  80. var selectedLocalBranches = new List<IGitRef>();
  81. var selectedRemoteBranches = new List<IGitRef>();
  82. var selectedRemotes = new List<string>();
  83. var selectedBranches = new List<IGitRef>();
  84. var selectedTags = new List<IGitRef>();
  85. var currentLocalBranches = new List<IGitRef>();
  86. var currentRemoteBranches = new List<IGitRef>();
  87. var currentRemote = "";
  88. var currentBranches = new List<IGitRef>();
  89. var currentTags = new List<IGitRef>();
  90. foreach (string option in Options)
  91. {
  92. if (string.IsNullOrEmpty(argument) || !argument.Contains(option))
  93. continue;
  94. if (option.StartsWith("{c") && currentRevision == null)
  95. {
  96. currentRevision = GetCurrentRevision(aModule, revisionGrid, currentTags, currentLocalBranches, currentRemoteBranches, currentBranches, currentRevision);
  97. if (currentLocalBranches.Count == 1)
  98. currentRemote = aModule.GetSetting(string.Format(SettingKeyString.BranchRemote, currentLocalBranches[0].Name));
  99. else
  100. {
  101. currentRemote = aModule.GetCurrentRemote();
  102. if (string.IsNullOrEmpty(currentRemote))
  103. currentRemote = aModule.GetSetting(string.Format(SettingKeyString.BranchRemote,
  104. askToSpecify(currentLocalBranches, "Current Revision Branch")));
  105. }
  106. }
  107. else if (option.StartsWith("{s") && selectedRevision == null && revisionGrid != null)
  108. {
  109. allSelectedRevisions = revisionGrid.GetSelectedRevisions();
  110. allSelectedRevisions.Reverse(); // Put first clicked revisions first
  111. selectedRevision = CalculateSelectedRevision(revisionGrid, selectedRemoteBranches, selectedRemotes, selectedLocalBranches, selectedBranches, selectedTags);
  112. }
  113. string remote;
  114. string url;
  115. switch (option)
  116. {
  117. case "{sHashes}":
  118. argument = argument.Replace(option,
  119. string.Join(" ", allSelectedRevisions.Select(revision => revision.Guid).ToArray()));
  120. break;
  121. case "{sTag}":
  122. if (selectedTags.Count == 1)
  123. argument = argument.Replace(option, selectedTags[0].Name);
  124. else if (selectedTags.Count != 0)
  125. argument = argument.Replace(option, askToSpecify(selectedTags, "Selected Revision Tag"));
  126. else
  127. argument = argument.Replace(option, "");
  128. break;
  129. case "{sBranch}":
  130. if (selectedBranches.Count == 1)
  131. argument = argument.Replace(option, selectedBranches[0].Name);
  132. else if (selectedBranches.Count != 0)
  133. argument = argument.Replace(option,
  134. askToSpecify(selectedBranches, "Selected Revision Branch"));
  135. else
  136. argument = argument.Replace(option, "");
  137. break;
  138. case "{sLocalBranch}":
  139. if (selectedLocalBranches.Count == 1)
  140. argument = argument.Replace(option, selectedLocalBranches[0].Name);
  141. else if (selectedLocalBranches.Count != 0)
  142. argument = argument.Replace(option,
  143. askToSpecify(selectedLocalBranches,
  144. "Selected Revision Local Branch"));
  145. else
  146. argument = argument.Replace(option, "");
  147. break;
  148. case "{sRemoteBranch}":
  149. if (selectedRemoteBranches.Count == 1)
  150. argument = argument.Replace(option, selectedRemoteBranches[0].Name);
  151. else if (selectedRemoteBranches.Count != 0)
  152. argument = argument.Replace(option,
  153. askToSpecify(selectedRemoteBranches,
  154. "Selected Revision Remote Branch"));
  155. else
  156. argument = argument.Replace(option, "");
  157. break;
  158. case "{sRemote}":
  159. if (selectedRemotes.Count == 0)
  160. {
  161. argument = argument.Replace(option, "");
  162. break;
  163. }
  164. if (selectedRemotes.Count == 1)
  165. remote = selectedRemotes[0];
  166. else
  167. remote = askToSpecify(selectedRemotes, "Selected Revision Remote");
  168. argument = argument.Replace(option, remote);
  169. break;
  170. case "{sRemoteUrl}":
  171. if (selectedRemotes.Count == 0)
  172. {
  173. argument = argument.Replace(option, "");
  174. break;
  175. }
  176. if (selectedRemotes.Count == 1)
  177. remote = selectedRemotes[0];
  178. else
  179. remote = askToSpecify(selectedRemotes, "Selected Revision Remote");
  180. url = aModule.GetSetting(string.Format(SettingKeyString.RemoteUrl, remote));
  181. argument = argument.Replace(option, url);
  182. break;
  183. case "{sRemotePathFromUrl}":
  184. if (selectedRemotes.Count == 0)
  185. {
  186. argument = argument.Replace(option, "");
  187. break;
  188. }
  189. if (selectedRemotes.Count == 1)
  190. remote = selectedRemotes[0];
  191. else
  192. remote = askToSpecify(selectedRemotes, "Selected Revision Remote");
  193. url = aModule.GetSetting(string.Format(SettingKeyString.RemoteUrl, remote));
  194. argument = argument.Replace(option, GetRemotePath(url));
  195. break;
  196. case "{sHash}":
  197. argument = argument.Replace(option, selectedRevision.Guid);
  198. break;
  199. case "{sMessage}":
  200. argument = argument.Replace(option, selectedRevision.Subject);
  201. break;
  202. case "{sAuthor}":
  203. argument = argument.Replace(option, selectedRevision.Author);
  204. break;
  205. case "{sCommitter}":
  206. argument = argument.Replace(option, selectedRevision.Committer);
  207. break;
  208. case "{sAuthorDate}":
  209. argument = argument.Replace(option, selectedRevision.AuthorDate.ToString());
  210. break;
  211. case "{sCommitDate}":
  212. argument = argument.Replace(option, selectedRevision.CommitDate.ToString());
  213. break;
  214. case "{cTag}":
  215. if (currentTags.Count == 1)
  216. argument = argument.Replace(option, currentTags[0].Name);
  217. else if (currentTags.Count != 0)
  218. argument = argument.Replace(option, askToSpecify(currentTags, "Current Revision Tag"));
  219. else
  220. argument = argument.Replace(option, "");
  221. break;
  222. case "{cBranch}":
  223. if (currentBranches.Count == 1)
  224. argument = argument.Replace(option, currentBranches[0].Name);
  225. else if (currentBranches.Count != 0)
  226. argument = argument.Replace(option,
  227. askToSpecify(currentBranches, "Current Revision Branch"));
  228. else
  229. argument = argument.Replace(option, "");
  230. break;
  231. case "{cLocalBranch}":
  232. if (currentLocalBranches.Count == 1)
  233. argument = argument.Replace(option, currentLocalBranches[0].Name);
  234. else if (currentLocalBranches.Count != 0)
  235. argument = argument.Replace(option,
  236. askToSpecify(currentLocalBranches,
  237. "Current Revision Local Branch"));
  238. else
  239. argument = argument.Replace(option, "");
  240. break;
  241. case "{cRemoteBranch}":
  242. if (currentRemoteBranches.Count == 1)
  243. argument = argument.Replace(option, currentRemoteBranches[0].Name);
  244. else if (currentRemoteBranches.Count != 0)
  245. argument = argument.Replace(option,
  246. askToSpecify(currentRemoteBranches,
  247. "Current Revision Remote Branch"));
  248. else
  249. argument = argument.Replace(option, "");
  250. break;
  251. case "{cHash}":
  252. argument = argument.Replace(option, currentRevision.Guid);
  253. break;
  254. case "{cMessage}":
  255. argument = argument.Replace(option, currentRevision.Subject);
  256. break;
  257. case "{cAuthor}":
  258. argument = argument.Replace(option, currentRevision.Author);
  259. break;
  260. case "{cCommitter}":
  261. argument = argument.Replace(option, currentRevision.Committer);
  262. break;
  263. case "{cAuthorDate}":
  264. argument = argument.Replace(option, currentRevision.AuthorDate.ToString());
  265. break;
  266. case "{cCommitDate}":
  267. argument = argument.Replace(option, currentRevision.CommitDate.ToString());
  268. break;
  269. case "{cDefaultRemote}":
  270. if (string.IsNullOrEmpty(currentRemote))
  271. {
  272. argument = argument.Replace(option, "");
  273. break;
  274. }
  275. argument = argument.Replace(option, currentRemote);
  276. break;
  277. case "{cDefaultRemoteUrl}":
  278. if (string.IsNullOrEmpty(currentRemote))
  279. {
  280. argument = argument.Replace(option, "");
  281. break;
  282. }
  283. url = aModule.GetSetting(string.Format(SettingKeyString.RemoteUrl, currentRemote));
  284. argument = argument.Replace(option, url);
  285. break;
  286. case "{cDefaultRemotePathFromUrl}":
  287. if (string.IsNullOrEmpty(currentRemote))
  288. {
  289. argument = argument.Replace(option, "");
  290. break;
  291. }
  292. url = aModule.GetSetting(string.Format(SettingKeyString.RemoteUrl, currentRemote));
  293. argument = argument.Replace(option, GetRemotePath(url));
  294. break;
  295. case "{UserInput}":
  296. using (SimplePrompt Prompt = new SimplePrompt())
  297. {
  298. Prompt.ShowDialog();
  299. argument = argument.Replace(option, Prompt.UserInput);
  300. }
  301. break;
  302. case "{WorkingDir}":
  303. argument = argument.Replace(option, aModule.WorkingDir);
  304. break;
  305. }
  306. }
  307. command = ExpandCommandVariables(command, aModule);
  308. if (scriptInfo.IsPowerShell)
  309. {
  310. PowerShellHelper.RunPowerShell(command, argument, aModule.WorkingDir, scriptInfo.RunInBackground);
  311. return false;
  312. }
  313. if (command.StartsWith(PluginPrefix))
  314. {
  315. command = command.Replace(PluginPrefix, "");
  316. foreach (var plugin in Plugin.LoadedPlugins.Plugins)
  317. if (plugin.Description.ToLower().Equals(command, StringComparison.CurrentCultureIgnoreCase))
  318. {
  319. var eventArgs = new GitUIEventArgs(owner, revisionGrid.UICommands, argument);
  320. return plugin.Execute(eventArgs);
  321. }
  322. return false;
  323. }
  324. if (!scriptInfo.RunInBackground)
  325. FormProcess.ShowDialog(owner, command, argument, aModule.WorkingDir, null, true);
  326. else
  327. {
  328. if (originalCommand.Equals("{openurl}", StringComparison.CurrentCultureIgnoreCase))
  329. Process.Start(argument);
  330. else
  331. aModule.RunExternalCmdDetached(command, argument);
  332. }
  333. return !scriptInfo.RunInBackground;
  334. }
  335. private static string ExpandCommandVariables(string originalCommand, GitModule aModule)
  336. {
  337. return originalCommand.Replace("{WorkingDir}", aModule.WorkingDir);
  338. }
  339. private static GitRevision CalculateSelectedRevision(RevisionGrid revisionGrid, List<IGitRef> selectedRemoteBranches,
  340. List<string> selectedRemotes, List<IGitRef> selectedLocalBranches,
  341. List<IGitRef> selectedBranches, List<IGitRef> selectedTags)
  342. {
  343. GitRevision selectedRevision = revisionGrid.LatestSelectedRevision;
  344. foreach (GitRef head in selectedRevision.Refs)
  345. {
  346. if (head.IsTag)
  347. selectedTags.Add(head);
  348. else if (head.IsHead || head.IsRemote)
  349. {
  350. selectedBranches.Add(head);
  351. if (head.IsRemote)
  352. {
  353. selectedRemoteBranches.Add(head);
  354. if (!selectedRemotes.Contains(head.Remote))
  355. selectedRemotes.Add(head.Remote);
  356. }
  357. else
  358. selectedLocalBranches.Add(head);
  359. }
  360. }
  361. return selectedRevision;
  362. }
  363. private static GitRevision GetCurrentRevision(GitModule aModule, RevisionGrid RevisionGrid, List<IGitRef> currentTags, List<IGitRef> currentLocalBranches,
  364. List<IGitRef> currentRemoteBranches, List<IGitRef> currentBranches,
  365. GitRevision currentRevision)
  366. {
  367. if (currentRevision == null)
  368. {
  369. IList<IGitRef> refs;
  370. if (RevisionGrid == null)
  371. {
  372. string currentRevisionGuid = aModule.GetCurrentCheckout();
  373. refs = aModule.GetRefs(true, true).Where(gitRef => gitRef.Guid == currentRevisionGuid).ToList();
  374. }
  375. else
  376. {
  377. currentRevision = RevisionGrid.GetCurrentRevision();
  378. refs = currentRevision.Refs;
  379. }
  380. foreach (IGitRef gitRef in refs)
  381. {
  382. if (gitRef.IsTag)
  383. currentTags.Add(gitRef);
  384. else if (gitRef.IsHead || gitRef.IsRemote)
  385. {
  386. currentBranches.Add(gitRef);
  387. if (gitRef.IsRemote)
  388. currentRemoteBranches.Add(gitRef);
  389. else
  390. currentLocalBranches.Add(gitRef);
  391. }
  392. }
  393. }
  394. return currentRevision;
  395. }
  396. private static string[] Options
  397. {
  398. get
  399. {
  400. string[] options =
  401. {
  402. "{sHashes}",
  403. "{sTag}",
  404. "{sBranch}",
  405. "{sLocalBranch}",
  406. "{sRemoteBranch}",
  407. "{sRemote}",
  408. "{sRemoteUrl}",
  409. "{sRemotePathFromUrl}",
  410. "{sHash}",
  411. "{sMessage}",
  412. "{sAuthor}",
  413. "{sCommitter}",
  414. "{sAuthorDate}",
  415. "{sCommitDate}",
  416. "{cTag}",
  417. "{cBranch}",
  418. "{cLocalBranch}",
  419. "{cRemoteBranch}",
  420. "{cHash}",
  421. "{cMessage}",
  422. "{cAuthor}",
  423. "{cCommitter}",
  424. "{cAuthorDate}",
  425. "{cCommitDate}",
  426. "{cDefaultRemote}",
  427. "{cDefaultRemoteUrl}",
  428. "{cDefaultRemotePathFromUrl}",
  429. "{UserInput}",
  430. "{WorkingDir}"
  431. };
  432. return options;
  433. }
  434. }
  435. private static string PluginPrefix = "plugin:";
  436. private static string OverrideCommandWhenNecessary(string originalCommand)
  437. {
  438. //Make sure we are able to run git, even if git is not in the path
  439. if (originalCommand.Equals("git", StringComparison.CurrentCultureIgnoreCase) ||
  440. originalCommand.Equals("{git}", StringComparison.CurrentCultureIgnoreCase))
  441. return AppSettings.GitCommand;
  442. if (originalCommand.Equals("gitextensions", StringComparison.CurrentCultureIgnoreCase) ||
  443. originalCommand.Equals("{gitextensions}", StringComparison.CurrentCultureIgnoreCase) ||
  444. originalCommand.Equals("gitex", StringComparison.CurrentCultureIgnoreCase) ||
  445. originalCommand.Equals("{gitex}", StringComparison.CurrentCultureIgnoreCase))
  446. return AppSettings.GetGitExtensionsFullPath();
  447. if (originalCommand.Equals("{openurl}", StringComparison.CurrentCultureIgnoreCase))
  448. return "explorer";
  449. //Prefix should be {plugin:pluginname},{plugin=pluginname}
  450. var match = System.Text.RegularExpressions.Regex.Match(originalCommand, @"\{plugin.(.+)\}", System.Text.RegularExpressions.RegexOptions.IgnoreCase);
  451. if (match.Success && match.Groups.Count > 1)
  452. originalCommand = string.Format("{0}{1}", PluginPrefix, match.Groups[1].Value.ToLower());
  453. return originalCommand;
  454. }
  455. private static string askToSpecify(IEnumerable<IGitRef> options, string title)
  456. {
  457. using (var f = new FormRunScriptSpecify(options, title))
  458. {
  459. f.ShowDialog();
  460. return f.ret;
  461. }
  462. }
  463. private static string askToSpecify(IEnumerable<string> options, string title)
  464. {
  465. using (var f = new FormRunScriptSpecify(options, title))
  466. {
  467. f.ShowDialog();
  468. return f.ret;
  469. }
  470. }
  471. }
  472. }