PageRenderTime 61ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/GitCommands/Git/GitCommandHelpers.cs

https://github.com/qgppl/gitextensions
C# | 1245 lines | 980 code | 177 blank | 88 comment | 176 complexity | 9e35e2a033b2df559a16483210d0e634 MD5 | raw file
Possible License(s): GPL-3.0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Diagnostics;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Runtime.InteropServices;
  8. using System.Text;
  9. using System.Text.RegularExpressions;
  10. using GitCommands.Git;
  11. using GitCommands.Utils;
  12. using JetBrains.Annotations;
  13. namespace GitCommands
  14. {
  15. /// <summary>Specifies whether to check untracked files/directories (e.g. via 'git status')</summary>
  16. public enum UntrackedFilesMode
  17. {
  18. /// <summary>Default is <see cref="All"/>; when <see cref="UntrackedFilesMode"/> is NOT used, 'git status' uses <see cref="Normal"/>.</summary>
  19. Default = 1,
  20. /// <summary>Show no untracked files.</summary>
  21. No = 2,
  22. /// <summary>Shows untracked files and directories.</summary>
  23. Normal = 3,
  24. /// <summary>Shows untracked files and directories, and individual files in untracked directories.</summary>
  25. All = 4
  26. }
  27. /// <summary>Specifies whether to ignore changes to submodules when looking for changes (e.g. via 'git status').</summary>
  28. public enum IgnoreSubmodulesMode
  29. {
  30. /// <summary>Default is <see cref="All"/> (hides all changes to submodules).</summary>
  31. Default = 1,
  32. /// <summary>Consider a submodule modified when it either:
  33. /// contains untracked or modified files,
  34. /// or its HEAD differs from the commit recorded in the superproject.</summary>
  35. None = 2,
  36. /// <summary>Submodules NOT considered dirty when they only contain <i>untracked</i> content
  37. /// (but they are still scanned for modified content).</summary>
  38. Untracked = 3,
  39. /// <summary>Ignores all changes to the work tree of submodules,
  40. /// only changes to the <i>commits</i> stored in the superproject are shown.</summary>
  41. Dirty = 4,
  42. /// <summary>Hides all changes to submodules
  43. /// (and suppresses the output of submodule summaries when the config option status.submodulesummary is set).</summary>
  44. All = 5
  45. }
  46. public static class GitCommandHelpers
  47. {
  48. public static void SetEnvironmentVariable(bool reload = false)
  49. {
  50. string path = Environment.GetEnvironmentVariable("PATH", EnvironmentVariableTarget.Process);
  51. if (!string.IsNullOrEmpty(AppSettings.GitBinDir) && !path.Contains(AppSettings.GitBinDir))
  52. Environment.SetEnvironmentVariable("PATH", string.Concat(path, ";", AppSettings.GitBinDir), EnvironmentVariableTarget.Process);
  53. if (!string.IsNullOrEmpty(AppSettings.CustomHomeDir))
  54. {
  55. Environment.SetEnvironmentVariable(
  56. "HOME",
  57. AppSettings.CustomHomeDir);
  58. return;
  59. }
  60. if (AppSettings.UserProfileHomeDir)
  61. {
  62. Environment.SetEnvironmentVariable(
  63. "HOME",
  64. Environment.GetEnvironmentVariable("USERPROFILE"));
  65. return;
  66. }
  67. if (reload)
  68. {
  69. Environment.SetEnvironmentVariable(
  70. "HOME",
  71. UserHomeDir);
  72. }
  73. //Default!
  74. Environment.SetEnvironmentVariable("HOME", GetDefaultHomeDir());
  75. //to prevent from leaking processes see issue #1092 for details
  76. Environment.SetEnvironmentVariable("TERM", "msys");
  77. string sshAskPass = Path.Combine(AppSettings.GetInstallDir(), @"GitExtSshAskPass.exe");
  78. if (EnvUtils.RunningOnWindows())
  79. {
  80. if (File.Exists(sshAskPass))
  81. Environment.SetEnvironmentVariable("SSH_ASKPASS", sshAskPass);
  82. }
  83. else if (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("SSH_ASKPASS")))
  84. Environment.SetEnvironmentVariable("SSH_ASKPASS", "ssh-askpass");
  85. }
  86. public static string GetHomeDir()
  87. {
  88. return Environment.GetEnvironmentVariable("HOME") ?? "";
  89. }
  90. public static string GetDefaultHomeDir()
  91. {
  92. if (!string.IsNullOrEmpty(UserHomeDir))
  93. return UserHomeDir;
  94. if (EnvUtils.RunningOnWindows())
  95. {
  96. return WindowsDefaultHomeDir;
  97. }
  98. return Environment.GetFolderPath(Environment.SpecialFolder.Personal);
  99. }
  100. private static string WindowsDefaultHomeDir
  101. {
  102. get
  103. {
  104. if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("HOMEDRIVE")))
  105. {
  106. string homePath = Environment.GetEnvironmentVariable("HOMEDRIVE");
  107. homePath += Environment.GetEnvironmentVariable("HOMEPATH");
  108. return homePath;
  109. }
  110. return Environment.GetEnvironmentVariable("USERPROFILE");
  111. }
  112. }
  113. public static ProcessStartInfo CreateProcessStartInfo(string fileName, string arguments, string workingDirectory, Encoding outputEncoding)
  114. {
  115. return new ProcessStartInfo
  116. {
  117. UseShellExecute = false,
  118. ErrorDialog = false,
  119. CreateNoWindow = true,
  120. RedirectStandardInput = true,
  121. RedirectStandardOutput = true,
  122. RedirectStandardError = true,
  123. StandardOutputEncoding = outputEncoding,
  124. StandardErrorEncoding = outputEncoding,
  125. FileName = fileName,
  126. Arguments = arguments,
  127. WorkingDirectory = workingDirectory
  128. };
  129. }
  130. internal static Process StartProcess(string fileName, string arguments, string workingDirectory, Encoding outputEncoding)
  131. {
  132. SetEnvironmentVariable();
  133. string quotedCmd = fileName;
  134. if (quotedCmd.IndexOf(' ') != -1)
  135. quotedCmd = quotedCmd.Quote();
  136. var executionStartTimestamp = DateTime.Now;
  137. var startInfo = CreateProcessStartInfo(fileName, arguments, workingDirectory, outputEncoding);
  138. var startProcess = Process.Start(startInfo);
  139. startProcess.Exited += (sender, args) =>
  140. {
  141. var executionEndTimestamp = DateTime.Now;
  142. AppSettings.GitLog.Log(quotedCmd + " " + arguments, executionStartTimestamp, executionEndTimestamp);
  143. };
  144. return startProcess;
  145. }
  146. public static bool UseSsh(string arguments)
  147. {
  148. var x = !Plink() && GetArgumentsRequiresSsh(arguments);
  149. return x || arguments.Contains("plink");
  150. }
  151. private static bool GetArgumentsRequiresSsh(string arguments)
  152. {
  153. return (arguments.Contains("@") && arguments.Contains("://")) ||
  154. (arguments.Contains("@") && arguments.Contains(":")) ||
  155. (arguments.Contains("ssh://")) ||
  156. (arguments.Contains("http://")) ||
  157. (arguments.Contains("git://")) ||
  158. (arguments.Contains("push")) ||
  159. (arguments.Contains("remote")) ||
  160. (arguments.Contains("fetch")) ||
  161. (arguments.Contains("pull"));
  162. }
  163. /// <summary>
  164. /// Transforms the given input Url to make it compatible with Plink, if necessary
  165. /// </summary>
  166. public static string GetPlinkCompatibleUrl(string inputUrl)
  167. {
  168. // We don't need putty for http:// links and git@... urls are already usable.
  169. // But ssh:// urls can cause problems
  170. if (!inputUrl.StartsWith("ssh") || !Uri.IsWellFormedUriString(inputUrl, UriKind.Absolute))
  171. return "\"" + inputUrl + "\"";
  172. // Turn ssh://user@host/path into user@host:path, which works better
  173. Uri uri = new Uri(inputUrl, UriKind.Absolute);
  174. string fixedUrl = "";
  175. if (!uri.IsDefaultPort)
  176. fixedUrl += "-P " + uri.Port + " ";
  177. fixedUrl += "\"";
  178. if (!String.IsNullOrEmpty(uri.UserInfo))
  179. fixedUrl += uri.UserInfo + "@";
  180. fixedUrl += uri.Host;
  181. fixedUrl += ":" + uri.LocalPath.Substring(1) + "\"";
  182. return fixedUrl;
  183. }
  184. private static IEnumerable<string> StartProcessAndReadLines(string arguments, string cmd, string workDir, string stdInput)
  185. {
  186. if (string.IsNullOrEmpty(cmd))
  187. yield break;
  188. //process used to execute external commands
  189. using (var process = StartProcess(cmd, arguments, workDir, GitModule.SystemEncoding))
  190. {
  191. if (!string.IsNullOrEmpty(stdInput))
  192. {
  193. process.StandardInput.Write(stdInput);
  194. process.StandardInput.Close();
  195. }
  196. string line;
  197. do
  198. {
  199. line = process.StandardOutput.ReadLine();
  200. if (line != null)
  201. yield return line;
  202. } while (line != null);
  203. do
  204. {
  205. line = process.StandardError.ReadLine();
  206. if (line != null)
  207. yield return line;
  208. } while (line != null);
  209. process.WaitForExit();
  210. }
  211. }
  212. /// <summary>
  213. /// Run command, console window is hidden, wait for exit, redirect output
  214. /// </summary>
  215. public static IEnumerable<string> ReadCmdOutputLines(string cmd, string arguments, string workDir, string stdInput)
  216. {
  217. SetEnvironmentVariable();
  218. arguments = arguments.Replace("$QUOTE$", "\\\"");
  219. return StartProcessAndReadLines(arguments, cmd, workDir, stdInput);
  220. }
  221. private static Process StartProcessAndReadAllText(string arguments, string cmd, string workDir, out string stdOutput, out string stdError, string stdInput)
  222. {
  223. if (string.IsNullOrEmpty(cmd))
  224. {
  225. stdOutput = stdError = "";
  226. return null;
  227. }
  228. //process used to execute external commands
  229. var process = StartProcess(cmd, arguments, workDir, GitModule.SystemEncoding);
  230. if (!string.IsNullOrEmpty(stdInput))
  231. {
  232. process.StandardInput.Write(stdInput);
  233. process.StandardInput.Close();
  234. }
  235. SynchronizedProcessReader.Read(process, out stdOutput, out stdError);
  236. return process;
  237. }
  238. /// <summary>
  239. /// Run command, console window is hidden, wait for exit, redirect output
  240. /// </summary>
  241. public static string RunCmd(string cmd, string arguments)
  242. {
  243. try
  244. {
  245. SetEnvironmentVariable();
  246. arguments = arguments.Replace("$QUOTE$", "\\\"");
  247. string output, error;
  248. using (var process = StartProcessAndReadAllText(arguments, cmd, "", out output, out error, null))
  249. {
  250. process.WaitForExit();
  251. }
  252. if (!string.IsNullOrEmpty(error))
  253. {
  254. output += Environment.NewLine + error;
  255. }
  256. return output;
  257. }
  258. catch (Win32Exception)
  259. {
  260. return string.Empty;
  261. }
  262. }
  263. private static Process StartProcessAndReadAllBytes(string arguments, string cmd, string workDir, out byte[] stdOutput, out byte[] stdError, byte[] stdInput)
  264. {
  265. if (string.IsNullOrEmpty(cmd))
  266. {
  267. stdOutput = stdError = null;
  268. return null;
  269. }
  270. //process used to execute external commands
  271. var process = StartProcess(cmd, arguments, workDir, Encoding.Default);
  272. if (stdInput != null && stdInput.Length > 0)
  273. {
  274. process.StandardInput.BaseStream.Write(stdInput, 0, stdInput.Length);
  275. process.StandardInput.Close();
  276. }
  277. SynchronizedProcessReader.ReadBytes(process, out stdOutput, out stdError);
  278. return process;
  279. }
  280. /// <summary>
  281. /// Run command, console window is hidden, wait for exit, redirect output
  282. /// </summary>
  283. public static int RunCmdByte(string cmd, string arguments, string workingdir, byte[] stdInput, out byte[] output, out byte[] error)
  284. {
  285. try
  286. {
  287. arguments = arguments.Replace("$QUOTE$", "\\\"");
  288. using (var process = StartProcessAndReadAllBytes(arguments, cmd, workingdir, out output, out error, stdInput))
  289. {
  290. process.WaitForExit();
  291. return process.ExitCode;
  292. }
  293. }
  294. catch (Win32Exception)
  295. {
  296. output = error = null;
  297. return 1;
  298. }
  299. }
  300. private static GitVersion _versionInUse;
  301. private static readonly string UserHomeDir = Environment.GetEnvironmentVariable("HOME", EnvironmentVariableTarget.User)
  302. ?? Environment.GetEnvironmentVariable("HOME", EnvironmentVariableTarget.Machine);
  303. public static GitVersion VersionInUse
  304. {
  305. get
  306. {
  307. if (_versionInUse == null || _versionInUse.IsUnknown)
  308. {
  309. var result = RunCmd(AppSettings.GitCommand, "--version");
  310. _versionInUse = new GitVersion(result);
  311. }
  312. return _versionInUse;
  313. }
  314. }
  315. public static string CherryPickCmd(string cherry, bool commit, string arguments)
  316. {
  317. string cherryPickCmd = commit ? "cherry-pick" : "cherry-pick --no-commit";
  318. return cherryPickCmd + " " + arguments + " \"" + cherry + "\"";
  319. }
  320. /// <summary>
  321. /// Check if a string represents a commit hash
  322. /// </summary>
  323. private static bool IsCommitHash(string value)
  324. {
  325. return GitRevision.Sha1HashRegex.IsMatch(value);
  326. }
  327. public static string GetFullBranchName(string branch)
  328. {
  329. if (branch == null)
  330. return null;
  331. branch = branch.Trim();
  332. if (string.IsNullOrEmpty(branch) || branch.StartsWith("refs/"))
  333. return branch;
  334. // If the branch represents a commit hash, return it as-is without appending refs/heads/ (fix issue #2240)
  335. // NOTE: We can use `String.IsNullOrEmpty(Module.RevParse(srcRev))` instead
  336. if (IsCommitHash(branch))
  337. {
  338. return branch;
  339. }
  340. return "refs/heads/" + branch;
  341. }
  342. public static string DeleteBranchCmd(string branchName, bool force, bool remoteBranch)
  343. {
  344. StringBuilder cmd = new StringBuilder("branch");
  345. cmd.Append(force ? " -D" : " -d");
  346. if (remoteBranch)
  347. cmd.Append(" -r");
  348. cmd.Append(" \"");
  349. cmd.Append(branchName);
  350. cmd.Append("\"");
  351. return cmd.ToString();
  352. }
  353. public static string DeleteTagCmd(string tagName)
  354. {
  355. return "tag -d \"" + tagName + "\"";
  356. }
  357. public static string SubmoduleUpdateCmd(string name)
  358. {
  359. if (string.IsNullOrEmpty(name))
  360. return "submodule update --init --recursive";
  361. return "submodule update --init --recursive \"" + name.Trim() + "\"";
  362. }
  363. public static string SubmoduleSyncCmd(string name)
  364. {
  365. if (string.IsNullOrEmpty(name))
  366. return "submodule sync";
  367. return "submodule sync \"" + name.Trim() + "\"";
  368. }
  369. public static string AddSubmoduleCmd(string remotePath, string localPath, string branch, bool force)
  370. {
  371. remotePath = remotePath.ToPosixPath();
  372. localPath = localPath.ToPosixPath();
  373. if (!string.IsNullOrEmpty(branch))
  374. branch = " -b \"" + branch.Trim() + "\"";
  375. var forceCmd = force ? " -f" : string.Empty;
  376. return "submodule add" + forceCmd + branch + " \"" + remotePath.Trim() + "\" \"" + localPath.Trim() + "\"";
  377. }
  378. public static string RevertCmd(string commit, bool autoCommit, int parentIndex)
  379. {
  380. var cmd = new StringBuilder("revert ");
  381. if (!autoCommit)
  382. {
  383. cmd.Append("--no-commit ");
  384. }
  385. if (parentIndex > 0)
  386. {
  387. cmd.AppendFormat("-m {0} ", parentIndex);
  388. }
  389. cmd.Append(commit);
  390. return cmd.ToString();
  391. }
  392. public static string ResetSoftCmd(string commit)
  393. {
  394. return "reset --soft \"" + commit + "\"";
  395. }
  396. public static string ResetMixedCmd(string commit)
  397. {
  398. return "reset --mixed \"" + commit + "\"";
  399. }
  400. public static string ResetHardCmd(string commit)
  401. {
  402. return "reset --hard \"" + commit + "\"";
  403. }
  404. public static string CloneCmd(string fromPath, string toPath)
  405. {
  406. return CloneCmd(fromPath, toPath, false, false, string.Empty, null);
  407. }
  408. /// <summary>
  409. /// Git Clone.
  410. /// </summary>
  411. /// <param name="fromPath"></param>
  412. /// <param name="toPath"></param>
  413. /// <param name="central">Makes a bare repo.</param>
  414. /// <param name="initSubmodules"></param>
  415. /// <param name="branch">
  416. /// <para><c>NULL</c>: do not checkout working copy (--no-checkout).</para>
  417. /// <para><c>""</c> (empty string): checkout remote HEAD (branch param omitted, default behavior for clone).</para>
  418. /// <para>(a non-empty string): checkout the given branch (--branch smth).</para>
  419. /// </param>
  420. /// <param name="depth">An int value for --depth param, or <c>NULL</c> to omit the param.</param>
  421. /// <param name="isSingleBranch">
  422. /// <para><c>True</c>: --single-branch.</para>
  423. /// <para><c>False</c>: --no-single-branch.</para>
  424. /// <para><c>NULL</c>: don't pass any such param to git.</para>
  425. /// </param>
  426. /// <returns></returns>
  427. public static string CloneCmd(string fromPath, string toPath, bool central, bool initSubmodules, [CanBeNull] string branch, int? depth, [Optional] bool? isSingleBranch)
  428. {
  429. var from = PathUtil.IsLocalFile(fromPath) ? fromPath.ToPosixPath() : fromPath;
  430. var to = toPath.ToPosixPath();
  431. var options = new List<string> { "-v" };
  432. if (central)
  433. options.Add("--bare");
  434. if (initSubmodules)
  435. options.Add("--recurse-submodules");
  436. if (depth.HasValue)
  437. options.Add("--depth " + depth);
  438. if(isSingleBranch.HasValue)
  439. options.Add(isSingleBranch.Value ? "--single-branch" : "--no-single-branch");
  440. options.Add("--progress");
  441. if (branch == null)
  442. options.Add("--no-checkout");
  443. else if (branch != "")
  444. options.Add("--branch " + branch);
  445. options.Add(string.Format("\"{0}\"", from.Trim()));
  446. options.Add(string.Format("\"{0}\"", to.Trim()));
  447. return "clone " + string.Join(" ", options.ToArray());
  448. }
  449. public static string CheckoutCmd(string branchOrRevisionName, LocalChangesAction changesAction)
  450. {
  451. string args = "";
  452. switch (changesAction)
  453. {
  454. case LocalChangesAction.Merge:
  455. args = " --merge";
  456. break;
  457. case LocalChangesAction.Reset:
  458. args = " --force";
  459. break;
  460. }
  461. return string.Format("checkout{0} \"{1}\"", args, branchOrRevisionName);
  462. }
  463. /// <summary>Create a new orphan branch from <paramref name="startPoint"/> and switch to it.</summary>
  464. public static string CreateOrphanCmd(string newBranchName, string startPoint = null)
  465. {
  466. return string.Format("checkout --orphan {0} {1}", newBranchName, startPoint);
  467. }
  468. /// <summary>Remove files from the working tree and from the index. <remarks>git rm</remarks></summary>
  469. /// <param name="force">Override the up-to-date check.</param>
  470. /// <param name="isRecursive">Allow recursive removal when a leading directory name is given.</param>
  471. /// <param name="files">Files to remove. Fileglobs can be given to remove matching files.</param>
  472. public static string RemoveCmd(bool force = true, bool isRecursive = true, params string[] files)
  473. {
  474. string file = ".";
  475. if (files.Any())
  476. file = string.Join(" ", files);
  477. return string.Format("rm {0} {1} {2}",
  478. force ? "--force" : string.Empty,
  479. isRecursive ? "-r" : string.Empty,
  480. file
  481. );
  482. }
  483. public static string BranchCmd(string branchName, string revision, bool checkout)
  484. {
  485. string cmd = null;
  486. if (checkout)
  487. {
  488. cmd = string.Format("checkout -b \"{0}\"", branchName.Trim());
  489. }
  490. else
  491. {
  492. cmd = string.Format("branch \"{0}\"", branchName.Trim());
  493. }
  494. if (revision.IsNullOrWhiteSpace())
  495. {
  496. return cmd;
  497. }
  498. else
  499. {
  500. return cmd + string.Format(" \"{0}\"", revision);
  501. }
  502. }
  503. public static string MergedBranches()
  504. {
  505. return "branch --merged";
  506. }
  507. /// <summary>Un-sets the git SSH command path.</summary>
  508. public static void UnsetSsh()
  509. {
  510. Environment.SetEnvironmentVariable("GIT_SSH", "", EnvironmentVariableTarget.Process);
  511. }
  512. /// <summary>Sets the git SSH command path.</summary>
  513. public static void SetSsh(string path)
  514. {
  515. if (!string.IsNullOrEmpty(path))
  516. Environment.SetEnvironmentVariable("GIT_SSH", path, EnvironmentVariableTarget.Process);
  517. }
  518. public static bool Plink()
  519. {
  520. var sshString = GetSsh();
  521. return sshString.EndsWith("plink.exe", StringComparison.CurrentCultureIgnoreCase);
  522. }
  523. /// <summary>Gets the git SSH command; or "" if the environment variable is NOT set.</summary>
  524. public static string GetSsh()
  525. {
  526. var ssh = Environment.GetEnvironmentVariable("GIT_SSH", EnvironmentVariableTarget.Process);
  527. return ssh ?? "";
  528. }
  529. /// <summary>Pushes multiple sets of local branches to remote branches.</summary>
  530. public static string PushMultipleCmd(string remote, IEnumerable<GitPushAction> pushActions)
  531. {
  532. remote = remote.ToPosixPath();
  533. return new GitPush(remote, pushActions)
  534. {
  535. ReportProgress = VersionInUse.PushCanAskForProgress
  536. }.ToString();
  537. }
  538. public static string PushTagCmd(string path, string tag, bool all,
  539. ForcePushOptions force = ForcePushOptions.DoNotForce)
  540. {
  541. path = path.ToPosixPath();
  542. tag = tag.Replace(" ", "");
  543. var sforce = GetForcePushArgument(force);
  544. var sprogressOption = "";
  545. if (VersionInUse.PushCanAskForProgress)
  546. sprogressOption = "--progress ";
  547. var options = String.Concat(sforce, sprogressOption);
  548. if (all)
  549. return "push " + options + "\"" + path.Trim() + "\" --tags";
  550. if (!string.IsNullOrEmpty(tag))
  551. return "push " + options + "\"" + path.Trim() + "\" tag " + tag;
  552. return "";
  553. }
  554. public static string GetForcePushArgument(ForcePushOptions force)
  555. {
  556. var sforce = "";
  557. if (force == ForcePushOptions.Force)
  558. sforce = "-f ";
  559. else if (force == ForcePushOptions.ForceWithLease)
  560. sforce = "--force-with-lease ";
  561. return sforce;
  562. }
  563. public static string StashSaveCmd(bool untracked, bool keepIndex, string message)
  564. {
  565. var cmd = "stash save";
  566. if (untracked && VersionInUse.StashUntrackedFilesSupported)
  567. cmd += " -u";
  568. if (keepIndex)
  569. cmd += " --keep-index";
  570. cmd = cmd.Combine(" ", message.QuoteNE());
  571. return cmd;
  572. }
  573. public static string ContinueRebaseCmd()
  574. {
  575. return "rebase --continue";
  576. }
  577. public static string SkipRebaseCmd()
  578. {
  579. return "rebase --skip";
  580. }
  581. public static string StartBisectCmd()
  582. {
  583. return "bisect start";
  584. }
  585. public static string ContinueBisectCmd(GitBisectOption bisectOption, params string[] revisions)
  586. {
  587. var bisectCommand = GetBisectCommand(bisectOption);
  588. if (revisions.Length == 0)
  589. return bisectCommand;
  590. return string.Format("{0} {1}", bisectCommand, string.Join(" ", revisions));
  591. }
  592. private static string GetBisectCommand(GitBisectOption bisectOption)
  593. {
  594. switch (bisectOption)
  595. {
  596. case GitBisectOption.Good:
  597. return "bisect good";
  598. case GitBisectOption.Bad:
  599. return "bisect bad";
  600. case GitBisectOption.Skip:
  601. return "bisect skip";
  602. default:
  603. throw new NotSupportedException(string.Format("Bisect option {0} is not supported", bisectOption));
  604. }
  605. }
  606. public static string StopBisectCmd()
  607. {
  608. return "bisect reset";
  609. }
  610. public static string RebaseCmd(string branch, bool interactive, bool preserveMerges, bool autosquash, bool autostash)
  611. {
  612. StringBuilder sb = new StringBuilder("rebase ");
  613. if (interactive)
  614. {
  615. sb.Append(" -i ");
  616. sb.Append(autosquash ? "--autosquash " : "--no-autosquash ");
  617. }
  618. if (preserveMerges)
  619. {
  620. sb.Append("--preserve-merges ");
  621. }
  622. if (autostash)
  623. {
  624. sb.Append("--autostash ");
  625. }
  626. sb.Append('"');
  627. sb.Append(branch);
  628. sb.Append('"');
  629. return sb.ToString();
  630. }
  631. public static string RebaseRangeCmd(string from, string branch, string onto, bool interactive, bool preserveMerges, bool autosquash, bool autostash)
  632. {
  633. StringBuilder sb = new StringBuilder("rebase ");
  634. if (interactive)
  635. {
  636. sb.Append(" -i ");
  637. sb.Append(autosquash ? "--autosquash " : "--no-autosquash ");
  638. }
  639. if (preserveMerges)
  640. {
  641. sb.Append("--preserve-merges ");
  642. }
  643. if (autostash)
  644. {
  645. sb.Append("--autostash ");
  646. }
  647. sb.Append('"')
  648. .Append(from)
  649. .Append("\" ");
  650. sb.Append('"')
  651. .Append(branch)
  652. .Append("\"");
  653. sb.Append(" --onto ")
  654. .Append(onto);
  655. return sb.ToString();
  656. }
  657. public static string AbortRebaseCmd()
  658. {
  659. return "rebase --abort";
  660. }
  661. public static string ResolvedCmd()
  662. {
  663. return "am --3way --resolved";
  664. }
  665. public static string SkipCmd()
  666. {
  667. return "am --3way --skip";
  668. }
  669. public static string AbortCmd()
  670. {
  671. return "am --3way --abort";
  672. }
  673. public static string PatchCmd(string patchFile)
  674. {
  675. if (IsDiffFile(patchFile))
  676. return "apply \"" + patchFile.ToPosixPath() + "\"";
  677. else
  678. return "am --3way --signoff \"" + patchFile.ToPosixPath() + "\"";
  679. }
  680. public static string PatchCmdIgnoreWhitespace(string patchFile)
  681. {
  682. if (IsDiffFile(patchFile))
  683. return "apply --ignore-whitespace \"" + patchFile.ToPosixPath() + "\"";
  684. else
  685. return "am --3way --signoff --ignore-whitespace \"" + patchFile.ToPosixPath() + "\"";
  686. }
  687. public static string PatchDirCmd()
  688. {
  689. return "am --3way --signoff";
  690. }
  691. public static string PatchDirCmdIgnoreWhitespace()
  692. {
  693. return PatchDirCmd() + " --ignore-whitespace";
  694. }
  695. public static string CleanUpCmd(bool dryrun, bool directories, bool nonignored, bool ignored, string paths = null)
  696. {
  697. string command = "clean";
  698. if (directories)
  699. command += " -d";
  700. if (!nonignored && !ignored)
  701. command += " -x";
  702. if (ignored)
  703. command += " -X";
  704. if (dryrun)
  705. command += " --dry-run";
  706. if (!dryrun)
  707. command += " -f";
  708. if (!paths.IsNullOrWhiteSpace())
  709. command += " " + paths;
  710. return command;
  711. }
  712. public static string GetAllChangedFilesCmd(bool excludeIgnoredFiles, UntrackedFilesMode untrackedFiles, IgnoreSubmodulesMode ignoreSubmodules = 0)
  713. {
  714. StringBuilder stringBuilder = new StringBuilder("status --porcelain -z");
  715. switch (untrackedFiles)
  716. {
  717. case UntrackedFilesMode.Default:
  718. stringBuilder.Append(" --untracked-files");
  719. break;
  720. case UntrackedFilesMode.No:
  721. stringBuilder.Append(" --untracked-files=no");
  722. break;
  723. case UntrackedFilesMode.Normal:
  724. stringBuilder.Append(" --untracked-files=normal");
  725. break;
  726. case UntrackedFilesMode.All:
  727. stringBuilder.Append(" --untracked-files=all");
  728. break;
  729. }
  730. switch (ignoreSubmodules)
  731. {
  732. case IgnoreSubmodulesMode.Default:
  733. stringBuilder.Append(" --ignore-submodules");
  734. break;
  735. case IgnoreSubmodulesMode.None:
  736. stringBuilder.Append(" --ignore-submodules=none");
  737. break;
  738. case IgnoreSubmodulesMode.Untracked:
  739. stringBuilder.Append(" --ignore-submodules=untracked");
  740. break;
  741. case IgnoreSubmodulesMode.Dirty:
  742. stringBuilder.Append(" --ignore-submodules=dirty");
  743. break;
  744. case IgnoreSubmodulesMode.All:
  745. stringBuilder.Append(" --ignore-submodules=all");
  746. break;
  747. }
  748. if (!excludeIgnoredFiles)
  749. stringBuilder.Append(" --ignored");
  750. return stringBuilder.ToString();
  751. }
  752. [CanBeNull]
  753. public static GitSubmoduleStatus GetCurrentSubmoduleChanges(GitModule module, string fileName, string oldFileName, bool staged)
  754. {
  755. PatchApply.Patch patch = module.GetCurrentChanges(fileName, oldFileName, staged, "", module.FilesEncoding);
  756. string text = patch != null ? patch.Text : "";
  757. return GetSubmoduleStatus(text, module, fileName);
  758. }
  759. [CanBeNull]
  760. public static GitSubmoduleStatus GetCurrentSubmoduleChanges(GitModule module, string submodule)
  761. {
  762. return GetCurrentSubmoduleChanges(module, submodule, submodule, false);
  763. }
  764. public static GitSubmoduleStatus GetSubmoduleStatus(string text, GitModule module, string fileName)
  765. {
  766. if (string.IsNullOrEmpty(text))
  767. return null;
  768. var status = new GitSubmoduleStatus();
  769. using (StringReader reader = new StringReader(text))
  770. {
  771. string line = reader.ReadLine();
  772. if (line != null)
  773. {
  774. var match = Regex.Match(line, @"diff --git a/(\S+) b/(\S+)");
  775. if (match.Groups.Count > 1)
  776. {
  777. status.Name = match.Groups[1].Value;
  778. status.OldName = match.Groups[2].Value;
  779. }
  780. else
  781. {
  782. match = Regex.Match(line, @"diff --cc (\S+)");
  783. if (match.Groups.Count > 1)
  784. {
  785. status.Name = match.Groups[1].Value;
  786. status.OldName = match.Groups[1].Value;
  787. }
  788. }
  789. }
  790. while ((line = reader.ReadLine()) != null)
  791. {
  792. if (!line.Contains("Subproject"))
  793. continue;
  794. char c = line[0];
  795. const string commit = "commit ";
  796. string hash = "";
  797. int pos = line.IndexOf(commit);
  798. if (pos >= 0)
  799. hash = line.Substring(pos + commit.Length);
  800. bool bdirty = hash.EndsWith("-dirty");
  801. hash = hash.Replace("-dirty", "");
  802. if (c == '-')
  803. {
  804. status.OldCommit = hash;
  805. }
  806. else if (c == '+')
  807. {
  808. status.Commit = hash;
  809. status.IsDirty = bdirty;
  810. }
  811. // TODO: Support combined merge
  812. }
  813. }
  814. if (status.OldCommit != null && status.Commit != null)
  815. {
  816. var submodule = module.GetSubmodule(fileName);
  817. status.AddedCommits = submodule.GetCommitCount(status.Commit, status.OldCommit);
  818. status.RemovedCommits = submodule.GetCommitCount(status.OldCommit, status.Commit);
  819. }
  820. return status;
  821. }
  822. /*
  823. source: https://git-scm.com/docs/git-status
  824. */
  825. public static List<GitItemStatus> GetAllChangedFilesFromString(GitModule module, string statusString, bool fromDiff = false)
  826. {
  827. var diffFiles = new List<GitItemStatus>();
  828. if (string.IsNullOrEmpty(statusString))
  829. return diffFiles;
  830. /*The status string can show warnings. This is a text block at the start or at the beginning
  831. of the file status. Strip it. Example:
  832. warning: LF will be replaced by CRLF in CustomDictionary.xml.
  833. The file will have its original line endings in your working directory.
  834. warning: LF will be replaced by CRLF in FxCop.targets.
  835. The file will have its original line endings in your working directory.*/
  836. var nl = new[] { '\n', '\r' };
  837. string trimmedStatus = statusString.Trim(nl);
  838. int lastNewLinePos = trimmedStatus.LastIndexOfAny(nl);
  839. if (lastNewLinePos > 0)
  840. {
  841. int ind = trimmedStatus.LastIndexOf('\0');
  842. if (ind < lastNewLinePos) //Warning at end
  843. {
  844. lastNewLinePos = trimmedStatus.IndexOfAny(nl, ind >= 0 ? ind : 0);
  845. trimmedStatus = trimmedStatus.Substring(0, lastNewLinePos).Trim(nl);
  846. }
  847. else //Warning at beginning
  848. trimmedStatus = trimmedStatus.Substring(lastNewLinePos).Trim(nl);
  849. }
  850. // Doesn't work with removed submodules
  851. IList<string> Submodules = module.GetSubmodulesLocalPaths();
  852. //Split all files on '\0' (WE NEED ALL COMMANDS TO BE RUN WITH -z! THIS IS ALSO IMPORTANT FOR ENCODING ISSUES!)
  853. var files = trimmedStatus.Split(new char[] { '\0' }, StringSplitOptions.RemoveEmptyEntries);
  854. for (int n = 0; n < files.Length; n++)
  855. {
  856. if (string.IsNullOrEmpty(files[n]))
  857. continue;
  858. int splitIndex = files[n].IndexOfAny(new char[] { '\0', '\t', ' ' }, 1);
  859. string status = string.Empty;
  860. string fileName = string.Empty;
  861. if (splitIndex < 0)
  862. {
  863. status = files[n];
  864. fileName = files[n + 1];
  865. n++;
  866. }
  867. else
  868. {
  869. status = files[n].Substring(0, splitIndex);
  870. fileName = files[n].Substring(splitIndex);
  871. }
  872. char x = status[0];
  873. char y = status.Length > 1 ? status[1] : ' ';
  874. if (x != '?' && x != '!' && x != ' ')
  875. {
  876. GitItemStatus gitItemStatusX = null;
  877. if (x == 'R' || x == 'C') // Find renamed files...
  878. {
  879. string nextfile = n + 1 < files.Length ? files[n + 1] : "";
  880. gitItemStatusX = GitItemStatusFromCopyRename(fromDiff, nextfile, fileName, x, status);
  881. n++;
  882. }
  883. else
  884. gitItemStatusX = GitItemStatusFromStatusCharacter(fileName, x);
  885. gitItemStatusX.IsStaged = true;
  886. if (Submodules.Contains(gitItemStatusX.Name))
  887. gitItemStatusX.IsSubmodule = true;
  888. diffFiles.Add(gitItemStatusX);
  889. }
  890. if (fromDiff || y == ' ')
  891. continue;
  892. GitItemStatus gitItemStatusY;
  893. if (y == 'R' || y == 'C') // Find renamed files...
  894. {
  895. string nextfile = n + 1 < files.Length ? files[n + 1] : "";
  896. gitItemStatusY = GitItemStatusFromCopyRename(false, nextfile, fileName, y, status);
  897. n++;
  898. }
  899. else
  900. gitItemStatusY = GitItemStatusFromStatusCharacter(fileName, y);
  901. gitItemStatusY.IsStaged = false;
  902. if (Submodules.Contains(gitItemStatusY.Name))
  903. gitItemStatusY.IsSubmodule = true;
  904. diffFiles.Add(gitItemStatusY);
  905. }
  906. return diffFiles;
  907. }
  908. public static List<GitItemStatus> GetAssumeUnchangedFilesFromString(GitModule module, string lsString)
  909. {
  910. List<GitItemStatus> result = new List<GitItemStatus>();
  911. string[] lines = lsString.SplitLines();
  912. foreach (string line in lines)
  913. {
  914. char statusCharacter = line[0];
  915. if (char.IsUpper(statusCharacter))
  916. continue;
  917. string fileName = line.Substring(line.IndexOf(' ') + 1);
  918. GitItemStatus gitItemStatus = GitItemStatusFromStatusCharacter(fileName, statusCharacter);
  919. gitItemStatus.IsStaged = false;
  920. gitItemStatus.IsAssumeUnchanged = true;
  921. result.Add(gitItemStatus);
  922. }
  923. return result;
  924. }
  925. private static GitItemStatus GitItemStatusFromCopyRename(bool fromDiff, string nextfile, string fileName, char x, string status)
  926. {
  927. var gitItemStatus = new GitItemStatus();
  928. //Find renamed files...
  929. if (fromDiff)
  930. {
  931. gitItemStatus.OldName = fileName.Trim();
  932. gitItemStatus.Name = nextfile.Trim();
  933. }
  934. else
  935. {
  936. gitItemStatus.Name = fileName.Trim();
  937. gitItemStatus.OldName = nextfile.Trim();
  938. }
  939. gitItemStatus.IsNew = false;
  940. gitItemStatus.IsChanged = false;
  941. gitItemStatus.IsDeleted = false;
  942. if (x == 'R')
  943. gitItemStatus.IsRenamed = true;
  944. else
  945. gitItemStatus.IsCopied = true;
  946. gitItemStatus.IsTracked = true;
  947. if (status.Length > 2)
  948. gitItemStatus.RenameCopyPercentage = status.Substring(1);
  949. return gitItemStatus;
  950. }
  951. private static GitItemStatus GitItemStatusFromStatusCharacter(string fileName, char x)
  952. {
  953. var gitItemStatus = new GitItemStatus();
  954. gitItemStatus.Name = fileName.Trim();
  955. gitItemStatus.IsNew = x == 'A' || x == '?' || x == '!';
  956. gitItemStatus.IsChanged = x == 'M';
  957. gitItemStatus.IsDeleted = x == 'D';
  958. gitItemStatus.IsRenamed = false;
  959. gitItemStatus.IsTracked = x != '?' && x != '!' && x != ' ' || !gitItemStatus.IsNew;
  960. gitItemStatus.IsConflict = x == 'U';
  961. return gitItemStatus;
  962. }
  963. public static string GetRemoteName(string completeName, IEnumerable<string> remotes)
  964. {
  965. string trimmedName = completeName.StartsWith("refs/remotes/") ? completeName.Substring(13) : completeName;
  966. foreach (string remote in remotes)
  967. {
  968. if (trimmedName.StartsWith(string.Concat(remote, "/")))
  969. return remote;
  970. }
  971. return string.Empty;
  972. }
  973. public static string MergeBranchCmd(string branch, bool allowFastForward, bool squash, bool noCommit, string strategy)
  974. {
  975. StringBuilder command = new StringBuilder("merge");
  976. if (!allowFastForward)
  977. command.Append(" --no-ff");
  978. if (!string.IsNullOrEmpty(strategy))
  979. {
  980. command.Append(" --strategy=");
  981. command.Append(strategy);
  982. }
  983. if (squash)
  984. command.Append(" --squash");
  985. if (noCommit)
  986. command.Append(" --no-commit");
  987. command.Append(" ");
  988. command.Append(branch);
  989. return command.ToString();
  990. }
  991. public static string GetFileExtension(string fileName)
  992. {
  993. if (fileName.Contains(".") && fileName.LastIndexOf(".") < fileName.Length)
  994. return fileName.Substring(fileName.LastIndexOf('.') + 1);
  995. return null;
  996. }
  997. // look into patch file and try to figure out if it's a raw diff (i.e from git diff -p)
  998. // only looks at start, as all we want is to tell from automail format
  999. // returns false on any problem, never throws
  1000. private static bool IsDiffFile(string path)
  1001. {
  1002. try
  1003. {
  1004. using (StreamReader sr = new StreamReader(path))
  1005. {
  1006. string line = sr.ReadLine();
  1007. return line.StartsWith("diff ") || line.StartsWith("Index: ");
  1008. }
  1009. }
  1010. catch (Exception)
  1011. {
  1012. return false;
  1013. }
  1014. }
  1015. // returns " --find-renames=..." according to app settings
  1016. public static string FindRenamesOpt()
  1017. {
  1018. string result = " --find-renames";
  1019. if (AppSettings.FollowRenamesInFileHistoryExactOnly)
  1020. {
  1021. result += "=\"100%\"";
  1022. }
  1023. return result;
  1024. }
  1025. // returns " --find-renames=... --find-copies=..." according to app settings
  1026. public static string FindRenamesAndCopiesOpts()
  1027. {
  1028. string findCopies = " --find-copies";
  1029. if (AppSettings.FollowRenamesInFileHistoryExactOnly)
  1030. {
  1031. findCopies += "=\"100%\"";
  1032. }
  1033. return FindRenamesOpt() + findCopies;
  1034. }
  1035. #if !__MonoCS__
  1036. static class NativeMethods
  1037. {
  1038. [DllImport("kernel32.dll")]
  1039. public static extern bool SetConsoleCtrlHandler(IntPtr HandlerRoutine,
  1040. bool Add);
  1041. [DllImport("kernel32.dll", SetLastError = true)]
  1042. public static extern bool AttachConsole(int dwProcessId);
  1043. [DllImport("kernel32.dll", SetLastError = true)]
  1044. [return: MarshalAs(UnmanagedType.Bool)]
  1045. public static extern bool GenerateConsoleCtrlEvent(uint dwCtrlEvent,
  1046. int dwProcessGroupId);
  1047. }
  1048. #endif
  1049. public static void TerminateTree(this Process process)
  1050. {
  1051. #if !__MonoCS__
  1052. if (EnvUtils.RunningOnWindows())
  1053. {
  1054. // Send Ctrl+C
  1055. NativeMethods.AttachConsole(process.Id);
  1056. NativeMethods.SetConsoleCtrlHandler(IntPtr.Zero, true);
  1057. NativeMethods.GenerateConsoleCtrlEvent(0, 0);
  1058. if (!process.HasExited)
  1059. System.Threading.Thread.Sleep(500);
  1060. }
  1061. #endif
  1062. if (!process.HasExited)
  1063. process.Kill();
  1064. }
  1065. }
  1066. }