PageRenderTime 66ms CodeModel.GetById 23ms RepoModel.GetById 1ms 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

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

  1. using System;
  2. using System.Collections.Generic;
  3. using System.ComponentModel;
  4. using System.Diagnostics;
  5. using System.IO;
  6. using System.Linq;
  7. using System.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,

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