PageRenderTime 54ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/GitUI/CommitInfo/CommitInfo.cs

https://github.com/qgppl/gitextensions
C# | 564 lines | 453 code | 73 blank | 38 comment | 64 complexity | a44d8cdde784875585a2f48e846096ce 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.Linq;
  6. using System.Net;
  7. using System.Text.RegularExpressions;
  8. using System.Threading;
  9. using System.Windows.Forms;
  10. using GitCommands;
  11. using GitCommands.Utils;
  12. using GitCommands.GitExtLinks;
  13. using GitUI.Editor.RichTextBoxExtension;
  14. using ResourceManager;
  15. namespace GitUI.CommitInfo
  16. {
  17. public partial class CommitInfo : GitModuleControl
  18. {
  19. private readonly TranslationString containedInBranches = new TranslationString("Contained in branches:");
  20. private readonly TranslationString containedInNoBranch = new TranslationString("Contained in no branch");
  21. private readonly TranslationString containedInTags = new TranslationString("Contained in tags:");
  22. private readonly TranslationString containedInNoTag = new TranslationString("Contained in no tag");
  23. private readonly TranslationString trsLinksRelatedToRevision = new TranslationString("Related links:");
  24. private const int MaximumDisplayedRefs = 20;
  25. public CommitInfo()
  26. {
  27. InitializeComponent();
  28. Translate();
  29. GitUICommandsSourceSet += (a, uiCommandsSource) =>
  30. {
  31. _sortedRefs = null;
  32. };
  33. }
  34. [DefaultValue(false)]
  35. public bool ShowBranchesAsLinks { get; set; }
  36. public event EventHandler<CommandEventArgs> CommandClick;
  37. private void RevisionInfoLinkClicked(object sender, LinkClickedEventArgs e)
  38. {
  39. try
  40. {
  41. var url = e.LinkText;
  42. var data = url.Split(new[] { '#' }, 2);
  43. try
  44. {
  45. if (data.Length > 1)
  46. {
  47. var result = new Uri(data[1]);
  48. if (result.Scheme == "gitext")
  49. {
  50. if (CommandClick != null)
  51. {
  52. string path = result.AbsolutePath.TrimStart('/');
  53. CommandClick(sender, new CommandEventArgs(result.Host, path));
  54. }
  55. return;
  56. }
  57. else
  58. {
  59. url = result.AbsoluteUri;
  60. }
  61. }
  62. }
  63. catch (UriFormatException)
  64. {
  65. }
  66. using (var process = new Process
  67. {
  68. EnableRaisingEvents = false,
  69. StartInfo = { FileName = url }
  70. })
  71. process.Start();
  72. }
  73. catch (Exception ex)
  74. {
  75. MessageBox.Show(ex.Message);
  76. }
  77. }
  78. private GitRevision _revision;
  79. private List<string> _children;
  80. public void SetRevisionWithChildren(GitRevision revision, List<string> children)
  81. {
  82. _revision = revision;
  83. _children = children;
  84. ReloadCommitInfo();
  85. }
  86. [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  87. [Browsable(false)]
  88. public GitRevision Revision
  89. {
  90. get
  91. {
  92. return _revision;
  93. }
  94. set
  95. {
  96. SetRevisionWithChildren(value, null);
  97. }
  98. }
  99. [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
  100. [Browsable(false)]
  101. public string RevisionGuid
  102. {
  103. get
  104. {
  105. return _revision.Guid;
  106. }
  107. }
  108. private string _revisionInfo;
  109. private string _linksInfo;
  110. private IDictionary<string, string> _annotatedTagsMessages;
  111. private string _annotatedTagsInfo;
  112. private List<string> _tags;
  113. private string _tagInfo;
  114. private List<string> _branches;
  115. private string _branchInfo;
  116. private IList<string> _sortedRefs;
  117. private void ReloadCommitInfo()
  118. {
  119. showContainedInBranchesToolStripMenuItem.Checked = AppSettings.CommitInfoShowContainedInBranchesLocal;
  120. showContainedInBranchesRemoteToolStripMenuItem.Checked = AppSettings.CommitInfoShowContainedInBranchesRemote;
  121. showContainedInBranchesRemoteIfNoLocalToolStripMenuItem.Checked = AppSettings.CommitInfoShowContainedInBranchesRemoteIfNoLocal;
  122. showContainedInTagsToolStripMenuItem.Checked = AppSettings.CommitInfoShowContainedInTags;
  123. showMessagesOfAnnotatedTagsToolStripMenuItem.Checked = AppSettings.ShowAnnotatedTagsMessages;
  124. ResetTextAndImage();
  125. if (string.IsNullOrEmpty(_revision.Guid))
  126. return; //is it regular case or should throw an exception
  127. _RevisionHeader.SelectionTabs = GetRevisionHeaderTabStops();
  128. _RevisionHeader.Text = string.Empty;
  129. _RevisionHeader.Refresh();
  130. string error = "";
  131. CommitData data = CommitData.CreateFromRevision(_revision);
  132. if (_revision.Body == null)
  133. {
  134. CommitData.UpdateCommitMessage(data, Module, _revision.Guid, ref error);
  135. _revision.Body = data.Body;
  136. }
  137. ThreadPool.QueueUserWorkItem(_ => loadLinksForRevision(_revision));
  138. if (_sortedRefs == null)
  139. ThreadPool.QueueUserWorkItem(_ => loadSortedRefs());
  140. data.ChildrenGuids = _children;
  141. CommitInformation commitInformation = CommitInformation.GetCommitInfo(data, CommandClick != null);
  142. _RevisionHeader.SetXHTMLText(commitInformation.Header);
  143. _RevisionHeader.Height = GetRevisionHeaderHeight();
  144. _revisionInfo = commitInformation.Body;
  145. updateText();
  146. LoadAuthorImage(data.Author ?? data.Committer);
  147. if (AppSettings.CommitInfoShowContainedInBranches)
  148. ThreadPool.QueueUserWorkItem(_ => loadBranchInfo(_revision.Guid));
  149. if (AppSettings.ShowAnnotatedTagsMessages)
  150. ThreadPool.QueueUserWorkItem(_ => loadAnnotatedTagInfo(_revision));
  151. if (AppSettings.CommitInfoShowContainedInTags)
  152. ThreadPool.QueueUserWorkItem(_ => loadTagInfo(_revision.Guid));
  153. }
  154. /// <summary>
  155. /// Returns an array of strings contains titles of fields field returned by GetHeader.
  156. /// Used to calculate layout in advance
  157. /// </summary>
  158. /// <returns></returns>
  159. private static string[] GetPossibleHeaders()
  160. {
  161. return new string[]
  162. {
  163. Strings.GetAuthorText(), Strings.GetAuthorDateText(), Strings.GetCommitterText(),
  164. Strings.GetCommitDateText(), Strings.GetCommitHashText(), Strings.GetChildrenText(),
  165. Strings.GetParentsText()
  166. };
  167. }
  168. private int[] _revisionHeaderTabStops;
  169. private int[] GetRevisionHeaderTabStops()
  170. {
  171. if (_revisionHeaderTabStops != null)
  172. return _revisionHeaderTabStops;
  173. int tabStop = 0;
  174. foreach (string s in GetPossibleHeaders())
  175. {
  176. tabStop = Math.Max(tabStop, TextRenderer.MeasureText(s + " ", _RevisionHeader.Font).Width);
  177. }
  178. // simulate a two column layout even when there's more then one tab used
  179. _revisionHeaderTabStops = new int[] { tabStop, tabStop + 1, tabStop + 2, tabStop + 3 };
  180. return _revisionHeaderTabStops;
  181. }
  182. private int GetRevisionHeaderHeight()
  183. {
  184. if (EnvUtils.IsMonoRuntime())
  185. return (int)(_RevisionHeader.Lines.Length * (0.8 + _RevisionHeader.Font.GetHeight()));
  186. return _RevisionHeader.GetPreferredSize(new System.Drawing.Size(0, 0)).Height;
  187. }
  188. private void loadSortedRefs()
  189. {
  190. _sortedRefs = Module.GetSortedRefs();
  191. this.InvokeAsync(updateText);
  192. }
  193. private void loadAnnotatedTagInfo(GitRevision revision)
  194. {
  195. _annotatedTagsMessages = GetAnnotatedTagsMessages(revision);
  196. this.InvokeAsync(updateText);
  197. }
  198. private IDictionary<string, string> GetAnnotatedTagsMessages(GitRevision revision)
  199. {
  200. if (revision == null)
  201. return null;
  202. IDictionary<string, string> result = new Dictionary<string, string>();
  203. foreach (GitRef gitRef in revision.Refs)
  204. {
  205. #region Note on annotated tags
  206. // Notice that for the annotated tags, gitRef's come in pairs because they're produced
  207. // by the "show-ref --dereference" command. GitRef's in such pair have the same Name,
  208. // a bit different CompleteName's, and completely different checksums:
  209. // GitRef_1:
  210. // {
  211. // Name: "some_tag"
  212. // CompleteName: "refs/tags/some_tag"
  213. // Guid: <some_tag_checksum>
  214. // },
  215. //
  216. // GitRef_2:
  217. // {
  218. // Name: "some_tag"
  219. // CompleteName: "refs/tags/some_tag^{}" <- by "^{}", IsDereference is true.
  220. // Guid: <target_object_checksum>
  221. // }
  222. //
  223. // The 2nd one is a dereference: a link between the tag and the object which it references.
  224. // GitRevions.Refs by design contains GitRef's where Guid's are equal to the GitRevision.Guid,
  225. // so this collection contains only derefencing GitRef's - just because GitRef_2 has the same
  226. // Guid as the GitRevision, while GitRef_1 doesn't. So annotated tag's GitRef would always be
  227. // of 2nd type in GitRevision.Refs collection, i.e. the one that has IsDereference==true.
  228. #endregion
  229. if (gitRef.IsTag && gitRef.IsDereference)
  230. {
  231. string content = WebUtility.HtmlEncode(Module.GetTagMessage(gitRef.LocalName));
  232. if (content != null)
  233. result.Add(gitRef.LocalName, content);
  234. }
  235. }
  236. return result;
  237. }
  238. private void loadTagInfo(string revision)
  239. {
  240. _tags = Module.GetAllTagsWhichContainGivenCommit(revision).ToList();
  241. this.InvokeAsync(updateText);
  242. }
  243. private void loadBranchInfo(string revision)
  244. {
  245. // Include local branches if explicitly requested or when needed to decide whether to show remotes
  246. bool getLocal = AppSettings.CommitInfoShowContainedInBranchesLocal ||
  247. AppSettings.CommitInfoShowContainedInBranchesRemoteIfNoLocal;
  248. // Include remote branches if requested
  249. bool getRemote = AppSettings.CommitInfoShowContainedInBranchesRemote ||
  250. AppSettings.CommitInfoShowContainedInBranchesRemoteIfNoLocal;
  251. _branches = Module.GetAllBranchesWhichContainGivenCommit(revision, getLocal, getRemote).ToList();
  252. this.InvokeAsync(updateText);
  253. }
  254. private void loadLinksForRevision(GitRevision revision)
  255. {
  256. if (revision == null)
  257. return;
  258. _linksInfo = GetLinksForRevision(revision);
  259. this.InvokeAsync(updateText);
  260. }
  261. private class ItemTpComparer : IComparer<string>
  262. {
  263. private readonly IList<string> _otherList;
  264. private readonly string _prefix;
  265. public ItemTpComparer(IList<string> otherList, string prefix)
  266. {
  267. _otherList = otherList;
  268. _prefix = prefix;
  269. }
  270. public int Compare(string a, string b)
  271. {
  272. if (a.StartsWith("remotes/"))
  273. a = "refs/" + a;
  274. else
  275. a = _prefix + a;
  276. if (b.StartsWith("remotes/"))
  277. b = "refs/" + b;
  278. else
  279. b = _prefix + b;
  280. int i = _otherList.IndexOf(a);
  281. int j = _otherList.IndexOf(b);
  282. return i - j;
  283. }
  284. }
  285. private void updateText()
  286. {
  287. if (_sortedRefs != null)
  288. {
  289. if (_annotatedTagsMessages != null &&
  290. _annotatedTagsMessages.Count() > 0 &&
  291. string.IsNullOrEmpty(_annotatedTagsInfo) &&
  292. Revision != null)
  293. {
  294. // having both lightweight & annotated tags in thisRevisionTagNames,
  295. // but GetAnnotatedTagsInfo will process annotated only:
  296. List<string> thisRevisionTagNames =
  297. Revision
  298. .Refs
  299. .Where(r => r.IsTag)
  300. .Select(r => r.LocalName)
  301. .ToList();
  302. thisRevisionTagNames.Sort(new ItemTpComparer(_sortedRefs, "refs/tags/"));
  303. _annotatedTagsInfo = GetAnnotatedTagsInfo(Revision, thisRevisionTagNames, _annotatedTagsMessages);
  304. }
  305. if (_tags != null && string.IsNullOrEmpty(_tagInfo))
  306. {
  307. _tags.Sort(new ItemTpComparer(_sortedRefs, "refs/tags/"));
  308. if (_tags.Count > MaximumDisplayedRefs)
  309. {
  310. _tags[MaximumDisplayedRefs - 2] = "…";
  311. _tags[MaximumDisplayedRefs - 1] = _tags[_tags.Count - 1];
  312. _tags.RemoveRange(MaximumDisplayedRefs, _tags.Count - MaximumDisplayedRefs);
  313. }
  314. _tagInfo = GetTagsWhichContainsThisCommit(_tags, ShowBranchesAsLinks);
  315. }
  316. if (_branches != null && string.IsNullOrEmpty(_branchInfo))
  317. {
  318. _branches.Sort(new ItemTpComparer(_sortedRefs, "refs/heads/"));
  319. if (_branches.Count > MaximumDisplayedRefs)
  320. {
  321. _branches[MaximumDisplayedRefs - 2] = "…";
  322. _branches[MaximumDisplayedRefs - 1] = _branches[_branches.Count - 1];
  323. _branches.RemoveRange(MaximumDisplayedRefs, _branches.Count - MaximumDisplayedRefs);
  324. }
  325. _branchInfo = GetBranchesWhichContainsThisCommit(_branches, ShowBranchesAsLinks);
  326. }
  327. }
  328. RevisionInfo.SuspendLayout();
  329. RevisionInfo.SetXHTMLText(_revisionInfo + "\n" + _annotatedTagsInfo + _linksInfo + _branchInfo + _tagInfo);
  330. RevisionInfo.SelectionStart = 0; //scroll up
  331. RevisionInfo.ScrollToCaret(); //scroll up
  332. RevisionInfo.ResumeLayout(true);
  333. }
  334. private static string GetAnnotatedTagsInfo(
  335. GitRevision revision,
  336. IEnumerable<string> tagNames,
  337. IDictionary<string, string> annotatedTagsMessages)
  338. {
  339. string result = string.Empty;
  340. foreach (string tag in tagNames)
  341. {
  342. string annotatedContents;
  343. if (annotatedTagsMessages.TryGetValue(tag, out annotatedContents))
  344. result += "<u>" + tag + "</u>: " + annotatedContents + Environment.NewLine;
  345. }
  346. if (result.IsNullOrEmpty())
  347. return string.Empty;
  348. return Environment.NewLine + result;
  349. }
  350. private void ResetTextAndImage()
  351. {
  352. _revisionInfo = string.Empty;
  353. _linksInfo = string.Empty;
  354. _branchInfo = string.Empty;
  355. _annotatedTagsInfo = string.Empty;
  356. _tagInfo = string.Empty;
  357. _branches = null;
  358. _annotatedTagsMessages = null;
  359. _tags = null;
  360. updateText();
  361. gravatar1.LoadImageForEmail("");
  362. }
  363. private void LoadAuthorImage(string author)
  364. {
  365. var matches = Regex.Matches(author, @"<([\w\-\.]+@[\w\-\.]+)>");
  366. if (matches.Count == 0)
  367. return;
  368. gravatar1.LoadImageForEmail(matches[0].Groups[1].Value);
  369. }
  370. private string GetBranchesWhichContainsThisCommit(IEnumerable<string> branches, bool showBranchesAsLinks)
  371. {
  372. const string remotesPrefix = "remotes/";
  373. // Include local branches if explicitly requested or when needed to decide whether to show remotes
  374. bool getLocal = AppSettings.CommitInfoShowContainedInBranchesLocal ||
  375. AppSettings.CommitInfoShowContainedInBranchesRemoteIfNoLocal;
  376. // Include remote branches if requested
  377. bool getRemote = AppSettings.CommitInfoShowContainedInBranchesRemote ||
  378. AppSettings.CommitInfoShowContainedInBranchesRemoteIfNoLocal;
  379. var links = new List<string>();
  380. bool allowLocal = AppSettings.CommitInfoShowContainedInBranchesLocal;
  381. bool allowRemote = getRemote;
  382. foreach (var branch in branches)
  383. {
  384. string noPrefixBranch = branch;
  385. bool branchIsLocal;
  386. if (getLocal && getRemote)
  387. {
  388. // "git branch -a" prefixes remote branches with "remotes/"
  389. // It is possible to create a local branch named "remotes/origin/something"
  390. // so this check is not 100% reliable.
  391. // This shouldn't be a big problem if we're only displaying information.
  392. branchIsLocal = !branch.StartsWith(remotesPrefix);
  393. if (!branchIsLocal)
  394. noPrefixBranch = branch.Substring(remotesPrefix.Length);
  395. }
  396. else
  397. {
  398. branchIsLocal = !getRemote;
  399. }
  400. if ((branchIsLocal && allowLocal) || (!branchIsLocal && allowRemote))
  401. {
  402. string branchText;
  403. if (showBranchesAsLinks)
  404. branchText = LinkFactory.CreateBranchLink(noPrefixBranch);
  405. else
  406. branchText = WebUtility.HtmlEncode(noPrefixBranch);
  407. links.Add(branchText);
  408. }
  409. if (branchIsLocal && AppSettings.CommitInfoShowContainedInBranchesRemoteIfNoLocal)
  410. allowRemote = false;
  411. }
  412. if (links.Any())
  413. return Environment.NewLine + WebUtility.HtmlEncode(containedInBranches.Text) + " " + links.Join(", ");
  414. return Environment.NewLine + WebUtility.HtmlEncode(containedInNoBranch.Text);
  415. }
  416. private string GetTagsWhichContainsThisCommit(IEnumerable<string> tags, bool showBranchesAsLinks)
  417. {
  418. var tagString = tags
  419. .Select(s => showBranchesAsLinks ? LinkFactory.CreateTagLink(s) : WebUtility.HtmlEncode(s)).Join(", ");
  420. if (!String.IsNullOrEmpty(tagString))
  421. return Environment.NewLine + WebUtility.HtmlEncode(containedInTags.Text) + " " + tagString;
  422. return Environment.NewLine + WebUtility.HtmlEncode(containedInNoTag.Text);
  423. }
  424. private string GetLinksForRevision(GitRevision revision)
  425. {
  426. GitExtLinksParser parser = new GitExtLinksParser(Module.EffectiveSettings);
  427. var links = parser.Parse(revision).Distinct();
  428. var linksString = string.Empty;
  429. foreach (var link in links)
  430. {
  431. linksString = linksString.Combine(", ", LinkFactory.CreateLink(link.Caption, link.URI));
  432. }
  433. if (linksString.IsNullOrEmpty())
  434. return string.Empty;
  435. else
  436. return Environment.NewLine + WebUtility.HtmlEncode(trsLinksRelatedToRevision.Text) + " " + linksString;
  437. }
  438. private void showContainedInBranchesToolStripMenuItem_Click(object sender, EventArgs e)
  439. {
  440. AppSettings.CommitInfoShowContainedInBranchesLocal = !AppSettings.CommitInfoShowContainedInBranchesLocal;
  441. ReloadCommitInfo();
  442. }
  443. private void showContainedInTagsToolStripMenuItem_Click(object sender, EventArgs e)
  444. {
  445. AppSettings.CommitInfoShowContainedInTags = !AppSettings.CommitInfoShowContainedInTags;
  446. ReloadCommitInfo();
  447. }
  448. private void copyCommitInfoToolStripMenuItem_Click(object sender, EventArgs e)
  449. {
  450. Clipboard.SetText(string.Concat(_RevisionHeader.GetPlaintText(), Environment.NewLine, RevisionInfo.GetPlaintText()));
  451. }
  452. private void showContainedInBranchesRemoteToolStripMenuItem_Click(object sender, EventArgs e)
  453. {
  454. AppSettings.CommitInfoShowContainedInBranchesRemote = !AppSettings.CommitInfoShowContainedInBranchesRemote;
  455. ReloadCommitInfo();
  456. }
  457. private void showContainedInBranchesRemoteIfNoLocalToolStripMenuItem_Click(object sender, EventArgs e)
  458. {
  459. AppSettings.CommitInfoShowContainedInBranchesRemoteIfNoLocal = !AppSettings.CommitInfoShowContainedInBranchesRemoteIfNoLocal;
  460. ReloadCommitInfo();
  461. }
  462. private void showMessagesOfAnnotatedTagsToolStripMenuItem_Click(object sender, EventArgs e)
  463. {
  464. AppSettings.ShowAnnotatedTagsMessages = !AppSettings.ShowAnnotatedTagsMessages;
  465. ReloadCommitInfo();
  466. }
  467. private void addNoteToolStripMenuItem_Click(object sender, EventArgs e)
  468. {
  469. Module.EditNotes(_revision.Guid);
  470. ReloadCommitInfo();
  471. }
  472. private void DoCommandClick(string command, string data)
  473. {
  474. if (CommandClick != null)
  475. {
  476. CommandClick(this, new CommandEventArgs(command, data));
  477. }
  478. }
  479. private void _RevisionHeader_MouseDown(object sender, MouseEventArgs e)
  480. {
  481. if (e.Button == MouseButtons.XButton1)
  482. {
  483. DoCommandClick("navigatebackward", null);
  484. }
  485. else if (e.Button == MouseButtons.XButton2)
  486. {
  487. DoCommandClick("navigateforward", null);
  488. }
  489. }
  490. }
  491. }