PageRenderTime 73ms CodeModel.GetById 33ms RepoModel.GetById 1ms app.codeStats 0ms

/GitUI/FormBrowse.cs

https://github.com/eisnerd/gitextensions
C# | 2484 lines | 2357 code | 91 blank | 36 comment | 76 complexity | a754b2455a0e0d098630f5d2bdcf37ed MD5 | raw file
Possible License(s): GPL-3.0, GPL-2.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.Collections.Specialized;
  4. using System.Diagnostics;
  5. using System.Drawing;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Text;
  9. using System.Threading;
  10. using System.Windows.Forms;
  11. using GitCommands;
  12. using GitCommands.Repository;
  13. using GitUI.Hotkey;
  14. using GitUI.Plugin;
  15. using GitUI.RepoHosting;
  16. using GitUI.Script;
  17. using GitUI.Statistics;
  18. using GitUIPluginInterfaces;
  19. using ICSharpCode.TextEditor.Util;
  20. #if !__MonoCS__
  21. using Microsoft.WindowsAPICodePack.Taskbar;
  22. #endif
  23. using ResourceManager.Translation;
  24. namespace GitUI
  25. {
  26. public partial class FormBrowse : GitExtensionsForm
  27. {
  28. #region Translation
  29. private readonly TranslationString _stashCount =
  30. new TranslationString("{0} saved {1}");
  31. private readonly TranslationString _stashPlural =
  32. new TranslationString("stashes");
  33. private readonly TranslationString _stashSingular =
  34. new TranslationString("stash");
  35. private readonly TranslationString _warningMiddleOfBisect =
  36. new TranslationString("Your are in the middle of a bisect");
  37. private readonly TranslationString _warningMiddleOfRebase =
  38. new TranslationString("You are in the middle of a rebase");
  39. private readonly TranslationString _warningMiddleOfPatchApply =
  40. new TranslationString("You are in the middle of a patch apply");
  41. private readonly TranslationString _hintUnresolvedMergeConflicts =
  42. new TranslationString("There are unresolved merge conflicts!");
  43. private static readonly TranslationString _noBranchTitle =
  44. new TranslationString("no branch");
  45. private readonly TranslationString _noSubmodulesPresent =
  46. new TranslationString("No submodules");
  47. private readonly TranslationString _saveFileFilterCurrentFormat =
  48. new TranslationString("Current format");
  49. private readonly TranslationString _saveFileFilterAllFiles =
  50. new TranslationString("All files");
  51. private readonly TranslationString _indexLockDeleted =
  52. new TranslationString("index.lock deleted.");
  53. private readonly TranslationString _indexLockNotFound =
  54. new TranslationString("index.lock not found at:");
  55. private readonly TranslationString _errorCaption =
  56. new TranslationString("Error");
  57. private readonly TranslationString _noReposHostPluginLoaded =
  58. new TranslationString("No repository host plugin loaded.");
  59. private readonly TranslationString _noReposHostFound =
  60. new TranslationString("Could not find any relevant repository hosts for the currently open repository.");
  61. private readonly TranslationString _noRevisionFoundError =
  62. new TranslationString("No revision found.");
  63. private readonly TranslationString _configureWorkingDirMenu =
  64. new TranslationString("Configure this menu");
  65. private readonly TranslationString _UnsupportedMultiselectAction =
  66. new TranslationString("Operation not supported");
  67. #endregion
  68. private string _NoDiffFilesChangesText;
  69. private readonly SynchronizationContext syncContext;
  70. private readonly IndexWatcher _indexWatcher = new IndexWatcher();
  71. private Dashboard _dashboard;
  72. private ToolStripItem _rebase;
  73. private ToolStripItem _bisect;
  74. private ToolStripItem _warning;
  75. #if !__MonoCS__
  76. private ThumbnailToolBarButton _commitButton;
  77. private ThumbnailToolBarButton _pushButton;
  78. private ThumbnailToolBarButton _pullButton;
  79. private bool _toolbarButtonsCreated;
  80. #endif
  81. private bool _dontUpdateOnIndexChange;
  82. private ToolStripGitStatus _toolStripGitStatus;
  83. private FilterRevisionsHelper filterRevisionsHelper;
  84. private FilterBranchHelper _FilterBranchHelper;
  85. public FormBrowse(string filter)
  86. {
  87. syncContext = SynchronizationContext.Current;
  88. InitializeComponent();
  89. filterRevisionsHelper = new FilterRevisionsHelper(toolStripTextBoxFilter, toolStripDropDownButton1, RevisionGrid, toolStripLabel2, this);
  90. _FilterBranchHelper = new FilterBranchHelper(toolStripBranches, toolStripDropDownButton2, RevisionGrid);
  91. Translate();
  92. _NoDiffFilesChangesText = DiffFiles.GetNoFilesText();
  93. #if !__MonoCS__
  94. if (Settings.RunningOnWindows() && TaskbarManager.IsPlatformSupported)
  95. {
  96. TaskbarManager.Instance.ApplicationId = "HenkWesthuis.GitExtensions";
  97. }
  98. #endif
  99. if (Settings.ShowGitStatusInBrowseToolbar)
  100. {
  101. _toolStripGitStatus = new ToolStripGitStatus
  102. {
  103. ImageTransparentColor = System.Drawing.Color.Magenta
  104. };
  105. _toolStripGitStatus.Click += StatusClick;
  106. ToolStrip.Items.Insert(ToolStrip.Items.IndexOf(toolStripButton1), _toolStripGitStatus);
  107. ToolStrip.Items.Remove(toolStripButton1);
  108. _toolStripGitStatus.CommitTranslatedString = toolStripButton1.Text;
  109. }
  110. RevisionGrid.SelectionChanged += RevisionGridSelectionChanged;
  111. DiffText.ExtraDiffArgumentsChanged += DiffTextExtraDiffArgumentsChanged;
  112. filterRevisionsHelper.SetFilter(filter);
  113. DiffText.SetFileLoader(getNextPatchFile);
  114. GitTree.ImageList = new ImageList();
  115. GitTree.ImageList.Images.Add(Properties.Resources.New); //File
  116. GitTree.ImageList.Images.Add(Properties.Resources.Folder); //Folder
  117. GitTree.ImageList.Images.Add(Properties.Resources.Submodule); //Submodule
  118. GitTree.MouseDown += GitTree_MouseDown;
  119. GitTree.MouseMove += GitTree_MouseMove;
  120. this.HotkeysEnabled = true;
  121. this.Hotkeys = HotkeySettingsManager.LoadHotkeys(HotkeySettingsName);
  122. this.toolPanel.SplitterDistance = this.ToolStrip.Height;
  123. this._dontUpdateOnIndexChange = false;
  124. Settings.WorkingDirChanged += (a, b, c) => RefreshPullIcon();
  125. RefreshPullIcon();
  126. dontSetAsDefaultToolStripMenuItem.Checked = Settings.DonSetAsLastPullAction;
  127. GitUICommands.Instance.BrowseInitialize += (a, b) => Initialize();
  128. }
  129. private void ShowDashboard()
  130. {
  131. if (_dashboard == null)
  132. {
  133. _dashboard = new Dashboard();
  134. _dashboard.WorkingDirChanged += DashboardWorkingDirChanged;
  135. toolPanel.Panel2.Controls.Add(_dashboard);
  136. _dashboard.Dock = DockStyle.Fill;
  137. }
  138. else
  139. _dashboard.Refresh();
  140. _dashboard.Visible = true;
  141. _dashboard.BringToFront();
  142. _dashboard.ShowRecentRepositories();
  143. }
  144. private void HideDashboard()
  145. {
  146. if (_dashboard != null)
  147. _dashboard.Visible = false;
  148. }
  149. private void DashboardWorkingDirChanged(object sender, EventArgs e)
  150. {
  151. WorkingDirChanged(false);
  152. }
  153. private void GitTree_AfterSelect(object sender, TreeViewEventArgs e)
  154. {
  155. var item = e.Node.Tag as GitItem;
  156. if (item == null)
  157. return;
  158. if (item.IsBlob)
  159. FileText.ViewGitItem(item.FileName, item.Guid);
  160. else
  161. FileText.ViewText("", "");
  162. }
  163. private void BrowseLoad(object sender, EventArgs e)
  164. {
  165. RevisionGrid.Load();
  166. RestorePosition("browse");
  167. Cursor.Current = Cursors.WaitCursor;
  168. InternalInitialize(false);
  169. RevisionGrid.Focus();
  170. RevisionGrid.ActionOnRepositoryPerformed += ActionOnRepositoryPerformed;
  171. IndexWatcher.Reset();
  172. IndexWatcher.Changed += _indexWatcher_Changed;
  173. Cursor.Current = Cursors.Default;
  174. try
  175. {
  176. if (Settings.PlaySpecialStartupSound)
  177. {
  178. new System.Media.SoundPlayer(Properties.Resources.cow_moo).Play();
  179. }
  180. }
  181. catch // This code is just for fun, we do not want the program to crash because of it.
  182. {
  183. }
  184. }
  185. void _indexWatcher_Changed(bool indexChanged)
  186. {
  187. syncContext.Post(o =>
  188. {
  189. RefreshButton.Image = indexChanged && Settings.UseFastChecks && Settings.Module.ValidWorkingDir()
  190. ? GitUI.Properties.Resources.arrow_refresh_dirty
  191. : GitUI.Properties.Resources.arrow_refresh;
  192. }, this);
  193. }
  194. private bool pluginsLoaded;
  195. private void LoadPluginsInPluginMenu()
  196. {
  197. if (!pluginsLoaded)
  198. {
  199. foreach (var plugin in LoadedPlugins.Plugins)
  200. {
  201. var item = new ToolStripMenuItem { Text = plugin.Description, Tag = plugin };
  202. item.Click += ItemClick;
  203. pluginsToolStripMenuItem.DropDownItems.Add(item);
  204. }
  205. pluginsLoaded = true;
  206. UpdatePluginMenu(Settings.Module.ValidWorkingDir());
  207. }
  208. }
  209. /// <summary>
  210. /// Execute plugin
  211. /// </summary>
  212. private void ItemClick(object sender, EventArgs e)
  213. {
  214. var menuItem = sender as ToolStripMenuItem;
  215. if (menuItem == null)
  216. return;
  217. var plugin = menuItem.Tag as IGitPlugin;
  218. if (plugin == null)
  219. return;
  220. var eventArgs = new GitUIEventArgs(this, GitUICommands.Instance);
  221. string workingDirBefore = Settings.WorkingDir;
  222. bool refresh = plugin.Execute(eventArgs);
  223. if (workingDirBefore != Settings.WorkingDir)
  224. WorkingDirChanged(false);
  225. else if (refresh)
  226. RefreshToolStripMenuItemClick(null, null);
  227. }
  228. private void UpdatePluginMenu(bool validWorkingDir)
  229. {
  230. foreach (ToolStripItem item in pluginsToolStripMenuItem.DropDownItems)
  231. {
  232. var plugin = item.Tag as IGitPluginForRepository;
  233. item.Enabled = plugin == null || validWorkingDir;
  234. }
  235. }
  236. private void ActionOnRepositoryPerformed(object sender, EventArgs e)
  237. {
  238. InternalInitialize(false);
  239. }
  240. protected void Initialize()
  241. {
  242. try
  243. {
  244. InternalInitialize(true);
  245. }
  246. catch (Exception exception)
  247. {
  248. MessageBox.Show(this, exception.Message, _errorCaption.Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
  249. }
  250. }
  251. private void InternalInitialize(bool hard)
  252. {
  253. Cursor.Current = Cursors.WaitCursor;
  254. GitUICommands.Instance.RaisePreBrowseInitialize(this);
  255. bool validWorkingDir = Settings.Module.ValidWorkingDir();
  256. bool hasWorkingDir = !string.IsNullOrEmpty(Settings.WorkingDir);
  257. branchSelect.Text = validWorkingDir ? Settings.Module.GetSelectedBranch() : "";
  258. if (hasWorkingDir)
  259. HideDashboard();
  260. else
  261. ShowDashboard();
  262. CommitInfoTabControl.Visible = validWorkingDir;
  263. fileExplorerToolStripMenuItem.Enabled = validWorkingDir;
  264. commandsToolStripMenuItem.Enabled = validWorkingDir;
  265. manageRemoteRepositoriesToolStripMenuItem1.Enabled = validWorkingDir;
  266. branchSelect.Enabled = validWorkingDir;
  267. toolStripButton1.Enabled = validWorkingDir;
  268. if (_toolStripGitStatus != null)
  269. _toolStripGitStatus.Enabled = validWorkingDir;
  270. toolStripButtonPull.Enabled = validWorkingDir;
  271. toolStripButtonPush.Enabled = validWorkingDir;
  272. submodulesToolStripMenuItem.Enabled = validWorkingDir;
  273. UpdatePluginMenu(validWorkingDir);
  274. gitMaintenanceToolStripMenuItem.Enabled = validWorkingDir;
  275. editgitignoreToolStripMenuItem1.Enabled = validWorkingDir;
  276. editgitattributesToolStripMenuItem.Enabled = validWorkingDir;
  277. editmailmapToolStripMenuItem.Enabled = validWorkingDir;
  278. toolStripSplitStash.Enabled = validWorkingDir;
  279. commitcountPerUserToolStripMenuItem.Enabled = validWorkingDir;
  280. _createPullRequestsToolStripMenuItem.Enabled = validWorkingDir;
  281. _viewPullRequestsToolStripMenuItem.Enabled = validWorkingDir;
  282. //Only show "Repository hosts" menu item when there is at least 1 repository host plugin loaded
  283. _repositoryHostsToolStripMenuItem.Visible = RepoHosts.GitHosters.Count > 0;
  284. if (RepoHosts.GitHosters.Count == 1)
  285. _repositoryHostsToolStripMenuItem.Text = RepoHosts.GitHosters[0].Description;
  286. _FilterBranchHelper.InitToolStripBranchFilter();
  287. if (hard)
  288. ShowRevisions();
  289. RefreshWorkingDirCombo();
  290. Text = GenerateWindowTitle(Settings.WorkingDir, validWorkingDir, branchSelect.Text);
  291. DiffText.Font = Settings.DiffFont;
  292. UpdateJumplist(validWorkingDir);
  293. CheckForMergeConflicts();
  294. UpdateStashCount();
  295. // load custom user menu
  296. LoadUserMenu();
  297. GitUICommands.Instance.RaisePostBrowseInitialize(this);
  298. Cursor.Current = Cursors.Default;
  299. }
  300. private void RefreshWorkingDirCombo()
  301. {
  302. if (Settings.RecentReposComboMinWidth > 0)
  303. {
  304. _NO_TRANSLATE_Workingdir.AutoSize = false;
  305. _NO_TRANSLATE_Workingdir.Width = Settings.RecentReposComboMinWidth;
  306. }
  307. else
  308. _NO_TRANSLATE_Workingdir.AutoSize = true;
  309. Repository r = null;
  310. if (Repositories.RepositoryHistory.Repositories.Count > 0)
  311. r = Repositories.RepositoryHistory.Repositories[0];
  312. List<RecentRepoInfo> mostRecentRepos = new List<RecentRepoInfo>();
  313. if (r == null || !r.Path.Equals(Settings.WorkingDir, StringComparison.InvariantCultureIgnoreCase))
  314. Repositories.AddMostRecentRepository(Settings.WorkingDir);
  315. using (var graphics = CreateGraphics())
  316. {
  317. var splitter = new RecentRepoSplitter
  318. {
  319. measureFont = _NO_TRANSLATE_Workingdir.Font,
  320. graphics = graphics
  321. };
  322. splitter.SplitRecentRepos(Repositories.RepositoryHistory.Repositories, mostRecentRepos, mostRecentRepos);
  323. }
  324. RecentRepoInfo ri = mostRecentRepos.Find((e) => e.Repo.Path.Equals(Settings.WorkingDir, StringComparison.InvariantCultureIgnoreCase));
  325. if (ri == null)
  326. _NO_TRANSLATE_Workingdir.Text = Settings.WorkingDir;
  327. else
  328. _NO_TRANSLATE_Workingdir.Text = ri.Caption;
  329. }
  330. /// <summary>
  331. /// Returns a short name for repository.
  332. /// If the repository contains a description it is returned,
  333. /// otherwise the last part of path is returned.
  334. /// </summary>
  335. /// <param name="repositoryDir">Path to repository.</param>
  336. /// <returns>Short name for repository</returns>
  337. private static String GetRepositoryShortName(string repositoryDir)
  338. {
  339. DirectoryInfo dirInfo = new DirectoryInfo(repositoryDir);
  340. if (dirInfo.Exists)
  341. {
  342. string desc = ReadRepositoryDescription(repositoryDir);
  343. if (desc.IsNullOrEmpty())
  344. {
  345. desc = Repositories.RepositoryHistory.Repositories
  346. .Where(repo => repo.Path.Equals(repositoryDir, StringComparison.CurrentCultureIgnoreCase)).Select(repo => repo.Title)
  347. .FirstOrDefault();
  348. }
  349. return desc ?? dirInfo.Name;
  350. }
  351. return dirInfo.Name;
  352. }
  353. private void LoadUserMenu()
  354. {
  355. var scripts = ScriptManager.GetScripts().Where(script => script.Enabled
  356. && script.OnEvent == ScriptEvent.ShowInUserMenuBar).ToList();
  357. for (int i = ToolStrip.Items.Count - 1; i >= 0; i--)
  358. if (ToolStrip.Items[i].Tag != null &&
  359. ToolStrip.Items[i].Tag as String == "userscript")
  360. ToolStrip.Items.RemoveAt(i);
  361. if (scripts.Count == 0)
  362. return;
  363. ToolStripSeparator toolstripseparator = new ToolStripSeparator();
  364. toolstripseparator.Tag = "userscript";
  365. ToolStrip.Items.Add(toolstripseparator);
  366. foreach (ScriptInfo scriptInfo in scripts)
  367. {
  368. ToolStripButton tempButton = new ToolStripButton();
  369. //store scriptname
  370. tempButton.Text = scriptInfo.Name;
  371. tempButton.Tag = "userscript";
  372. //add handler
  373. tempButton.Click += UserMenu_Click;
  374. tempButton.Enabled = true;
  375. tempButton.Visible = true;
  376. //tempButton.Image = GitUI.Properties.Resources.bug;
  377. //scriptInfo.Icon = "Cow";
  378. tempButton.Image = scriptInfo.GetIcon();
  379. tempButton.DisplayStyle = ToolStripItemDisplayStyle.ImageAndText;
  380. //add to toolstrip
  381. ToolStrip.Items.Add(tempButton);
  382. }
  383. }
  384. private void UserMenu_Click(object sender, EventArgs e)
  385. {
  386. ScriptRunner.RunScript(((ToolStripButton)sender).Text, null);
  387. RevisionGrid.RefreshRevisions();
  388. }
  389. private void UpdateJumplist(bool validWorkingDir)
  390. {
  391. #if !__MonoCS__
  392. if (Settings.RunningOnWindows() && TaskbarManager.IsPlatformSupported)
  393. {
  394. //Call this method using reflection. This is a workaround to *not* reference WPF libraries, becuase of how the WindowsAPICodePack was implimented.
  395. TaskbarManager.Instance.GetType().InvokeMember("SetApplicationIdForSpecificWindow", System.Reflection.BindingFlags.InvokeMethod, null, TaskbarManager.Instance, new object[] { Handle, "GitExtensions" });
  396. if (validWorkingDir)
  397. {
  398. string repositoryDescription = GetRepositoryShortName(Settings.WorkingDir);
  399. string baseFolder = Path.Combine(Settings.ApplicationDataPath, "Recent");
  400. if (!Directory.Exists(baseFolder))
  401. {
  402. Directory.CreateDirectory(baseFolder);
  403. }
  404. //Remove InvalidPathChars
  405. StringBuilder sb = new StringBuilder(repositoryDescription);
  406. foreach (char c in Path.GetInvalidFileNameChars())
  407. {
  408. sb.Replace(c, '_');
  409. }
  410. string path = Path.Combine(baseFolder, String.Format("{0}.{1}", sb, "gitext"));
  411. File.WriteAllText(path, Settings.WorkingDir);
  412. JumpList.AddToRecent(path);
  413. }
  414. CreateOrUpdateTaskBarButtons(validWorkingDir);
  415. }
  416. #endif
  417. }
  418. private void CreateOrUpdateTaskBarButtons(bool validRepo)
  419. {
  420. #if !__MonoCS__
  421. if (Settings.RunningOnWindows() && TaskbarManager.IsPlatformSupported)
  422. {
  423. if (!_toolbarButtonsCreated)
  424. {
  425. _commitButton = new ThumbnailToolBarButton(MakeIcon(toolStripButton1.Image, 48, true), toolStripButton1.Text);
  426. _commitButton.Click += ToolStripButton1Click;
  427. _pushButton = new ThumbnailToolBarButton(MakeIcon(toolStripButtonPush.Image, 48, true), toolStripButtonPush.Text);
  428. _pushButton.Click += PushToolStripMenuItemClick;
  429. _pullButton = new ThumbnailToolBarButton(MakeIcon(toolStripButtonPull.Image, 48, true), toolStripButtonPull.Text);
  430. _pullButton.Click += PullToolStripMenuItemClick;
  431. _toolbarButtonsCreated = true;
  432. ThumbnailToolBarButton[] buttons = new[] { _commitButton, _pullButton, _pushButton };
  433. //Call this method using reflection. This is a workaround to *not* reference WPF libraries, becuase of how the WindowsAPICodePack was implimented.
  434. TaskbarManager.Instance.ThumbnailToolBars.GetType().InvokeMember("AddButtons", System.Reflection.BindingFlags.InvokeMethod, null, TaskbarManager.Instance.ThumbnailToolBars,
  435. new object[] { Handle, buttons });
  436. }
  437. _commitButton.Enabled = validRepo;
  438. _pushButton.Enabled = validRepo;
  439. _pullButton.Enabled = validRepo;
  440. }
  441. #endif
  442. }
  443. /// <summary>
  444. /// Converts an image into an icon. This was taken off of the interwebs.
  445. /// It's on a billion different sites and forum posts, so I would say its creative commons by now. -tekmaven
  446. /// </summary>
  447. /// <param name="img">The image that shall become an icon</param>
  448. /// <param name="size">The width and height of the icon. Standard
  449. /// sizes are 16x16, 32x32, 48x48, 64x64.</param>
  450. /// <param name="keepAspectRatio">Whether the image should be squashed into a
  451. /// square or whether whitespace should be put around it.</param>
  452. /// <returns>An icon!!</returns>
  453. private static Icon MakeIcon(Image img, int size, bool keepAspectRatio)
  454. {
  455. Bitmap square = new Bitmap(size, size); // create new bitmap
  456. Graphics g = Graphics.FromImage(square); // allow drawing to it
  457. int x, y, w, h; // dimensions for new image
  458. if (!keepAspectRatio || img.Height == img.Width)
  459. {
  460. // just fill the square
  461. x = y = 0; // set x and y to 0
  462. w = h = size; // set width and height to size
  463. }
  464. else
  465. {
  466. // work out the aspect ratio
  467. float r = (float)img.Width / (float)img.Height;
  468. // set dimensions accordingly to fit inside size^2 square
  469. if (r > 1)
  470. { // w is bigger, so divide h by r
  471. w = size;
  472. h = (int)((float)size / r);
  473. x = 0; y = (size - h) / 2; // center the image
  474. }
  475. else
  476. { // h is bigger, so multiply w by r
  477. w = (int)((float)size * r);
  478. h = size;
  479. y = 0; x = (size - w) / 2; // center the image
  480. }
  481. }
  482. // make the image shrink nicely by using HighQualityBicubic mode
  483. g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
  484. g.DrawImage(img, x, y, w, h); // draw image with specified dimensions
  485. g.Flush(); // make sure all drawing operations complete before we get the icon
  486. // following line would work directly on any image, but then
  487. // it wouldn't look as nice.
  488. return Icon.FromHandle(square.GetHicon());
  489. }
  490. private void UpdateStashCount()
  491. {
  492. if (Settings.ShowStashCount)
  493. {
  494. int stashCount = Settings.Module.GetStashes().Count;
  495. toolStripSplitStash.Text = string.Format(_stashCount.Text, stashCount,
  496. stashCount != 1 ? _stashPlural.Text : _stashSingular.Text);
  497. }
  498. else
  499. {
  500. toolStripSplitStash.Text = string.Empty;
  501. }
  502. }
  503. private void CheckForMergeConflicts()
  504. {
  505. bool validWorkingDir = Settings.Module.ValidWorkingDir();
  506. if (validWorkingDir && Settings.Module.InTheMiddleOfBisect())
  507. {
  508. if (_bisect == null)
  509. {
  510. _bisect = new WarningToolStripItem { Text = _warningMiddleOfBisect.Text };
  511. _bisect.Click += BisectClick;
  512. statusStrip.Items.Add(_bisect);
  513. }
  514. }
  515. else
  516. {
  517. if (_bisect != null)
  518. {
  519. _bisect.Click -= BisectClick;
  520. statusStrip.Items.Remove(_bisect);
  521. _bisect = null;
  522. }
  523. }
  524. if (validWorkingDir &&
  525. (Settings.Module.InTheMiddleOfRebase() || Settings.Module.InTheMiddleOfPatch()))
  526. {
  527. if (_rebase == null)
  528. {
  529. _rebase = new WarningToolStripItem
  530. {
  531. Text = Settings.Module.InTheMiddleOfRebase()
  532. ? _warningMiddleOfRebase.Text
  533. : _warningMiddleOfPatchApply.Text
  534. };
  535. _rebase.Click += RebaseClick;
  536. statusStrip.Items.Add(_rebase);
  537. }
  538. }
  539. else
  540. {
  541. if (_rebase != null)
  542. {
  543. _rebase.Click -= RebaseClick;
  544. statusStrip.Items.Remove(_rebase);
  545. _rebase = null;
  546. }
  547. }
  548. if (validWorkingDir && Settings.Module.InTheMiddleOfConflictedMerge() &&
  549. !Directory.Exists(Settings.Module.GetGitDirectory() + "rebase-apply\\"))
  550. {
  551. if (_warning == null)
  552. {
  553. _warning = new WarningToolStripItem { Text = _hintUnresolvedMergeConflicts.Text };
  554. _warning.Click += WarningClick;
  555. statusStrip.Items.Add(_warning);
  556. }
  557. }
  558. else
  559. {
  560. if (_warning != null)
  561. {
  562. _warning.Click -= WarningClick;
  563. statusStrip.Items.Remove(_warning);
  564. _warning = null;
  565. }
  566. }
  567. //Only show status strip when there are status items on it.
  568. //There is always a close (x) button, do not count first item.
  569. if (statusStrip.Items.Count > 1)
  570. statusStrip.Show();
  571. else
  572. statusStrip.Hide();
  573. }
  574. /// <summary>
  575. /// Generates main window title according to given repository.
  576. /// </summary>
  577. /// <param name="workingDir">Path to repository.</param>
  578. /// <param name="isWorkingDirValid">If the given path contains valid repository.</param>
  579. /// <param name="branchName">Current branch name.</param>
  580. private static string GenerateWindowTitle(string workingDir, bool isWorkingDirValid, string branchName)
  581. {
  582. #if DEBUG
  583. const string defaultTitle = "Git Extensions -> DEBUG <-";
  584. const string repositoryTitleFormat = "{0} ({1}) - Git Extensions -> DEBUG <-";
  585. #else
  586. const string defaultTitle = "Git Extensions";
  587. const string repositoryTitleFormat = "{0} ({1}) - Git Extensions";
  588. #endif
  589. if (!isWorkingDirValid)
  590. return defaultTitle;
  591. string repositoryDescription = GetRepositoryShortName(workingDir);
  592. if (string.IsNullOrEmpty(branchName))
  593. branchName = _noBranchTitle.Text;
  594. return string.Format(repositoryTitleFormat, repositoryDescription, branchName.Trim('(', ')'));
  595. }
  596. /// <summary>
  597. /// Reads repository description's first line from ".git\description" file.
  598. /// </summary>
  599. /// <param name="workingDir">Path to repository.</param>
  600. /// <returns>If the repository has description, returns that description, else returns <c>null</c>.</returns>
  601. private static string ReadRepositoryDescription(string workingDir)
  602. {
  603. const string repositoryDescriptionFileName = "description";
  604. const string defaultDescription = "Unnamed repository; edit this file 'description' to name the repository.";
  605. var repositoryPath = GitModule.GetGitDirectory(workingDir);
  606. var repositoryDescriptionFilePath = Path.Combine(repositoryPath, repositoryDescriptionFileName);
  607. if (!File.Exists(repositoryDescriptionFilePath))
  608. return null;
  609. try
  610. {
  611. var repositoryDescription = File.ReadAllLines(repositoryDescriptionFilePath).FirstOrDefault();
  612. return string.Equals(repositoryDescription, defaultDescription, StringComparison.CurrentCulture)
  613. ? null
  614. : repositoryDescription;
  615. }
  616. catch (IOException)
  617. {
  618. return null;
  619. }
  620. }
  621. private void RebaseClick(object sender, EventArgs e)
  622. {
  623. if (Settings.Module.InTheMiddleOfRebase())
  624. GitUICommands.Instance.StartRebaseDialog(this, null);
  625. else
  626. GitUICommands.Instance.StartApplyPatchDialog(this);
  627. Initialize();
  628. }
  629. private void ShowRevisions()
  630. {
  631. if (IndexWatcher.IndexChanged)
  632. {
  633. RevisionGrid.RefreshRevisions();
  634. FillFileTree();
  635. FillDiff();
  636. FillCommitInfo();
  637. }
  638. IndexWatcher.Reset();
  639. }
  640. //store strings to not keep references to nodes
  641. private readonly Stack<string> lastSelectedNodes = new Stack<string>();
  642. private void FillFileTree()
  643. {
  644. if (CommitInfoTabControl.SelectedTab != Tree)
  645. return;
  646. try
  647. {
  648. GitTree.SuspendLayout();
  649. // Save state only when there is selected node
  650. if (GitTree.SelectedNode != null)
  651. {
  652. TreeNode node = GitTree.SelectedNode;
  653. FileText.SaveCurrentScrollPos();
  654. lastSelectedNodes.Clear();
  655. while (node != null)
  656. {
  657. lastSelectedNodes.Push(node.Text);
  658. node = node.Parent;
  659. }
  660. }
  661. // Refresh tree
  662. GitTree.Nodes.Clear();
  663. //restore selected file and scroll position when new selection is done
  664. if (RevisionGrid.GetSelectedRevisions().Count > 0)
  665. {
  666. LoadInTree(RevisionGrid.GetSelectedRevisions()[0].SubItems, GitTree.Nodes);
  667. //GitTree.Sort();
  668. TreeNode lastMatchedNode = null;
  669. // Load state
  670. var currenNodes = GitTree.Nodes;
  671. TreeNode matchedNode = null;
  672. while (lastSelectedNodes.Count > 0 && currenNodes != null)
  673. {
  674. var next = lastSelectedNodes.Pop();
  675. foreach (TreeNode node in currenNodes)
  676. {
  677. if (node.Text != next && next.Length != 40)
  678. continue;
  679. node.Expand();
  680. matchedNode = node;
  681. break;
  682. }
  683. if (matchedNode == null)
  684. currenNodes = null;
  685. else
  686. {
  687. lastMatchedNode = matchedNode;
  688. currenNodes = matchedNode.Nodes;
  689. }
  690. }
  691. //if there is no exact match, don't restore scroll position
  692. if (lastMatchedNode != matchedNode)
  693. FileText.ResetCurrentScrollPos();
  694. GitTree.SelectedNode = lastMatchedNode;
  695. }
  696. if (GitTree.SelectedNode == null)
  697. {
  698. FileText.ViewText("", "");
  699. }
  700. }
  701. finally
  702. {
  703. GitTree.ResumeLayout();
  704. }
  705. }
  706. private void FillDiff()
  707. {
  708. if (CommitInfoTabControl.SelectedTab != Diff)
  709. return;
  710. var revisions = RevisionGrid.GetSelectedRevisions();
  711. DiffText.SaveCurrentScrollPos();
  712. DiffFiles.SetNoFilesText(_NoDiffFilesChangesText);
  713. switch (revisions.Count)
  714. {
  715. case 2:
  716. bool artificialRevSelected = revisions[0].IsArtificial() || revisions[1].IsArtificial();
  717. if (artificialRevSelected)
  718. {
  719. DiffFiles.SetNoFilesText(_UnsupportedMultiselectAction.Text);
  720. DiffFiles.GitItemStatuses = null;
  721. }
  722. else
  723. DiffFiles.GitItemStatuses =
  724. Settings.Module.GetDiffFiles(revisions[0].Guid, revisions[1].Guid);
  725. break;
  726. case 0:
  727. DiffFiles.GitItemStatuses = null;
  728. return;
  729. default:
  730. var revision = revisions[0];
  731. DiffFiles.Revision = revision;
  732. if (revision == null)
  733. DiffFiles.GitItemStatuses = null;
  734. else if (revision.ParentGuids == null || revision.ParentGuids.Length == 0)
  735. DiffFiles.GitItemStatuses = Settings.Module.GetTreeFiles(revision.TreeGuid, true);
  736. else
  737. {
  738. if (revision.Guid == GitRevision.UncommittedWorkingDirGuid) //working dir changes
  739. DiffFiles.GitItemStatuses = Settings.Module.GetUnstagedFiles();
  740. else if (revision.Guid == GitRevision.IndexGuid) //index
  741. DiffFiles.GitItemStatuses = Settings.Module.GetStagedFiles();
  742. else
  743. DiffFiles.GitItemStatuses = Settings.Module.GetDiffFiles(revision.Guid, revision.ParentGuids[0]);
  744. }
  745. break;
  746. }
  747. }
  748. private void FillCommitInfo()
  749. {
  750. if (CommitInfoTabControl.SelectedTab != CommitInfo)
  751. return;
  752. if (RevisionGrid.GetSelectedRevisions().Count == 0)
  753. return;
  754. var revision = RevisionGrid.GetSelectedRevisions()[0];
  755. if (revision != null)
  756. RevisionInfo.SetRevision(revision.Guid);
  757. }
  758. public void FileHistoryOnClick(object sender, EventArgs e)
  759. {
  760. var item = GitTree.SelectedNode.Tag as GitItem;
  761. if (item == null)
  762. return;
  763. if (item.IsBlob || item.IsTree)
  764. {
  765. IList<GitRevision> revisions = RevisionGrid.GetSelectedRevisions();
  766. if (revisions.Count == 0)
  767. GitUICommands.Instance.StartFileHistoryDialog(this, item.FileName);
  768. else
  769. GitUICommands.Instance.StartFileHistoryDialog(this, item.FileName, revisions[0], false, false);
  770. }
  771. }
  772. public void FindFileOnClick(object sender, EventArgs e)
  773. {
  774. var searchWindow = new SearchWindow<string>(FindFileMatches)
  775. {
  776. Owner = this
  777. };
  778. searchWindow.ShowDialog(this);
  779. string selectedItem = searchWindow.SelectedItem;
  780. if (string.IsNullOrEmpty(selectedItem))
  781. {
  782. return;
  783. }
  784. string[] items = selectedItem.Split(new[] { '/' });
  785. TreeNodeCollection nodes = GitTree.Nodes;
  786. for (int i = 0; i < items.Length - 1; i++)
  787. {
  788. TreeNode selectedNode = Find(nodes, items[i]);
  789. if (selectedNode == null)
  790. {
  791. return; //Item does not exist in the tree
  792. }
  793. selectedNode.Expand();
  794. nodes = selectedNode.Nodes;
  795. }
  796. var lastItem = Find(nodes, items[items.Length - 1]);
  797. if (lastItem != null)
  798. {
  799. GitTree.SelectedNode = lastItem;
  800. }
  801. }
  802. private static TreeNode Find(TreeNodeCollection nodes, string label)
  803. {
  804. for (int i = 0; i < nodes.Count; i++)
  805. {
  806. if (nodes[i].Text == label)
  807. {
  808. return nodes[i];
  809. }
  810. }
  811. return null;
  812. }
  813. private IList<string> FindFileMatches(string name)
  814. {
  815. var candidates = Settings.Module.GetFullTree(RevisionGrid.GetSelectedRevisions()[0].TreeGuid);
  816. string nameAsLower = name.ToLower();
  817. return candidates.Where(fileName => fileName.ToLower().Contains(nameAsLower)).ToList();
  818. }
  819. public void OpenWithOnClick(object sender, EventArgs e)
  820. {
  821. var gitItem = GitTree.SelectedNode.Tag as GitItem;
  822. if (gitItem == null || !gitItem.IsBlob)
  823. return;
  824. var fileName = gitItem.FileName;
  825. if (fileName.Contains("\\") && fileName.LastIndexOf("\\") < fileName.Length)
  826. fileName = fileName.Substring(fileName.LastIndexOf('\\') + 1);
  827. if (fileName.Contains("/") && fileName.LastIndexOf("/") < fileName.Length)
  828. fileName = fileName.Substring(fileName.LastIndexOf('/') + 1);
  829. fileName = (Path.GetTempPath() + fileName).Replace(Settings.PathSeparatorWrong, Settings.PathSeparator);
  830. Settings.Module.SaveBlobAs(fileName, gitItem.Guid);
  831. OpenWith.OpenAs(fileName);
  832. }
  833. private void FileTreeContextMenu_Opening(object sender, System.ComponentModel.CancelEventArgs e)
  834. {
  835. var gitItem = (GitTree.SelectedNode != null) ? GitTree.SelectedNode.Tag as GitItem : null;
  836. var enableItems = gitItem != null && gitItem.IsBlob;
  837. saveAsToolStripMenuItem.Enabled = enableItems;
  838. openFileToolStripMenuItem.Enabled = enableItems;
  839. openFileWithToolStripMenuItem.Enabled = enableItems;
  840. openWithToolStripMenuItem.Enabled = enableItems;
  841. copyFilenameToClipboardToolStripMenuItem.Enabled = enableItems;
  842. fileHistoryToolStripMenuItem.Enabled = enableItems;
  843. editCheckedOutFileToolStripMenuItem.Enabled = enableItems;
  844. }
  845. public void OpenOnClick(object sender, EventArgs e)
  846. {
  847. try
  848. {
  849. var gitItem = GitTree.SelectedNode.Tag as GitItem;
  850. if (gitItem == null || !gitItem.IsBlob)
  851. return;
  852. var fileName = gitItem.FileName;
  853. if (fileName.Contains("\\") && fileName.LastIndexOf("\\") < fileName.Length)
  854. fileName = fileName.Substring(fileName.LastIndexOf('\\') + 1);
  855. if (fileName.Contains("/") && fileName.LastIndexOf("/") < fileName.Length)
  856. fileName = fileName.Substring(fileName.LastIndexOf('/') + 1);
  857. fileName = (Path.GetTempPath() + fileName).Replace(Settings.PathSeparatorWrong, Settings.PathSeparator);
  858. Settings.Module.SaveBlobAs(fileName, (gitItem).Guid);
  859. Process.Start(fileName);
  860. }
  861. catch (Exception ex)
  862. {
  863. MessageBox.Show(this, ex.Message);
  864. }
  865. }
  866. protected void LoadInTree(List<IGitItem> items, TreeNodeCollection node)
  867. {
  868. items.Sort(new GitFileTreeComparer());
  869. foreach (var item in items)
  870. {
  871. var subNode = node.Add(item.Name);
  872. subNode.Tag = item;
  873. var gitItem = item as GitItem;
  874. if (gitItem == null)
  875. subNode.Nodes.Add(new TreeNode());
  876. else
  877. {
  878. if (gitItem.IsTree)
  879. {
  880. subNode.ImageIndex = 1;
  881. subNode.SelectedImageIndex = 1;
  882. subNode.Nodes.Add(new TreeNode());
  883. }
  884. else
  885. if (gitItem.IsCommit)
  886. {
  887. subNode.ImageIndex = 2;
  888. subNode.SelectedImageIndex = 2;
  889. subNode.Text = item.Name + " (Submodule)";
  890. }
  891. }
  892. }
  893. }
  894. private void RevisionGridSelectionChanged(object sender, EventArgs e)
  895. {
  896. try
  897. {
  898. var revisions = RevisionGrid.GetSelectedRevisions();
  899. if (revisions.Count > 0 &&
  900. (revisions[0].Guid == GitRevision.UncommittedWorkingDirGuid ||
  901. revisions[0].Guid == GitRevision.IndexGuid))
  902. {
  903. CommitInfoTabControl.RemoveIfExists(CommitInfo);
  904. CommitInfoTabControl.RemoveIfExists(Tree);
  905. }
  906. else
  907. {
  908. CommitInfoTabControl.InsertIfNotExists(0, CommitInfo);
  909. CommitInfoTabControl.InsertIfNotExists(1, Tree);
  910. }
  911. FillFileTree();
  912. FillDiff();
  913. FillCommitInfo();
  914. }
  915. catch (Exception ex)
  916. {
  917. Trace.WriteLine(ex.Message);
  918. }
  919. }
  920. private void OpenToolStripMenuItemClick(object sender, EventArgs e)
  921. {
  922. if (new Open().ShowDialog(this) == DialogResult.OK)
  923. {
  924. WorkingDirChanged(false);
  925. }
  926. }
  927. private void CheckoutToolStripMenuItemClick(object sender, EventArgs e)
  928. {
  929. if (GitUICommands.Instance.StartCheckoutRevisionDialog(this))
  930. Initialize();
  931. }
  932. private void GitTreeDoubleClick(object sender, EventArgs e)
  933. {
  934. if (GitTree.SelectedNode == null || !(GitTree.SelectedNode.Tag is IGitItem))
  935. return;
  936. var item = GitTree.SelectedNode.Tag as GitItem;
  937. if (item == null)
  938. return;
  939. if (!item.IsBlob)
  940. return;
  941. if (GitUICommands.Instance.StartFileHistoryDialog(this, item.FileName, null))
  942. Initialize();
  943. }
  944. private void ViewDiffToolStripMenuItemClick(object sender, EventArgs e)
  945. {
  946. if (GitUICommands.Instance.StartCompareRevisionsDialog(this))
  947. Initialize();
  948. }
  949. private void CloneToolStripMenuItemClick(object sender, EventArgs e)
  950. {
  951. if (GitUICommands.Instance.StartCloneDialog(this))
  952. Initialize();
  953. }
  954. private void CommitToolStripMenuItemClick(object sender, EventArgs e)
  955. {
  956. GitUICommands.Instance.StartCommitDialog(Initialize, this);
  957. }
  958. private void InitNewRepositoryToolStripMenuItemClick(object sender, EventArgs e)
  959. {
  960. if (GitUICommands.Instance.StartInitializeDialog(this))
  961. Initialize();
  962. }
  963. private void PushToolStripMenuItemClick(object sender, EventArgs e)
  964. {
  965. bool bSilent = (ModifierKeys & Keys.Shift) != 0;
  966. if (GitUICommands.Instance.StartPushDialog(this, bSilent))
  967. Initialize();
  968. }
  969. private void PullToolStripMenuItemClick(object sender, EventArgs e)
  970. {
  971. bool bSilent;
  972. if (sender == toolStripButtonPull || sender == pullToolStripMenuItem)
  973. {
  974. if (Settings.LastPullAction == Settings.PullAction.None)
  975. {
  976. bSilent = (ModifierKeys & Keys.Shift) != 0;
  977. }
  978. else
  979. {
  980. bSilent = true;
  981. Settings.LastPullActionToPullMerge();
  982. }
  983. }
  984. else
  985. {
  986. bSilent = sender != pullToolStripMenuItem1;
  987. RefreshPullIcon();
  988. Settings.LastPullActionToPullMerge();
  989. }
  990. if (GitUICommands.Instance.StartPullDialog(this, bSilent))
  991. Initialize();
  992. }
  993. private void RefreshToolStripMenuItemClick(object sender, EventArgs e)
  994. {
  995. if (_dashboard != null)
  996. {
  997. _dashboard.Refresh();
  998. }
  999. if (_dashboard == null || !_dashboard.Visible)
  1000. {
  1001. RevisionGrid.ForceRefreshRevisions();
  1002. InternalInitialize(false);
  1003. IndexWatcher.Reset();
  1004. }
  1005. }
  1006. private void AboutToolStripMenuItemClick(object sender, EventArgs e)
  1007. {
  1008. new AboutBox().ShowDialog(this);
  1009. }
  1010. private void PatchToolStripMenuItemClick(object sender, EventArgs e)
  1011. {
  1012. if (GitUICommands.Instance.StartViewPatchDialog(this))
  1013. Initialize();
  1014. }
  1015. private void ApplyPatchToolStripMenuItemClick(object sender, EventArgs e)
  1016. {
  1017. if (GitUICommands.Instance.StartApplyPatchDialog(this))
  1018. Initialize();
  1019. }
  1020. private void GitBashToolStripMenuItemClick1(object sender, EventArgs e)
  1021. {
  1022. Settings.Module.RunBash();
  1023. }
  1024. private void GitGuiToolStripMenuItemClick(object sender, EventArgs e)
  1025. {
  1026. Settings.Module.RunGui();
  1027. }
  1028. private void FormatPatchToolStripMenuItemClick(object sender, EventArgs e)
  1029. {
  1030. if (GitUICommands.Instance.StartFormatPatchDialog(this))
  1031. Initialize();
  1032. }
  1033. private void GitcommandLogToolStripMenuItemClick(object sender, EventArgs e)
  1034. {
  1035. GitLogForm.ShowOrActivate(this);
  1036. }
  1037. private void CheckoutBranchToolStripMenuItemClick(object sender, EventArgs e)
  1038. {
  1039. if (GitUICommands.Instance.StartCheckoutBranchDialog(this))
  1040. Initialize();
  1041. }
  1042. private void StashToolStripMenuItemClick(object sender, EventArgs e)
  1043. {
  1044. if (GitUICommands.Instance.StartStashDialog(this))
  1045. Initialize();
  1046. }
  1047. private void RunMergetoolToolStripMenuItemClick(object sender, EventArgs e)
  1048. {
  1049. if (GitUICommands.Instance.StartResolveConflictsDialog(this))
  1050. Initialize();
  1051. }
  1052. private void WarningClick(object sender, EventArgs e)
  1053. {
  1054. if (GitUICommands.Instance.StartResolveConflictsDialog(this))
  1055. Initialize();
  1056. }
  1057. private void WorkingdirClick(object sender, EventArgs e)
  1058. {
  1059. _NO_TRANSLATE_Workingdir.ShowDropDown();
  1060. }
  1061. private void CurrentBranchClick(object sender, EventArgs e)
  1062. {
  1063. branchSelect.ShowDropDown();
  1064. }
  1065. private void DeleteBranchToolStripMenuItemClick(object sender, EventArgs e)
  1066. {
  1067. if (GitUICommands.Instance.StartDeleteBranchDialog(this, null))
  1068. Initialize();
  1069. }
  1070. private void CherryPickToolStripMenuItemClick(object sender, EventArgs e)
  1071. {
  1072. if (GitUICommands.Instance.StartCherryPickDialog(this))
  1073. Initialize();
  1074. }
  1075. private void MergeBranchToolStripMenuItemClick(object sender, EventArgs e)
  1076. {
  1077. if (GitUICommands.Instance.StartMergeBranchDialog(this, null))
  1078. Initialize();
  1079. }
  1080. private void ToolStripButton1Click(object sender, EventArgs e)
  1081. {
  1082. CommitToolStripMenuItemClick(sender, e);
  1083. }
  1084. private void Set

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