PageRenderTime 45ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/GitCommands/CommitData.cs

https://github.com/vbjay/gitextensions
C# | 424 lines | 300 code | 78 blank | 46 comment | 57 complexity | 1bb9e8d00f973a5e1fe5cade56926476 MD5 | raw file
Possible License(s): GPL-3.0, GPL-2.0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.ObjectModel;
  4. using System.Diagnostics;
  5. using System.Globalization;
  6. using System.Linq;
  7. using System.Net;
  8. using System.Text;
  9. namespace GitCommands
  10. {
  11. public class CommitData
  12. {
  13. private const int COMMITHEADER_STRING_LENGTH = 16;
  14. /// <summary>
  15. /// Private constructor
  16. /// </summary>
  17. private CommitData(string guid,
  18. string treeGuid, ReadOnlyCollection<string> parentGuids,
  19. string author, DateTimeOffset authorDate,
  20. string committer, DateTimeOffset commitDate,
  21. string body)
  22. {
  23. Guid = guid;
  24. TreeGuid = treeGuid;
  25. ParentGuids = parentGuids;
  26. Author = author;
  27. AuthorDate = authorDate;
  28. Committer = committer;
  29. CommitDate = commitDate;
  30. Body = body;
  31. }
  32. public string Guid { get; private set; }
  33. public string TreeGuid { get; private set; }
  34. public ReadOnlyCollection<string> ParentGuids { get; private set; }
  35. public List<string> ChildrenGuids { get; set; }
  36. public string Author { get; private set; }
  37. public DateTimeOffset AuthorDate { get; private set; }
  38. public string Committer { get; private set; }
  39. public DateTimeOffset CommitDate { get; private set; }
  40. public string Body { get; private set; }
  41. /// <summary>
  42. /// Generate header.
  43. /// </summary>
  44. /// <returns></returns>
  45. public string GetHeader(bool showRevisionsAsLinks)
  46. {
  47. StringBuilder header = new StringBuilder();
  48. string authorEmail = GetEmail(Author);
  49. header.AppendLine(FillToLength(WebUtility.HtmlEncode(Strings.GetAuthorText()) + ":", COMMITHEADER_STRING_LENGTH) +
  50. "<a href='mailto:" + WebUtility.HtmlEncode(authorEmail) + "'>" + WebUtility.HtmlEncode(Author) + "</a>");
  51. header.AppendLine(FillToLength(WebUtility.HtmlEncode(Strings.GetAuthorDateText()) + ":", COMMITHEADER_STRING_LENGTH) +
  52. WebUtility.HtmlEncode(GitCommandHelpers.GetRelativeDateString(DateTime.UtcNow, AuthorDate.UtcDateTime) + " (" + GitCommandHelpers.GetFullDateString(AuthorDate) + ")"));
  53. string committerEmail = GetEmail(Committer);
  54. header.AppendLine(FillToLength(WebUtility.HtmlEncode(Strings.GetCommitterText()) + ":", COMMITHEADER_STRING_LENGTH) +
  55. "<a href='mailto:" + WebUtility.HtmlEncode(committerEmail) + "'>" + WebUtility.HtmlEncode(Committer) + "</a>");
  56. header.AppendLine(FillToLength(WebUtility.HtmlEncode(Strings.GetCommitDateText()) + ":", COMMITHEADER_STRING_LENGTH) +
  57. WebUtility.HtmlEncode(GitCommandHelpers.GetRelativeDateString(DateTime.UtcNow, CommitDate.UtcDateTime) + " (" + GitCommandHelpers.GetFullDateString(CommitDate) + ")"));
  58. header.Append(FillToLength(WebUtility.HtmlEncode(Strings.GetCommitHashText()) + ":", COMMITHEADER_STRING_LENGTH) +
  59. WebUtility.HtmlEncode(Guid));
  60. if (ChildrenGuids != null && ChildrenGuids.Count != 0)
  61. {
  62. header.AppendLine();
  63. string commitsString;
  64. if (showRevisionsAsLinks)
  65. commitsString = ChildrenGuids.Select(LinkFactory.CreateCommitLink).Join(" ");
  66. else
  67. commitsString = ChildrenGuids.Select(guid => guid.Substring(0, 10)).Join(" ");
  68. header.Append(FillToLength(WebUtility.HtmlEncode(Strings.GetChildrenText()) + ":",
  69. COMMITHEADER_STRING_LENGTH) + commitsString);
  70. }
  71. var parentGuids = ParentGuids.Where(s => !string.IsNullOrEmpty(s));
  72. if (parentGuids.Any())
  73. {
  74. header.AppendLine();
  75. string commitsString;
  76. if (showRevisionsAsLinks)
  77. commitsString = parentGuids.Select(LinkFactory.CreateCommitLink).Join(" ");
  78. else
  79. commitsString = parentGuids.Select(guid => guid.Substring(0, 10)).Join(" ");
  80. header.Append(FillToLength(WebUtility.HtmlEncode(Strings.GetParentsText()) + ":",
  81. COMMITHEADER_STRING_LENGTH) + commitsString);
  82. }
  83. return RemoveRedundancies(header.ToString());
  84. }
  85. /// <summary>
  86. /// Returns an array of strings containg titles of fields field returned by GetHeader.
  87. /// Used to calculate layout in advance
  88. /// </summary>
  89. /// <returns></returns>
  90. public static string[] GetPossibleHeaders()
  91. {
  92. return new string[] {Strings.GetAuthorText(), Strings.GetAuthorDateText(), Strings.GetCommitterText(),
  93. Strings.GetCommitDateText(), Strings.GetCommitHashText(), Strings.GetChildrenText(),
  94. Strings.GetParentsText()};
  95. }
  96. /// <summary>
  97. /// Generate header.
  98. /// </summary>
  99. /// <returns></returns>
  100. public string GetHeaderPlain()
  101. {
  102. StringBuilder header = new StringBuilder();
  103. header.AppendLine(FillToLength(Strings.GetAuthorText() + ":", COMMITHEADER_STRING_LENGTH) + Author);
  104. header.AppendLine(FillToLength(Strings.GetAuthorDateText() + ":", COMMITHEADER_STRING_LENGTH) +
  105. GitCommandHelpers.GetRelativeDateString(DateTime.UtcNow, AuthorDate.UtcDateTime) + " (" + GitCommandHelpers.GetFullDateString(AuthorDate) + ")");
  106. header.AppendLine(FillToLength(Strings.GetCommitterText() + ":", COMMITHEADER_STRING_LENGTH) +
  107. Committer);
  108. header.AppendLine(FillToLength(Strings.GetCommitDateText() + ":", COMMITHEADER_STRING_LENGTH) +
  109. GitCommandHelpers.GetRelativeDateString(DateTime.UtcNow, CommitDate.UtcDateTime) + " (" + GitCommandHelpers.GetFullDateString(CommitDate) + ")");
  110. header.Append(FillToLength(Strings.GetCommitHashText() + ":", COMMITHEADER_STRING_LENGTH) +
  111. Guid);
  112. return RemoveRedundancies(header.ToString());
  113. }
  114. private static string GetEmail(string author)
  115. {
  116. if (String.IsNullOrEmpty(author))
  117. return "";
  118. int ind = author.IndexOf("<") + 1;
  119. if (ind == -1)
  120. return "";
  121. return author.Substring(ind, author.LastIndexOf(">") - ind);
  122. }
  123. /// <summary>
  124. /// Gets the commit info for submodule.
  125. /// </summary>
  126. public static void UpdateCommitMessage(CommitData data, GitModule module, string sha1, ref string error)
  127. {
  128. if (module == null)
  129. throw new ArgumentNullException("module");
  130. if (sha1 == null)
  131. throw new ArgumentNullException("sha1");
  132. //Do not cache this command, since notes can be added
  133. string arguments = string.Format(CultureInfo.InvariantCulture,
  134. "log -1 --pretty=\"format:" + ShortLogFormat + "\" {0}", sha1);
  135. var info = module.RunGitCmd(arguments, GitModule.LosslessEncoding);
  136. if (info.Trim().StartsWith("fatal"))
  137. {
  138. error = "Cannot find commit " + sha1;
  139. return;
  140. }
  141. int index = info.IndexOf(sha1) + sha1.Length;
  142. if (index < 0)
  143. {
  144. error = "Cannot find commit " + sha1;
  145. return;
  146. }
  147. if (index >= info.Length)
  148. {
  149. error = info;
  150. return;
  151. }
  152. UpdateBodyInCommitData(data, info, module);
  153. }
  154. /// <summary>
  155. /// Gets the commit info for submodule.
  156. /// </summary>
  157. public static CommitData GetCommitData(GitModule module, string sha1, ref string error)
  158. {
  159. if (module == null)
  160. throw new ArgumentNullException("module");
  161. if (sha1 == null)
  162. throw new ArgumentNullException("sha1");
  163. //Do not cache this command, since notes can be added
  164. string arguments = string.Format(CultureInfo.InvariantCulture,
  165. "log -1 --pretty=\"format:"+LogFormat+"\" {0}", sha1);
  166. var info = module.RunGitCmd(arguments, GitModule.LosslessEncoding);
  167. if (info.Trim().StartsWith("fatal"))
  168. {
  169. error = "Cannot find commit " + sha1;
  170. return null;
  171. }
  172. int index = info.IndexOf(sha1) + sha1.Length;
  173. if (index < 0)
  174. {
  175. error = "Cannot find commit " + sha1;
  176. return null;
  177. }
  178. if (index >= info.Length)
  179. {
  180. error = info;
  181. return null;
  182. }
  183. CommitData commitInformation = CreateFromFormatedData(info, module);
  184. return commitInformation;
  185. }
  186. public const string LogFormat = "%H%n%T%n%P%n%aN <%aE>%n%at%n%cN <%cE>%n%ct%n%e%n%B%nNotes:%n%-N";
  187. /// <summary>
  188. /// Creates a CommitData object from formated commit info data from git. The string passed in should be
  189. /// exact output of a log or show command using --format=LogFormat.
  190. /// </summary>
  191. /// <param name="data">Formated commit data from git.</param>
  192. /// <returns>CommitData object populated with parsed info from git string.</returns>
  193. public static CommitData CreateFromFormatedData(string data, GitModule aModule)
  194. {
  195. if (data == null)
  196. throw new ArgumentNullException("data");
  197. var lines = data.Split('\n');
  198. var guid = lines[0];
  199. // TODO: we can use this to add more relationship info like gitk does if wanted
  200. var treeGuid = lines[1];
  201. // TODO: we can use this to add more relationship info like gitk does if wanted
  202. string[] parentLines = lines[2].Split(new char[]{' '});
  203. ReadOnlyCollection<string> parentGuids = parentLines.ToList().AsReadOnly();
  204. var author = aModule.ReEncodeStringFromLossless(lines[3]);
  205. var authorDate = DateTimeUtils.ParseUnixTime(lines[4]);
  206. var committer = aModule.ReEncodeStringFromLossless(lines[5]);
  207. var commitDate = DateTimeUtils.ParseUnixTime(lines[6]);
  208. string commitEncoding = lines[7];
  209. const int startIndex = 8;
  210. string message = ProccessDiffNotes(startIndex, lines);
  211. //commit message is not reencoded by git when format is given
  212. var body = aModule.ReEncodeCommitMessage(message, commitEncoding);
  213. var commitInformation = new CommitData(guid, treeGuid, parentGuids, author, authorDate,
  214. committer, commitDate, body);
  215. return commitInformation;
  216. }
  217. public const string ShortLogFormat = "%H%n%e%n%B%nNotes:%n%-N";
  218. /// <summary>
  219. /// Creates a CommitData object from formated commit info data from git. The string passed in should be
  220. /// exact output of a log or show command using --format=LogFormat.
  221. /// </summary>
  222. /// <param name="data">Formated commit data from git.</param>
  223. /// <returns>CommitData object populated with parsed info from git string.</returns>
  224. public static void UpdateBodyInCommitData(CommitData commitData, string data, GitModule aModule)
  225. {
  226. if (data == null)
  227. throw new ArgumentNullException("data");
  228. var lines = data.Split('\n');
  229. var guid = lines[0];
  230. string commitEncoding = lines[1];
  231. const int startIndex = 2;
  232. string message = ProccessDiffNotes(startIndex, lines);
  233. //commit message is not reencoded by git when format is given
  234. Debug.Assert(commitData.Guid == guid);
  235. commitData.Body = aModule.ReEncodeCommitMessage(message, commitEncoding);
  236. }
  237. private static string ProccessDiffNotes(int startIndex, string[] lines)
  238. {
  239. int endIndex = lines.Length - 1;
  240. if (lines[endIndex] == "Notes:")
  241. endIndex--;
  242. var message = new StringBuilder();
  243. bool bNotesStart = false;
  244. for (int i = startIndex; i <= endIndex; i++)
  245. {
  246. string line = lines[i];
  247. if (bNotesStart)
  248. line = " " + line;
  249. message.AppendLine(line);
  250. if (lines[i] == "Notes:")
  251. bNotesStart = true;
  252. }
  253. return message.ToString();
  254. }
  255. /// <summary>
  256. /// Creates a CommitData object from Git revision.
  257. /// </summary>
  258. /// <param name="revision">Git commit.</param>
  259. /// <returns>CommitData object populated with parsed info from git string.</returns>
  260. public static CommitData CreateFromRevision(GitRevision revision)
  261. {
  262. if (revision == null)
  263. throw new ArgumentNullException("revision");
  264. CommitData data = new CommitData(revision.Guid, revision.TreeGuid, revision.ParentGuids.ToList().AsReadOnly(),
  265. String.Format("{0} <{1}>", revision.Author, revision.AuthorEmail), revision.AuthorDate,
  266. String.Format("{0} <{1}>", revision.Committer, revision.CommitterEmail), revision.CommitDate,
  267. revision.Body ?? revision.Message);
  268. return data;
  269. }
  270. private static string FillToLength(string input, int length)
  271. {
  272. return FillToLength(input, length, 0);
  273. }
  274. private static string FillToLength(string input, int length, int skip)
  275. {
  276. // length
  277. const int tabsize = 8;
  278. if ((input.Length - skip) < length)
  279. {
  280. int l = length - (input.Length - skip);
  281. return input + new string('\t', (l / tabsize) + ((l % tabsize) == 0 ? 0 : 1));
  282. }
  283. return input;
  284. }
  285. private static string RemoveRedundancies(string info)
  286. {
  287. string author = GetField(info, Strings.GetAuthorText() + ":");
  288. string committer = GetField(info, Strings.GetCommitterText() + ":");
  289. if (String.Equals(author, committer, StringComparison.CurrentCulture))
  290. {
  291. info = RemoveField(info, Strings.GetCommitterText() + ":");
  292. }
  293. string authorDate = GetField(info, Strings.GetAuthorDateText() + ":");
  294. string commitDate = GetField(info, Strings.GetCommitDateText() + ":");
  295. if (String.Equals(authorDate, commitDate, StringComparison.CurrentCulture))
  296. {
  297. info =
  298. RemoveField(info, Strings.GetCommitDateText() + ":").Replace(
  299. FillToLength(Strings.GetAuthorDateText() + ":", COMMITHEADER_STRING_LENGTH), FillToLength(Strings.GetDateText() + ":", COMMITHEADER_STRING_LENGTH));
  300. }
  301. return info;
  302. }
  303. private static string RemoveField(string data, string header)
  304. {
  305. int headerIndex = data.IndexOf(header);
  306. if (headerIndex == -1)
  307. return data;
  308. int endIndex = data.IndexOf('\n', headerIndex);
  309. if (endIndex == -1)
  310. endIndex = data.Length - 1;
  311. int length = endIndex - headerIndex + 1;
  312. return data.Remove(headerIndex, length);
  313. }
  314. private static string GetField(string data, string header)
  315. {
  316. int valueIndex = IndexOfValue(data, header);
  317. if (valueIndex == -1)
  318. return null;
  319. int length = LengthOfValue(data, valueIndex);
  320. return data.Substring(valueIndex, length);
  321. }
  322. private static int LengthOfValue(string data, int valueIndex)
  323. {
  324. if (valueIndex == -1)
  325. return 0;
  326. int endIndex = data.IndexOf('\n', valueIndex);
  327. if (endIndex == -1)
  328. endIndex = data.Length - 1;
  329. return endIndex - valueIndex;
  330. }
  331. private static int IndexOfValue(string data, string header)
  332. {
  333. int headerIndex = data.IndexOf(header);
  334. if (headerIndex == -1)
  335. return -1;
  336. int valueIndex = headerIndex + header.Length;
  337. while (data[valueIndex] == '\t')
  338. {
  339. valueIndex++;
  340. if (valueIndex == data.Length)
  341. return -1;
  342. }
  343. return valueIndex;
  344. }
  345. }
  346. }