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

/GitUI/CommandsDialogs/FormFileHistory.cs

https://github.com/qgppl/gitextensions
C# | 499 lines | 384 code | 86 blank | 29 comment | 70 complexity | abc6086fb930cf1e7ec22c88f747c276 MD5 | raw file
Possible License(s): GPL-3.0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Diagnostics;
  4. using System.IO;
  5. using System.Linq;
  6. using System.Text;
  7. using System.Windows.Forms;
  8. using GitCommands;
  9. using GitCommands.Utils;
  10. using ResourceManager;
  11. namespace GitUI.CommandsDialogs
  12. {
  13. public sealed partial class FormFileHistory : GitModuleForm
  14. {
  15. private readonly FilterRevisionsHelper _filterRevisionsHelper;
  16. private readonly FilterBranchHelper _filterBranchHelper;
  17. private readonly AsyncLoader _asyncLoader;
  18. private readonly FormBrowseMenus _formBrowseMenus;
  19. private readonly TranslationString _buildReportTabCaption =
  20. new TranslationString("Build Report");
  21. private FormFileHistory()
  22. : this(null)
  23. { }
  24. internal FormFileHistory(GitUICommands aCommands)
  25. : base(aCommands)
  26. {
  27. InitializeComponent();
  28. _asyncLoader = new AsyncLoader();
  29. // set tab page images
  30. {
  31. var imageList = new ImageList();
  32. tabControl1.ImageList = imageList;
  33. imageList.ColorDepth = ColorDepth.Depth8Bit;
  34. imageList.Images.Add(global::GitUI.Properties.Resources.IconViewFile);
  35. imageList.Images.Add(global::GitUI.Properties.Resources.IconDiff);
  36. imageList.Images.Add(global::GitUI.Properties.Resources.IconBlame);
  37. tabControl1.TabPages[0].ImageIndex = 0;
  38. tabControl1.TabPages[1].ImageIndex = 1;
  39. tabControl1.TabPages[2].ImageIndex = 2;
  40. }
  41. _filterBranchHelper = new FilterBranchHelper(toolStripBranchFilterComboBox, toolStripBranchFilterDropDownButton, FileChanges);
  42. _filterRevisionsHelper = new FilterRevisionsHelper(toolStripRevisionFilterTextBox, toolStripRevisionFilterDropDownButton, FileChanges, toolStripRevisionFilterLabel, ShowFirstParent, form: this);
  43. _formBrowseMenus = new FormBrowseMenus(FileHistoryContextMenu);
  44. _formBrowseMenus.ResetMenuCommandSets();
  45. _formBrowseMenus.AddMenuCommandSet(MainMenuItem.NavigateMenu, FileChanges.MenuCommands.GetNavigateMenuCommands());
  46. _formBrowseMenus.AddMenuCommandSet(MainMenuItem.ViewMenu, FileChanges.MenuCommands.GetViewMenuCommands());
  47. _formBrowseMenus.InsertAdditionalMainMenuItems(toolStripSeparator4);
  48. }
  49. public FormFileHistory(GitUICommands aCommands, string fileName, GitRevision revision, bool filterByRevision)
  50. : this(aCommands)
  51. {
  52. FileChanges.SetInitialRevision(revision);
  53. Translate();
  54. FileChanges.ShowBuildServerInfo = true;
  55. FileName = fileName;
  56. SetTitle(string.Empty);
  57. Diff.ExtraDiffArgumentsChanged += DiffExtraDiffArgumentsChanged;
  58. FileChanges.SelectionChanged += FileChangesSelectionChanged;
  59. FileChanges.DisableContextMenu();
  60. UpdateFollowHistoryMenuItems();
  61. fullHistoryToolStripMenuItem.Checked = AppSettings.FullHistoryInFileHistory;
  62. loadHistoryOnShowToolStripMenuItem.Checked = AppSettings.LoadFileHistoryOnShow;
  63. loadBlameOnShowToolStripMenuItem.Checked = AppSettings.LoadBlameOnShow;
  64. if (filterByRevision && revision != null && revision.Guid != null)
  65. _filterBranchHelper.SetBranchFilter(revision.Guid, false);
  66. }
  67. public FormFileHistory(GitUICommands aCommands, string fileName)
  68. : this(aCommands, fileName, null, false)
  69. {
  70. }
  71. protected override void OnRuntimeLoad(EventArgs e)
  72. {
  73. base.OnRuntimeLoad(e);
  74. bool autoLoad = (tabControl1.SelectedTab == BlameTab && AppSettings.LoadBlameOnShow) || AppSettings.LoadFileHistoryOnShow;
  75. if (autoLoad)
  76. LoadFileHistory();
  77. else
  78. FileChanges.Visible = false;
  79. }
  80. private string FileName { get; set; }
  81. public void SelectBlameTab()
  82. {
  83. tabControl1.SelectedTab = BlameTab;
  84. }
  85. private void LoadFileHistory()
  86. {
  87. FileChanges.Visible = true;
  88. _asyncLoader.Load(() => BuildFilter(FileName), (filter) =>
  89. {
  90. if (filter == null)
  91. return;
  92. FileChanges.FixedRevisionFilter = filter.RevisionFilter;
  93. FileChanges.FixedPathFilter = filter.PathFilter;
  94. FileChanges.FiltredFileName = FileName;
  95. FileChanges.AllowGraphWithFilter = true;
  96. FileChanges.Load();
  97. });
  98. }
  99. private class FixedFilterTuple
  100. {
  101. public string RevisionFilter;
  102. public string PathFilter;
  103. }
  104. private FixedFilterTuple BuildFilter(string fileName)
  105. {
  106. if (string.IsNullOrEmpty(fileName))
  107. return null;
  108. //Replace windows path separator to Linux path separator.
  109. //This is needed to keep the file history working when started from file tree in
  110. //browse dialog.
  111. fileName = fileName.ToPosixPath();
  112. // we will need this later to look up proper casing for the file
  113. var fullFilePath = Path.Combine(Module.WorkingDir, fileName);
  114. //The section below contains native windows (kernel32) calls
  115. //and breaks on Linux. Only use it on Windows. Casing is only
  116. //a Windows problem anyway.
  117. if (EnvUtils.RunningOnWindows() && File.Exists(fullFilePath))
  118. {
  119. // grab the 8.3 file path
  120. var shortPath = new StringBuilder(4096);
  121. NativeMethods.GetShortPathName(fullFilePath, shortPath, shortPath.Capacity);
  122. // use 8.3 file path to get properly cased full file path
  123. var longPath = new StringBuilder(4096);
  124. NativeMethods.GetLongPathName(shortPath.ToString(), longPath, longPath.Capacity);
  125. // remove the working directory and now we have a properly cased file name.
  126. fileName = longPath.ToString().Substring(Module.WorkingDir.Length);
  127. }
  128. if (fileName.StartsWith(Module.WorkingDir, StringComparison.InvariantCultureIgnoreCase))
  129. fileName = fileName.Substring(Module.WorkingDir.Length);
  130. FileName = fileName;
  131. FixedFilterTuple res = new FixedFilterTuple();
  132. res.PathFilter = " \"" + fileName + "\"";
  133. if (AppSettings.FollowRenamesInFileHistory && !Directory.Exists(fullFilePath))
  134. {
  135. // git log --follow is not working as expected (see http://kerneltrap.org/mailarchive/git/2009/1/30/4856404/thread)
  136. //
  137. // But we can take a more complicated path to get reasonable results:
  138. // 1. use git log --follow to get all previous filenames of the file we are interested in
  139. // 2. use git log "list of files names" to get the history graph
  140. //
  141. // note: This implementation is quite a quick hack (by someone who does not speak C# fluently).
  142. //
  143. string arg = "log --format=\"%n\" --name-only --follow "+
  144. GitCommandHelpers.FindRenamesAndCopiesOpts()
  145. + " -- \"" + fileName + "\"";
  146. Process p = Module.RunGitCmdDetached(arg);
  147. // the sequence of (quoted) file names - start with the initial filename for the search.
  148. var listOfFileNames = new StringBuilder("\"" + fileName + "\"");
  149. // keep a set of the file names already seen
  150. var setOfFileNames = new HashSet<string> { fileName };
  151. string line;
  152. do
  153. {
  154. line = p.StandardOutput.ReadLine();
  155. if (!string.IsNullOrEmpty(line) && setOfFileNames.Add(line))
  156. {
  157. listOfFileNames.Append(" \"");
  158. listOfFileNames.Append(line);
  159. listOfFileNames.Append('\"');
  160. }
  161. } while (line != null);
  162. // here we need --name-only to get the previous filenames in the revision graph
  163. res.PathFilter = listOfFileNames.ToString();
  164. res.RevisionFilter += " --name-only --parents" + GitCommandHelpers.FindRenamesAndCopiesOpts();
  165. }
  166. else if (AppSettings.FollowRenamesInFileHistory)
  167. {
  168. // history of a directory
  169. // --parents doesn't work with --follow enabled, but needed to graph a filtered log
  170. res.RevisionFilter = " " + GitCommandHelpers.FindRenamesOpt() + " --follow --parents";
  171. }
  172. else
  173. {
  174. // rename following disabled
  175. res.RevisionFilter = " --parents";
  176. }
  177. if (AppSettings.FullHistoryInFileHistory)
  178. {
  179. res.RevisionFilter = string.Concat(" --full-history ", res.RevisionFilter);
  180. }
  181. return res;
  182. }
  183. private void DiffExtraDiffArgumentsChanged(object sender, EventArgs e)
  184. {
  185. UpdateSelectedFileViewers();
  186. }
  187. private void FileChangesSelectionChanged(object sender, EventArgs e)
  188. {
  189. View.SaveCurrentScrollPos();
  190. Diff.SaveCurrentScrollPos();
  191. var selectedRows = FileChanges.GetSelectedRevisions();
  192. if (selectedRows.Count > 0)
  193. {
  194. GitRevision revision = selectedRows[0];
  195. if (revision.IsArtificial())
  196. tabControl1.RemoveIfExists(BlameTab);
  197. else
  198. tabControl1.InsertIfNotExists(2, BlameTab);
  199. }
  200. UpdateSelectedFileViewers();
  201. }
  202. private void SetTitle(string fileName)
  203. {
  204. Text = string.Format("File History - {0}", FileName);
  205. if (!fileName.IsNullOrEmpty() && !fileName.Equals(FileName))
  206. Text = Text + string.Format(" ({0})", fileName);
  207. Text += " - " + Module.WorkingDir;
  208. }
  209. private void UpdateSelectedFileViewers()
  210. {
  211. var selectedRows = FileChanges.GetSelectedRevisions();
  212. if (selectedRows.Count == 0) return;
  213. GitRevision revision = selectedRows[0];
  214. var children = FileChanges.GetRevisionChildren(revision.Guid);
  215. var fileName = revision.Name;
  216. if (string.IsNullOrEmpty(fileName))
  217. fileName = FileName;
  218. SetTitle(fileName);
  219. if (tabControl1.SelectedTab == BlameTab)
  220. Blame.LoadBlame(revision, children, fileName, FileChanges, BlameTab, Diff.Encoding);
  221. else if (tabControl1.SelectedTab == ViewTab)
  222. {
  223. var scrollpos = View.ScrollPos;
  224. View.Encoding = Diff.Encoding;
  225. View.ViewGitItemRevision(fileName, revision.Guid);
  226. View.ScrollPos = scrollpos;
  227. }
  228. else if (tabControl1.SelectedTab == DiffTab)
  229. {
  230. GitItemStatus file = new GitItemStatus();
  231. file.IsTracked = true;
  232. file.Name = fileName;
  233. file.IsSubmodule = GitModule.IsValidGitWorkingDir(Path.Combine(Module.WorkingDir, fileName));
  234. Diff.ViewChanges(FileChanges.GetSelectedRevisions(), file, "You need to select at least one revision to view diff.");
  235. }
  236. if (!EnvUtils.IsMonoRuntime())
  237. {
  238. if (_buildReportTabPageExtension == null)
  239. _buildReportTabPageExtension = new BuildReportTabPageExtension(tabControl1, _buildReportTabCaption.Text);
  240. _buildReportTabPageExtension.FillBuildReport(selectedRows.Count == 1 ? revision : null);
  241. }
  242. }
  243. private BuildReportTabPageExtension _buildReportTabPageExtension;
  244. private void TabControl1SelectedIndexChanged(object sender, EventArgs e)
  245. {
  246. FileChangesSelectionChanged(sender, e);
  247. }
  248. private void FileChangesDoubleClick(object sender, EventArgs e)
  249. {
  250. FileChanges.ViewSelectedRevisions();
  251. }
  252. private void OpenWithDifftoolToolStripMenuItemClick(object sender, EventArgs e)
  253. {
  254. var selectedRows = FileChanges.GetSelectedRevisions();
  255. string orgFileName = null;
  256. if (selectedRows.Count > 0)
  257. {
  258. orgFileName = selectedRows[0].Name;
  259. }
  260. FileChanges.OpenWithDifftool(FileName, orgFileName, GitUIExtensions.DiffWithRevisionKind.DiffAB, null);
  261. }
  262. private void saveAsToolStripMenuItem_Click(object sender, EventArgs e)
  263. {
  264. var selectedRows = FileChanges.GetSelectedRevisions();
  265. if (selectedRows.Count > 0)
  266. {
  267. string orgFileName = selectedRows[0].Name;
  268. if (string.IsNullOrEmpty(orgFileName))
  269. orgFileName = FileName;
  270. string fullName = Module.WorkingDir + orgFileName.ToNativePath();
  271. using (var fileDialog = new SaveFileDialog
  272. {
  273. InitialDirectory = Path.GetDirectoryName(fullName),
  274. FileName = Path.GetFileName(fullName),
  275. DefaultExt = GitCommandHelpers.GetFileExtension(fullName),
  276. AddExtension = true
  277. })
  278. {
  279. fileDialog.Filter =
  280. "Current format (*." +
  281. fileDialog.DefaultExt + ")|*." +
  282. fileDialog.DefaultExt +
  283. "|All files (*.*)|*.*";
  284. if (fileDialog.ShowDialog(this) == DialogResult.OK)
  285. {
  286. Module.SaveBlobAs(fileDialog.FileName, selectedRows[0].Guid + ":\"" + orgFileName + "\"");
  287. }
  288. }
  289. }
  290. }
  291. private void followFileHistoryToolStripMenuItem_Click(object sender, EventArgs e)
  292. {
  293. AppSettings.FollowRenamesInFileHistory = !AppSettings.FollowRenamesInFileHistory;
  294. UpdateFollowHistoryMenuItems();
  295. LoadFileHistory();
  296. }
  297. private void UpdateFollowHistoryMenuItems()
  298. {
  299. followFileHistoryToolStripMenuItem.Checked = AppSettings.FollowRenamesInFileHistory;
  300. followFileHistoryRenamesToolStripMenuItem.Enabled = AppSettings.FollowRenamesInFileHistory;
  301. followFileHistoryRenamesToolStripMenuItem.Checked = AppSettings.FollowRenamesInFileHistoryExactOnly;
  302. }
  303. private void fullHistoryToolStripMenuItem_Click(object sender, EventArgs e)
  304. {
  305. AppSettings.FullHistoryInFileHistory = !AppSettings.FullHistoryInFileHistory;
  306. fullHistoryToolStripMenuItem.Checked = AppSettings.FullHistoryInFileHistory;
  307. LoadFileHistory();
  308. }
  309. private void cherryPickThisCommitToolStripMenuItem_Click(object sender, EventArgs e)
  310. {
  311. var selectedRevisions = FileChanges.GetSelectedRevisions();
  312. if (selectedRevisions.Count == 1)
  313. {
  314. UICommands.StartCherryPickDialog(this, selectedRevisions[0]);
  315. }
  316. }
  317. private void revertCommitToolStripMenuItem_Click(object sender, EventArgs e)
  318. {
  319. var selectedRevisions = FileChanges.GetSelectedRevisions();
  320. if (selectedRevisions.Count == 1)
  321. {
  322. UICommands.StartRevertCommitDialog(this, selectedRevisions[0]);
  323. }
  324. }
  325. private void viewCommitToolStripMenuItem_Click(object sender, EventArgs e)
  326. {
  327. FileChanges.ViewSelectedRevisions();
  328. }
  329. private const string FormBrowseName = "FormBrowse";
  330. public override void AddTranslationItems(ITranslation translation)
  331. {
  332. base.AddTranslationItems(translation);
  333. TranslationUtils.AddTranslationItemsFromFields(FormBrowseName, _filterRevisionsHelper, translation);
  334. TranslationUtils.AddTranslationItemsFromFields(FormBrowseName, _filterBranchHelper, translation);
  335. }
  336. public override void TranslateItems(ITranslation translation)
  337. {
  338. base.TranslateItems(translation);
  339. TranslationUtils.TranslateItemsFromFields(FormBrowseName, _filterRevisionsHelper, translation);
  340. TranslationUtils.TranslateItemsFromFields(FormBrowseName, _filterBranchHelper, translation);
  341. }
  342. private void diffToolremotelocalStripMenuItem_Click(object sender, EventArgs e)
  343. {
  344. FileChanges.OpenWithDifftool(FileName, string.Empty, GitUIExtensions.DiffWithRevisionKind.DiffBLocal, null);
  345. }
  346. private void toolStripSplitLoad_ButtonClick(object sender, EventArgs e)
  347. {
  348. LoadFileHistory();
  349. }
  350. private void loadHistoryOnShowToolStripMenuItem_Click(object sender, EventArgs e)
  351. {
  352. AppSettings.LoadFileHistoryOnShow = !AppSettings.LoadFileHistoryOnShow;
  353. loadHistoryOnShowToolStripMenuItem.Checked = AppSettings.LoadFileHistoryOnShow;
  354. }
  355. private void loadBlameOnShowToolStripMenuItem_Click(object sender, EventArgs e)
  356. {
  357. AppSettings.LoadBlameOnShow = !AppSettings.LoadBlameOnShow;
  358. loadBlameOnShowToolStripMenuItem.Checked = AppSettings.LoadBlameOnShow;
  359. }
  360. private void Blame_CommandClick(object sender, CommitInfo.CommandEventArgs e)
  361. {
  362. if (e.Command == "gotocommit")
  363. {
  364. FileChanges.SetSelectedRevision(new GitRevision(Module, e.Data));
  365. }
  366. else if (e.Command == "gotobranch" || e.Command == "gototag")
  367. {
  368. string error = "";
  369. CommitData commit = CommitData.GetCommitData(Module, e.Data, ref error);
  370. if (commit != null)
  371. FileChanges.SetSelectedRevision(new GitRevision(Module, commit.Guid));
  372. }
  373. else if (e.Command == "navigatebackward")
  374. {
  375. FileChanges.NavigateBackward();
  376. }
  377. else if (e.Command == "navigateforward")
  378. {
  379. FileChanges.NavigateForward();
  380. }
  381. }
  382. private void DiffContextMenu_Opening(object sender, System.ComponentModel.CancelEventArgs e)
  383. {
  384. }
  385. private void ToolStrip_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
  386. {
  387. }
  388. private void followFileHistoryRenamesToolStripMenuItem_Click(object sender, EventArgs e)
  389. {
  390. AppSettings.FollowRenamesInFileHistoryExactOnly = !AppSettings.FollowRenamesInFileHistoryExactOnly;
  391. UpdateFollowHistoryMenuItems();
  392. LoadFileHistory();
  393. }
  394. /// <summary>
  395. /// Clean up any resources being used.
  396. /// </summary>
  397. /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
  398. protected override void Dispose(bool disposing)
  399. {
  400. if (disposing)
  401. {
  402. _asyncLoader.Cancel();
  403. _asyncLoader.Dispose();
  404. _filterRevisionsHelper.Dispose();
  405. _filterBranchHelper.Dispose();
  406. _formBrowseMenus.Dispose();
  407. if (components != null)
  408. components.Dispose();
  409. }
  410. base.Dispose(disposing);
  411. }
  412. }
  413. }