PageRenderTime 39ms CodeModel.GetById 9ms RepoModel.GetById 0ms app.codeStats 0ms

/GitCommands/RevisionGraph.cs

https://github.com/eisnerd/gitextensions
C# | 343 lines | 276 code | 60 blank | 7 comment | 51 complexity | 5fa8daab9e76257a43b1cc1136ff647d MD5 | raw file
Possible License(s): GPL-3.0, GPL-2.0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.Globalization;
  5. using System.Text;
  6. using System.Threading;
  7. using System.Windows.Forms;
  8. using GitCommands.Config;
  9. using System.Linq;
  10. namespace GitCommands
  11. {
  12. public abstract class RevisionGraphInMemFilter
  13. {
  14. public abstract bool PassThru(GitRevision rev);
  15. }
  16. public sealed class RevisionGraph : IDisposable
  17. {
  18. public event EventHandler Exited;
  19. public event EventHandler<AsyncErrorEventArgs> Error
  20. {
  21. add
  22. {
  23. backgroundLoader.LoadingError += value;
  24. }
  25. remove
  26. {
  27. backgroundLoader.LoadingError -= value;
  28. }
  29. }
  30. public event EventHandler Updated;
  31. public event EventHandler BeginUpdate;
  32. public int RevisionCount { get; set; }
  33. public class RevisionGraphUpdatedEventArgs : EventArgs
  34. {
  35. public RevisionGraphUpdatedEventArgs(GitRevision revision)
  36. {
  37. Revision = revision;
  38. }
  39. public readonly GitRevision Revision;
  40. }
  41. public bool BackgroundThread { get; set; }
  42. public bool ShaOnly { get; set; }
  43. private readonly char[] splitChars = " \t\n".ToCharArray();
  44. private readonly char[] hexChars = "0123456789ABCDEFabcdef".ToCharArray();
  45. private const string COMMIT_BEGIN = "<(__BEGIN_COMMIT__)>"; // Something unlikely to show up in a comment
  46. private Dictionary<string, List<GitHead>> heads;
  47. private enum ReadStep
  48. {
  49. Commit,
  50. Hash,
  51. Parents,
  52. Tree,
  53. AuthorName,
  54. AuthorEmail,
  55. AuthorDate,
  56. CommitterName,
  57. CommitterDate,
  58. CommitMessageEncoding,
  59. CommitMessage,
  60. FileName,
  61. Done,
  62. }
  63. private ReadStep nextStep = ReadStep.Commit;
  64. private GitRevision revision;
  65. private AsyncLoader backgroundLoader = new AsyncLoader();
  66. public RevisionGraph()
  67. {
  68. BackgroundThread = true;
  69. }
  70. ~RevisionGraph()
  71. {
  72. Dispose();
  73. }
  74. public void Dispose()
  75. {
  76. backgroundLoader.Cancel();
  77. }
  78. public string LogParam = "HEAD --all";//--branches --remotes --tags";
  79. public string BranchFilter = String.Empty;
  80. public RevisionGraphInMemFilter InMemFilter;
  81. private string selectedBranchName;
  82. public void Execute()
  83. {
  84. if (BackgroundThread)
  85. {
  86. backgroundLoader.Load(execute, executed);
  87. }
  88. else
  89. {
  90. execute(new FixedLoadingTaskState(false));
  91. executed();
  92. }
  93. }
  94. private void execute(ILoadingTaskState taskState)
  95. {
  96. RevisionCount = 0;
  97. heads = GetHeads().ToDictionaryOfList(head => head.Guid);
  98. string formatString =
  99. /* <COMMIT> */ COMMIT_BEGIN + "%n" +
  100. /* Hash */ "%H%n" +
  101. /* Parents */ "%P%n";
  102. if (!ShaOnly)
  103. {
  104. formatString +=
  105. /* Tree */ "%T%n" +
  106. /* Author Name */ "%aN%n" +
  107. /* Author Email */ "%aE%n" +
  108. /* Author Date */ "%at%n" +
  109. /* Committer Name */ "%cN%n" +
  110. /* Committer Date */ "%ct%n" +
  111. /* Commit message encoding */ "%e%n" + //there is a bug: git does not recode commit message when format is given
  112. /* Commit Message */ "%s";
  113. }
  114. // NOTE:
  115. // when called from FileHistory and FollowRenamesInFileHistory is enabled the "--name-only" argument is set.
  116. // the filename is the next line after the commit-format defined above.
  117. if (Settings.OrderRevisionByDate)
  118. {
  119. LogParam = " --date-order " + LogParam;
  120. }
  121. else
  122. {
  123. LogParam = " --topo-order " + LogParam;
  124. }
  125. string arguments = String.Format(CultureInfo.InvariantCulture,
  126. "log -z {2} --pretty=format:\"{1}\" {0}",
  127. LogParam,
  128. formatString,
  129. BranchFilter);
  130. using (GitCommandsInstance gitGetGraphCommand = new GitCommandsInstance())
  131. {
  132. gitGetGraphCommand.StreamOutput = true;
  133. gitGetGraphCommand.CollectOutput = false;
  134. Encoding LogOutputEncoding = Settings.LogOutputEncoding;
  135. gitGetGraphCommand.SetupStartInfoCallback = startInfo =>
  136. {
  137. startInfo.StandardOutputEncoding = Settings.LosslessEncoding;
  138. startInfo.StandardErrorEncoding = Settings.LosslessEncoding;
  139. };
  140. Process p = gitGetGraphCommand.CmdStartProcess(Settings.GitCommand, arguments);
  141. if (taskState.IsCanceled())
  142. return;
  143. previousFileName = null;
  144. if (BeginUpdate != null)
  145. BeginUpdate(this, EventArgs.Empty);
  146. string line;
  147. do
  148. {
  149. line = p.StandardOutput.ReadLine();
  150. //commit message is not encoded by git
  151. if (nextStep != ReadStep.CommitMessage)
  152. line = GitCommandHelpers.ReEncodeString(line, Settings.LosslessEncoding, LogOutputEncoding);
  153. if (line != null)
  154. {
  155. foreach (string entry in line.Split('\0'))
  156. {
  157. dataReceived(entry);
  158. }
  159. }
  160. } while (line != null && !taskState.IsCanceled());
  161. }
  162. }
  163. private void executed()
  164. {
  165. finishRevision();
  166. previousFileName = null;
  167. if (Exited != null)
  168. Exited(this, EventArgs.Empty);
  169. }
  170. private List<GitHead> GetHeads()
  171. {
  172. var result = Settings.Module.GetHeads(true);
  173. bool validWorkingDir = Settings.Module.ValidWorkingDir();
  174. selectedBranchName = validWorkingDir ? Settings.Module.GetSelectedBranch() : string.Empty;
  175. GitHead selectedHead = result.Find(head => head.Name == selectedBranchName);
  176. if (selectedHead != null)
  177. {
  178. selectedHead.Selected = true;
  179. ConfigFile localConfigFile = Settings.Module.GetLocalConfig();
  180. GitHead selectedHeadMergeSource =
  181. result.Find(head => head.IsRemote
  182. && selectedHead.GetTrackingRemote(localConfigFile) == head.Remote
  183. && selectedHead.GetMergeWith(localConfigFile) == head.LocalName);
  184. if (selectedHeadMergeSource != null)
  185. selectedHeadMergeSource.SelectedHeadMergeSource = true;
  186. }
  187. return result;
  188. }
  189. private string previousFileName = null;
  190. void finishRevision()
  191. {
  192. if (revision != null)
  193. {
  194. if (revision.Name == null)
  195. revision.Name = previousFileName;
  196. else
  197. previousFileName = revision.Name;
  198. }
  199. if (revision == null || revision.Guid.Trim(hexChars).Length == 0)
  200. {
  201. if ((revision == null) || (InMemFilter == null) || InMemFilter.PassThru(revision))
  202. {
  203. if (revision != null)
  204. RevisionCount++;
  205. if (Updated != null)
  206. Updated(this, new RevisionGraphUpdatedEventArgs(revision));
  207. }
  208. }
  209. nextStep = ReadStep.Commit;
  210. }
  211. void dataReceived(string line)
  212. {
  213. if (line == null)
  214. return;
  215. if (line == COMMIT_BEGIN)
  216. {
  217. // a new commit finalizes the last revision
  218. finishRevision();
  219. nextStep = ReadStep.Commit;
  220. }
  221. switch (nextStep)
  222. {
  223. case ReadStep.Commit:
  224. // Sanity check
  225. if (line == COMMIT_BEGIN)
  226. {
  227. revision = new GitRevision(null);
  228. }
  229. else
  230. {
  231. // Bail out until we see what we expect
  232. return;
  233. }
  234. break;
  235. case ReadStep.Hash:
  236. revision.Guid = line;
  237. List<GitHead> headList;
  238. if (heads.TryGetValue(revision.Guid, out headList))
  239. revision.Heads.AddRange(headList);
  240. break;
  241. case ReadStep.Parents:
  242. revision.ParentGuids = line.Split(splitChars, StringSplitOptions.RemoveEmptyEntries);
  243. break;
  244. case ReadStep.Tree:
  245. revision.TreeGuid = line;
  246. break;
  247. case ReadStep.AuthorName:
  248. revision.Author = line;
  249. break;
  250. case ReadStep.AuthorEmail:
  251. revision.AuthorEmail = line;
  252. break;
  253. case ReadStep.AuthorDate:
  254. {
  255. DateTime dateTime;
  256. if (DateTimeUtils.TryParseUnixTime(line, out dateTime))
  257. revision.AuthorDate = dateTime;
  258. }
  259. break;
  260. case ReadStep.CommitterName:
  261. revision.Committer = line;
  262. break;
  263. case ReadStep.CommitterDate:
  264. {
  265. DateTime dateTime;
  266. if (DateTimeUtils.TryParseUnixTime(line, out dateTime))
  267. revision.CommitDate = dateTime;
  268. }
  269. break;
  270. case ReadStep.CommitMessageEncoding:
  271. revision.MessageEncoding = line;
  272. break;
  273. case ReadStep.CommitMessage:
  274. revision.Message = GitCommandHelpers.ReEncodeCommitMessage(line, revision.MessageEncoding);
  275. break;
  276. case ReadStep.FileName:
  277. revision.Name = line;
  278. break;
  279. }
  280. nextStep++;
  281. }
  282. }
  283. }