PageRenderTime 61ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/GitUI/CommandsDialogs/FormBrowse.cs

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

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