PageRenderTime 60ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 1ms

/GitCommands/Git/GitModule.cs

https://github.com/qgppl/gitextensions
C# | 3328 lines | 2633 code | 513 blank | 182 comment | 471 complexity | d8767b6b440fc459a31caa93fbcbda50 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.Net.Mail;
  8. using System.Security.Permissions;
  9. using System.Text;
  10. using System.Text.RegularExpressions;
  11. using System.Threading.Tasks;
  12. using GitCommands.Config;
  13. using GitCommands.Settings;
  14. using GitCommands.Utils;
  15. using GitUIPluginInterfaces;
  16. using JetBrains.Annotations;
  17. using PatchApply;
  18. using SmartFormat;
  19. namespace GitCommands
  20. {
  21. public class GitModuleEventArgs : EventArgs
  22. {
  23. public GitModuleEventArgs(GitModule gitModule)
  24. {
  25. GitModule = gitModule;
  26. }
  27. public GitModule GitModule { get; private set; }
  28. }
  29. public enum SubmoduleStatus
  30. {
  31. Unknown,
  32. NewSubmodule,
  33. FastForward,
  34. Rewind,
  35. NewerTime,
  36. OlderTime,
  37. SameTime
  38. }
  39. public enum ForcePushOptions
  40. {
  41. DoNotForce,
  42. Force,
  43. ForceWithLease,
  44. }
  45. public struct ConflictedFileData
  46. {
  47. public ConflictedFileData(string hash, string filename)
  48. {
  49. Hash = hash;
  50. Filename = filename;
  51. }
  52. public string Hash;
  53. public string Filename;
  54. }
  55. [DebuggerDisplay("{Filename}")]
  56. public struct ConflictData
  57. {
  58. public ConflictData(ConflictedFileData _base, ConflictedFileData _local,
  59. ConflictedFileData _remote)
  60. {
  61. Base = _base;
  62. Local = _local;
  63. Remote = _remote;
  64. }
  65. public ConflictedFileData Base;
  66. public ConflictedFileData Local;
  67. public ConflictedFileData Remote;
  68. public string Filename
  69. {
  70. get { return Local.Filename ?? Base.Filename ?? Remote.Filename; }
  71. }
  72. }
  73. /// <summary>Provides manipulation with git module.
  74. /// <remarks>Several instances may be created for submodules.</remarks></summary>
  75. [DebuggerDisplay("GitModule ( {_workingDir} )")]
  76. public sealed class GitModule : IGitModule
  77. {
  78. private static readonly Regex DefaultHeadPattern = new Regex("refs/remotes/[^/]+/HEAD", RegexOptions.Compiled);
  79. private static readonly Regex CpEncodingPattern = new Regex("cp\\d+", RegexOptions.Compiled);
  80. private readonly object _lock = new object();
  81. public const string NoNewLineAtTheEnd = "\\ No newline at end of file";
  82. public GitModule(string workingdir)
  83. {
  84. _superprojectInit = false;
  85. _workingDir = (workingdir ?? "").EnsureTrailingPathSeparator();
  86. }
  87. #region IGitCommands
  88. [NotNull]
  89. private readonly string _workingDir;
  90. [NotNull]
  91. public string WorkingDir
  92. {
  93. get
  94. {
  95. return _workingDir;
  96. }
  97. }
  98. /// <summary>Gets the path to the git application executable.</summary>
  99. public string GitCommand
  100. {
  101. get
  102. {
  103. return AppSettings.GitCommand;
  104. }
  105. }
  106. public Version AppVersion
  107. {
  108. get
  109. {
  110. return AppSettings.AppVersion;
  111. }
  112. }
  113. public string GravatarCacheDir
  114. {
  115. get
  116. {
  117. return AppSettings.GravatarCachePath;
  118. }
  119. }
  120. #endregion
  121. private bool _superprojectInit;
  122. private GitModule _superprojectModule;
  123. private string _submoduleName;
  124. private string _submodulePath;
  125. public string SubmoduleName
  126. {
  127. get
  128. {
  129. InitSuperproject();
  130. return _submoduleName;
  131. }
  132. }
  133. public string SubmodulePath
  134. {
  135. get
  136. {
  137. InitSuperproject();
  138. return _submodulePath;
  139. }
  140. }
  141. public GitModule SuperprojectModule
  142. {
  143. get
  144. {
  145. InitSuperproject();
  146. return _superprojectModule;
  147. }
  148. }
  149. private void InitSuperproject()
  150. {
  151. if (!_superprojectInit)
  152. {
  153. string superprojectDir = FindGitSuperprojectPath(out _submoduleName, out _submodulePath);
  154. _superprojectModule = superprojectDir == null ? null : new GitModule(superprojectDir);
  155. _superprojectInit = true;
  156. }
  157. }
  158. public GitModule FindTopProjectModule()
  159. {
  160. GitModule module = SuperprojectModule;
  161. if (module == null)
  162. return null;
  163. do
  164. {
  165. if (module.SuperprojectModule == null)
  166. return module;
  167. module = module.SuperprojectModule;
  168. } while (module != null);
  169. return module;
  170. }
  171. private RepoDistSettings _effectiveSettings;
  172. public RepoDistSettings EffectiveSettings
  173. {
  174. get
  175. {
  176. lock (_lock)
  177. {
  178. if (_effectiveSettings == null)
  179. _effectiveSettings = RepoDistSettings.CreateEffective(this);
  180. }
  181. return _effectiveSettings;
  182. }
  183. }
  184. public ISettingsSource GetEffectiveSettings()
  185. {
  186. return EffectiveSettings;
  187. }
  188. private RepoDistSettings _distributedSettings;
  189. public RepoDistSettings DistributedSettings
  190. {
  191. get
  192. {
  193. lock (_lock)
  194. {
  195. if (_distributedSettings == null)
  196. _distributedSettings = new RepoDistSettings(null, EffectiveSettings.LowerPriority.SettingsCache);
  197. }
  198. return _distributedSettings;
  199. }
  200. }
  201. private RepoDistSettings _localSettings;
  202. public RepoDistSettings LocalSettings
  203. {
  204. get
  205. {
  206. lock (_lock)
  207. {
  208. if (_localSettings == null)
  209. _localSettings = new RepoDistSettings(null, EffectiveSettings.SettingsCache);
  210. }
  211. return _localSettings;
  212. }
  213. }
  214. private ConfigFileSettings _effectiveConfigFile;
  215. public ConfigFileSettings EffectiveConfigFile
  216. {
  217. get
  218. {
  219. lock (_lock)
  220. {
  221. if (_effectiveConfigFile == null)
  222. _effectiveConfigFile = ConfigFileSettings.CreateEffective(this);
  223. }
  224. return _effectiveConfigFile;
  225. }
  226. }
  227. public ConfigFileSettings LocalConfigFile
  228. {
  229. get { return new ConfigFileSettings(null, EffectiveConfigFile.SettingsCache); }
  230. }
  231. ISettingsValueGetter IGitModule.LocalConfigFile
  232. {
  233. get { return LocalConfigFile; }
  234. }
  235. //encoding for files paths
  236. private static Encoding _systemEncoding;
  237. public static Encoding SystemEncoding
  238. {
  239. get
  240. {
  241. if (_systemEncoding == null)
  242. {
  243. //check whether GitExtensions works with standard msysgit or msysgit-unicode
  244. // invoke a git command that returns an invalid argument in its response, and
  245. // check if a unicode-only character is reported back. If so assume msysgit-unicode
  246. // git config --get with a malformed key (no section) returns:
  247. // "error: key does not contain a section: <key>"
  248. const string controlStr = "ą"; // "a caudata"
  249. string arguments = string.Format("config --get {0}", controlStr);
  250. String s = new GitModule("").RunGitCmd(arguments, Encoding.UTF8);
  251. if (s != null && s.IndexOf(controlStr) != -1)
  252. _systemEncoding = new UTF8Encoding(false);
  253. else
  254. _systemEncoding = Encoding.Default;
  255. Debug.WriteLine("System encoding: " + _systemEncoding.EncodingName);
  256. }
  257. return _systemEncoding;
  258. }
  259. }
  260. //Encoding that let us read all bytes without replacing any char
  261. //It is using to read output of commands, which may consist of:
  262. //1) commit header (message, author, ...) encoded in CommitEncoding, recoded to LogOutputEncoding or not dependent of
  263. // pretty parameter (pretty=raw - recoded, pretty=format - not recoded)
  264. //2) file content encoded in its original encoding
  265. //3) file path (file name is encoded in system default encoding),
  266. // when core.quotepath is on, every non ASCII character is escaped
  267. // with \ followed by its code as a three digit octal number
  268. //4) branch, tag name, errors, warnings, hints encoded in system default encoding
  269. public static readonly Encoding LosslessEncoding = Encoding.GetEncoding("ISO-8859-1");//is any better?
  270. public Encoding FilesEncoding
  271. {
  272. get
  273. {
  274. Encoding result = EffectiveConfigFile.FilesEncoding;
  275. if (result == null)
  276. result = new UTF8Encoding(false);
  277. return result;
  278. }
  279. }
  280. public Encoding CommitEncoding
  281. {
  282. get
  283. {
  284. Encoding result = EffectiveConfigFile.CommitEncoding;
  285. if (result == null)
  286. result = new UTF8Encoding(false);
  287. return result;
  288. }
  289. }
  290. /// <summary>
  291. /// Encoding for commit header (message, notes, author, committer, emails)
  292. /// </summary>
  293. public Encoding LogOutputEncoding
  294. {
  295. get
  296. {
  297. Encoding result = EffectiveConfigFile.LogOutputEncoding;
  298. if (result == null)
  299. result = CommitEncoding;
  300. return result;
  301. }
  302. }
  303. /// <summary>"(no branch)"</summary>
  304. public static readonly string DetachedBranch = "(no branch)";
  305. private static readonly string[] DetachedPrefixes = { "(no branch", "(detached from ", "(HEAD detached at " };
  306. public AppSettings.PullAction LastPullAction
  307. {
  308. get { return AppSettings.GetEnum("LastPullAction_" + WorkingDir, AppSettings.PullAction.None); }
  309. set { AppSettings.SetEnum("LastPullAction_" + WorkingDir, value); }
  310. }
  311. public void LastPullActionToFormPullAction()
  312. {
  313. if (LastPullAction == AppSettings.PullAction.FetchAll)
  314. AppSettings.FormPullAction = AppSettings.PullAction.Fetch;
  315. else if (LastPullAction != AppSettings.PullAction.None)
  316. AppSettings.FormPullAction = LastPullAction;
  317. }
  318. /// <summary>Indicates whether the <see cref="WorkingDir"/> contains a git repository.</summary>
  319. public bool IsValidGitWorkingDir()
  320. {
  321. return IsValidGitWorkingDir(_workingDir);
  322. }
  323. /// <summary>Indicates whether the specified directory contains a git repository.</summary>
  324. public static bool IsValidGitWorkingDir(string dir)
  325. {
  326. if (string.IsNullOrEmpty(dir))
  327. return false;
  328. string dirPath = dir.EnsureTrailingPathSeparator();
  329. string path = dirPath + ".git";
  330. if (Directory.Exists(path) || File.Exists(path))
  331. return true;
  332. return Directory.Exists(dirPath + "info") &&
  333. Directory.Exists(dirPath + "objects") &&
  334. Directory.Exists(dirPath + "refs");
  335. }
  336. /// <summary>Gets the ".git" directory path.</summary>
  337. public string GetGitDirectory()
  338. {
  339. return GetGitDirectory(_workingDir);
  340. }
  341. public static string GetGitDirectory(string repositoryPath)
  342. {
  343. var gitpath = Path.Combine(repositoryPath, ".git");
  344. if (File.Exists(gitpath))
  345. {
  346. var lines = File.ReadLines(gitpath);
  347. foreach (string line in lines)
  348. {
  349. if (line.StartsWith("gitdir:"))
  350. {
  351. string path = line.Substring(7).Trim().ToNativePath();
  352. if (Path.IsPathRooted(path))
  353. return path.EnsureTrailingPathSeparator();
  354. else
  355. return
  356. Path.GetFullPath(Path.Combine(repositoryPath,
  357. path.EnsureTrailingPathSeparator()));
  358. }
  359. }
  360. }
  361. gitpath = gitpath.EnsureTrailingPathSeparator();
  362. if (!Directory.Exists(gitpath))
  363. return repositoryPath;
  364. return gitpath;
  365. }
  366. public bool IsBareRepository()
  367. {
  368. return WorkingDir == GetGitDirectory();
  369. }
  370. public static bool IsBareRepository(string repositoryPath)
  371. {
  372. return repositoryPath == GetGitDirectory(repositoryPath);
  373. }
  374. public bool IsSubmodule(string submodulePath)
  375. {
  376. var result = RunGitCmdResult("submodule status " + submodulePath);
  377. if (result.ExitCode == 0
  378. // submodule removed
  379. || result.StdError.StartsWith("No submodule mapping found in .gitmodules for path"))
  380. return true;
  381. return false;
  382. }
  383. public bool HasSubmodules()
  384. {
  385. return GetSubmodulesLocalPaths(recursive: false).Any();
  386. }
  387. /// <summary>
  388. /// This is a faster function to get the names of all submodules then the
  389. /// GetSubmodules() function. The command @git submodule is very slow.
  390. /// </summary>
  391. public IList<string> GetSubmodulesLocalPaths(bool recursive = true)
  392. {
  393. var configFile = GetSubmoduleConfigFile();
  394. var submodules = configFile.ConfigSections.Select(configSection => configSection.GetPathValue("path").Trim()).ToList();
  395. if (recursive)
  396. {
  397. for (int i = 0; i < submodules.Count; i++)
  398. {
  399. var submodule = GetSubmodule(submodules[i]);
  400. var submoduleConfigFile = submodule.GetSubmoduleConfigFile();
  401. var subsubmodules = submoduleConfigFile.ConfigSections.Select(configSection => configSection.GetPathValue("path").Trim()).ToList();
  402. for (int j = 0; j < subsubmodules.Count; j++)
  403. subsubmodules[j] = submodules[i] + '/' + subsubmodules[j];
  404. submodules.InsertRange(i + 1, subsubmodules);
  405. i += subsubmodules.Count;
  406. }
  407. }
  408. return submodules;
  409. }
  410. public static string FindGitWorkingDir(string startDir)
  411. {
  412. if (string.IsNullOrEmpty(startDir))
  413. return "";
  414. var dir = startDir.Trim();
  415. do
  416. {
  417. if (IsValidGitWorkingDir(dir))
  418. return dir.EnsureTrailingPathSeparator();
  419. dir = PathUtil.GetDirectoryName(dir);
  420. }
  421. while (!string.IsNullOrEmpty(dir));
  422. return startDir;
  423. }
  424. private static Process StartProccess(string fileName, string arguments, string workingDir, bool showConsole)
  425. {
  426. GitCommandHelpers.SetEnvironmentVariable();
  427. string quotedCmd = fileName;
  428. if (quotedCmd.IndexOf(' ') != -1)
  429. quotedCmd = quotedCmd.Quote();
  430. var executionStartTimestamp = DateTime.Now;
  431. var startInfo = new ProcessStartInfo
  432. {
  433. FileName = fileName,
  434. Arguments = arguments,
  435. WorkingDirectory = workingDir
  436. };
  437. if (!showConsole)
  438. {
  439. startInfo.UseShellExecute = false;
  440. startInfo.CreateNoWindow = true;
  441. }
  442. var startProcess = Process.Start(startInfo);
  443. startProcess.Exited += (sender, args) =>
  444. {
  445. var executionEndTimestamp = DateTime.Now;
  446. AppSettings.GitLog.Log(quotedCmd + " " + arguments, executionStartTimestamp, executionEndTimestamp);
  447. };
  448. return startProcess;
  449. }
  450. /// <summary>
  451. /// Run command, console window is visible
  452. /// </summary>
  453. public Process RunExternalCmdDetachedShowConsole(string cmd, string arguments)
  454. {
  455. try
  456. {
  457. return StartProccess(cmd, arguments, _workingDir, showConsole: true);
  458. }
  459. catch (Exception ex)
  460. {
  461. Trace.WriteLine(ex.Message);
  462. }
  463. return null;
  464. }
  465. /// <summary>
  466. /// Run command, console window is visible, wait for exit
  467. /// </summary>
  468. public void RunExternalCmdShowConsole(string cmd, string arguments)
  469. {
  470. try
  471. {
  472. using (var process = StartProccess(cmd, arguments, _workingDir, showConsole: true))
  473. process.WaitForExit();
  474. }
  475. catch (Exception ex)
  476. {
  477. Trace.WriteLine(ex.Message);
  478. }
  479. }
  480. /// <summary>
  481. /// Run command, console window is hidden
  482. /// </summary>
  483. public static Process RunExternalCmdDetached(string fileName, string arguments, string workingDir)
  484. {
  485. try
  486. {
  487. return StartProccess(fileName, arguments, workingDir, showConsole: false);
  488. }
  489. catch (Exception ex)
  490. {
  491. Trace.WriteLine(ex.Message);
  492. }
  493. return null;
  494. }
  495. /// <summary>
  496. /// Run command, console window is hidden
  497. /// </summary>
  498. public Process RunExternalCmdDetached(string cmd, string arguments)
  499. {
  500. return RunExternalCmdDetached(cmd, arguments, _workingDir);
  501. }
  502. /// <summary>
  503. /// Run git command, console window is hidden, redirect output
  504. /// </summary>
  505. public Process RunGitCmdDetached(string arguments, Encoding encoding = null)
  506. {
  507. if (encoding == null)
  508. encoding = SystemEncoding;
  509. return GitCommandHelpers.StartProcess(AppSettings.GitCommand, arguments, _workingDir, encoding);
  510. }
  511. /// <summary>
  512. /// Run command, cache results, console window is hidden, wait for exit, redirect output
  513. /// </summary>
  514. [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
  515. public string RunCacheableCmd(string cmd, string arguments = "", Encoding encoding = null)
  516. {
  517. if (encoding == null)
  518. encoding = SystemEncoding;
  519. byte[] cmdout, cmderr;
  520. if (GitCommandCache.TryGet(arguments, out cmdout, out cmderr))
  521. return EncodingHelper.DecodeString(cmdout, cmderr, ref encoding);
  522. GitCommandHelpers.RunCmdByte(cmd, arguments, _workingDir, null, out cmdout, out cmderr);
  523. GitCommandCache.Add(arguments, cmdout, cmderr);
  524. return EncodingHelper.DecodeString(cmdout, cmderr, ref encoding);
  525. }
  526. /// <summary>
  527. /// Run command, console window is hidden, wait for exit, redirect output
  528. /// </summary>
  529. [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
  530. public CmdResult RunCmdResult(string cmd, string arguments, Encoding encoding = null, byte[] stdInput = null)
  531. {
  532. byte[] output, error;
  533. int exitCode = GitCommandHelpers.RunCmdByte(cmd, arguments, _workingDir, stdInput, out output, out error);
  534. if (encoding == null)
  535. encoding = SystemEncoding;
  536. return new CmdResult
  537. {
  538. StdOutput = output == null ? string.Empty : encoding.GetString(output),
  539. StdError = error == null ? string.Empty : encoding.GetString(error),
  540. ExitCode = exitCode
  541. };
  542. }
  543. /// <summary>
  544. /// Run command, console window is hidden, wait for exit, redirect output
  545. /// </summary>
  546. [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
  547. public string RunCmd(string cmd, string arguments, Encoding encoding = null, byte[] stdInput = null)
  548. {
  549. return RunCmdResult(cmd, arguments, encoding, stdInput).GetString();
  550. }
  551. /// <summary>
  552. /// Run git command, console window is hidden, wait for exit, redirect output
  553. /// </summary>
  554. public string RunGitCmd(string arguments, Encoding encoding = null, byte[] stdInput = null)
  555. {
  556. return RunCmd(AppSettings.GitCommand, arguments, encoding, stdInput);
  557. }
  558. /// <summary>
  559. /// Run git command, console window is hidden, wait for exit, redirect output
  560. /// </summary>
  561. public CmdResult RunGitCmdResult(string arguments, Encoding encoding = null, byte[] stdInput = null)
  562. {
  563. return RunCmdResult(AppSettings.GitCommand, arguments, encoding, stdInput);
  564. }
  565. /// <summary>
  566. /// Run command, console window is hidden, wait for exit, redirect output
  567. /// </summary>
  568. [PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
  569. private IEnumerable<string> ReadCmdOutputLines(string cmd, string arguments, string stdInput)
  570. {
  571. return GitCommandHelpers.ReadCmdOutputLines(cmd, arguments, _workingDir, stdInput);
  572. }
  573. /// <summary>
  574. /// Run git command, console window is hidden, wait for exit, redirect output
  575. /// </summary>
  576. public IEnumerable<string> ReadGitOutputLines(string arguments)
  577. {
  578. return ReadCmdOutputLines(AppSettings.GitCommand, arguments, null);
  579. }
  580. /// <summary>
  581. /// Run batch file, console window is hidden, wait for exit, redirect output
  582. /// </summary>
  583. public string RunBatchFile(string batchFile)
  584. {
  585. string tempFileName = Path.ChangeExtension(Path.GetTempFileName(), ".cmd");
  586. using (var writer = new StreamWriter(tempFileName))
  587. {
  588. writer.WriteLine("@prompt $G");
  589. writer.Write(batchFile);
  590. }
  591. string result = RunCmd("cmd.exe", "/C \"" + tempFileName + "\"");
  592. File.Delete(tempFileName);
  593. return result;
  594. }
  595. public void EditNotes(string revision)
  596. {
  597. string editor = GetEffectivePathSetting("core.editor").ToLower();
  598. if (editor.Contains("gitextensions") || editor.Contains("notepad") ||
  599. editor.Contains("notepad++"))
  600. {
  601. RunGitCmd("notes edit " + revision);
  602. }
  603. else
  604. {
  605. RunExternalCmdShowConsole(AppSettings.GitCommand, "notes edit " + revision);
  606. }
  607. }
  608. public bool InTheMiddleOfConflictedMerge()
  609. {
  610. return !string.IsNullOrEmpty(RunGitCmd("ls-files -z --unmerged"));
  611. }
  612. public bool HandleConflictSelectSide(string fileName, string side)
  613. {
  614. Directory.SetCurrentDirectory(_workingDir);
  615. fileName = fileName.ToPosixPath();
  616. side = GetSide(side);
  617. string result = RunGitCmd(String.Format("checkout-index -f --stage={0} -- \"{1}\"", side, fileName));
  618. if (!result.IsNullOrEmpty())
  619. {
  620. return false;
  621. }
  622. result = RunGitCmd(String.Format("add -- \"{0}\"", fileName));
  623. return result.IsNullOrEmpty();
  624. }
  625. public bool HandleConflictsSaveSide(string fileName, string saveAsFileName, string side)
  626. {
  627. Directory.SetCurrentDirectory(_workingDir);
  628. fileName = fileName.ToPosixPath();
  629. side = GetSide(side);
  630. var result = RunGitCmd(String.Format("checkout-index --stage={0} --temp -- \"{1}\"", side, fileName));
  631. if (result.IsNullOrEmpty())
  632. {
  633. return false;
  634. }
  635. if (!result.StartsWith(".merge_file_"))
  636. {
  637. return false;
  638. }
  639. // Parse temporary file name from command line result
  640. var splitResult = result.Split(new[] { "\t", "\n", "\r" }, StringSplitOptions.RemoveEmptyEntries);
  641. if (splitResult.Length != 2)
  642. {
  643. return false;
  644. }
  645. var temporaryFileName = splitResult[0].Trim();
  646. if (!File.Exists(temporaryFileName))
  647. {
  648. return false;
  649. }
  650. var retValue = false;
  651. try
  652. {
  653. if (File.Exists(saveAsFileName))
  654. {
  655. File.Delete(saveAsFileName);
  656. }
  657. File.Move(temporaryFileName, saveAsFileName);
  658. retValue = true;
  659. }
  660. catch
  661. {
  662. }
  663. finally
  664. {
  665. if (File.Exists(temporaryFileName))
  666. {
  667. File.Delete(temporaryFileName);
  668. }
  669. }
  670. return retValue;
  671. }
  672. public void SaveBlobAs(string saveAs, string blob)
  673. {
  674. using (var ms = (MemoryStream)GetFileStream(blob)) //Ugly, has implementation info.
  675. {
  676. byte[] buf = ms.ToArray();
  677. if (EffectiveConfigFile.core.autocrlf.Value == AutoCRLFType.@true)
  678. {
  679. if (!FileHelper.IsBinaryFile(this, saveAs) && !FileHelper.IsBinaryFileAccordingToContent(buf))
  680. {
  681. buf = GitConvert.ConvertCrLfToWorktree(buf);
  682. }
  683. }
  684. using (FileStream fileOut = File.Create(saveAs))
  685. {
  686. fileOut.Write(buf, 0, buf.Length);
  687. }
  688. }
  689. }
  690. private static string GetSide(string side)
  691. {
  692. if (side.Equals("REMOTE", StringComparison.CurrentCultureIgnoreCase))
  693. side = "3";
  694. if (side.Equals("LOCAL", StringComparison.CurrentCultureIgnoreCase))
  695. side = "2";
  696. if (side.Equals("BASE", StringComparison.CurrentCultureIgnoreCase))
  697. side = "1";
  698. return side;
  699. }
  700. public string[] CheckoutConflictedFiles(ConflictData unmergedData)
  701. {
  702. Directory.SetCurrentDirectory(_workingDir);
  703. var filename = unmergedData.Filename;
  704. string[] fileNames =
  705. {
  706. filename + ".BASE",
  707. filename + ".LOCAL",
  708. filename + ".REMOTE"
  709. };
  710. var unmerged = new[] { unmergedData.Base.Filename, unmergedData.Local.Filename, unmergedData.Remote.Filename };
  711. for (int i = 0; i < unmerged.Length; i++)
  712. {
  713. if (unmerged[i] == null)
  714. continue;
  715. var tempFile =
  716. RunGitCmd("checkout-index --temp --stage=" + (i + 1) + " -- \"" + filename + "\"");
  717. tempFile = tempFile.Split('\t')[0];
  718. tempFile = Path.Combine(_workingDir, tempFile);
  719. var newFileName = Path.Combine(_workingDir, fileNames[i]);
  720. try
  721. {
  722. fileNames[i] = newFileName;
  723. var index = 1;
  724. while (File.Exists(fileNames[i]) && index < 50)
  725. {
  726. fileNames[i] = newFileName + index;
  727. index++;
  728. }
  729. File.Move(tempFile, fileNames[i]);
  730. }
  731. catch (Exception ex)
  732. {
  733. Trace.WriteLine(ex);
  734. }
  735. }
  736. if (!File.Exists(fileNames[0])) fileNames[0] = null;
  737. if (!File.Exists(fileNames[1])) fileNames[1] = null;
  738. if (!File.Exists(fileNames[2])) fileNames[2] = null;
  739. return fileNames;
  740. }
  741. public ConflictData GetConflict(string filename)
  742. {
  743. return GetConflicts(filename).SingleOrDefault();
  744. }
  745. public List<ConflictData> GetConflicts(string filename = "")
  746. {
  747. filename = filename.ToPosixPath();
  748. var list = new List<ConflictData>();
  749. var unmerged = RunGitCmd("ls-files -z --unmerged " + filename.QuoteNE()).Split(new[] { '\0', '\n' }, StringSplitOptions.RemoveEmptyEntries);
  750. var item = new ConflictedFileData[3];
  751. string prevItemName = null;
  752. foreach (var line in unmerged)
  753. {
  754. int findSecondWhitespace = line.IndexOfAny(new[] { ' ', '\t' });
  755. string fileStage = findSecondWhitespace >= 0 ? line.Substring(findSecondWhitespace).Trim() : "";
  756. findSecondWhitespace = fileStage.IndexOfAny(new[] { ' ', '\t' });
  757. string hash = findSecondWhitespace >= 0 ? fileStage.Substring(0, findSecondWhitespace).Trim() : "";
  758. fileStage = findSecondWhitespace >= 0 ? fileStage.Substring(findSecondWhitespace).Trim() : "";
  759. int stage;
  760. if (fileStage.Length > 2 && Int32.TryParse(fileStage[0].ToString(), out stage) && stage >= 1 && stage <= 3)
  761. {
  762. var itemName = fileStage.Substring(2);
  763. if (prevItemName != itemName && prevItemName != null)
  764. {
  765. list.Add(new ConflictData(item[0], item[1], item[2]));
  766. item = new ConflictedFileData[3];
  767. }
  768. item[stage - 1] = new ConflictedFileData(hash, itemName);
  769. prevItemName = itemName;
  770. }
  771. }
  772. if (prevItemName != null)
  773. list.Add(new ConflictData(item[0], item[1], item[2]));
  774. return list;
  775. }
  776. public IList<string> GetSortedRefs()
  777. {
  778. string command = "for-each-ref --sort=-committerdate --sort=-taggerdate --format=\"%(refname)\" refs/";
  779. var tree = RunGitCmd(command, SystemEncoding);
  780. return tree.Split();
  781. }
  782. public Dictionary<IGitRef, IGitItem> GetSubmoduleItemsForEachRef(string filename, Func<IGitRef, bool> showRemoteRef)
  783. {
  784. string command = GetSortedRefsCommand();
  785. if (command == null)
  786. return new Dictionary<IGitRef, IGitItem>();
  787. filename = filename.ToPosixPath();
  788. var tree = RunGitCmd(command, SystemEncoding);
  789. var refs = GetTreeRefs(tree);
  790. return refs.Where(showRemoteRef).ToDictionary(r => r, r => GetSubmoduleCommitHash(filename, r.Name));
  791. }
  792. private string GetSortedRefsCommand()
  793. {
  794. if (AppSettings.ShowSuperprojectRemoteBranches)
  795. return "for-each-ref --sort=-committerdate --format=\"%(objectname) %(refname)\" refs/";
  796. if (AppSettings.ShowSuperprojectBranches || AppSettings.ShowSuperprojectTags)
  797. return "for-each-ref --sort=-committerdate --format=\"%(objectname) %(refname)\""
  798. + (AppSettings.ShowSuperprojectBranches ? " refs/heads/" : null)
  799. + (AppSettings.ShowSuperprojectTags ? " refs/tags/" : null);
  800. return null;
  801. }
  802. private IGitItem GetSubmoduleCommitHash(string filename, string refName)
  803. {
  804. string str = RunGitCmd("ls-tree " + refName + " \"" + filename + "\"");
  805. return GitItem.CreateGitItemFromString(this, str);
  806. }
  807. public int? GetCommitCount(string parentHash, string childHash)
  808. {
  809. string result = RunGitCmd("rev-list " + parentHash + " ^" + childHash + " --count");
  810. int commitCount;
  811. if (int.TryParse(result, out commitCount))
  812. return commitCount;
  813. return null;
  814. }
  815. public string GetCommitCountString(string from, string to)
  816. {
  817. int? removed = GetCommitCount(from, to);
  818. int? added = GetCommitCount(to, from);
  819. if (removed == null || added == null)
  820. return "";
  821. if (removed == 0 && added == 0)
  822. return "=";
  823. return
  824. (removed > 0 ? ("-" + removed) : "") +
  825. (added > 0 ? ("+" + added) : "");
  826. }
  827. public string GetMergeMessage()
  828. {
  829. var file = GetGitDirectory() + "MERGE_MSG";
  830. return
  831. File.Exists(file)
  832. ? File.ReadAllText(file)
  833. : "";
  834. }
  835. public void RunGitK()
  836. {
  837. if (EnvUtils.RunningOnUnix())
  838. {
  839. RunExternalCmdDetachedShowConsole("gitk", "");
  840. }
  841. else
  842. {
  843. RunExternalCmdDetached("cmd.exe", "/c \"\"" + AppSettings.GitCommand.Replace("git.cmd", "gitk.cmd")
  844. .Replace("bin\\git.exe", "cmd\\gitk.cmd")
  845. .Replace("bin/git.exe", "cmd/gitk.cmd") + "\" --branches --tags --remotes\"");
  846. }
  847. }
  848. public void RunGui()
  849. {
  850. if (EnvUtils.RunningOnUnix())
  851. {
  852. RunExternalCmdDetachedShowConsole(AppSettings.GitCommand, "gui");
  853. }
  854. else
  855. {
  856. RunExternalCmdDetached("cmd.exe", "/c \"\"" + AppSettings.GitCommand + "\" gui\"");
  857. }
  858. }
  859. /// <summary>Runs a bash or shell command.</summary>
  860. public Process RunBash(string bashCommand = null)
  861. {
  862. if (EnvUtils.RunningOnUnix())
  863. {
  864. string[] termEmuCmds =
  865. {
  866. "gnome-terminal",
  867. "konsole",
  868. "Terminal",
  869. "xterm"
  870. };
  871. string args = "";
  872. string cmd = termEmuCmds.FirstOrDefault(termEmuCmd => !string.IsNullOrEmpty(RunCmd("which", termEmuCmd)));
  873. if (string.IsNullOrEmpty(cmd))
  874. {
  875. cmd = "bash";
  876. args = "--login -i";
  877. }
  878. return RunExternalCmdDetachedShowConsole(cmd, args);
  879. }
  880. else
  881. {
  882. string shellPath;
  883. if (PathUtil.TryFindShellPath("git-bash.exe", out shellPath))
  884. {
  885. return RunExternalCmdDetachedShowConsole(shellPath, string.Empty);
  886. }
  887. string args;
  888. if (string.IsNullOrWhiteSpace(bashCommand))
  889. {
  890. args = "--login -i\"";
  891. }
  892. else
  893. {
  894. args = "--login -i -c \"" + bashCommand.Replace("\"", "\\\"") + "\"";
  895. }
  896. args = "/c \"\"{0}\" " + args;
  897. if (PathUtil.TryFindShellPath("bash.exe", out shellPath))
  898. {
  899. return RunExternalCmdDetachedShowConsole("cmd.exe", string.Format(args, shellPath));
  900. }
  901. if (PathUtil.TryFindShellPath("sh.exe", out shellPath))
  902. {
  903. return RunExternalCmdDetachedShowConsole("cmd.exe", string.Format(args, shellPath));
  904. }
  905. return RunExternalCmdDetachedShowConsole("cmd.exe", @"/K echo git bash command not found! :( Please add a folder containing 'bash.exe' to your PATH...");
  906. }
  907. }
  908. public string Init(bool bare, bool shared)
  909. {
  910. return RunGitCmd(Smart.Format("init{0: --bare|}{1: --shared=all|}", bare, shared));
  911. }
  912. public bool IsMerge(string commit)
  913. {
  914. string[] parents = GetParents(commit);
  915. return parents.Length > 1;
  916. }
  917. private static string ProccessDiffNotes(int startIndex, string[] lines)
  918. {
  919. int endIndex = lines.Length - 1;
  920. if (lines[endIndex] == "Notes:")
  921. endIndex--;
  922. var message = new StringBuilder();
  923. bool bNotesStart = false;
  924. for (int i = startIndex; i <= endIndex; i++)
  925. {
  926. string line = lines[i];
  927. if (bNotesStart)
  928. line = " " + line;
  929. message.AppendLine(line);
  930. if (lines[i] == "Notes:")
  931. bNotesStart = true;
  932. }
  933. return message.ToString();
  934. }
  935. public GitRevision GetRevision(string commit, bool shortFormat = false)
  936. {
  937. const string formatString =
  938. /* Hash */ "%H%n" +
  939. /* Tree */ "%T%n" +
  940. /* Parents */ "%P%n" +
  941. /* Author Name */ "%aN%n" +
  942. /* Author EMail */ "%aE%n" +
  943. /* Author Date */ "%at%n" +
  944. /* Committer Name */ "%cN%n" +
  945. /* Committer EMail*/ "%cE%n" +
  946. /* Committer Date */ "%ct%n";
  947. const string messageFormat = "%e%n%B%nNotes:%n%-N";
  948. string cmd = "log -n1 --format=format:" + formatString + (shortFormat ? "%e%n%s" : messageFormat) + " " + commit;
  949. var revInfo = RunCacheableCmd(AppSettings.GitCommand, cmd, LosslessEncoding);
  950. string[] lines = revInfo.Split('\n');
  951. var revision = new GitRevision(this, lines[0])
  952. {
  953. TreeGuid = lines[1],
  954. ParentGuids = lines[2].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries),
  955. Author = ReEncodeStringFromLossless(lines[3]),
  956. AuthorEmail = ReEncodeStringFromLossless(lines[4]),
  957. Committer = ReEncodeStringFromLossless(lines[6]),
  958. CommitterEmail = ReEncodeStringFromLossless(lines[7])
  959. };
  960. revision.AuthorDate = DateTimeUtils.ParseUnixTime(lines[5]);
  961. revision.CommitDate = DateTimeUtils.ParseUnixTime(lines[8]);
  962. revision.MessageEncoding = lines[9];
  963. if (shortFormat)
  964. {
  965. revision.Subject = ReEncodeCommitMessage(lines[10], revision.MessageEncoding);
  966. }
  967. else
  968. {
  969. string message = ProccessDiffNotes(10, lines);
  970. //commit message is not reencoded by git when format is given
  971. revision.Body = ReEncodeCommitMessage(message, revision.MessageEncoding);
  972. revision.Subject = revision.Body.Substring(0, revision.Body.IndexOfAny(new[] { '\r', '\n' }));
  973. }
  974. return revision;
  975. }
  976. public string[] GetParents(string commit)
  977. {
  978. string output = RunGitCmd("log -n 1 --format=format:%P \"" + commit + "\"");
  979. return output.Split(' ');
  980. }
  981. public GitRevision[] GetParentsRevisions(string commit)
  982. {
  983. string[] parents = GetParents(commit);
  984. var parentsRevisions = new GitRevision[parents.Length];
  985. for (int i = 0; i < parents.Length; i++)
  986. parentsRevisions[i] = GetRevision(parents[i], true);
  987. return parentsRevisions;
  988. }
  989. public string ShowSha1(string sha1)
  990. {
  991. return ReEncodeShowString(RunCacheableCmd(AppSettings.GitCommand, "show " + sha1, LosslessEncoding));
  992. }
  993. public string DeleteTag(string tagName)
  994. {
  995. return RunGitCmd(GitCommandHelpers.DeleteTagCmd(tagName));
  996. }
  997. public string GetCurrentCheckout()
  998. {
  999. return RunGitCmd("rev-parse HEAD").TrimEnd();
  1000. }
  1001. public KeyValuePair<char, string> GetSuperprojectCurrentCheckout()
  1002. {
  1003. if (SuperprojectModule == null)
  1004. return new KeyValuePair<char, string>(' ', "");
  1005. var lines = SuperprojectModule.RunGitCmd("submodule status --cached " + _submodulePath).Split('\n');
  1006. if (lines.Length == 0)
  1007. return new KeyValuePair<char, string>(' ', "");
  1008. string submodule = lines[0];
  1009. if (submodule.Length < 43)
  1010. return new KeyValuePair<char, string>(' ', "");
  1011. var currentCommitGuid = submodule.Substring(1, 40).Trim();
  1012. return new KeyValuePair<char, string>(submodule[0], currentCommitGuid);
  1013. }
  1014. public bool ExistsMergeCommit(string startRev, string endRev)
  1015. {
  1016. if (startRev.IsNullOrEmpty() || endRev.IsNullOrEmpty())
  1017. return false;
  1018. string revisions = RunGitCmd("rev-list --parents --no-walk " + startRev + ".." + endRev);
  1019. string[] revisionsTab = revisions.Split('\n');
  1020. Func<string, bool> ex = (string parents) =>
  1021. {
  1022. string[] tab = parents.Split(' ');
  1023. return tab.Length > 2 && tab.All(parent => GitRevision.Sha1HashRegex.IsMatch(parent));
  1024. };
  1025. return revisionsTab.Any(ex);
  1026. }
  1027. public ConfigFile GetSubmoduleConfigFile()
  1028. {
  1029. return new ConfigFile(_workingDir + ".gitmodules", true);
  1030. }
  1031. public string GetCurrentSubmoduleLocalPath()
  1032. {
  1033. if (SuperprojectModule == null)
  1034. return null;
  1035. string submodulePath = WorkingDir.Substring(SuperprojectModule.WorkingDir.Length);
  1036. submodulePath = PathUtil.GetDirectoryName(submodulePath.ToPosixPath());
  1037. return submodulePath;
  1038. }
  1039. public string GetSubmoduleNameByPath(string localPath)
  1040. {
  1041. var configFile = GetSubmoduleConfigFile();
  1042. var submodule = configFile.ConfigSections.FirstOrDefault(configSection => configSection.GetPathValue("path").Trim() == localPath);
  1043. if (submodule != null)
  1044. return submodule.SubSection.Trim();
  1045. return null;
  1046. }
  1047. public string GetSubmoduleRemotePath(string name)
  1048. {
  1049. var configFile = GetSubmoduleConfigFile();
  1050. return configFile.GetPathValue(string.Format("submodule.{0}.url", name)).Trim();
  1051. }
  1052. public string GetSubmoduleFullPath(string localPath)
  1053. {
  1054. string dir = Path.Combine(_workingDir, localPath.EnsureTrailingPathSeparator());
  1055. return Path.GetFullPath(dir); // fix slashes
  1056. }
  1057. public GitModule GetSubmodule(string localPath)
  1058. {
  1059. return new GitModule(GetSubmoduleFullPath(localPath));
  1060. }
  1061. IGitModule IGitModule.GetSubmodule(string submoduleName)
  1062. {
  1063. return GetSubmodule(submoduleName);
  1064. }
  1065. private GitSubmoduleInfo GetSubmoduleInfo(string submodule)
  1066. {
  1067. var gitSubmodule =
  1068. new GitSubmoduleInfo(this)
  1069. {
  1070. Initialized = submodule[0] != '-',
  1071. UpToDate = submodule[0] != '+',
  1072. CurrentCommitGuid = submodule.Substring(1, 40).Trim()
  1073. };
  1074. var localPath = submodule.Substring(42).Trim();
  1075. if (localPath.Contains("("))
  1076. {
  1077. gitSubmodule.LocalPath = localPath.Substring(0, localPath.IndexOf("(")).TrimEnd();
  1078. gitSubmodule.Branch = localPath.Substring(localPath.IndexOf("(")).Trim(new[] { '(', ')', ' ' });
  1079. }
  1080. else
  1081. gitSubmodule.LocalPath = localPath;
  1082. return gitSubmodule;
  1083. }
  1084. public IEnumerable<IGitSubmoduleInfo> GetSubmodulesInfo()
  1085. {
  1086. var submodules = ReadGitOutputLines("submodule status");
  1087. string lastLine = null;
  1088. foreach (var submodule in submodules)
  1089. {
  1090. if (submodule.Length < 43)
  1091. continue;
  1092. if (submodule.Equals(lastLine))
  1093. continue;
  1094. lastLine = submodule;
  1095. yield return GetSubmoduleInfo(submodule);
  1096. }
  1097. }
  1098. public string FindGitSuperprojectPath(out string submoduleName, out string submodulePath)
  1099. {
  1100. submoduleName = null;
  1101. submodulePath = null;
  1102. if (!IsValidGitWorkingDir())
  1103. return null;
  1104. string superprojectPath = null;
  1105. string currentPath = Path.GetDirectoryName(_workingDir); // remove last slash
  1106. if (!string.IsNullOrEmpty(currentPath))
  1107. {
  1108. string path = Path.GetDirectoryName(currentPath);
  1109. for (int i = 0; i < 5; i++)
  1110. {
  1111. if (string.IsNullOrEmpty(path))
  1112. break;
  1113. if (File.Exists(Path.Combine(path, ".gitmodules")) &&
  1114. IsValidGitWorkingDir(path))
  1115. {
  1116. superprojectPath = path.EnsureTrailingPathSeparator();
  1117. break;
  1118. }
  1119. // Check upper directory
  1120. path = Path.GetDirectoryName(path);
  1121. }
  1122. }
  1123. if (File.Exists(_workingDir + ".git") &&
  1124. superprojectPath == null)
  1125. {
  1126. var lines = File.ReadLines(_workingDir + ".git");
  1127. foreach (string line in lines)
  1128. {
  1129. if (line.StartsWith("gitdir:"))
  1130. {
  1131. string gitpath = line.Substring(7).Trim();
  1132. int pos = gitpath.IndexOf("/.git/modules/");
  1133. if (pos != -1)
  1134. {
  1135. gitpath = gitpath.Substring(0, pos + 1).Replace('/', '\\');
  1136. gitpath = Path.GetFullPath(Path.Combine(_workingDir, gitpath));
  1137. if (File.Exists(gitpath + ".gitmodules") && IsValidGitWorkingDir(gitpath))
  1138. superprojectPath = gitpath;
  1139. }
  1140. }
  1141. }
  1142. }
  1143. if (!string.IsNullOrEmpty(superprojectPath))
  1144. {
  1145. submodulePath = currentPath.Substring(superprojectPath.Length).ToPosixPath();
  1146. var configFile = new ConfigFile(superprojectPath + ".gitmodules", true);
  1147. foreach (ConfigSection configSection in configFile.ConfigSections)
  1148. {
  1149. if (configSection.GetPathValue("path") == submodulePath.ToPosixPath())
  1150. {
  1151. submoduleName = configSection.SubSection;
  1152. return superprojectPath;
  1153. }
  1154. }
  1155. }
  1156. return null;
  1157. }
  1158. public string GetSubmoduleSummary(string submodule)
  1159. {
  1160. var arguments = string.Format("submodule summary {0}", submodule);
  1161. return RunGitCmd(arguments);
  1162. }
  1163. public string ResetSoft(string commit)
  1164. {
  1165. return ResetSoft(commit, "");
  1166. }
  1167. public string ResetMixed(string commit)
  1168. {
  1169. return ResetMixed(commit, "");
  1170. }
  1171. public string ResetHard(string commit)
  1172. {
  1173. return ResetHard(commit, "");
  1174. }
  1175. public string ResetSoft(string commit, string file)
  1176. {
  1177. var args = "reset --soft";
  1178. if (!string.IsNullOrEmpty(commit))
  1179. args += " \"" + commit + "\"";
  1180. if (!string.IsNullOrEmpty(file))
  1181. args += " -- \"" + file + "\"";
  1182. return RunGitCmd(args);
  1183. }
  1184. public string ResetMixed(string commit, string file)
  1185. {
  1186. var args = "reset --mixed";
  1187. if (!string.IsNullOrEmpty(commit))
  1188. args += " \"" + commit + "\"";
  1189. if (!string.IsNullOrEmpty(file))
  1190. args += " -- \"" + file + "\"";
  1191. return RunGitCmd(args);
  1192. }
  1193. public string ResetHard(string commit, string file)
  1194. {
  1195. var args = "reset --hard";
  1196. if (!string.IsNullOrEmpty(commit))
  1197. args += " \"" + commit + "\"";
  1198. if (!string.IsNullOrEmpty(file))
  1199. args += " -- \"" + file + "\"";
  1200. return RunGitCmd(args);
  1201. }
  1202. public string ResetFile(string file)
  1203. {
  1204. file = file.ToPosixPath();
  1205. return RunGitCmd("checkout-index --index --force -- \"" + file + "\"");
  1206. }
  1207. public string FormatPatch(string from, string to, string output, int start)
  1208. {
  1209. output = output.ToPosixPath();
  1210. var result = RunGitCmd("format-patch -M -C -B --start-number " + start + " \"" + from + "\"..\"" + to +
  1211. "\" -o \"" + output + "\"");
  1212. return result;
  1213. }
  1214. public string FormatPatch(string from, string to, string output)
  1215. {
  1216. output = output.ToPosixPath();
  1217. var result = RunGitCmd("format-patch -M -C -B \"" + from + "\"..\"" + to + "\" -o \"" + output + "\"");
  1218. return result;
  1219. }
  1220. public string Tag(string tagName, string revision, bool annotation, bool force)
  1221. {
  1222. if (annotation)
  1223. return RunGitCmd(string.Format("tag \"{0}\" -a {1} -F \"{2}\\TAGMESSAGE\" -- \"{3}\"", tagName.Trim(), (force ? "-f" : ""), GetGitDirectory(), revision));
  1224. return RunGitCmd(string.Format("tag {0} \"{1}\" \"{2}\"", (force ? "-f" : ""), tagName.Trim(), revision));
  1225. }
  1226. public string CheckoutFiles(IEnumerable<string> fileList, string revision, bool force)
  1227. {
  1228. string files = fileList.Select(s => s.Quote()).Join(" ");
  1229. if (files.IsNullOrWhiteSpace())
  1230. return string.Empty;
  1231. return RunGitCmd("checkout " + force.AsForce() + revision.Quote() + " -- " + files);
  1232. }
  1233. public string RemoveFiles(IEnumerable<string> fileList, bool force)
  1234. {
  1235. string files = fileList.Select(s => s.Quote()).Join(" ");
  1236. if (files.IsNullOrWhiteSpace())
  1237. return string.Empty;
  1238. return RunGitCmd("rm " + force.AsForce() + " -- " + files);
  1239. }
  1240. /// <summary>Tries to start Pageant for the specified remote repo (using the remote's PuTTY key file).</summary>
  1241. /// <returns>true if the remote has a PuTTY key file; otherwise, false.</returns>
  1242. public bool StartPageantForRemote(string remote)
  1243. {
  1244. var sshKeyFile = GetPuttyKeyFileForRemote(remote);
  1245. if (string.IsNullOrEmpty(sshKeyFile) || !File.Exists(sshKeyFile))
  1246. return false;
  1247. StartPageantWithKey(sshKeyFile);
  1248. return true;
  1249. }
  1250. public static void StartPageantWithKey(string sshKeyFile)
  1251. {
  1252. //ensure pageant is loaded, so we can wait for loading a key in the next command
  1253. //otherwise we'll stuck there waiting until pageant exits
  1254. var pageantProcName = Path.GetFileNameWithoutExtension(AppSettings.Pageant);
  1255. if (Process.GetProcessesByName(pageantProcName).Length == 0)
  1256. {
  1257. Process pageantProcess = RunExternalCmdDetached(AppSettings.Pageant, "", "");
  1258. pageantProcess.WaitForInputIdle();
  1259. }
  1260. GitCommandHelpers.RunCmd(AppSettings.Pageant, "\"" + sshKeyFile + "\"");
  1261. }
  1262. public string GetPuttyKeyFileForRemote(string remote)
  1263. {
  1264. if (string.IsNullOrEmpty(remote) ||
  1265. string.IsNullOrEmpty(AppSettings.Pageant) ||
  1266. !AppSettings.AutoStartPageant ||
  1267. !GitCommandHelpers.Plink())
  1268. return "";
  1269. return GetPathSetting(string.Format("remote.{0}.puttykeyfile", remote));
  1270. }
  1271. public static bool PathIsUrl(string path)
  1272. {
  1273. return path.Contains(Path.DirectorySeparatorChar) || path.Contains(AppSettings.PosixPathSeparator.ToString());
  1274. }
  1275. public string FetchCmd(string remote, string remoteBranch, string localBranch, bool? fetchTags = false, bool isUnshallow = false)
  1276. {
  1277. var progressOption = "";
  1278. if (GitCommandHelpers.VersionInUse.FetchCanAskForProgress)
  1279. progressOption = "--progress ";
  1280. if (string.IsNullOrEmpty(remote) && string.IsNullOrEmpty(remoteBranch) && string.IsNullOrEmpty(localBranch))
  1281. return "fetch " + progressOption;
  1282. return "fetch " + progressOption + GetFetchArgs(remote, remoteBranch, localBranch, fetchTags, isUnshallow);
  1283. }
  1284. public string PullCmd(string remote, string remoteBranch, string localBranch, bool rebase, bool? fetchTags = false, bool isUnshallow = false)
  1285. {
  1286. var pullArgs = "";
  1287. if (GitCommandHelpers.VersionInUse.FetchCanAskForProgress)
  1288. pullArgs = "--progress ";
  1289. if (rebase)
  1290. pullArgs = "--rebase".Combine(" ", pullArgs);
  1291. return "pull " + pullArgs + GetFetchArgs(remote, remoteBranch, localBranch, fetchTags, isUnshallow);
  1292. }
  1293. private string GetFetchArgs(string remote, string remoteBranch, string localBranch, bool? fetchTags, bool isUnshallow)
  1294. {
  1295. remote = remote.ToPosixPath();
  1296. //Remove spaces...
  1297. if (remoteBranch != null)
  1298. remoteBranch = remoteBranch.Replace(" ", "");
  1299. if (localBranch != null)
  1300. localBranch = localBranch.Replace(" ", "");
  1301. string remoteBranchArguments;
  1302. if (string.IsNullOrEmpty(remoteBranch))
  1303. remoteBranchArguments = "";
  1304. else
  1305. {
  1306. if (remoteBranch.StartsWith("+"))
  1307. remoteBranch = remoteBranch.Remove(0, 1);
  1308. remoteBranchArguments = "+" + FormatBranchName(remoteBranch);
  1309. }
  1310. string localBranchArguments;
  1311. var remoteUrl = GetPathSetting(string.Format(SettingKeyString.RemoteUrl, remote));
  1312. if (PathIsUrl(remote) && !string.IsNullOrEmpty(localBranch) && string.IsNullOrEmpty(remoteUrl))
  1313. localBranchArguments = ":" + GitCommandHelpers.GetFullBranchName(localBranch);
  1314. else if (string.IsNullOrEmpty(localBranch) || PathIsUrl(remote) || string.IsNullOrEmpty(remoteUrl))
  1315. localBranchArguments = "";
  1316. else
  1317. localBranchArguments = ":" + "refs/remotes/" + remote.Trim() + "/" + localBranch;
  1318. string arguments = fetchTags == true ? " --tags" : fetchTags == false ? " --no-tags" : "";
  1319. if (isUnshallow)
  1320. arguments += " --unshallow";
  1321. return "\"" + remote.Trim() + "\" " + remoteBranchArguments + localBranchArguments + arguments;
  1322. }
  1323. public string GetRebaseDir()
  1324. {
  1325. string gitDirectory = GetGitDirectory();
  1326. if (Directory.Exists(gitDirectory + "rebase-merge" + Path.DirectorySeparatorChar))
  1327. return gitDirectory + "rebase-merge" + Path.DirectorySeparatorChar;
  1328. if (Directory.Exists(gitDirectory + "rebase-apply" + Path.DirectorySeparatorChar))
  1329. return gitDirectory + "rebase-apply" + Path.DirectorySeparatorChar;
  1330. if (Directory.Exists(gitDirectory + "rebase" + Path.DirectorySeparatorChar))
  1331. return gitDirectory + "rebase" + Path.DirectorySeparatorChar;
  1332. return "";
  1333. }
  1334. /// <summary>Creates a 'git push' command using the specified parameters.</summary>
  1335. /// <param name="remote">Remote repository that is the destination of the push operation.</param>
  1336. /// <param name="force">If a remote ref is not an ancestor of the local ref, overwrite it.
  1337. /// <remarks>This can cause the remote repository to lose commits; use it with care.</remarks></param>
  1338. /// <param name="track">For every branch that is up to date or successfully pushed, add upstream (tracking) reference.</param>
  1339. /// <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>
  1340. /// <returns>'git push' command with the specified parameters.</returns>
  1341. public string PushAllCmd(string remote, ForcePushOptions force, bool track, int recursiveSubmodules)
  1342. {
  1343. remote = remote.ToPosixPath();
  1344. var sforce = GitCommandHelpers.GetForcePushArgument(force);
  1345. var strack = "";
  1346. if (track)
  1347. strack = "-u ";
  1348. var srecursiveSubmodules = "";
  1349. if (recursiveSubmodules == 1)
  1350. srecursiveSubmodules = "--recurse-submodules=check ";
  1351. if (recursiveSubmodules == 2)
  1352. srecursiveSubmodules = "--recurse-submodules=on-demand ";
  1353. var sprogressOption = "";
  1354. if (GitCommandHelpers.VersionInUse.PushCanAskForProgress)
  1355. sprogressOption = "--progress ";
  1356. var options = String.Concat(sforce, strack, srecursiveSubmodules, sprogressOption);
  1357. return String.Format("push {0}--all \"{1}\"", options, remote.Trim());
  1358. }
  1359. /// <summary>Creates a 'git push' command using the specified parameters.</summary>
  1360. /// <param name="remote">Remote repository that is the destination of the push operation.</param>
  1361. /// <param name="fromBranch">Name of the branch to push.</param>
  1362. /// <param name="toBranch">Name of the ref on the remote side to update with the push.</param>
  1363. /// <param name="force">If a remote ref is not an ancestor of the local ref, overwrite it.
  1364. /// <remarks>This can cause the remote repository to lose commits; use it with care.</remarks></param>
  1365. /// <param name="track">For every branch that is up to date or successfully pushed, add upstream (tracking) reference.</param>
  1366. /// <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>
  1367. /// <returns>'git push' command with the specified parameters.</returns>
  1368. public string PushCmd(string remote, string fromBranch, string toBranch,
  1369. ForcePushOptions force, bool track, int recursiveSubmodules)
  1370. {
  1371. remote = remote.ToPosixPath();
  1372. // This method is for pushing to remote branches, so fully qualify the
  1373. // remote branch name with refs/heads/.
  1374. fromBranch = FormatBranchName(fromBranch);
  1375. toBranch = GitCommandHelpers.GetFullBranchName(toBranch);
  1376. if (String.IsNullOrEmpty(fromBranch) && !String.IsNullOrEmpty(toBranch))
  1377. fromBranch = "HEAD";
  1378. if (toBranch != null) toBranch = toBranch.Replace(" ", "");
  1379. var sforce = GitCommandHelpers.GetForcePushArgument(force);
  1380. var strack = "";
  1381. if (track)
  1382. strack = "-u ";
  1383. var srecursiveSubmodules = "";
  1384. if (recursiveSubmodules == 1)
  1385. srecursiveSubmodules = "--recurse-submodules=check ";
  1386. if (recursiveSubmodules == 2)
  1387. srecursiveSubmodules = "--recurse-submodules=on-demand ";
  1388. var sprogressOption = "";
  1389. if (GitCommandHelpers.VersionInUse.PushCanAskForProgress)
  1390. sprogressOption = "--progress ";
  1391. var options = String.Concat(sforce, strack, srecursiveSubmodules, sprogressOption);
  1392. if (!String.IsNullOrEmpty(toBranch) && !String.IsNullOrEmpty(fromBranch))
  1393. return String.Format("push {0}\"{1}\" {2}:{3}", options, remote.Trim(), fromBranch, toBranch);
  1394. return String.Format("push {0}\"{1}\" {2}", options, remote.Trim(), fromBranch);
  1395. }
  1396. private ProcessStartInfo CreateGitStartInfo(string arguments)
  1397. {
  1398. return GitCommandHelpers.CreateProcessStartInfo(AppSettings.GitCommand, arguments, _workingDir, SystemEncoding);
  1399. }
  1400. public string ApplyPatch(string dir, string amCommand)
  1401. {
  1402. var startInfo = CreateGitStartInfo(amCommand);
  1403. using (var process = Process.Start(startInfo))
  1404. {
  1405. var files = Directory.GetFiles(dir);
  1406. if (files.Length == 0)
  1407. return "";
  1408. foreach (var file in files)
  1409. {
  1410. using (var fs = new FileStream(file, FileMode.Open))
  1411. {
  1412. fs.CopyTo(process.StandardInput.BaseStream);
  1413. }
  1414. }
  1415. process.StandardInput.Close();
  1416. process.WaitForExit();
  1417. return process.StandardOutput.ReadToEnd().Trim();
  1418. }
  1419. }
  1420. public string AssumeUnchangedFiles(IList<GitItemStatus> files, bool assumeUnchanged, out bool wereErrors)
  1421. {
  1422. var output = "";
  1423. string error = "";
  1424. wereErrors = false;
  1425. var startInfo = CreateGitStartInfo("update-index --" + (assumeUnchanged ? "" : "no-") + "assume-unchanged --stdin");
  1426. var processReader = new Lazy<SynchronizedProcessReader>(() => new SynchronizedProcessReader(Process.Start(startInfo)));
  1427. foreach (var file in files.Where(file => file.IsAssumeUnchanged != assumeUnchanged))
  1428. {
  1429. UpdateIndex(processReader, file.Name);
  1430. }
  1431. if (processReader.IsValueCreated)
  1432. {
  1433. processReader.Value.Process.StandardInput.Close();
  1434. processReader.Value.WaitForExit();
  1435. wereErrors = processReader.Value.Process.ExitCode != 0;
  1436. output = processReader.Value.OutputString(SystemEncoding);
  1437. error = processReader.Value.ErrorString(SystemEncoding);
  1438. }
  1439. return output.Combine(Environment.NewLine, error);
  1440. }
  1441. public string StageFiles(IList<GitItemStatus> files, out bool wereErrors)
  1442. {
  1443. var output = "";
  1444. string error = "";
  1445. wereErrors = false;
  1446. var startInfo = CreateGitStartInfo("update-index --add --stdin");
  1447. var processReader = new Lazy<SynchronizedProcessReader>(() => new SynchronizedProcessReader(Process.Start(startInfo)));
  1448. foreach (var file in files.Where(file => !file.IsDeleted))
  1449. {
  1450. UpdateIndex(processReader, file.Name);
  1451. }
  1452. if (processReader.IsValueCreated)
  1453. {
  1454. processReader.Value.Process.StandardInput.Close();
  1455. processReader.Value.WaitForExit();
  1456. wereErrors = processReader.Value.Process.ExitCode != 0;
  1457. output = processReader.Value.OutputString(SystemEncoding);
  1458. error = processReader.Value.ErrorString(SystemEncoding);
  1459. }
  1460. startInfo.Arguments = "update-index --remove --stdin";
  1461. processReader = new Lazy<SynchronizedProcessReader>(() => new SynchronizedProcessReader(Process.Start(startInfo)));
  1462. foreach (var file in files.Where(file => file.IsDeleted))
  1463. {
  1464. UpdateIndex(processReader, file.Name);
  1465. }
  1466. if (processReader.IsValueCreated)
  1467. {
  1468. processReader.Value.Process.StandardInput.Close();
  1469. processReader.Value.WaitForExit();
  1470. output = output.Combine(Environment.NewLine, processReader.Value.OutputString(SystemEncoding));
  1471. error = error.Combine(Environment.NewLine, processReader.Value.ErrorString(SystemEncoding));
  1472. wereErrors = wereErrors || processReader.Value.Process.ExitCode != 0;
  1473. }
  1474. return output.Combine(Environment.NewLine, error);
  1475. }
  1476. public string UnstageFiles(IList<GitItemStatus> files)
  1477. {
  1478. var output = "";
  1479. string error = "";
  1480. var startInfo = CreateGitStartInfo("update-index --info-only --index-info");
  1481. var processReader = new Lazy<SynchronizedProcessReader>(() => new SynchronizedProcessReader(Process.Start(startInfo)));
  1482. foreach (var file in files.Where(file => !file.IsNew))
  1483. {
  1484. processReader.Value.Process.StandardInput.WriteLine("0 0000000000000000000000000000000000000000\t\"" + file.Name.ToPosixPath() + "\"");
  1485. }
  1486. if (processReader.IsValueCreated)
  1487. {
  1488. processReader.Value.Process.StandardInput.Close();
  1489. processReader.Value.WaitForExit();
  1490. output = processReader.Value.OutputString(SystemEncoding);
  1491. error = processReader.Value.ErrorString(SystemEncoding);
  1492. }
  1493. startInfo.Arguments = "update-index --force-remove --stdin";
  1494. processReader = new Lazy<SynchronizedProcessReader>(() => new SynchronizedProcessReader(Process.Start(startInfo)));
  1495. foreach (var file in files.Where(file => file.IsNew))
  1496. {
  1497. UpdateIndex(processReader, file.Name);
  1498. }
  1499. if (processReader.IsValueCreated)
  1500. {
  1501. processReader.Value.Process.StandardInput.Close();
  1502. processReader.Value.WaitForExit();
  1503. output = output.Combine(Environment.NewLine, processReader.Value.OutputString(SystemEncoding));
  1504. error = error.Combine(Environment.NewLine, processReader.Value.ErrorString(SystemEncoding));
  1505. }
  1506. return output.Combine(Environment.NewLine, error);
  1507. }
  1508. private void UpdateIndex(Lazy<SynchronizedProcessReader> processReader, string filename)
  1509. {
  1510. //process.StandardInput.WriteLine("\"" + ToPosixPath(file.Name) + "\"");
  1511. byte[] bytearr = EncodingHelper.ConvertTo(SystemEncoding,
  1512. "\"" + filename.ToPosixPath() + "\"" + processReader.Value.Process.StandardInput.NewLine);
  1513. processReader.Value.Process.StandardInput.BaseStream.Write(bytearr, 0, bytearr.Length);
  1514. }
  1515. public bool InTheMiddleOfBisect()
  1516. {
  1517. return File.Exists(Path.Combine(GetGitDirectory(), "BISECT_START"));
  1518. }
  1519. public bool InTheMiddleOfRebase()
  1520. {
  1521. return !File.Exists(GetRebaseDir() + "applying") &&
  1522. Directory.Exists(GetRebaseDir());
  1523. }
  1524. public bool InTheMiddleOfPatch()
  1525. {
  1526. return !File.Exists(GetRebaseDir() + "rebasing") &&
  1527. Directory.Exists(GetRebaseDir());
  1528. }
  1529. public bool InTheMiddleOfAction()
  1530. {
  1531. return InTheMiddleOfConflictedMerge() || InTheMiddleOfRebase();
  1532. }
  1533. public string GetNextRebasePatch()
  1534. {
  1535. var file = GetRebaseDir() + "next";
  1536. return File.Exists(file) ? File.ReadAllText(file).Trim() : "";
  1537. }
  1538. private static string AppendQuotedString(string str1, string str2)
  1539. {
  1540. var m1 = QuotedText.Match(str1);
  1541. var m2 = QuotedText.Match(str2);
  1542. if (!m1.Success || !m2.Success)
  1543. return str1 + str2;
  1544. Debug.Assert(m1.Groups[1].Value == m2.Groups[1].Value);
  1545. return str1.Substring(0, str1.Length - 2) + m2.Groups[2].Value + "?=";
  1546. }
  1547. private static string DecodeString(string str)
  1548. {
  1549. // decode QuotedPrintable text using .NET internal decoder
  1550. Attachment attachment = Attachment.CreateAttachmentFromString("", str);
  1551. return attachment.Name;
  1552. }
  1553. private static readonly Regex HeadersMatch = new Regex(@"^(?<header_key>[-A-Za-z0-9]+)(?::[ \t]*)(?<header_value>.*)$", RegexOptions.Compiled);
  1554. private static readonly Regex QuotedText = new Regex(@"=\?([\w-]+)\?q\?(.*)\?=$", RegexOptions.Compiled);
  1555. public bool InTheMiddleOfInteractiveRebase()
  1556. {
  1557. return File.Exists(GetRebaseDir() + "git-rebase-todo");
  1558. }
  1559. public IList<PatchFile> GetInteractiveRebasePatchFiles()
  1560. {
  1561. string todoFile = GetRebaseDir() + "git-rebase-todo";
  1562. string[] todoCommits = File.Exists(todoFile) ? File.ReadAllText(todoFile).Trim().Split(new char[] { '\n', '\r' }, StringSplitOptions.RemoveEmptyEntries) : null;
  1563. IList<PatchFile> patchFiles = new List<PatchFile>();
  1564. if (todoCommits != null)
  1565. {
  1566. foreach (string todoCommit in todoCommits)
  1567. {
  1568. if (todoCommit.StartsWith("#"))
  1569. continue;
  1570. string[] parts = todoCommit.Split(' ');
  1571. if (parts.Length >= 3)
  1572. {
  1573. string error = string.Empty;
  1574. CommitData data = CommitData.GetCommitData(this, parts[1], ref error);
  1575. PatchFile nextCommitPatch = new PatchFile();
  1576. nextCommitPatch.Author = string.IsNullOrEmpty(error) ? data.Author : error;
  1577. nextCommitPatch.Subject = string.IsNullOrEmpty(error) ? data.Body : error;
  1578. nextCommitPatch.Name = parts[0];
  1579. nextCommitPatch.Date = string.IsNullOrEmpty(error) ? data.CommitDate.LocalDateTime.ToString() : error;
  1580. nextCommitPatch.IsNext = patchFiles.Count == 0;
  1581. patchFiles.Add(nextCommitPatch);
  1582. }
  1583. }
  1584. }
  1585. return patchFiles;
  1586. }
  1587. public IList<PatchFile> GetRebasePatchFiles()
  1588. {
  1589. var patchFiles = new List<PatchFile>();
  1590. var nextFile = GetNextRebasePatch();
  1591. int next;
  1592. int.TryParse(nextFile, out next);
  1593. var files = new string[0];
  1594. if (Directory.Exists(GetRebaseDir()))
  1595. files = Directory.GetFiles(GetRebaseDir());
  1596. foreach (var fullFileName in files)
  1597. {
  1598. int n;
  1599. var file = PathUtil.GetFileName(fullFileName);
  1600. if (!int.TryParse(file, out n))
  1601. continue;
  1602. var patchFile =
  1603. new PatchFile
  1604. {
  1605. Name = file,
  1606. FullName = fullFileName,
  1607. IsNext = n == next,
  1608. IsSkipped = n < next
  1609. };
  1610. if (File.Exists(GetRebaseDir() + file))
  1611. {
  1612. string key = null;
  1613. string value = "";
  1614. foreach (var line in File.ReadLines(GetRebaseDir() + file))
  1615. {
  1616. var m = HeadersMatch.Match(line);
  1617. if (key == null)
  1618. {
  1619. if (!string.IsNullOrWhiteSpace(line) && !m.Success)
  1620. continue;
  1621. }
  1622. else if (string.IsNullOrWhiteSpace(line) || m.Success)
  1623. {
  1624. value = DecodeString(value);
  1625. switch (key)
  1626. {
  1627. case "From":
  1628. if (value.IndexOf('<') > 0 && value.IndexOf('<') < value.Length)
  1629. {
  1630. var author = RFC2047Decoder.Parse(value);
  1631. patchFile.Author = author.Substring(0, author.IndexOf('<')).Trim();
  1632. }
  1633. else
  1634. patchFile.Author = value;
  1635. break;
  1636. case "Date":
  1637. if (value.IndexOf('+') > 0 && value.IndexOf('<') < value.Length)
  1638. patchFile.Date = value.Substring(0, value.IndexOf('+')).Trim();
  1639. else
  1640. patchFile.Date = value;
  1641. break;
  1642. case "Subject":
  1643. patchFile.Subject = value;
  1644. break;
  1645. }
  1646. }
  1647. if (m.Success)
  1648. {
  1649. key = m.Groups[1].Value;
  1650. value = m.Groups[2].Value;
  1651. }
  1652. else
  1653. value = AppendQuotedString(value, line.Trim());
  1654. if (string.IsNullOrEmpty(line) ||
  1655. !string.IsNullOrEmpty(patchFile.Author) &&
  1656. !string.IsNullOrEmpty(patchFile.Date) &&
  1657. !string.IsNullOrEmpty(patchFile.Subject))
  1658. break;
  1659. }
  1660. }
  1661. patchFiles.Add(patchFile);
  1662. }
  1663. return patchFiles;
  1664. }
  1665. public string CommitCmd(bool amend, bool signOff = false, string author = "", bool useExplicitCommitMessage = true, bool noVerify = false)
  1666. {
  1667. string command = "commit";
  1668. if (amend)
  1669. command += " --amend";
  1670. if (noVerify)
  1671. command += " --no-verify";
  1672. if (signOff)
  1673. command += " --signoff";
  1674. if (!string.IsNullOrEmpty(author))
  1675. command += " --author=\"" + author + "\"";
  1676. if (useExplicitCommitMessage)
  1677. {
  1678. var path = Path.Combine(GetGitDirectory(), "COMMITMESSAGE");
  1679. command += " -F \"" + path + "\"";
  1680. }
  1681. return command;
  1682. }
  1683. public string RemoveRemote(string name)
  1684. {
  1685. return RunGitCmd("remote rm \"" + name + "\"");
  1686. }
  1687. public string RenameRemote(string name, string newName)
  1688. {
  1689. return RunGitCmd("remote rename \"" + name + "\" \"" + newName + "\"");
  1690. }
  1691. public string RenameBranch(string name, string newName)
  1692. {
  1693. return RunGitCmd("branch -m \"" + name + "\" \"" + newName + "\"");
  1694. }
  1695. public string AddRemote(string name, string path)
  1696. {
  1697. var location = path.ToPosixPath();
  1698. if (string.IsNullOrEmpty(name))
  1699. return "Please enter a name.";
  1700. return
  1701. string.IsNullOrEmpty(location)
  1702. ? RunGitCmd(string.Format("remote add \"{0}\" \"\"", name))
  1703. : RunGitCmd(string.Format("remote add \"{0}\" \"{1}\"", name, location));
  1704. }
  1705. public string[] GetRemotes(bool allowEmpty = true)
  1706. {
  1707. string remotes = RunGitCmd("remote show");
  1708. return allowEmpty ? remotes.Split('\n') : remotes.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
  1709. }
  1710. public IEnumerable<string> GetSettings(string setting)
  1711. {
  1712. return LocalConfigFile.GetValues(setting);
  1713. }
  1714. public string GetSetting(string setting)
  1715. {
  1716. return LocalConfigFile.GetValue(setting);
  1717. }
  1718. public string GetPathSetting(string setting)
  1719. {
  1720. return GetSetting(setting);
  1721. }
  1722. public string GetEffectiveSetting(string setting)
  1723. {
  1724. return EffectiveConfigFile.GetValue(setting);
  1725. }
  1726. public string GetEffectivePathSetting(string setting)
  1727. {
  1728. return GetEffectiveSetting(setting);
  1729. }
  1730. public void UnsetSetting(string setting)
  1731. {
  1732. SetSetting(setting, null);
  1733. }
  1734. public void SetSetting(string setting, string value)
  1735. {
  1736. LocalConfigFile.SetValue(setting, value);
  1737. }
  1738. public void SetPathSetting(string setting, string value)
  1739. {
  1740. LocalConfigFile.SetPathValue(setting, value);
  1741. }
  1742. public IList<GitStash> GetStashes()
  1743. {
  1744. var list = RunGitCmd("stash list").Split('\n');
  1745. var stashes = new List<GitStash>();
  1746. for (int i = 0; i < list.Length; i++)
  1747. {
  1748. string stashString = list[i];
  1749. if (stashString.IndexOf(':') > 0 && !stashString.StartsWith("fatal: "))
  1750. {
  1751. stashes.Add(new GitStash(stashString, i));
  1752. }
  1753. }
  1754. return stashes;
  1755. }
  1756. public Patch GetSingleDiff(string @from, string to, string fileName, string oldFileName, string extraDiffArguments, Encoding encoding, bool cacheResult)
  1757. {
  1758. if (!string.IsNullOrEmpty(fileName))
  1759. {
  1760. fileName = fileName.ToPosixPath();
  1761. }
  1762. if (!string.IsNullOrEmpty(oldFileName))
  1763. {
  1764. oldFileName = oldFileName.ToPosixPath();
  1765. }
  1766. //fix refs slashes
  1767. from = from.ToPosixPath();
  1768. to = to.ToPosixPath();
  1769. string commitRange = string.Empty;
  1770. if (!to.IsNullOrEmpty())
  1771. commitRange = "\"" + to + "\"";
  1772. if (!from.IsNullOrEmpty())
  1773. commitRange = string.Join(" ", commitRange, "\"" + from + "\"");
  1774. if (AppSettings.UsePatienceDiffAlgorithm)
  1775. extraDiffArguments = string.Concat(extraDiffArguments, " --patience");
  1776. var patchManager = new PatchManager();
  1777. var arguments = String.Format("diff {0} -M -C {1} -- {2} {3}", extraDiffArguments, commitRange,
  1778. fileName.Quote(), oldFileName.Quote());
  1779. string patch;
  1780. if (cacheResult)
  1781. patch = RunCacheableCmd(AppSettings.GitCommand, arguments, LosslessEncoding);
  1782. else
  1783. patch = RunCmd(AppSettings.GitCommand, arguments, LosslessEncoding);
  1784. patchManager.LoadPatch(patch, false, encoding);
  1785. return GetPatch(patchManager, fileName, oldFileName);
  1786. }
  1787. private Patch GetPatch(PatchApply.PatchManager patchManager, string fileName, string oldFileName)
  1788. {
  1789. foreach (Patch p in patchManager.Patches)
  1790. if (fileName == p.FileNameB &&
  1791. (fileName == p.FileNameA || oldFileName == p.FileNameA))
  1792. return p;
  1793. return patchManager.Patches.Count > 0 ? patchManager.Patches[patchManager.Patches.Count - 1] : null;
  1794. }
  1795. public string GetStatusText(bool untracked)
  1796. {
  1797. string cmd = "status -s";
  1798. if (untracked)
  1799. cmd = cmd + " -u";
  1800. return RunGitCmd(cmd);
  1801. }
  1802. public string GetDiffFilesText(string from, string to)
  1803. {
  1804. return GetDiffFilesText(from, to, false);
  1805. }
  1806. public string GetDiffFilesText(string from, string to, bool noCache)
  1807. {
  1808. string cmd = "diff -M -C --name-status \"" + to + "\" \"" + from + "\"";
  1809. return noCache ? RunGitCmd(cmd) : this.RunCacheableCmd(AppSettings.GitCommand, cmd, SystemEncoding);
  1810. }
  1811. public List<GitItemStatus> GetDiffFilesWithSubmodulesStatus(string from, string to)
  1812. {
  1813. var status = GetDiffFiles(from, to);
  1814. GetSubmoduleStatus(status, from, to);
  1815. return status;
  1816. }
  1817. public List<GitItemStatus> GetDiffFiles(string from, string to, bool noCache = false)
  1818. {
  1819. string cmd = "diff -M -C -z --name-status \"" + to + "\" \"" + from + "\"";
  1820. string result = noCache ? RunGitCmd(cmd) : this.RunCacheableCmd(AppSettings.GitCommand, cmd, SystemEncoding);
  1821. return GitCommandHelpers.GetAllChangedFilesFromString(this, result, true);
  1822. }
  1823. public IList<GitItemStatus> GetStashDiffFiles(string stashName)
  1824. {
  1825. var resultCollection = GetDiffFiles(stashName, stashName + "^", true);
  1826. // shows untracked files
  1827. string untrackedTreeHash = RunGitCmd("log " + stashName + "^3 --pretty=format:\"%T\" --max-count=1");
  1828. if (GitRevision.Sha1HashRegex.IsMatch(untrackedTreeHash))
  1829. {
  1830. var files = GetTreeFiles(untrackedTreeHash, true);
  1831. resultCollection.AddRange(files);
  1832. }
  1833. return resultCollection;
  1834. }
  1835. public IList<GitItemStatus> GetTreeFiles(string treeGuid, bool full)
  1836. {
  1837. var tree = GetTree(treeGuid, full);
  1838. var list = tree
  1839. .Select(file => new GitItemStatus
  1840. {
  1841. IsNew = true,
  1842. IsChanged = false,
  1843. IsDeleted = false,
  1844. IsStaged = false,
  1845. Name = file.Name,
  1846. TreeGuid = file.Guid
  1847. }).ToList();
  1848. // Doesn't work with removed submodules
  1849. var submodulesList = GetSubmodulesLocalPaths();
  1850. foreach (var item in list)
  1851. {
  1852. if (submodulesList.Contains(item.Name))
  1853. item.IsSubmodule = true;
  1854. }
  1855. return list;
  1856. }
  1857. public IList<GitItemStatus> GetAllChangedFiles(bool excludeIgnoredFiles = true, bool excludeAssumeUnchangedFiles = true, UntrackedFilesMode untrackedFiles = UntrackedFilesMode.Default)
  1858. {
  1859. var status = RunGitCmd(GitCommandHelpers.GetAllChangedFilesCmd(excludeIgnoredFiles, untrackedFiles));
  1860. List<GitItemStatus> result = GitCommandHelpers.GetAllChangedFilesFromString(this, status);
  1861. if (!excludeAssumeUnchangedFiles)
  1862. {
  1863. string lsOutput = RunGitCmd("ls-files -v");
  1864. result.AddRange(GitCommandHelpers.GetAssumeUnchangedFilesFromString(this, lsOutput));
  1865. }
  1866. return result;
  1867. }
  1868. public IList<GitItemStatus> GetAllChangedFilesWithSubmodulesStatus(bool excludeIgnoredFiles = true, bool excludeAssumeUnchangedFiles = true, UntrackedFilesMode untrackedFiles = UntrackedFilesMode.Default)
  1869. {
  1870. var status = GetAllChangedFiles(excludeIgnoredFiles, excludeAssumeUnchangedFiles, untrackedFiles);
  1871. GetCurrentSubmoduleStatus(status);
  1872. return status;
  1873. }
  1874. private void GetCurrentSubmoduleStatus(IList<GitItemStatus> status)
  1875. {
  1876. foreach (var item in status)
  1877. if (item.IsSubmodule)
  1878. {
  1879. var localItem = item;
  1880. localItem.SubmoduleStatus = Task.Factory.StartNew(() =>
  1881. {
  1882. var submoduleStatus = GitCommandHelpers.GetCurrentSubmoduleChanges(this, localItem.Name, localItem.OldName, localItem.IsStaged);
  1883. if (submoduleStatus != null && submoduleStatus.Commit != submoduleStatus.OldCommit)
  1884. {
  1885. var submodule = submoduleStatus.GetSubmodule(this);
  1886. submoduleStatus.CheckSubmoduleStatus(submodule);
  1887. }
  1888. return submoduleStatus;
  1889. });
  1890. }
  1891. }
  1892. private void GetSubmoduleStatus(IList<GitItemStatus> status, string from, string to)
  1893. {
  1894. status.ForEach(item =>
  1895. {
  1896. if (item.IsSubmodule)
  1897. {
  1898. item.SubmoduleStatus = Task.Factory.StartNew(() =>
  1899. {
  1900. Patch patch = GetSingleDiff(from, to, item.Name, item.OldName, "", SystemEncoding, true);
  1901. string text = patch != null ? patch.Text : "";
  1902. var submoduleStatus = GitCommandHelpers.GetSubmoduleStatus(text, this, item.Name);
  1903. if (submoduleStatus.Commit != submoduleStatus.OldCommit)
  1904. {
  1905. var submodule = submoduleStatus.GetSubmodule(this);
  1906. submoduleStatus.CheckSubmoduleStatus(submodule);
  1907. }
  1908. return submoduleStatus;
  1909. });
  1910. }
  1911. });
  1912. }
  1913. public IList<GitItemStatus> GetStagedFiles()
  1914. {
  1915. string status = RunGitCmd("diff -M -C -z --cached --name-status", SystemEncoding);
  1916. if (status.Length < 50 && status.Contains("fatal: No HEAD commit to compare"))
  1917. {
  1918. //This command is a little more expensive because it will return both staged and unstaged files
  1919. string command = GitCommandHelpers.GetAllChangedFilesCmd(true, UntrackedFilesMode.No);
  1920. status = RunGitCmd(command, SystemEncoding);
  1921. IList<GitItemStatus> stagedFiles = GitCommandHelpers.GetAllChangedFilesFromString(this, status, false);
  1922. return stagedFiles.Where(f => f.IsStaged).ToList();
  1923. }
  1924. return GitCommandHelpers.GetAllChangedFilesFromString(this, status, true);
  1925. }
  1926. public IList<GitItemStatus> GetStagedFilesWithSubmodulesStatus()
  1927. {
  1928. var status = GetStagedFiles();
  1929. GetCurrentSubmoduleStatus(status);
  1930. return status;
  1931. }
  1932. public IList<GitItemStatus> GetUnstagedFiles()
  1933. {
  1934. return GetAllChangedFiles().Where(x => !x.IsStaged).ToArray();
  1935. }
  1936. public IList<GitItemStatus> GetUnstagedFilesWithSubmodulesStatus()
  1937. {
  1938. return GetAllChangedFilesWithSubmodulesStatus().Where(x => !x.IsStaged).ToArray();
  1939. }
  1940. public IList<GitItemStatus> GitStatus(UntrackedFilesMode untrackedFilesMode, IgnoreSubmodulesMode ignoreSubmodulesMode = 0)
  1941. {
  1942. string command = GitCommandHelpers.GetAllChangedFilesCmd(true, untrackedFilesMode, ignoreSubmodulesMode);
  1943. string status = RunGitCmd(command);
  1944. return GitCommandHelpers.GetAllChangedFilesFromString(this, status);
  1945. }
  1946. /// <summary>Indicates whether there are any changes to the repository,
  1947. /// including any untracked files or directories; excluding submodules.</summary>
  1948. public bool IsDirtyDir()
  1949. {
  1950. return GitStatus(UntrackedFilesMode.All, IgnoreSubmodulesMode.Default).Count > 0;
  1951. }
  1952. public Patch GetCurrentChanges(string fileName, string oldFileName, bool staged, string extraDiffArguments, Encoding encoding)
  1953. {
  1954. fileName = fileName.ToPosixPath();
  1955. if (!string.IsNullOrEmpty(oldFileName))
  1956. oldFileName = oldFileName.ToPosixPath();
  1957. if (AppSettings.UsePatienceDiffAlgorithm)
  1958. extraDiffArguments = string.Concat(extraDiffArguments, " --patience");
  1959. var args = string.Concat("diff ", extraDiffArguments, " -- ", fileName.Quote());
  1960. if (staged)
  1961. args = string.Concat("diff -M -C --cached ", extraDiffArguments, " -- ", fileName.Quote(), " ", oldFileName.Quote());
  1962. String result = RunGitCmd(args, LosslessEncoding);
  1963. var patchManager = new PatchManager();
  1964. patchManager.LoadPatch(result, false, encoding);
  1965. return GetPatch(patchManager, fileName, oldFileName);
  1966. }
  1967. private string GetFileContents(string path)
  1968. {
  1969. var contents = RunGitCmdResult(string.Format("show HEAD:\"{0}\"", path.ToPosixPath()));
  1970. if (contents.ExitCode == 0)
  1971. return contents.StdOutput;
  1972. return null;
  1973. }
  1974. public string GetFileContents(GitItemStatus file)
  1975. {
  1976. var contents = new StringBuilder();
  1977. string currentContents = GetFileContents(file.Name);
  1978. if (currentContents != null)
  1979. contents.Append(currentContents);
  1980. if (file.OldName != null)
  1981. {
  1982. string oldContents = GetFileContents(file.OldName);
  1983. if (oldContents != null)
  1984. contents.Append(oldContents);
  1985. }
  1986. return contents.Length > 0 ? contents.ToString() : null;
  1987. }
  1988. public string StageFile(string file)
  1989. {
  1990. return RunGitCmd("update-index --add" + " \"" + file.ToPosixPath() + "\"");
  1991. }
  1992. public string StageFileToRemove(string file)
  1993. {
  1994. return RunGitCmd("update-index --remove" + " \"" + file.ToPosixPath() + "\"");
  1995. }
  1996. public string UnstageFile(string file)
  1997. {
  1998. return RunGitCmd("rm --cached \"" + file.ToPosixPath() + "\"");
  1999. }
  2000. public string UnstageFileToRemove(string file)
  2001. {
  2002. return RunGitCmd("reset HEAD -- \"" + file.ToPosixPath() + "\"");
  2003. }
  2004. /// <summary>Dirty but fast. This sometimes fails.</summary>
  2005. public static string GetSelectedBranchFast(string repositoryPath)
  2006. {
  2007. if (string.IsNullOrEmpty(repositoryPath))
  2008. return string.Empty;
  2009. string head;
  2010. string headFileName = Path.Combine(GetGitDirectory(repositoryPath), "HEAD");
  2011. if (File.Exists(headFileName))
  2012. {
  2013. head = File.ReadAllText(headFileName, SystemEncoding);
  2014. if (!head.Contains("ref:"))
  2015. return DetachedBranch;
  2016. }
  2017. else
  2018. {
  2019. return string.Empty;
  2020. }
  2021. if (!string.IsNullOrEmpty(head))
  2022. {
  2023. return head.Replace("ref:", "").Replace("refs/heads/", string.Empty).Trim();
  2024. }
  2025. return string.Empty;
  2026. }
  2027. /// <summary>Gets the current branch; or "(no branch)" if HEAD is detached.</summary>
  2028. public string GetSelectedBranch(string repositoryPath)
  2029. {
  2030. string head = GetSelectedBranchFast(repositoryPath);
  2031. if (string.IsNullOrEmpty(head))
  2032. {
  2033. var result = RunGitCmdResult("symbolic-ref HEAD");
  2034. if (result.ExitCode == 1)
  2035. return DetachedBranch;
  2036. return result.StdOutput;
  2037. }
  2038. return head;
  2039. }
  2040. /// <summary>Gets the current branch; or "(no branch)" if HEAD is detached.</summary>
  2041. public string GetSelectedBranch()
  2042. {
  2043. return GetSelectedBranch(_workingDir);
  2044. }
  2045. /// <summary>Indicates whether HEAD is not pointing to a branch.</summary>
  2046. public bool IsDetachedHead()
  2047. {
  2048. return IsDetachedHead(GetSelectedBranch());
  2049. }
  2050. public static bool IsDetachedHead(string branch)
  2051. {
  2052. return DetachedPrefixes.Any(a => branch.StartsWith(a, StringComparison.Ordinal));
  2053. }
  2054. /// <summary>Gets the remote of the current branch; or "origin" if no remote is configured.</summary>
  2055. public string GetCurrentRemote()
  2056. {
  2057. string remote = GetSetting(string.Format(SettingKeyString.BranchRemote, GetSelectedBranch()));
  2058. return remote;
  2059. }
  2060. /// <summary>Gets the remote branch of the specified local branch; or "" if none is configured.</summary>
  2061. public string GetRemoteBranch(string branch)
  2062. {
  2063. string remote = GetSetting(string.Format(SettingKeyString.BranchRemote, branch));
  2064. string merge = GetSetting(string.Format("branch.{0}.merge", branch));
  2065. if (String.IsNullOrEmpty(remote) || String.IsNullOrEmpty(merge))
  2066. return "";
  2067. return remote + "/" + (merge.StartsWith("refs/heads/") ? merge.Substring(11) : merge);
  2068. }
  2069. public IEnumerable<IGitRef> GetRemoteBranches()
  2070. {
  2071. return GetRefs().Where(r => r.IsRemote);
  2072. }
  2073. public RemoteActionResult<IList<IGitRef>> GetRemoteServerRefs(string remote, bool tags, bool branches)
  2074. {
  2075. var result = new RemoteActionResult<IList<IGitRef>>()
  2076. {
  2077. AuthenticationFail = false,
  2078. HostKeyFail = false,
  2079. Result = null
  2080. };
  2081. remote = remote.ToPosixPath();
  2082. result.CmdResult = GetTreeFromRemoteRefs(remote, tags, branches);
  2083. var tree = result.CmdResult.StdOutput;
  2084. // If the authentication failed because of a missing key, ask the user to supply one.
  2085. if (tree.Contains("FATAL ERROR") && tree.Contains("authentication"))
  2086. {
  2087. result.AuthenticationFail = true;
  2088. }
  2089. else if (tree.ToLower().Contains("the server's host key is not cached in the registry"))
  2090. {
  2091. result.HostKeyFail = true;
  2092. }
  2093. else if (result.CmdResult.ExitedSuccessfully)
  2094. {
  2095. result.Result = GetTreeRefs(tree);
  2096. }
  2097. return result;
  2098. }
  2099. private CmdResult GetTreeFromRemoteRefsEx(string remote, bool tags, bool branches)
  2100. {
  2101. if (tags && branches)
  2102. return RunGitCmdResult("ls-remote --heads --tags \"" + remote + "\"");
  2103. if (tags)
  2104. return RunGitCmdResult("ls-remote --tags \"" + remote + "\"");
  2105. if (branches)
  2106. return RunGitCmdResult("ls-remote --heads \"" + remote + "\"");
  2107. return new CmdResult();
  2108. }
  2109. private CmdResult GetTreeFromRemoteRefs(string remote, bool tags, bool branches)
  2110. {
  2111. return GetTreeFromRemoteRefsEx(remote, tags, branches);
  2112. }
  2113. public IList<IGitRef> GetRefs(bool tags = true, bool branches = true)
  2114. {
  2115. var tree = GetTree(tags, branches);
  2116. return GetTreeRefs(tree);
  2117. }
  2118. /// <summary>
  2119. ///
  2120. /// </summary>
  2121. /// <param name="option">Ordery by date is slower.</param>
  2122. /// <returns></returns>
  2123. public IList<IGitRef> GetTagRefs(GetTagRefsSortOrder option)
  2124. {
  2125. var list = GetRefs(true, false);
  2126. List<IGitRef> sortedList;
  2127. if (option == GetTagRefsSortOrder.ByCommitDateAscending)
  2128. {
  2129. sortedList = list.OrderBy(head =>
  2130. {
  2131. var r = new GitRevision(this, head.Guid);
  2132. return r.CommitDate;
  2133. }).ToList();
  2134. }
  2135. else if (option == GetTagRefsSortOrder.ByCommitDateDescending)
  2136. {
  2137. sortedList = list.OrderByDescending(head =>
  2138. {
  2139. var r = new GitRevision(this, head.Guid);
  2140. return r.CommitDate;
  2141. }).ToList();
  2142. }
  2143. else
  2144. sortedList = new List<IGitRef>(list);
  2145. return sortedList;
  2146. }
  2147. public enum GetTagRefsSortOrder
  2148. {
  2149. /// <summary>
  2150. /// default
  2151. /// </summary>
  2152. ByName,
  2153. /// <summary>
  2154. /// slower than ByName
  2155. /// </summary>
  2156. ByCommitDateAscending,
  2157. /// <summary>
  2158. /// slower than ByName
  2159. /// </summary>
  2160. ByCommitDateDescending
  2161. }
  2162. public ICollection<string> GetMergedBranches()
  2163. {
  2164. return RunGitCmd(GitCommandHelpers.MergedBranches()).Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
  2165. }
  2166. private string GetTree(bool tags, bool branches)
  2167. {
  2168. if (tags && branches)
  2169. return RunGitCmd("show-ref --dereference", SystemEncoding);
  2170. if (tags)
  2171. return RunGitCmd("show-ref --tags", SystemEncoding);
  2172. if (branches)
  2173. return RunGitCmd("show-ref --dereference --heads", SystemEncoding);
  2174. return "";
  2175. }
  2176. public IList<IGitRef> GetTreeRefs(string tree)
  2177. {
  2178. var itemsStrings = tree.Split('\n');
  2179. var gitRefs = new List<IGitRef>();
  2180. var defaultHeads = new Dictionary<string, GitRef>(); // remote -> HEAD
  2181. var remotes = GetRemotes(false);
  2182. foreach (var itemsString in itemsStrings)
  2183. {
  2184. if (itemsString == null || itemsString.Length <= 42 || itemsString.StartsWith("error: "))
  2185. continue;
  2186. var completeName = itemsString.Substring(41).Trim();
  2187. var guid = itemsString.Substring(0, 40);
  2188. var remoteName = GitCommandHelpers.GetRemoteName(completeName, remotes);
  2189. var head = new GitRef(this, guid, completeName, remoteName);
  2190. if (DefaultHeadPattern.IsMatch(completeName))
  2191. defaultHeads[remoteName] = head;
  2192. else
  2193. gitRefs.Add(head);
  2194. }
  2195. // do not show default head if remote has a branch on the same commit
  2196. GitRef defaultHead;
  2197. foreach (var gitRef in gitRefs.Where(head => defaultHeads.TryGetValue(head.Remote, out defaultHead) && head.Guid == defaultHead.Guid))
  2198. {
  2199. defaultHeads.Remove(gitRef.Remote);
  2200. }
  2201. gitRefs.AddRange(defaultHeads.Values);
  2202. return gitRefs;
  2203. }
  2204. /// <summary>
  2205. /// Gets branches which contain the given commit.
  2206. /// If both local and remote branches are requested, remote branches are prefixed with "remotes/"
  2207. /// (as returned by git branch -a)
  2208. /// </summary>
  2209. /// <param name="sha1">The sha1.</param>
  2210. /// <param name="getLocal">Pass true to include local branches</param>
  2211. /// <param name="getRemote">Pass true to include remote branches</param>
  2212. /// <returns></returns>
  2213. public IEnumerable<string> GetAllBranchesWhichContainGivenCommit(string sha1, bool getLocal, bool getRemote)
  2214. {
  2215. string args = "--contains " + sha1;
  2216. if (getRemote && getLocal)
  2217. args = "-a " + args;
  2218. else if (getRemote)
  2219. args = "-r " + args;
  2220. else if (!getLocal)
  2221. return new string[] { };
  2222. string info = RunGitCmd("branch " + args);
  2223. if (info.Trim().StartsWith("fatal") || info.Trim().StartsWith("error:"))
  2224. return new List<string>();
  2225. string[] result = info.Split(new[] { '\r', '\n', '*' }, StringSplitOptions.RemoveEmptyEntries);
  2226. // Remove symlink targets as in "origin/HEAD -> origin/master"
  2227. for (int i = 0; i < result.Length; i++)
  2228. {
  2229. string item = result[i].Trim();
  2230. int idx;
  2231. if (getRemote && ((idx = item.IndexOf(" ->")) >= 0))
  2232. {
  2233. item = item.Substring(0, idx);
  2234. }
  2235. result[i] = item;
  2236. }
  2237. return result;
  2238. }
  2239. /// <summary>
  2240. /// Gets all tags which contain the given commit.
  2241. /// </summary>
  2242. /// <param name="sha1">The sha1.</param>
  2243. /// <returns></returns>
  2244. public IEnumerable<string> GetAllTagsWhichContainGivenCommit(string sha1)
  2245. {
  2246. string info = RunGitCmd("tag --contains " + sha1, SystemEncoding);
  2247. if (info.Trim().StartsWith("fatal") || info.Trim().StartsWith("error:"))
  2248. return new List<string>();
  2249. return info.Split(new[] { '\r', '\n', '*', ' ' }, StringSplitOptions.RemoveEmptyEntries);
  2250. }
  2251. /// <summary>
  2252. /// Returns tag's message. If the lightweight tag is passed, corresponding commit message
  2253. /// is returned.
  2254. /// </summary>
  2255. public string GetTagMessage(string tag)
  2256. {
  2257. if (string.IsNullOrWhiteSpace(tag))
  2258. return null;
  2259. tag = tag.Trim();
  2260. string info = RunGitCmd("tag -l -n10 " + tag, SystemEncoding);
  2261. if (info.Trim().StartsWith("fatal") || info.Trim().StartsWith("error:"))
  2262. return null;
  2263. if (!info.StartsWith(tag))
  2264. return null;
  2265. info = info.Substring(tag.Length).Trim();
  2266. if (info.Length == 0)
  2267. return null;
  2268. return info;
  2269. }
  2270. /// <summary>
  2271. /// Returns list of filenames which would be ignored
  2272. /// </summary>
  2273. /// <param name="ignorePatterns">Patterns to ignore (.gitignore syntax)</param>
  2274. /// <returns></returns>
  2275. public IList<string> GetIgnoredFiles(IEnumerable<string> ignorePatterns)
  2276. {
  2277. var notEmptyPatterns = ignorePatterns
  2278. .Where(pattern => !pattern.IsNullOrWhiteSpace());
  2279. if (notEmptyPatterns.Count() != 0)
  2280. {
  2281. var excludeParams =
  2282. notEmptyPatterns
  2283. .Select(pattern => "-x " + pattern.Quote())
  2284. .Join(" ");
  2285. // filter duplicates out of the result because options -c and -m may return
  2286. // same files at times
  2287. return RunGitCmd("ls-files -z -o -m -c -i " + excludeParams)
  2288. .Split(new[] { '\0', '\n' }, StringSplitOptions.RemoveEmptyEntries)
  2289. .Distinct()
  2290. .ToList();
  2291. }
  2292. else
  2293. {
  2294. return new string[] { };
  2295. }
  2296. }
  2297. public string[] GetFullTree(string id)
  2298. {
  2299. string tree = this.RunCacheableCmd(AppSettings.GitCommand, String.Format("ls-tree -z -r --name-only {0}", id), SystemEncoding);
  2300. return tree.Split(new char[] { '\0', '\n' });
  2301. }
  2302. public IList<IGitItem> GetTree(string id, bool full)
  2303. {
  2304. string args = "-z";
  2305. if (full)
  2306. args += " -r";
  2307. var tree = this.RunCacheableCmd(AppSettings.GitCommand, "ls-tree " + args + " \"" + id + "\"", SystemEncoding);
  2308. return GitItem.CreateIGitItemsFromString(this, tree);
  2309. }
  2310. public GitBlame Blame(string filename, string from, Encoding encoding)
  2311. {
  2312. return Blame(filename, from, null, encoding);
  2313. }
  2314. public GitBlame Blame(string filename, string from, string lines, Encoding encoding)
  2315. {
  2316. from = from.ToPosixPath();
  2317. filename = filename.ToPosixPath();
  2318. string blameCommand = string.Format("blame --porcelain -M -w -l{0} \"{1}\" -- \"{2}\"", lines != null ? " -L " + lines : "", from, filename);
  2319. var itemsStrings =
  2320. RunCacheableCmd(
  2321. AppSettings.GitCommand,
  2322. blameCommand,
  2323. LosslessEncoding
  2324. )
  2325. .Split('\n');
  2326. GitBlame blame = new GitBlame();
  2327. GitBlameHeader blameHeader = null;
  2328. GitBlameLine blameLine = null;
  2329. for (int i = 0; i < itemsStrings.GetLength(0); i++)
  2330. {
  2331. try
  2332. {
  2333. string line = itemsStrings[i];
  2334. //The contents of the actual line is output after the above header, prefixed by a TAB. This is to allow adding more header elements later.
  2335. if (line.StartsWith("\t"))
  2336. {
  2337. blameLine.LineText = line.Substring(1) //trim ONLY first tab
  2338. .Trim(new char[] { '\r' }); //trim \r, this is a workaround for a \r\n bug
  2339. blameLine.LineText = ReEncodeStringFromLossless(blameLine.LineText, encoding);
  2340. }
  2341. else if (line.StartsWith("author-mail"))
  2342. blameHeader.AuthorMail = ReEncodeStringFromLossless(line.Substring("author-mail".Length).Trim());
  2343. else if (line.StartsWith("author-time"))
  2344. blameHeader.AuthorTime = DateTimeUtils.ParseUnixTime(line.Substring("author-time".Length).Trim());
  2345. else if (line.StartsWith("author-tz"))
  2346. blameHeader.AuthorTimeZone = line.Substring("author-tz".Length).Trim();
  2347. else if (line.StartsWith("author"))
  2348. {
  2349. blameHeader = new GitBlameHeader();
  2350. blameHeader.CommitGuid = blameLine.CommitGuid;
  2351. blameHeader.Author = ReEncodeStringFromLossless(line.Substring("author".Length).Trim());
  2352. blame.Headers.Add(blameHeader);
  2353. }
  2354. else if (line.StartsWith("committer-mail"))
  2355. blameHeader.CommitterMail = line.Substring("committer-mail".Length).Trim();
  2356. else if (line.StartsWith("committer-time"))
  2357. blameHeader.CommitterTime = DateTimeUtils.ParseUnixTime(line.Substring("committer-time".Length).Trim());
  2358. else if (line.StartsWith("committer-tz"))
  2359. blameHeader.CommitterTimeZone = line.Substring("committer-tz".Length).Trim();
  2360. else if (line.StartsWith("committer"))
  2361. blameHeader.Committer = ReEncodeStringFromLossless(line.Substring("committer".Length).Trim());
  2362. else if (line.StartsWith("summary"))
  2363. blameHeader.Summary = ReEncodeStringFromLossless(line.Substring("summary".Length).Trim());
  2364. else if (line.StartsWith("filename"))
  2365. blameHeader.FileName = ReEncodeFileNameFromLossless(line.Substring("filename".Length).Trim());
  2366. else if (line.IndexOf(' ') == 40) //SHA1, create new line!
  2367. {
  2368. blameLine = new GitBlameLine();
  2369. var headerParams = line.Split(' ');
  2370. blameLine.CommitGuid = headerParams[0];
  2371. if (headerParams.Length >= 3)
  2372. {
  2373. blameLine.OriginLineNumber = int.Parse(headerParams[1]);
  2374. blameLine.FinalLineNumber = int.Parse(headerParams[2]);
  2375. }
  2376. blame.Lines.Add(blameLine);
  2377. }
  2378. }
  2379. catch
  2380. {
  2381. //Catch all parser errors, and ignore them all!
  2382. //We should never get here...
  2383. AppSettings.GitLog.Log("Error parsing output from command: " + blameCommand + "\n\nPlease report a bug!", DateTime.Now, DateTime.Now);
  2384. }
  2385. }
  2386. return blame;
  2387. }
  2388. public string GetFileText(string id, Encoding encoding)
  2389. {
  2390. return RunCacheableCmd(AppSettings.GitCommand, "cat-file blob \"" + id + "\"", encoding);
  2391. }
  2392. public string GetFileBlobHash(string fileName, string revision)
  2393. {
  2394. if (revision == GitRevision.UnstagedGuid) //working directory changes
  2395. {
  2396. return null;
  2397. }
  2398. if (revision == GitRevision.IndexGuid) //index
  2399. {
  2400. string blob = RunGitCmd(string.Format("ls-files -s \"{0}\"", fileName));
  2401. string[] s = blob.Split(new char[] { ' ', '\t' });
  2402. if (s.Length >= 2)
  2403. return s[1];
  2404. }
  2405. else
  2406. {
  2407. string blob = RunGitCmd(string.Format("ls-tree -r {0} \"{1}\"", revision, fileName));
  2408. string[] s = blob.Split(new char[] { ' ', '\t' });
  2409. if (s.Length >= 3)
  2410. return s[2];
  2411. }
  2412. return string.Empty;
  2413. }
  2414. public static void StreamCopy(Stream input, Stream output)
  2415. {
  2416. int read;
  2417. var buffer = new byte[2048];
  2418. do
  2419. {
  2420. read = input.Read(buffer, 0, buffer.Length);
  2421. output.Write(buffer, 0, read);
  2422. } while (read > 0);
  2423. }
  2424. public Stream GetFileStream(string blob)
  2425. {
  2426. try
  2427. {
  2428. var newStream = new MemoryStream();
  2429. using (var process = RunGitCmdDetached("cat-file blob " + blob))
  2430. {
  2431. StreamCopy(process.StandardOutput.BaseStream, newStream);
  2432. newStream.Position = 0;
  2433. process.WaitForExit();
  2434. return newStream;
  2435. }
  2436. }
  2437. catch (Win32Exception ex)
  2438. {
  2439. Trace.WriteLine(ex);
  2440. }
  2441. return null;
  2442. }
  2443. public IEnumerable<string> GetPreviousCommitMessages(int count)
  2444. {
  2445. return GetPreviousCommitMessages("HEAD", count);
  2446. }
  2447. public IEnumerable<string> GetPreviousCommitMessages(string revision, int count)
  2448. {
  2449. string sep = "d3fb081b9000598e658da93657bf822cc87b2bf6";
  2450. string output = RunGitCmd("log -n " + count + " " + revision + " --pretty=format:" + sep + "%e%n%s%n%n%b", LosslessEncoding);
  2451. string[] messages = output.Split(new string[] { sep }, StringSplitOptions.RemoveEmptyEntries);
  2452. if (messages.Length == 0)
  2453. return new string[] { string.Empty };
  2454. return messages.Select(cm =>
  2455. {
  2456. int idx = cm.IndexOf("\n");
  2457. string encodingName = cm.Substring(0, idx);
  2458. cm = cm.Substring(idx + 1, cm.Length - idx - 1);
  2459. cm = ReEncodeCommitMessage(cm, encodingName);
  2460. return cm;
  2461. });
  2462. }
  2463. public string OpenWithDifftool(string filename, string oldFileName = "", string revision1 = null, string revision2 = null, string extraDiffArguments = "")
  2464. {
  2465. var output = "";
  2466. if (!filename.IsNullOrEmpty())
  2467. filename = filename.Quote();
  2468. if (!oldFileName.IsNullOrEmpty())
  2469. oldFileName = oldFileName.Quote();
  2470. string args = string.Join(" ", extraDiffArguments, revision2.QuoteNE(), revision1.QuoteNE(), "--", filename, oldFileName);
  2471. RunGitCmdDetached("difftool --gui --no-prompt " + args);
  2472. return output;
  2473. }
  2474. public string RevParse(string revisionExpression)
  2475. {
  2476. string revparseCommand = string.Format("rev-parse \"{0}~0\"", revisionExpression);
  2477. var result = RunGitCmdResult(revparseCommand);
  2478. return result.ExitCode == 0 ? result.StdOutput.Split('\n')[0] : "";
  2479. }
  2480. public string GetMergeBase(string a, string b)
  2481. {
  2482. return RunGitCmd("merge-base " + a + " " + b).TrimEnd();
  2483. }
  2484. public SubmoduleStatus CheckSubmoduleStatus(string commit, string oldCommit, CommitData data, CommitData olddata, bool loaddata = false)
  2485. {
  2486. if (!IsValidGitWorkingDir() || oldCommit == null)
  2487. return SubmoduleStatus.NewSubmodule;
  2488. if (commit == null || commit == oldCommit)
  2489. return SubmoduleStatus.Unknown;
  2490. string baseCommit = GetMergeBase(commit, oldCommit);
  2491. if (baseCommit == oldCommit)
  2492. return SubmoduleStatus.FastForward;
  2493. else if (baseCommit == commit)
  2494. return SubmoduleStatus.Rewind;
  2495. string error = "";
  2496. if (loaddata)
  2497. olddata = CommitData.GetCommitData(this, oldCommit, ref error);
  2498. if (olddata == null)
  2499. return SubmoduleStatus.NewSubmodule;
  2500. if (loaddata)
  2501. data = CommitData.GetCommitData(this, commit, ref error);
  2502. if (data == null)
  2503. return SubmoduleStatus.Unknown;
  2504. if (data.CommitDate > olddata.CommitDate)
  2505. return SubmoduleStatus.NewerTime;
  2506. else if (data.CommitDate < olddata.CommitDate)
  2507. return SubmoduleStatus.OlderTime;
  2508. else if (data.CommitDate == olddata.CommitDate)
  2509. return SubmoduleStatus.SameTime;
  2510. return SubmoduleStatus.Unknown;
  2511. }
  2512. public SubmoduleStatus CheckSubmoduleStatus(string commit, string oldCommit)
  2513. {
  2514. return CheckSubmoduleStatus(commit, oldCommit, null, null, true);
  2515. }
  2516. /// <summary>
  2517. /// Uses check-ref-format to ensure that a branch name is well formed.
  2518. /// </summary>
  2519. /// <param name="branchName">Branch name to test.</param>
  2520. /// <returns>true if <see cref="branchName"/> is valid reference name, otherwise false.</returns>
  2521. public bool CheckBranchFormat([NotNull] string branchName)
  2522. {
  2523. if (branchName == null)
  2524. throw new ArgumentNullException("branchName");
  2525. if (branchName.IsNullOrWhiteSpace())
  2526. return false;
  2527. branchName = branchName.Replace("\"", "\\\"");
  2528. var result = RunGitCmdResult(string.Format("check-ref-format --branch \"{0}\"", branchName));
  2529. return result.ExitCode == 0;
  2530. }
  2531. /// <summary>
  2532. /// Format branch name, check if name is valid for repository.
  2533. /// </summary>
  2534. /// <param name="branchName">Branch name to test.</param>
  2535. /// <returns>Well formed branch name.</returns>
  2536. public string FormatBranchName([NotNull] string branchName)
  2537. {
  2538. if (branchName == null)
  2539. throw new ArgumentNullException("branchName");
  2540. string fullBranchName = GitCommandHelpers.GetFullBranchName(branchName);
  2541. if (String.IsNullOrEmpty(RevParse(fullBranchName)))
  2542. fullBranchName = branchName;
  2543. return fullBranchName;
  2544. }
  2545. public bool IsLockedIndex()
  2546. {
  2547. return IsLockedIndex(_workingDir);
  2548. }
  2549. public static bool IsLockedIndex(string repositoryPath)
  2550. {
  2551. var gitDir = GetGitDirectory(repositoryPath);
  2552. var indexLockFile = Path.Combine(gitDir, "index.lock");
  2553. return File.Exists(indexLockFile);
  2554. }
  2555. public bool IsRunningGitProcess()
  2556. {
  2557. if (IsLockedIndex())
  2558. {
  2559. return true;
  2560. }
  2561. if (EnvUtils.RunningOnWindows())
  2562. {
  2563. return Process.GetProcessesByName("git").Length > 0;
  2564. }
  2565. // Get processes by "ps" command.
  2566. var cmd = Path.Combine(AppSettings.GitBinDir, "ps");
  2567. var arguments = "x";
  2568. var output = RunCmd(cmd, arguments);
  2569. var lines = output.Split('\n');
  2570. if (lines.Count() >= 2)
  2571. return false;
  2572. var headers = lines[0].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
  2573. var commandIndex = Array.IndexOf(headers, "COMMAND");
  2574. for (int i = 1; i < lines.Count(); i++)
  2575. {
  2576. var columns = lines[i].Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
  2577. if (commandIndex < columns.Count())
  2578. {
  2579. var command = columns[commandIndex];
  2580. if (command.EndsWith("/git"))
  2581. {
  2582. return true;
  2583. }
  2584. }
  2585. }
  2586. return false;
  2587. }
  2588. public static string UnquoteFileName(string fileName)
  2589. {
  2590. char[] chars = fileName.ToCharArray();
  2591. IList<byte> blist = new List<byte>();
  2592. int i = 0;
  2593. StringBuilder sb = new StringBuilder();
  2594. while (i < chars.Length)
  2595. {
  2596. char c = chars[i];
  2597. if (c == '\\')
  2598. {
  2599. //there should be 3 digits
  2600. if (chars.Length >= i + 3)
  2601. {
  2602. string octNumber = "" + chars[i + 1] + chars[i + 2] + chars[i + 3];
  2603. try
  2604. {
  2605. int code = Convert.ToInt32(octNumber, 8);
  2606. blist.Add((byte)code);
  2607. i += 4;
  2608. }
  2609. catch (Exception)
  2610. {
  2611. }
  2612. }
  2613. }
  2614. else
  2615. {
  2616. if (blist.Count > 0)
  2617. {
  2618. sb.Append(SystemEncoding.GetString(blist.ToArray()));
  2619. blist.Clear();
  2620. }
  2621. sb.Append(c);
  2622. i++;
  2623. }
  2624. }
  2625. if (blist.Count > 0)
  2626. {
  2627. sb.Append(SystemEncoding.GetString(blist.ToArray()));
  2628. blist.Clear();
  2629. }
  2630. return sb.ToString();
  2631. }
  2632. public static string ReEncodeFileNameFromLossless(string fileName)
  2633. {
  2634. fileName = ReEncodeStringFromLossless(fileName, SystemEncoding);
  2635. return UnquoteFileName(fileName);
  2636. }
  2637. public static string ReEncodeString(string s, Encoding fromEncoding, Encoding toEncoding)
  2638. {
  2639. if (s == null || fromEncoding.HeaderName.Equals(toEncoding.HeaderName))
  2640. return s;
  2641. else
  2642. {
  2643. byte[] bytes = fromEncoding.GetBytes(s);
  2644. s = toEncoding.GetString(bytes);
  2645. return s;
  2646. }
  2647. }
  2648. /// <summary>
  2649. /// reencodes string from GitCommandHelpers.LosslessEncoding to toEncoding
  2650. /// </summary>
  2651. /// <param name="s"></param>
  2652. /// <returns></returns>
  2653. public static string ReEncodeStringFromLossless(string s, Encoding toEncoding)
  2654. {
  2655. if (toEncoding == null)
  2656. return s;
  2657. return ReEncodeString(s, LosslessEncoding, toEncoding);
  2658. }
  2659. public string ReEncodeStringFromLossless(string s)
  2660. {
  2661. return ReEncodeStringFromLossless(s, LogOutputEncoding);
  2662. }
  2663. //there is a bug: git does not recode commit message when format is given
  2664. //Lossless encoding is used, because LogOutputEncoding might not be lossless and not recoded
  2665. //characters could be replaced by replacement character while reencoding to LogOutputEncoding
  2666. public string ReEncodeCommitMessage(string s, string toEncodingName)
  2667. {
  2668. bool isABug = true;
  2669. Encoding encoding;
  2670. try
  2671. {
  2672. if (isABug)
  2673. {
  2674. if (toEncodingName.IsNullOrEmpty())
  2675. encoding = Encoding.UTF8;
  2676. else if (toEncodingName.Equals(LosslessEncoding.HeaderName, StringComparison.InvariantCultureIgnoreCase))
  2677. encoding = null; //no recoding is needed
  2678. else if (CpEncodingPattern.IsMatch(toEncodingName)) // Encodings written as e.g. "cp1251", which is not a supported encoding string
  2679. encoding = Encoding.GetEncoding(int.Parse(toEncodingName.Substring(2)));
  2680. else
  2681. encoding = Encoding.GetEncoding(toEncodingName);
  2682. }
  2683. else//if bug will be fixed, git should recode commit message to LogOutputEncoding
  2684. encoding = LogOutputEncoding;
  2685. }
  2686. catch (Exception)
  2687. {
  2688. return s + "\n\n! Unsupported commit message encoding: " + toEncodingName + " !";
  2689. }
  2690. return ReEncodeStringFromLossless(s, encoding);
  2691. }
  2692. /// <summary>
  2693. /// header part of show result is encoded in logoutputencoding (including reencoded commit message)
  2694. /// diff part is raw data in file's original encoding
  2695. /// s should be encoded in LosslessEncoding
  2696. /// </summary>
  2697. /// <param name="s"></param>
  2698. /// <returns></returns>
  2699. public string ReEncodeShowString(string s)
  2700. {
  2701. if (s.IsNullOrEmpty())
  2702. return s;
  2703. int p = s.IndexOf("diff --git");
  2704. string header;
  2705. string diffHeader;
  2706. string diffContent;
  2707. string diff;
  2708. if (p > 0)
  2709. {
  2710. header = s.Substring(0, p);
  2711. diff = s.Substring(p);
  2712. }
  2713. else
  2714. {
  2715. header = string.Empty;
  2716. diff = s;
  2717. }
  2718. p = diff.IndexOf("@@");
  2719. if (p > 0)
  2720. {
  2721. diffHeader = diff.Substring(0, p);
  2722. diffContent = diff.Substring(p);
  2723. }
  2724. else
  2725. {
  2726. diffHeader = string.Empty;
  2727. diffContent = diff;
  2728. }
  2729. header = ReEncodeString(header, LosslessEncoding, LogOutputEncoding);
  2730. diffHeader = ReEncodeFileNameFromLossless(diffHeader);
  2731. diffContent = ReEncodeString(diffContent, LosslessEncoding, FilesEncoding);
  2732. return header + diffHeader + diffContent;
  2733. }
  2734. public override bool Equals(object obj)
  2735. {
  2736. if (obj == null) { return false; }
  2737. if (obj == this) { return true; }
  2738. GitModule other = obj as GitModule;
  2739. return (other != null) && Equals(other);
  2740. }
  2741. bool Equals(GitModule other)
  2742. {
  2743. return
  2744. string.Equals(_workingDir, other._workingDir) &&
  2745. Equals(_superprojectModule, other._superprojectModule);
  2746. }
  2747. public override int GetHashCode()
  2748. {
  2749. return _workingDir.GetHashCode();
  2750. }
  2751. public override string ToString()
  2752. {
  2753. return WorkingDir;
  2754. }
  2755. public string GetLocalTrackingBranchName(string remoteName, string branch)
  2756. {
  2757. var branchName = remoteName.Length > 0 ? branch.Substring(remoteName.Length + 1) : branch;
  2758. foreach (var section in LocalConfigFile.GetConfigSections())
  2759. {
  2760. if (section.SectionName == "branch" && section.GetValue("remote") == remoteName)
  2761. {
  2762. var remoteBranch = section.GetValue("merge").Replace("refs/heads/", string.Empty);
  2763. if (remoteBranch == branchName)
  2764. {
  2765. return section.SubSection;
  2766. }
  2767. }
  2768. }
  2769. return branchName;
  2770. }
  2771. public IList<GitItemStatus> GetCombinedDiffFileList(string shaOfMergeCommit)
  2772. {
  2773. var fileList = RunGitCmd("diff-tree --name-only -z --cc --no-commit-id " + shaOfMergeCommit);
  2774. var ret = new List<GitItemStatus>();
  2775. if (string.IsNullOrWhiteSpace(fileList))
  2776. {
  2777. return ret;
  2778. }
  2779. var files = fileList.Split(new[] { '\0' }, StringSplitOptions.RemoveEmptyEntries);
  2780. foreach (var file in files)
  2781. {
  2782. var item = new GitItemStatus
  2783. {
  2784. IsChanged = true,
  2785. IsConflict = true,
  2786. IsTracked = true,
  2787. IsDeleted = false,
  2788. IsStaged = false,
  2789. IsNew = false,
  2790. Name = file,
  2791. };
  2792. ret.Add(item);
  2793. }
  2794. return ret;
  2795. }
  2796. public string GetCombinedDiffContent(GitRevision revisionOfMergeCommit, string filePath,
  2797. string extraArgs, Encoding encoding)
  2798. {
  2799. var cmd = string.Format("diff-tree {4} --no-commit-id {0} {1} {2} -- {3}",
  2800. extraArgs,
  2801. revisionOfMergeCommit.Guid,
  2802. AppSettings.UsePatienceDiffAlgorithm ? "--patience" : "",
  2803. filePath,
  2804. AppSettings.OmitUninterestingDiff ? "--cc" : "-c -p");
  2805. var patchManager = new PatchManager();
  2806. var patch = RunCacheableCmd(AppSettings.GitCommand, cmd, LosslessEncoding);
  2807. if (string.IsNullOrWhiteSpace(patch))
  2808. {
  2809. return "";
  2810. }
  2811. patchManager.LoadPatch(patch, false, encoding);
  2812. return GetPatch(patchManager, filePath, filePath).Text;
  2813. }
  2814. }
  2815. }