PageRenderTime 54ms CodeModel.GetById 11ms RepoModel.GetById 0ms app.codeStats 0ms

/GitCommands/Git/GitCommandHelpers.cs

https://github.com/vbjay/gitextensions
C# | 1396 lines | 1107 code | 193 blank | 96 comment | 214 complexity | c16567983504378dcc69fbd07fb0fa99 MD5 | raw file
Possible License(s): GPL-3.0, GPL-2.0

Large files files are truncated, but you can click here to view the full file

  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. internal 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. AppSettings.GitLog.Log(quotedCmd + " " + arguments);
  137. var startInfo = CreateProcessStartInfo(fileName, arguments, workingDirectory, outputEncoding);
  138. return Process.Start(startInfo);
  139. }
  140. internal static bool UseSsh(string arguments)
  141. {
  142. var x = !Plink() && GetArgumentsRequiresSsh(arguments);
  143. return x || arguments.Contains("plink");
  144. }
  145. private static bool GetArgumentsRequiresSsh(string arguments)
  146. {
  147. return (arguments.Contains("@") && arguments.Contains("://")) ||
  148. (arguments.Contains("@") && arguments.Contains(":")) ||
  149. (arguments.Contains("ssh://")) ||
  150. (arguments.Contains("http://")) ||
  151. (arguments.Contains("git://")) ||
  152. (arguments.Contains("push")) ||
  153. (arguments.Contains("remote")) ||
  154. (arguments.Contains("fetch")) ||
  155. (arguments.Contains("pull"));
  156. }
  157. private static IEnumerable<string> StartProcessAndReadLines(string arguments, string cmd, string workDir, string stdInput)
  158. {
  159. if (string.IsNullOrEmpty(cmd))
  160. yield break;
  161. //process used to execute external commands
  162. using (var process = StartProcess(cmd, arguments, workDir, GitModule.SystemEncoding))
  163. {
  164. if (!string.IsNullOrEmpty(stdInput))
  165. {
  166. process.StandardInput.Write(stdInput);
  167. process.StandardInput.Close();
  168. }
  169. string line;
  170. do
  171. {
  172. line = process.StandardOutput.ReadLine();
  173. if (line != null)
  174. yield return line;
  175. } while (line != null);
  176. do
  177. {
  178. line = process.StandardError.ReadLine();
  179. if (line != null)
  180. yield return line;
  181. } while (line != null);
  182. process.WaitForExit();
  183. }
  184. }
  185. /// <summary>
  186. /// Run command, console window is hidden, wait for exit, redirect output
  187. /// </summary>
  188. public static IEnumerable<string> ReadCmdOutputLines(string cmd, string arguments, string workDir, string stdInput)
  189. {
  190. SetEnvironmentVariable();
  191. arguments = arguments.Replace("$QUOTE$", "\\\"");
  192. return StartProcessAndReadLines(arguments, cmd, workDir, stdInput);
  193. }
  194. private static Process StartProcessAndReadAllText(string arguments, string cmd, string workDir, out string stdOutput, out string stdError, string stdInput)
  195. {
  196. if (string.IsNullOrEmpty(cmd))
  197. {
  198. stdOutput = stdError = "";
  199. return null;
  200. }
  201. //process used to execute external commands
  202. var process = StartProcess(cmd, arguments, workDir, GitModule.SystemEncoding);
  203. if (!string.IsNullOrEmpty(stdInput))
  204. {
  205. process.StandardInput.Write(stdInput);
  206. process.StandardInput.Close();
  207. }
  208. SynchronizedProcessReader.Read(process, out stdOutput, out stdError);
  209. return process;
  210. }
  211. /// <summary>
  212. /// Run command, console window is hidden, wait for exit, redirect output
  213. /// </summary>
  214. public static string RunCmd(string cmd, string arguments)
  215. {
  216. try
  217. {
  218. SetEnvironmentVariable();
  219. arguments = arguments.Replace("$QUOTE$", "\\\"");
  220. string output, error;
  221. using (var process = StartProcessAndReadAllText(arguments, cmd, "", out output, out error, null))
  222. {
  223. process.WaitForExit();
  224. }
  225. if (!string.IsNullOrEmpty(error))
  226. {
  227. output += Environment.NewLine + error;
  228. }
  229. return output;
  230. }
  231. catch (Win32Exception)
  232. {
  233. return string.Empty;
  234. }
  235. }
  236. private static Process StartProcessAndReadAllBytes(string arguments, string cmd, string workDir, out byte[] stdOutput, out byte[] stdError, byte[] stdInput)
  237. {
  238. if (string.IsNullOrEmpty(cmd))
  239. {
  240. stdOutput = stdError = null;
  241. return null;
  242. }
  243. //process used to execute external commands
  244. var process = StartProcess(cmd, arguments, workDir, Encoding.Default);
  245. if (stdInput != null && stdInput.Length > 0)
  246. {
  247. process.StandardInput.BaseStream.Write(stdInput, 0, stdInput.Length);
  248. process.StandardInput.Close();
  249. }
  250. SynchronizedProcessReader.ReadBytes(process, out stdOutput, out stdError);
  251. return process;
  252. }
  253. /// <summary>
  254. /// Run command, console window is hidden, wait for exit, redirect output
  255. /// </summary>
  256. public static int RunCmdByte(string cmd, string arguments, string workingdir, byte[] stdInput, out byte[] output, out byte[] error)
  257. {
  258. try
  259. {
  260. arguments = arguments.Replace("$QUOTE$", "\\\"");
  261. using (var process = StartProcessAndReadAllBytes(arguments, cmd, workingdir, out output, out error, stdInput))
  262. {
  263. process.WaitForExit();
  264. return process.ExitCode;
  265. }
  266. }
  267. catch (Win32Exception)
  268. {
  269. output = error = null;
  270. return 1;
  271. }
  272. }
  273. private static GitVersion _versionInUse;
  274. private static readonly string UserHomeDir = Environment.GetEnvironmentVariable("HOME", EnvironmentVariableTarget.User);
  275. public static GitVersion VersionInUse
  276. {
  277. get
  278. {
  279. if (_versionInUse == null || _versionInUse.IsUnknown)
  280. {
  281. var result = RunCmd(AppSettings.GitCommand, "--version");
  282. _versionInUse = new GitVersion(result);
  283. }
  284. return _versionInUse;
  285. }
  286. }
  287. public static string CherryPickCmd(string cherry, bool commit, string arguments)
  288. {
  289. string cherryPickCmd = commit ? "cherry-pick" : "cherry-pick --no-commit";
  290. return cherryPickCmd + " " + arguments + " \"" + cherry + "\"";
  291. }
  292. /// <summary>
  293. /// Check if a string represents a commit hash
  294. /// </summary>
  295. private static bool IsCommitHash(string value)
  296. {
  297. return GitRevision.Sha1HashRegex.IsMatch(value);
  298. }
  299. public static string GetFullBranchName(string branch)
  300. {
  301. if (string.IsNullOrEmpty(branch) || branch.StartsWith("refs/"))
  302. return branch;
  303. // If the branch represents a commit hash, return it as-is without appending refs/heads/ (fix issue #2240)
  304. if (IsCommitHash(branch))
  305. {
  306. return branch;
  307. }
  308. return "refs/heads/" + branch;
  309. }
  310. public static string DeleteBranchCmd(string branchName, bool force, bool remoteBranch)
  311. {
  312. StringBuilder cmd = new StringBuilder("branch");
  313. cmd.Append(force ? " -D" : " -d");
  314. if (remoteBranch)
  315. cmd.Append(" -r");
  316. cmd.Append(" \"");
  317. cmd.Append(branchName);
  318. cmd.Append("\"");
  319. return cmd.ToString();
  320. }
  321. public static string DeleteTagCmd(string tagName)
  322. {
  323. return "tag -d \"" + tagName + "\"";
  324. }
  325. public static string SubmoduleUpdateCmd(string name)
  326. {
  327. if (string.IsNullOrEmpty(name))
  328. return "submodule update --init --recursive";
  329. return "submodule update --init --recursive \"" + name.Trim() + "\"";
  330. }
  331. public static string SubmoduleSyncCmd(string name)
  332. {
  333. if (string.IsNullOrEmpty(name))
  334. return "submodule sync";
  335. return "submodule sync \"" + name.Trim() + "\"";
  336. }
  337. public static string AddSubmoduleCmd(string remotePath, string localPath, string branch, bool force)
  338. {
  339. remotePath = remotePath.ToPosixPath();
  340. localPath = localPath.ToPosixPath();
  341. if (!string.IsNullOrEmpty(branch))
  342. branch = " -b \"" + branch.Trim() + "\"";
  343. var forceCmd = force ? " -f" : string.Empty;
  344. return "submodule add" + forceCmd + branch + " \"" + remotePath.Trim() + "\" \"" + localPath.Trim() + "\"";
  345. }
  346. public static string RevertCmd(string commit, bool autoCommit, int parentIndex)
  347. {
  348. var cmd = new StringBuilder("revert ");
  349. if (!autoCommit)
  350. {
  351. cmd.Append("--no-commit ");
  352. }
  353. if (parentIndex > 0)
  354. {
  355. cmd.AppendFormat("-m {0} ", parentIndex);
  356. }
  357. cmd.Append(commit);
  358. return cmd.ToString();
  359. }
  360. public static string ResetSoftCmd(string commit)
  361. {
  362. return "reset --soft \"" + commit + "\"";
  363. }
  364. public static string ResetMixedCmd(string commit)
  365. {
  366. return "reset --mixed \"" + commit + "\"";
  367. }
  368. public static string ResetHardCmd(string commit)
  369. {
  370. return "reset --hard \"" + commit + "\"";
  371. }
  372. public static string CloneCmd(string fromPath, string toPath)
  373. {
  374. return CloneCmd(fromPath, toPath, false, false, string.Empty, null);
  375. }
  376. public static string CloneCmd(string fromPath, string toPath, bool central, bool initSubmodules, string branch, int? depth)
  377. {
  378. var from = fromPath.ToPosixPath();
  379. var to = toPath.ToPosixPath();
  380. var options = new List<string> { "-v" };
  381. if (central)
  382. options.Add("--bare");
  383. if (initSubmodules)
  384. options.Add("--recurse-submodules");
  385. if (depth.HasValue)
  386. options.Add("--depth " + depth);
  387. options.Add("--progress");
  388. if (!string.IsNullOrEmpty(branch))
  389. options.Add("--branch " + branch);
  390. options.Add(string.Format("\"{0}\"", from.Trim()));
  391. options.Add(string.Format("\"{0}\"", to.Trim()));
  392. return "clone " + string.Join(" ", options.ToArray());
  393. }
  394. public static string CheckoutCmd(string branchOrRevisionName, LocalChangesAction changesAction)
  395. {
  396. string args = "";
  397. switch (changesAction)
  398. {
  399. case LocalChangesAction.Merge:
  400. args = " --merge";
  401. break;
  402. case LocalChangesAction.Reset:
  403. args = " --force";
  404. break;
  405. }
  406. return string.Format("checkout{0} \"{1}\"", args, branchOrRevisionName);
  407. }
  408. /// <summary>Create a new orphan branch from <paramref name="startPoint"/> and switch to it.</summary>
  409. public static string CreateOrphanCmd(string newBranchName, string startPoint = null)
  410. {
  411. return string.Format("checkout --orphan {0} {1}", newBranchName, startPoint);
  412. }
  413. /// <summary>Remove files from the working tree and from the index. <remarks>git rm</remarks></summary>
  414. /// <param name="force">Override the up-to-date check.</param>
  415. /// <param name="isRecursive">Allow recursive removal when a leading directory name is given.</param>
  416. /// <param name="files">Files to remove. Fileglobs can be given to remove matching files.</param>
  417. public static string RemoveCmd(bool force = true, bool isRecursive = true, params string[] files)
  418. {
  419. string file = ".";
  420. if (files.Any())
  421. file = string.Join(" ", files);
  422. return string.Format("rm {0} {1} {2}",
  423. force ? "--force" : string.Empty,
  424. isRecursive ? "-r" : string.Empty,
  425. file
  426. );
  427. }
  428. public static string BranchCmd(string branchName, string revision, bool checkout)
  429. {
  430. if (checkout)
  431. return string.Format("checkout -b \"{0}\" \"{1}\"", branchName.Trim(), revision);
  432. return string.Format("branch \"{0}\" \"{1}\"", branchName.Trim(), revision);
  433. }
  434. public static string MergedBranches()
  435. {
  436. return "branch --merged";
  437. }
  438. /// <summary>Un-sets the git SSH command path.</summary>
  439. public static void UnsetSsh()
  440. {
  441. Environment.SetEnvironmentVariable("GIT_SSH", "", EnvironmentVariableTarget.Process);
  442. }
  443. /// <summary>Sets the git SSH command path.</summary>
  444. public static void SetSsh(string path)
  445. {
  446. if (!string.IsNullOrEmpty(path))
  447. Environment.SetEnvironmentVariable("GIT_SSH", path, EnvironmentVariableTarget.Process);
  448. }
  449. public static bool Plink()
  450. {
  451. var sshString = GetSsh();
  452. return sshString.EndsWith("plink.exe", StringComparison.CurrentCultureIgnoreCase);
  453. }
  454. /// <summary>Gets the git SSH command; or "" if the environment variable is NOT set.</summary>
  455. public static string GetSsh()
  456. {
  457. var ssh = Environment.GetEnvironmentVariable("GIT_SSH", EnvironmentVariableTarget.Process);
  458. return ssh ?? "";
  459. }
  460. /// <summary>Creates a 'git push' command using the specified parameters, pushing from HEAD.</summary>
  461. /// <param name="remote">Remote repository that is the destination of the push operation.</param>
  462. /// <param name="toBranch">Name of the ref on the remote side to update with the push.</param>
  463. /// <param name="all">All refs under 'refs/heads/' will be pushed.</param>
  464. /// <returns>'git push' command with the specified parameters.</returns>
  465. public static string PushCmd(string remote, string toBranch, bool all)
  466. {
  467. return PushCmd(remote, null, toBranch, all, false, true, 0);
  468. }
  469. /// <summary>Creates a 'git push' command using the specified parameters.</summary>
  470. /// <param name="remote">Remote repository that is the destination of the push operation.</param>
  471. /// <param name="fromBranch">Name of the branch to push.</param>
  472. /// <param name="toBranch">Name of the ref on the remote side to update with the push.</param>
  473. /// <param name="force">If a remote ref is not an ancestor of the local ref, overwrite it.
  474. /// <remarks>This can cause the remote repository to lose commits; use it with care.</remarks></param>
  475. /// <returns>'git push' command with the specified parameters.</returns>
  476. public static string PushCmd(string remote, string fromBranch, string toBranch, bool force = false)
  477. {
  478. return PushCmd(remote, fromBranch, toBranch, false, force, false, 0);
  479. }
  480. /// <summary>Creates a 'git push' command using the specified parameters.</summary>
  481. /// <param name="remote">Remote repository that is the destination of the push operation.</param>
  482. /// <param name="fromBranch">Name of the branch to push.</param>
  483. /// <param name="toBranch">Name of the ref on the remote side to update with the push.</param>
  484. /// <param name="all">All refs under 'refs/heads/' will be pushed.</param>
  485. /// <param name="force">If a remote ref is not an ancestor of the local ref, overwrite it.
  486. /// <remarks>This can cause the remote repository to lose commits; use it with care.</remarks></param>
  487. /// <param name="track">For every branch that is up to date or successfully pushed, add upstream (tracking) reference.</param>
  488. /// <param name="recursiveSubmodules">If '1', check whether all submodule commits used by the revisions to be pushed are available on a remote tracking branch; otherwise, the push will be aborted.</param>
  489. /// <returns>'git push' command with the specified parameters.</returns>
  490. public static string PushCmd(string remote, string fromBranch, string toBranch,
  491. bool all, bool force, bool track, int recursiveSubmodules)
  492. {
  493. remote = remote.ToPosixPath();
  494. // This method is for pushing to remote branches, so fully qualify the
  495. // remote branch name with refs/heads/.
  496. fromBranch = GetFullBranchName(fromBranch);
  497. toBranch = GetFullBranchName(toBranch);
  498. if (string.IsNullOrEmpty(fromBranch) && !string.IsNullOrEmpty(toBranch))
  499. fromBranch = "HEAD";
  500. if (toBranch != null) toBranch = toBranch.Replace(" ", "");
  501. var sforce = "";
  502. if (force)
  503. sforce = "-f ";
  504. var strack = "";
  505. if (track)
  506. strack = "-u ";
  507. var srecursiveSubmodules = "";
  508. if (recursiveSubmodules == 1)
  509. srecursiveSubmodules = "--recurse-submodules=check ";
  510. if (recursiveSubmodules == 2)
  511. srecursiveSubmodules = "--recurse-submodules=on-demand ";
  512. var sprogressOption = "";
  513. if (VersionInUse.PushCanAskForProgress)
  514. sprogressOption = "--progress ";
  515. var options = String.Concat(sforce, strack, srecursiveSubmodules, sprogressOption);
  516. if (all)
  517. return string.Format("push {0}--all \"{1}\"", options, remote.Trim());
  518. if (!string.IsNullOrEmpty(toBranch) && !string.IsNullOrEmpty(fromBranch))
  519. return string.Format("push {0}\"{1}\" {2}:{3}", options, remote.Trim(), fromBranch, toBranch);
  520. return string.Format("push {0}\"{1}\" {2}", options, remote.Trim(), fromBranch);
  521. }
  522. /// <summary>Pushes multiple sets of local branches to remote branches.</summary>
  523. public static string PushMultipleCmd(string remote, IEnumerable<GitPushAction> pushActions)
  524. {
  525. remote = remote.ToPosixPath();
  526. return new GitPush(remote, pushActions)
  527. {
  528. ReportProgress = VersionInUse.PushCanAskForProgress
  529. }.ToString();
  530. }
  531. public static string PushTagCmd(string path, string tag, bool all, bool force = false)
  532. {
  533. path = path.ToPosixPath();
  534. tag = tag.Replace(" ", "");
  535. var sforce = "";
  536. if (force)
  537. sforce = "-f ";
  538. var sprogressOption = "";
  539. if (VersionInUse.PushCanAskForProgress)
  540. sprogressOption = "--progress ";
  541. var options = String.Concat(sforce, sprogressOption);
  542. if (all)
  543. return "push " + options + "\"" + path.Trim() + "\" --tags";
  544. if (!string.IsNullOrEmpty(tag))
  545. return "push " + options + "\"" + path.Trim() + "\" tag " + tag;
  546. return "";
  547. }
  548. public static string StashSaveCmd(bool untracked, bool keepIndex, string message)
  549. {
  550. var cmd = "stash save";
  551. if (untracked && VersionInUse.StashUntrackedFilesSupported)
  552. cmd += " -u";
  553. if (keepIndex)
  554. cmd += " --keep-index";
  555. cmd = cmd.Combine(" ", message.QuoteNE());
  556. return cmd;
  557. }
  558. public static string ContinueRebaseCmd()
  559. {
  560. return "rebase --continue";
  561. }
  562. public static string SkipRebaseCmd()
  563. {
  564. return "rebase --skip";
  565. }
  566. public static string StartBisectCmd()
  567. {
  568. return "bisect start";
  569. }
  570. public static string ContinueBisectCmd(GitBisectOption bisectOption, params string[] revisions)
  571. {
  572. var bisectCommand = GetBisectCommand(bisectOption);
  573. if (revisions.Length == 0)
  574. return bisectCommand;
  575. return string.Format("{0} {1}", bisectCommand, string.Join(" ", revisions));
  576. }
  577. private static string GetBisectCommand(GitBisectOption bisectOption)
  578. {
  579. switch (bisectOption)
  580. {
  581. case GitBisectOption.Good:
  582. return "bisect good";
  583. case GitBisectOption.Bad:
  584. return "bisect bad";
  585. case GitBisectOption.Skip:
  586. return "bisect skip";
  587. default:
  588. throw new NotSupportedException(string.Format("Bisect option {0} is not supported", bisectOption));
  589. }
  590. }
  591. public static string StopBisectCmd()
  592. {
  593. return "bisect reset";
  594. }
  595. public static string RebaseCmd(string branch, bool interactive, bool preserveMerges, bool autosquash)
  596. {
  597. StringBuilder sb = new StringBuilder("rebase ");
  598. if (interactive)
  599. {
  600. sb.Append(" -i ");
  601. sb.Append(autosquash ? "--autosquash " : "--no-autosquash ");
  602. }
  603. if (preserveMerges)
  604. {
  605. sb.Append("--preserve-merges ");
  606. }
  607. sb.Append('"');
  608. sb.Append(branch);
  609. sb.Append('"');
  610. return sb.ToString();
  611. }
  612. public static string RebaseRangeCmd(string from, string branch, string onto, bool interactive, bool preserveMerges, bool autosquash)
  613. {
  614. StringBuilder sb = new StringBuilder("rebase ");
  615. if (interactive)
  616. {
  617. sb.Append(" -i ");
  618. sb.Append(autosquash ? "--autosquash " : "--no-autosquash ");
  619. }
  620. if (preserveMerges)
  621. {
  622. sb.Append("--preserve-merges ");
  623. }
  624. sb.Append('"')
  625. .Append(from)
  626. .Append("\" ");
  627. sb.Append('"')
  628. .Append(branch)
  629. .Append("\"");
  630. sb.Append(" --onto ")
  631. .Append(onto);
  632. return sb.ToString();
  633. }
  634. public static string AbortRebaseCmd()
  635. {
  636. return "rebase --abort";
  637. }
  638. public static string ResolvedCmd()
  639. {
  640. return "am --3way --resolved";
  641. }
  642. public static string SkipCmd()
  643. {
  644. return "am --3way --skip";
  645. }
  646. public static string AbortCmd()
  647. {
  648. return "am --3way --abort";
  649. }
  650. public static string PatchCmd(string patchFile)
  651. {
  652. if (IsDiffFile(patchFile))
  653. return "apply \"" + patchFile.ToPosixPath() + "\"";
  654. else
  655. return "am --3way --signoff \"" + patchFile.ToPosixPath() + "\"";
  656. }
  657. public static string PatchCmdIgnoreWhitespace(string patchFile)
  658. {
  659. if (IsDiffFile(patchFile))
  660. return "apply --ignore-whitespace \"" + patchFile.ToPosixPath() + "\"";
  661. else
  662. return "am --3way --signoff --ignore-whitespace \"" + patchFile.ToPosixPath() + "\"";
  663. }
  664. public static string PatchDirCmd()
  665. {
  666. return "am --3way --signoff";
  667. }
  668. public static string PatchDirCmdIgnoreWhitespace()
  669. {
  670. return PatchDirCmd() + " --ignore-whitespace";
  671. }
  672. public static string CleanUpCmd(bool dryrun, bool directories, bool nonignored, bool ignored, string paths = null)
  673. {
  674. string command = "clean";
  675. if (directories)
  676. command += " -d";
  677. if (!nonignored && !ignored)
  678. command += " -x";
  679. if (ignored)
  680. command += " -X";
  681. if (dryrun)
  682. command += " --dry-run";
  683. if (!dryrun)
  684. command += " -f";
  685. if (!paths.IsNullOrWhiteSpace())
  686. command += " " + paths;
  687. return command;
  688. }
  689. public static string GetAllChangedFilesCmd(bool excludeIgnoredFiles, UntrackedFilesMode untrackedFiles, IgnoreSubmodulesMode ignoreSubmodules = 0)
  690. {
  691. StringBuilder stringBuilder = new StringBuilder("status --porcelain -z");
  692. switch (untrackedFiles)
  693. {
  694. case UntrackedFilesMode.Default:
  695. stringBuilder.Append(" --untracked-files");
  696. break;
  697. case UntrackedFilesMode.No:
  698. stringBuilder.Append(" --untracked-files=no");
  699. break;
  700. case UntrackedFilesMode.Normal:
  701. stringBuilder.Append(" --untracked-files=normal");
  702. break;
  703. case UntrackedFilesMode.All:
  704. stringBuilder.Append(" --untracked-files=all");
  705. break;
  706. }
  707. switch (ignoreSubmodules)
  708. {
  709. case IgnoreSubmodulesMode.Default:
  710. stringBuilder.Append(" --ignore-submodules");
  711. break;
  712. case IgnoreSubmodulesMode.None:
  713. stringBuilder.Append(" --ignore-submodules=none");
  714. break;
  715. case IgnoreSubmodulesMode.Untracked:
  716. stringBuilder.Append(" --ignore-submodules=untracked");
  717. break;
  718. case IgnoreSubmodulesMode.Dirty:
  719. stringBuilder.Append(" --ignore-submodules=dirty");
  720. break;
  721. case IgnoreSubmodulesMode.All:
  722. stringBuilder.Append(" --ignore-submodules=all");
  723. break;
  724. }
  725. if (!excludeIgnoredFiles)
  726. stringBuilder.Append(" --ignored");
  727. return stringBuilder.ToString();
  728. }
  729. [CanBeNull]
  730. public static GitSubmoduleStatus GetCurrentSubmoduleChanges(GitModule module, string fileName, string oldFileName, bool staged)
  731. {
  732. PatchApply.Patch patch = module.GetCurrentChanges(fileName, oldFileName, staged, "", module.FilesEncoding);
  733. string text = patch != null ? patch.Text : "";
  734. return GetSubmoduleStatus(text, module, fileName);
  735. }
  736. [CanBeNull]
  737. public static GitSubmoduleStatus GetCurrentSubmoduleChanges(GitModule module, string submodule)
  738. {
  739. return GetCurrentSubmoduleChanges(module, submodule, submodule, false);
  740. }
  741. public static GitSubmoduleStatus GetSubmoduleStatus(string text, GitModule module, string fileName)
  742. {
  743. if (string.IsNullOrEmpty(text))
  744. return null;
  745. var status = new GitSubmoduleStatus();
  746. using (StringReader reader = new StringReader(text))
  747. {
  748. string line = reader.ReadLine();
  749. if (line != null)
  750. {
  751. var match = Regex.Match(line, @"diff --git a/(\S+) b/(\S+)");
  752. if (match.Groups.Count > 1)
  753. {
  754. status.Name = match.Groups[1].Value;
  755. status.OldName = match.Groups[2].Value;
  756. }
  757. else
  758. {
  759. match = Regex.Match(line, @"diff --cc (\S+)");
  760. if (match.Groups.Count > 1)
  761. {
  762. status.Name = match.Groups[1].Value;
  763. status.OldName = match.Groups[1].Value;
  764. }
  765. }
  766. }
  767. while ((line = reader.ReadLine()) != null)
  768. {
  769. if (!line.Contains("Subproject"))
  770. continue;
  771. char c = line[0];
  772. const string commit = "commit ";
  773. string hash = "";
  774. int pos = line.IndexOf(commit);
  775. if (pos >= 0)
  776. hash = line.Substring(pos + commit.Length);
  777. bool bdirty = hash.EndsWith("-dirty");
  778. hash = hash.Replace("-dirty", "");
  779. if (c == '-')
  780. {
  781. status.OldCommit = hash;
  782. }
  783. else if (c == '+')
  784. {
  785. status.Commit = hash;
  786. status.IsDirty = bdirty;
  787. }
  788. // TODO: Support combined merge
  789. }
  790. }
  791. if (status.OldCommit != null && status.Commit != null)
  792. {
  793. var submodule = module.GetSubmodule(fileName);
  794. status.AddedCommits = submodule.GetCommitCount(status.Commit, status.OldCommit);
  795. status.RemovedCommits = submodule.GetCommitCount(status.OldCommit, status.Commit);
  796. }
  797. return status;
  798. }
  799. /*
  800. source: C:\Program Files\msysgit\doc\git\html\git-status.html
  801. */
  802. public static List<GitItemStatus> GetAllChangedFilesFromString(GitModule module, string statusString, bool fromDiff = false)
  803. {
  804. var diffFiles = new List<GitItemStatus>();
  805. if (string.IsNullOrEmpty(statusString))
  806. return diffFiles;
  807. /*The status string can show warnings. This is a text block at the start or at the beginning
  808. of the file status. Strip it. Example:
  809. warning: LF will be replaced by CRLF in CustomDictionary.xml.
  810. The file will have its original line endings in your working directory.
  811. warning: LF will be replaced by CRLF in FxCop.targets.
  812. The file will have its original line endings in your working directory.*/
  813. var nl = new[] { '\n', '\r' };
  814. string trimmedStatus = statusString.Trim(nl);
  815. int lastNewLinePos = trimmedStatus.LastIndexOfAny(nl);
  816. if (lastNewLinePos > 0)
  817. {
  818. int ind = trimmedStatus.LastIndexOf('\0');
  819. if (ind < lastNewLinePos) //Warning at end
  820. {
  821. lastNewLinePos = trimmedStatus.IndexOfAny(nl, ind >= 0 ? ind : 0);
  822. trimmedStatus = trimmedStatus.Substring(0, lastNewLinePos).Trim(nl);
  823. }
  824. else //Warning at beginning
  825. trimmedStatus = trimmedStatus.Substring(lastNewLinePos).Trim(nl);
  826. }
  827. // Doesn't work with removed submodules
  828. IList<string> Submodules = module.GetSubmodulesLocalPathes();
  829. //Split all files on '\0' (WE NEED ALL COMMANDS TO BE RUN WITH -z! THIS IS ALSO IMPORTANT FOR ENCODING ISSUES!)
  830. var files = trimmedStatus.Split(new char[] { '\0' }, StringSplitOptions.RemoveEmptyEntries);
  831. for (int n = 0; n < files.Length; n++)
  832. {
  833. if (string.IsNullOrEmpty(files[n]))
  834. continue;
  835. int splitIndex = files[n].IndexOfAny(new char[] { '\0', '\t', ' ' }, 1);
  836. string status = string.Empty;
  837. string fileName = string.Empty;
  838. if (splitIndex < 0)
  839. {
  840. status = files[n];
  841. fileName = files[n + 1];
  842. n++;
  843. }
  844. else
  845. {
  846. status = files[n].Substring(0, splitIndex);
  847. fileName = files[n].Substring(splitIndex);
  848. }
  849. char x = status[0];
  850. char y = status.Length > 1 ? status[1] : ' ';
  851. if (x != '?' && x != '!' && x != ' ')
  852. {
  853. GitItemStatus gitItemStatusX = null;
  854. if (x == 'R' || x == 'C') // Find renamed files...
  855. {
  856. string nextfile = n + 1 < files.Length ? files[n + 1] : "";
  857. gitItemStatusX = GitItemStatusFromCopyRename(fromDiff, nextfile, fileName, x, status);
  858. n++;
  859. }
  860. else
  861. gitItemStatusX = GitItemStatusFromStatusCharacter(fileName, x);
  862. gitItemStatusX.IsStaged = true;
  863. if (Submodules.Contains(gitItemStatusX.Name))
  864. gitItemStatusX.IsSubmodule = true;
  865. diffFiles.Add(gitItemStatusX);
  866. }
  867. if (fromDiff || y == ' ')
  868. continue;
  869. GitItemStatus gitItemStatusY;
  870. if (y == 'R' || y == 'C') // Find renamed files...
  871. {
  872. string nextfile = n + 1 < files.Length ? files[n + 1] : "";
  873. gitItemStatusY = GitItemStatusFromCopyRename(false, nextfile, fileName, y, status);
  874. n++;
  875. }
  876. else
  877. gitItemStatusY = GitItemStatusFromStatusCharacter(fileName, y);
  878. gitItemStatusY.IsStaged = false;
  879. if (Submodules.Contains(gitItemStatusY.Name))
  880. gitItemStatusY.IsSubmodule = true;
  881. diffFiles.Add(gitItemStatusY);
  882. }
  883. return diffFiles;
  884. }
  885. private static GitItemStatus GitItemStatusFromCopyRename(bool fromDiff, string nextfile, string fileName, char x, string status)
  886. {
  887. var gitItemStatus = new GitItemStatus();
  888. //Find renamed files...
  889. if (fromDiff)
  890. {
  891. gitItemStatus.OldName = fileName.Trim();
  892. gitItemStatus.Name = nextfile.Trim();
  893. }
  894. else
  895. {
  896. gitItemStatus.Name = fileName.Trim();
  897. gitItemStatus.OldName = nextfile.Trim();
  898. }
  899. gitItemStatus.IsNew = false;
  900. gitItemStatus.IsChanged = false;
  901. gitItemStatus.IsDeleted = false;
  902. if (x == 'R')
  903. gitItemStatus.IsRenamed = true;
  904. else
  905. gitItemStatus.IsCopied = true;
  906. gitItemStatus.IsTracked = true;
  907. if (status.Length > 2)
  908. gitItemStatus.RenameCopyPercentage = status.Substring(1);
  909. return gitItemStatus;
  910. }
  911. private static GitItemStatus GitItemStatusFromStatusCharacter(string fileName, char x)
  912. {
  913. var gitItemStatus = new GitItemStatus();
  914. gitItemStatus.Name = fileName.Trim();
  915. gitItemStatus.IsNew = x == 'A' || x == '?' || x == '!';
  916. gitItemStatus.IsChanged = x == 'M';
  917. gitItemStatus.IsDeleted = x == 'D';
  918. gitItemStatus.IsRenamed = false;
  919. gitItemStatus.IsTracked = x != '?' && x != '!' && x != ' ' || !gitItemStatus.IsNew;
  920. gitItemStatus.IsConflict = x == 'U';
  921. return gitItemStatus;
  922. }
  923. public static string GetSubmoduleText(GitModule superproject, string name, string hash)
  924. {
  925. StringBuilder sb = new StringBuilder();
  926. sb.AppendLine("Submodule " + name);
  927. sb.AppendLine();
  928. GitModule module = superproject.GetSubmodule(name);
  929. if (module.IsValidGitWorkingDir())
  930. {
  931. string error = "";
  932. CommitData data = CommitData.GetCommitData(module, hash, ref error);
  933. if (data == null)
  934. {
  935. sb.AppendLine("Commit hash:\t" + hash);
  936. return sb.ToString();
  937. }
  938. string header = data.GetHeaderPlain();
  939. string body = "\n" + data.Body.Trim();
  940. sb.AppendLine(header);
  941. sb.Append(body);
  942. }
  943. else
  944. sb.AppendLine("Commit hash:\t" + hash);
  945. return sb.ToString();
  946. }
  947. public static string ProcessSubmodulePatch(GitModule module, string fileName, PatchApply.Patch patch)
  948. {
  949. string text = patch != null ? patch.Text : null;
  950. var status = GetSubmoduleStatus(text, module, fileName);
  951. if (status == null)
  952. return "";
  953. return ProcessSubmoduleStatus(module, status);
  954. }
  955. public static string ProcessSubmoduleStatus([NotNull] GitModule module, [NotNull] GitSubmoduleStatus status)
  956. {
  957. if (module == null)
  958. throw new ArgumentNullException("module");
  959. if (status == null)
  960. throw new ArgumentNullException("status");
  961. GitModule gitmodule = module.GetSubmodule(status.Name);
  962. StringBuilder sb = new StringBuilder();
  963. sb.AppendLine("Submodule " + status.Name + " Change");
  964. sb.AppendLine();
  965. sb.AppendLine("From:\t" + (status.OldCommit ?? "null"));
  966. CommitData oldCommitData = null;
  967. if (gitmodule.IsValidGitWorkingDir())
  968. {
  969. string error = "";
  970. if (status.OldCommit != null)
  971. oldCommitData = CommitData.GetCommitData(gitmodule, status.OldCommit, ref error);
  972. if (oldCommitData != null)
  973. {
  974. sb.AppendLine("\t\t\t\t\t" + GetRelativeDateString(DateTime.UtcNow, oldCommitData.CommitDate.UtcDateTime) + " (" + GetFullDateString(oldCommitData.CommitDate) + ")");
  975. var delim = new char[] { '\n', '\r' };
  976. var lines = oldCommitData.Body.Trim(delim).Split(new string[] { "\r\n" }, 0);
  977. foreach (var curline in lines)
  978. sb.AppendLine("\t\t" + curline);
  979. }
  980. }
  981. else
  982. sb.AppendLine();
  983. sb.AppendLine();
  984. string dirty = !status.IsDirty ? "" : " (dirty)";
  985. sb.AppendLine("To:\t\t" + (status.Commit ?? "null") + dirty);
  986. CommitData commitData = null;
  987. if (gitmodule.IsValidGitWorkingDir())
  988. {
  989. string error = "";
  990. if (status.Commit != null)
  991. commitData = CommitData.GetCommitData(gitmodule, status.Commit, ref error);
  992. if (commitData != null)
  993. {
  994. sb.AppendLine("\t\t\t\t\t" + GetRelativeDateString(DateTime.UtcNow, commitData.CommitDate.UtcDateTime) + " (" + GetFullDateString(commitData.CommitDate) + ")");
  995. var delim = new char[] { '\n', '\r' };
  996. var lines = commitData.Body.Trim(delim).Split(new string[] { "\r\n" }, 0);
  997. foreach (var curline in lines)
  998. sb.AppendLine("\t\t" + curline);
  999. }
  1000. }
  1001. else
  1002. sb.AppendLine();
  1003. sb.AppendLine();
  1004. var submoduleStatus = gitmodule.CheckSubmoduleStatus(status.Commit, status.OldCommit, commitData, oldCommitData);
  1005. sb.Append("Type: ");
  1006. switch (submoduleStatus)
  1007. {
  1008. case SubmoduleStatus.NewSubmodule:
  1009. sb.AppendLine("New submodule");
  1010. break;
  1011. case SubmoduleStatus.FastForward:
  1012. sb.AppendLine("Fast Forward");
  1013. break;
  1014. case SubmoduleStatus.Rewind:
  1015. sb.AppendLine("Rewind");
  1016. break;
  1017. case SubmoduleStatus.NewerTime:
  1018. sb.AppendLine("Newer commit time");
  1019. break;
  1020. case SubmoduleStatus.OlderTime:
  1021. sb.AppendLine("Older commit time");
  1022. break;
  1023. case SubmoduleStatus.SameTime:
  1024. sb.AppendLine("Same commit time");
  1025. break;
  1026. default:
  1027. sb.AppendLine("Unknown");
  1028. break;
  1029. }
  1030. if (status.AddedCommits != null && status.RemovedCommits != null &&
  1031. (status.AddedCommits != 0 || status.RemovedCommits != 0))
  1032. {
  1033. sb.Append("\nCommits: ");
  1034. if (status.RemovedCommits > 0)
  1035. {
  1036. sb.Append(status.RemovedCommits + " removed");
  1037. if (status.AddedCommits > 0)
  1038. sb.Append(", ");
  1039. }
  1040. if (status.AddedCommits > 0)
  1041. sb.Append(status.AddedCommits + " added");
  1042. sb.AppendLine();
  1043. }
  1044. if (status.Commit != null && status.OldCommit != null)
  1045. {
  1046. if (status.IsDirty)
  1047. {
  1048. string statusText = gitmodule.GetStatusText(false);
  1049. if (!String.IsNullOrEmpty(statusText))
  1050. {
  1051. sb.AppendLine("\nStatus:");
  1052. sb.Append(statusText);
  1053. }
  1054. }
  1055. string diffs = gitmodule.GetDiffFilesText(status.OldCommit, status.Commit);
  1056. if (!String.IsNullOrEmpty(diffs))
  1057. {
  1058. sb.AppendLine("\nDifferences:");
  1059. sb.Append(diffs);
  1060. }
  1061. }
  1062. return sb.ToString();
  1063. }
  1064. public static string GetRemoteName(string completeName, IEnumerable<string> remotes)
  1065. {
  1066. string trimmedName = completeName.StartsWith("refs/remotes/") ? completeName.Substring(13) : completeName;
  1067. foreach (string remote in remotes)
  1068. {
  1069. if (trimmedName.StartsWith(string.Concat(remote, "/")))
  1070. return remote;
  1071. }
  1072. return string.Empty;
  1073. }
  1074. public static string MergeBranchCmd(string branch, bool allowFastForward, bool squash, bool noCommit, string strategy)
  1075. {
  1076. StringBuilder command = new StringBuilder("merge");
  1077. if (!allowFastForward)
  1078. command.Append(" --no-ff");
  1079. if (!string.IsNullOrEmpty(strategy))
  1080. {
  1081. command.Append(" --strategy=");
  1082. command.Append(strategy);
  1083. }
  1084. if (squash)
  1085. command.Append(" --squash");
  1086. if (noCommit)
  1087. command.Append(" --no-commit");
  1088. command.Append(" ");
  1089. command.Append(branch);
  1090. return command.ToString();
  1091. }
  1092. public static string GetFileExtension(string fileName)
  1093. {
  1094. if (fileName.Contains(".") && fileName.LastIndexOf(".") < fileName.Length)
  1095. return fileName.Substring(fileName.LastIndexOf('.') + 1);
  1096. return null;
  1097. }
  1098. private static DateTime RoundDateTime(DateTime dateTime)
  1099. {
  1100. return new DateTime(dateTime.Year, dateTime.Month, dateTime.Day, dateTime.Hour, dateTime.Minute, dateTime.Second);
  1101. }
  1102. /// <summary>
  1103. /// Takes a date/time which and determines a friendly string for time from now to be displayed for the relative time from the date.
  1104. /// It is important to note that times are compared using the current timezone, so the date that is passed in should be converted
  1105. /// to the local timezone before passing it in.
  1106. /// </summary>
  1107. /// <param name="originDate">Current date.</param>
  1108. /// <param name="previousDate">The date to get relative time string for.</param>
  1109. /// <param name="displayWeeks">Indicates whether to display weeks.</param>
  1110. /// <returns>…

Large files files are truncated, but you can click here to view the full file