PageRenderTime 60ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 1ms

/GitUI/CommandsDialogs/FormCommit.cs

https://github.com/vbjay/gitextensions
C# | 2418 lines | 1982 code | 387 blank | 49 comment | 337 complexity | 5e44b918597f049a2c4e0d09a95b13e9 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.ObjectModel;
  4. using System.Diagnostics;
  5. using System.Drawing;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Text;
  9. using System.Text.RegularExpressions;
  10. using System.Threading;
  11. using System.Threading.Tasks;
  12. using System.Windows.Forms;
  13. using GitCommands;
  14. using GitCommands.Utils;
  15. using GitUI.CommandsDialogs.CommitDialog;
  16. using GitUI.HelperDialogs;
  17. using GitUI.Hotkey;
  18. using GitUI.Script;
  19. using PatchApply;
  20. using ResourceManager;
  21. using Timer = System.Windows.Forms.Timer;
  22. namespace GitUI.CommandsDialogs
  23. {
  24. public sealed partial class FormCommit : GitModuleForm //, IHotkeyable
  25. {
  26. const string USER_NAME_KEY = "user.name";
  27. const string USER_EMAIL_KEY = "user.email";
  28. #region Translation
  29. private readonly TranslationString _amendCommit =
  30. new TranslationString("You are about to rewrite history." + Environment.NewLine +
  31. "Only use amend if the commit is not published yet!" + Environment.NewLine +
  32. Environment.NewLine + "Do you want to continue?");
  33. private readonly TranslationString _amendCommitCaption = new TranslationString("Amend commit");
  34. private readonly TranslationString _deleteFailed = new TranslationString("Delete file failed");
  35. private readonly TranslationString _deleteSelectedFiles =
  36. new TranslationString("Are you sure you want delete the selected file(s)?");
  37. private readonly TranslationString _deleteSelectedFilesCaption = new TranslationString("Delete");
  38. private readonly TranslationString _deleteUntrackedFiles =
  39. new TranslationString("Are you sure you want to delete all untracked files?");
  40. private readonly TranslationString _deleteUntrackedFilesCaption =
  41. new TranslationString("Delete untracked files.");
  42. private readonly TranslationString _enterCommitMessage = new TranslationString("Please enter commit message");
  43. private readonly TranslationString _enterCommitMessageCaption = new TranslationString("Commit message");
  44. private readonly TranslationString _commitMessageDisabled = new TranslationString("Commit Message is requested during commit");
  45. private readonly TranslationString _enterCommitMessageHint = new TranslationString("Enter commit message");
  46. private readonly TranslationString _mergeConflicts =
  47. new TranslationString("There are unresolved mergeconflicts, solve mergeconflicts before committing.");
  48. private readonly TranslationString _mergeConflictsCaption = new TranslationString("Merge conflicts");
  49. private readonly TranslationString _noFilesStagedAndNothingToCommit =
  50. new TranslationString("There are no files staged for this commit.");
  51. private readonly TranslationString _noFilesStagedButSuggestToCommitAllUnstaged =
  52. new TranslationString("There are no files staged for this commit. Stage and commit all unstaged files?");
  53. private readonly TranslationString _noFilesStagedAndConfirmAnEmptyMergeCommit =
  54. new TranslationString("There are no files staged for this commit.\nAre you sure you want to commit?");
  55. private readonly TranslationString _noStagedChanges = new TranslationString("There are no staged changes");
  56. private readonly TranslationString _noUnstagedChanges = new TranslationString("There are no unstaged changes");
  57. private readonly TranslationString _notOnBranchMainInstruction = new TranslationString("You are not working on a branch");
  58. private readonly TranslationString _notOnBranch =
  59. new TranslationString("This commit will be unreferenced when switching to another branch and can be lost." +
  60. Environment.NewLine + "" + Environment.NewLine + "Do you want to continue?");
  61. private readonly TranslationString _notOnBranchButtons = new TranslationString("Checkout branch|Create branch|Continue");
  62. private readonly TranslationString _notOnBranchCaption = new TranslationString("Not on a branch");
  63. private readonly TranslationString _onlyStageChunkOfSingleFileError =
  64. new TranslationString("You can only use this option when selecting a single file");
  65. private readonly TranslationString _resetChangesCaption = new TranslationString("Reset changes");
  66. private readonly TranslationString _resetSelectedChangesText =
  67. new TranslationString("Are you sure you want to reset all selected files?");
  68. private readonly TranslationString _resetStageChunkOfFileCaption = new TranslationString("Unstage chunk of file");
  69. private readonly TranslationString _stageDetails = new TranslationString("Stage Details");
  70. private readonly TranslationString _stageFiles = new TranslationString("Stage {0} files");
  71. private readonly TranslationString _selectOnlyOneFile = new TranslationString("You must have only one file selected.");
  72. private readonly TranslationString _selectOnlyOneFileCaption = new TranslationString("Error");
  73. private readonly TranslationString _stageSelectedLines = new TranslationString("Stage selected line(s)");
  74. private readonly TranslationString _unstageSelectedLines = new TranslationString("Unstage selected line(s)");
  75. private readonly TranslationString _resetSelectedLines = new TranslationString("Reset selected line(s)");
  76. private readonly TranslationString _resetSelectedLinesConfirmation = new TranslationString("Are you sure you want to reset the changes to the selected lines?");
  77. private readonly TranslationString _formTitle = new TranslationString("Commit to {0} ({1})");
  78. private readonly TranslationString _selectionFilterToolTip = new TranslationString("Enter a regular expression to select unstaged files.");
  79. private readonly TranslationString _selectionFilterErrorToolTip = new TranslationString("Error {0}");
  80. private readonly TranslationString _commitMsgFirstLineInvalid = new TranslationString("First line of commit message contains too many characters."
  81. + Environment.NewLine + "Do you want to continue?");
  82. private readonly TranslationString _commitMsgLineInvalid = new TranslationString("The following line of commit message contains too many characters:"
  83. + Environment.NewLine + Environment.NewLine + "{0}" + Environment.NewLine + Environment.NewLine + "Do you want to continue?");
  84. private readonly TranslationString _commitMsgSecondLineNotEmpty = new TranslationString("Second line of commit message is not empty." + Environment.NewLine + "Do you want to continue?");
  85. private readonly TranslationString _commitMsgRegExNotMatched = new TranslationString("Commit message does not match RegEx." + Environment.NewLine + "Do you want to continue?");
  86. private readonly TranslationString _commitValidationCaption = new TranslationString("Commit validation");
  87. private readonly TranslationString _commitTemplateSettings = new TranslationString("Settings");
  88. private readonly TranslationString _commitAuthorInfo = new TranslationString("Author ");
  89. private readonly TranslationString _commitCommitterInfo = new TranslationString("Committer ");
  90. private readonly TranslationString _commitCommitterToolTip = new TranslationString("Click to change committer information.");
  91. #endregion
  92. private FileStatusList _currentFilesList;
  93. private bool _skipUpdate;
  94. private readonly TaskScheduler _taskScheduler;
  95. private GitItemStatus _currentItem;
  96. private bool _currentItemStaged;
  97. private readonly CommitKind _commitKind;
  98. private readonly GitRevision _editedCommit;
  99. private readonly ToolStripMenuItem _stageSelectedLinesToolStripMenuItem;
  100. private readonly ToolStripMenuItem _resetSelectedLinesToolStripMenuItem;
  101. private string _commitTemplate;
  102. private bool IsMergeCommit { get; set; }
  103. private bool _shouldRescanChanges = true;
  104. private bool _shouldReloadCommitTemplates = true;
  105. private readonly AsyncLoader _unstagedLoader;
  106. private readonly bool _useFormCommitMessage;
  107. private CancellationTokenSource _interactiveAddBashCloseWaitCts = new CancellationTokenSource();
  108. private string _userName = "";
  109. private string _userEmail = "";
  110. private bool RepoUserSettings = false;
  111. /// <summary>
  112. /// For VS designer
  113. /// </summary>
  114. private FormCommit()
  115. : this(null)
  116. {
  117. }
  118. public FormCommit(GitUICommands aCommands)
  119. : this(aCommands, CommitKind.Normal, null)
  120. { }
  121. public FormCommit(GitUICommands aCommands, CommitKind commitKind, GitRevision editedCommit)
  122. : base(true, aCommands)
  123. {
  124. _taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
  125. _unstagedLoader = new AsyncLoader(_taskScheduler);
  126. _useFormCommitMessage = AppSettings.UseFormCommitMessage;
  127. InitializeComponent();
  128. Message.TextChanged += Message_TextChanged;
  129. Message.TextAssigned += Message_TextAssigned;
  130. Loading.Image = Properties.Resources.loadingpanel;
  131. Translate();
  132. SolveMergeconflicts.Font = new Font(SystemFonts.MessageBoxFont, FontStyle.Bold);
  133. SelectedDiff.ExtraDiffArgumentsChanged += SelectedDiffExtraDiffArgumentsChanged;
  134. if (IsUICommandsInitialized)
  135. StageInSuperproject.Visible = Module.SuperprojectModule != null;
  136. StageInSuperproject.Checked = AppSettings.StageInSuperprojectAfterCommit;
  137. closeDialogAfterEachCommitToolStripMenuItem.Checked = AppSettings.CloseCommitDialogAfterCommit;
  138. closeDialogAfterAllFilesCommittedToolStripMenuItem.Checked = AppSettings.CloseCommitDialogAfterLastCommit;
  139. refreshDialogOnFormFocusToolStripMenuItem.Checked = AppSettings.RefreshCommitDialogOnFormFocus;
  140. Unstaged.SetNoFilesText(_noUnstagedChanges.Text);
  141. Staged.SetNoFilesText(_noStagedChanges.Text);
  142. Message.Enabled = _useFormCommitMessage;
  143. commitMessageToolStripMenuItem.Enabled = _useFormCommitMessage;
  144. commitTemplatesToolStripMenuItem.Enabled = _useFormCommitMessage;
  145. Message.WatermarkText = _useFormCommitMessage
  146. ? _enterCommitMessageHint.Text
  147. : _commitMessageDisabled.Text;
  148. _commitKind = commitKind;
  149. _editedCommit = editedCommit;
  150. HotkeysEnabled = true;
  151. Hotkeys = HotkeySettingsManager.LoadHotkeys(HotkeySettingsName);
  152. SelectedDiff.AddContextMenuSeparator();
  153. _stageSelectedLinesToolStripMenuItem = SelectedDiff.AddContextMenuEntry(_stageSelectedLines.Text, StageSelectedLinesToolStripMenuItemClick);
  154. _stageSelectedLinesToolStripMenuItem.ShortcutKeyDisplayString = GetShortcutKeys((int)Commands.StageSelectedFile).ToShortcutKeyDisplayString();
  155. _resetSelectedLinesToolStripMenuItem = SelectedDiff.AddContextMenuEntry(_resetSelectedLines.Text, ResetSelectedLinesToolStripMenuItemClick);
  156. _resetSelectedLinesToolStripMenuItem.ShortcutKeyDisplayString = GetShortcutKeys((int)Commands.ResetSelectedFiles).ToShortcutKeyDisplayString();
  157. _resetSelectedLinesToolStripMenuItem.Image = Reset.Image;
  158. resetChanges.ShortcutKeyDisplayString = _resetSelectedLinesToolStripMenuItem.ShortcutKeyDisplayString;
  159. commitAuthorStatus.ToolTipText = _commitCommitterToolTip.Text;
  160. toolAuthor.Control.PreviewKeyDown += ToolAuthor_PreviewKeyDown;
  161. }
  162. void ToolAuthor_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
  163. {
  164. if (e.Alt)
  165. e.IsInputKey = true;
  166. }
  167. private void FormCommit_Load(object sender, EventArgs e)
  168. {
  169. if (AppSettings.CommitDialogSplitter != -1)
  170. splitMain.SplitterDistance = AppSettings.CommitDialogSplitter;
  171. if (AppSettings.CommitDialogRightSplitter != -1)
  172. splitRight.SplitterDistance = AppSettings.CommitDialogRightSplitter;
  173. Reset.Visible = AppSettings.ShowResetAllChanges;
  174. ResetUnStaged.Visible = AppSettings.ShowResetUnstagedChanges;
  175. CommitAndPush.Visible = AppSettings.ShowCommitAndPush;
  176. AdjustCommitButtonPanelHeight();
  177. }
  178. private void AdjustCommitButtonPanelHeight()
  179. {
  180. splitRight.Panel2MinSize = Math.Max(splitRight.Panel2MinSize, flowCommitButtons.PreferredSize.Height);
  181. splitRight.SplitterDistance = Math.Min(splitRight.SplitterDistance, splitRight.Height - splitRight.Panel2MinSize);
  182. }
  183. private void FormCommitFormClosing(object sender, FormClosingEventArgs e)
  184. {
  185. // Do not remember commit message of fixup or squash commits, since they have
  186. // a special meaning, and can be dangerous if used inappropriately.
  187. if (CommitKind.Normal == _commitKind)
  188. GitCommands.CommitHelper.SetCommitMessage(Module, Message.Text);
  189. AppSettings.CommitDialogSplitter = splitMain.SplitterDistance;
  190. AppSettings.CommitDialogRightSplitter = splitRight.SplitterDistance;
  191. }
  192. void SelectedDiff_ContextMenuOpening(object sender, System.ComponentModel.CancelEventArgs e)
  193. {
  194. _stageSelectedLinesToolStripMenuItem.Enabled = SelectedDiff.HasAnyPatches() || _currentItem != null && _currentItem.IsNew;
  195. _resetSelectedLinesToolStripMenuItem.Enabled = _stageSelectedLinesToolStripMenuItem.Enabled;
  196. }
  197. #region Hotkey commands
  198. public const string HotkeySettingsName = "Commit";
  199. internal enum Commands
  200. {
  201. AddToGitIgnore,
  202. DeleteSelectedFiles,
  203. FocusUnstagedFiles,
  204. FocusSelectedDiff,
  205. FocusStagedFiles,
  206. FocusCommitMessage,
  207. ResetSelectedFiles,
  208. StageSelectedFile,
  209. UnStageSelectedFile,
  210. ToggleSelectionFilter
  211. }
  212. private bool AddToGitIgnore()
  213. {
  214. if (Unstaged.Focused)
  215. {
  216. AddFileTogitignoreToolStripMenuItemClick(this, null);
  217. return true;
  218. }
  219. return false;
  220. }
  221. private bool DeleteSelectedFiles()
  222. {
  223. if (Unstaged.Focused)
  224. {
  225. DeleteFileToolStripMenuItemClick(this, null);
  226. return true;
  227. }
  228. return false;
  229. }
  230. private bool FocusStagedFiles()
  231. {
  232. Staged.Focus();
  233. return true;
  234. }
  235. private bool FocusUnstagedFiles()
  236. {
  237. Unstaged.Focus();
  238. return true;
  239. }
  240. private bool FocusSelectedDiff()
  241. {
  242. SelectedDiff.Focus();
  243. return true;
  244. }
  245. private bool FocusCommitMessage()
  246. {
  247. Message.Focus();
  248. return true;
  249. }
  250. private bool ResetSelectedFiles()
  251. {
  252. if (Unstaged.Focused)
  253. {
  254. ResetSoftClick(this, null);
  255. return true;
  256. }
  257. else if (SelectedDiff.ContainsFocus && _resetSelectedLinesToolStripMenuItem.Enabled)
  258. {
  259. ResetSelectedLinesToolStripMenuItemClick(this, null);
  260. return true;
  261. }
  262. return false;
  263. }
  264. private bool StageSelectedFile()
  265. {
  266. if (Unstaged.Focused)
  267. {
  268. StageClick(this, null);
  269. return true;
  270. }
  271. else if (SelectedDiff.ContainsFocus && !_currentItemStaged && _stageSelectedLinesToolStripMenuItem.Enabled)
  272. {
  273. StageSelectedLinesToolStripMenuItemClick(this, null);
  274. return true;
  275. }
  276. return false;
  277. }
  278. private bool UnStageSelectedFile()
  279. {
  280. if (Staged.Focused)
  281. {
  282. UnstageFilesClick(this, null);
  283. return true;
  284. }
  285. else if (SelectedDiff.ContainsFocus && _currentItemStaged && _stageSelectedLinesToolStripMenuItem.Enabled)
  286. {
  287. StageSelectedLinesToolStripMenuItemClick(this, null);
  288. return true;
  289. }
  290. return false;
  291. }
  292. private bool ToggleSelectionFilter()
  293. {
  294. selectionFilterToolStripMenuItem.Checked = !selectionFilterToolStripMenuItem.Checked;
  295. toolbarSelectionFilter.Visible = selectionFilterToolStripMenuItem.Checked;
  296. return true;
  297. }
  298. protected override bool ExecuteCommand(int cmd)
  299. {
  300. switch ((Commands)cmd)
  301. {
  302. case Commands.AddToGitIgnore: return AddToGitIgnore();
  303. case Commands.DeleteSelectedFiles: return DeleteSelectedFiles();
  304. case Commands.FocusStagedFiles: return FocusStagedFiles();
  305. case Commands.FocusUnstagedFiles: return FocusUnstagedFiles();
  306. case Commands.FocusSelectedDiff: return FocusSelectedDiff();
  307. case Commands.FocusCommitMessage: return FocusCommitMessage();
  308. case Commands.ResetSelectedFiles: return ResetSelectedFiles();
  309. case Commands.StageSelectedFile: return StageSelectedFile();
  310. case Commands.UnStageSelectedFile: return UnStageSelectedFile();
  311. case Commands.ToggleSelectionFilter: return ToggleSelectionFilter();
  312. default: return base.ExecuteCommand(cmd);
  313. }
  314. }
  315. #endregion
  316. public void ShowDialogWhenChanges()
  317. {
  318. ShowDialogWhenChanges(null);
  319. }
  320. private void ComputeUnstagedFiles(Action<IList<GitItemStatus>> onComputed, bool DoAsync)
  321. {
  322. Func<IList<GitItemStatus>> getAllChangedFilesWithSubmodulesStatus = () => Module.GetAllChangedFilesWithSubmodulesStatus(
  323. !showIgnoredFilesToolStripMenuItem.Checked,
  324. showUntrackedFilesToolStripMenuItem.Checked ? UntrackedFilesMode.Default : UntrackedFilesMode.No);
  325. if (DoAsync)
  326. _unstagedLoader.Load(getAllChangedFilesWithSubmodulesStatus, onComputed);
  327. else
  328. {
  329. _unstagedLoader.Cancel();
  330. onComputed(getAllChangedFilesWithSubmodulesStatus());
  331. }
  332. }
  333. public void ShowDialogWhenChanges(IWin32Window owner)
  334. {
  335. ComputeUnstagedFiles((allChangedFiles) =>
  336. {
  337. if (allChangedFiles.Count > 0)
  338. {
  339. LoadUnstagedOutput(allChangedFiles);
  340. Initialize(false);
  341. ShowDialog(owner);
  342. }
  343. else
  344. Close();
  345. #if !__MonoCS__ // animated GIFs are not supported in Mono/Linux
  346. //trying to properly dispose loading image issue #1037
  347. Loading.Image.Dispose();
  348. #endif
  349. }, false
  350. );
  351. }
  352. private bool _selectedDiffReloaded = true;
  353. private void StageSelectedLinesToolStripMenuItemClick(object sender, EventArgs e)
  354. {
  355. //to prevent multiple clicks
  356. if (!_selectedDiffReloaded)
  357. return;
  358. Debug.Assert(_currentItem != null);
  359. // Prepare git command
  360. string args = "apply --cached --whitespace=nowarn";
  361. if (_currentItemStaged) //staged
  362. args += " --reverse";
  363. byte[] patch;
  364. if (!_currentItemStaged && _currentItem.IsNew)
  365. patch = PatchManager.GetSelectedLinesAsNewPatch(Module, _currentItem.Name,
  366. SelectedDiff.GetText(), SelectedDiff.GetSelectionPosition(),
  367. SelectedDiff.GetSelectionLength(), SelectedDiff.Encoding, false);
  368. else
  369. patch = PatchManager.GetSelectedLinesAsPatch(Module, SelectedDiff.GetText(),
  370. SelectedDiff.GetSelectionPosition(), SelectedDiff.GetSelectionLength(),
  371. _currentItemStaged, SelectedDiff.Encoding, _currentItem.IsNew);
  372. if (patch != null && patch.Length > 0)
  373. {
  374. string output = Module.RunGitCmd(args, null, patch);
  375. ProcessApplyOutput(output, patch);
  376. }
  377. }
  378. private void ProcessApplyOutput(string output, byte[] patch)
  379. {
  380. if (!string.IsNullOrEmpty(output))
  381. {
  382. MessageBox.Show(this, output + "\n\n" + SelectedDiff.Encoding.GetString(patch));
  383. }
  384. if (_currentItemStaged)
  385. Staged.StoreNextIndexToSelect();
  386. else
  387. Unstaged.StoreNextIndexToSelect();
  388. ScheduleGoToLine();
  389. _selectedDiffReloaded = false;
  390. RescanChanges();
  391. }
  392. private void ScheduleGoToLine()
  393. {
  394. int selectedDifflineToSelect = SelectedDiff.GetText().Substring(0, SelectedDiff.GetSelectionPosition()).Count(c => c == '\n');
  395. int scrollPosition = SelectedDiff.ScrollPos;
  396. string selectedFileName = _currentItem.Name;
  397. Action stageAreaLoaded = null;
  398. stageAreaLoaded = () =>
  399. {
  400. EventHandler textLoaded = null;
  401. textLoaded = (a, b) =>
  402. {
  403. if (_currentItem != null && _currentItem.Name.Equals(selectedFileName))
  404. {
  405. SelectedDiff.GoToLine(selectedDifflineToSelect);
  406. SelectedDiff.ScrollPos = scrollPosition;
  407. }
  408. SelectedDiff.TextLoaded -= textLoaded;
  409. _selectedDiffReloaded = true;
  410. };
  411. SelectedDiff.TextLoaded += textLoaded;
  412. OnStageAreaLoaded -= stageAreaLoaded;
  413. };
  414. OnStageAreaLoaded += stageAreaLoaded;
  415. }
  416. private void ResetSelectedLinesToolStripMenuItemClick(object sender, EventArgs e)
  417. {
  418. //to prevent multiple clicks
  419. if (!_selectedDiffReloaded)
  420. return;
  421. if (MessageBox.Show(this, _resetSelectedLinesConfirmation.Text, _resetChangesCaption.Text,
  422. MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No)
  423. return;
  424. Debug.Assert(_currentItem != null);
  425. // Prepare git command
  426. string args = "apply --whitespace=nowarn";
  427. if (_currentItemStaged) //staged
  428. args += " --reverse --index";
  429. byte[] patch;
  430. if (_currentItemStaged)
  431. patch = PatchManager.GetSelectedLinesAsPatch(Module, SelectedDiff.GetText(),
  432. SelectedDiff.GetSelectionPosition(), SelectedDiff.GetSelectionLength(),
  433. _currentItemStaged, SelectedDiff.Encoding, _currentItem.IsNew);
  434. else if (_currentItem.IsNew)
  435. patch = PatchManager.GetSelectedLinesAsNewPatch(Module, _currentItem.Name,
  436. SelectedDiff.GetText(), SelectedDiff.GetSelectionPosition(), SelectedDiff.GetSelectionLength(),
  437. SelectedDiff.Encoding, true);
  438. else
  439. patch = PatchManager.GetResetUnstagedLinesAsPatch(Module, SelectedDiff.GetText(),
  440. SelectedDiff.GetSelectionPosition(), SelectedDiff.GetSelectionLength(),
  441. _currentItemStaged, SelectedDiff.Encoding);
  442. if (patch != null && patch.Length > 0)
  443. {
  444. string output = Module.RunGitCmd(args, null, patch);
  445. if (EnvUtils.RunningOnWindows())
  446. {
  447. //remove file mode warnings on windows
  448. Regex regEx = new Regex("warning: .*has type .* expected .*", RegexOptions.Compiled);
  449. output = output.RemoveLines(regEx.IsMatch);
  450. }
  451. ProcessApplyOutput(output, patch);
  452. }
  453. }
  454. private void EnableStageButtons(bool enable)
  455. {
  456. toolUnstageItem.Enabled = enable;
  457. toolUnstageAllItem.Enabled = enable;
  458. toolStageItem.Enabled = enable;
  459. toolStageAllItem.Enabled = enable;
  460. workingToolStripMenuItem.Enabled = enable;
  461. ResetUnStaged.Enabled = Unstaged.AllItems.Any();
  462. }
  463. private bool _initialized;
  464. private void Initialize(bool loadUnstaged)
  465. {
  466. _initialized = true;
  467. Cursor.Current = Cursors.WaitCursor;
  468. if (loadUnstaged)
  469. {
  470. Loading.Visible = true;
  471. LoadingStaged.Visible = true;
  472. Commit.Enabled = false;
  473. CommitAndPush.Enabled = false;
  474. Amend.Enabled = false;
  475. Reset.Enabled = false;
  476. ResetUnStaged.Enabled = false;
  477. EnableStageButtons(false);
  478. ComputeUnstagedFiles(LoadUnstagedOutput, true);
  479. }
  480. UpdateMergeHead();
  481. Message.TextBoxFont = AppSettings.CommitFont;
  482. // Check if commit.template is used
  483. string fileName = Module.GetEffectivePathSetting("commit.template");
  484. if (!string.IsNullOrEmpty(fileName))
  485. {
  486. using (var commitReader = new StreamReader(fileName))
  487. {
  488. _commitTemplate = commitReader.ReadToEnd().Replace("\r", "");
  489. }
  490. Message.Text = _commitTemplate;
  491. }
  492. Cursor.Current = Cursors.Default;
  493. }
  494. private void Initialize()
  495. {
  496. Initialize(true);
  497. }
  498. private void UpdateMergeHead()
  499. {
  500. var mergeHead = Module.RevParse("MERGE_HEAD");
  501. IsMergeCommit = Regex.IsMatch(mergeHead, GitRevision.Sha1HashPattern);
  502. }
  503. private void InitializedStaged()
  504. {
  505. Cursor.Current = Cursors.WaitCursor;
  506. SolveMergeconflicts.Visible = Module.InTheMiddleOfConflictedMerge();
  507. Staged.GitItemStatuses = Module.GetStagedFilesWithSubmodulesStatus();
  508. Cursor.Current = Cursors.Default;
  509. }
  510. private event Action OnStageAreaLoaded;
  511. private bool _loadUnstagedOutputFirstTime = true;
  512. /// <summary>
  513. /// Loads the unstaged output.
  514. /// This method is passed in to the SetTextCallBack delegate
  515. /// to set the Text property of textBox1.
  516. /// </summary>
  517. private void LoadUnstagedOutput(IList<GitItemStatus> allChangedFiles)
  518. {
  519. var lastSelection = new List<GitItemStatus>();
  520. if (_currentFilesList != null)
  521. lastSelection = _currentSelection;
  522. var unStagedFiles = new List<GitItemStatus>();
  523. var stagedFiles = new List<GitItemStatus>();
  524. foreach (var fileStatus in allChangedFiles)
  525. {
  526. if (fileStatus.IsStaged)
  527. stagedFiles.Add(fileStatus);
  528. else
  529. unStagedFiles.Add(fileStatus);
  530. }
  531. Unstaged.GitItemStatuses = unStagedFiles;
  532. Staged.GitItemStatuses = stagedFiles;
  533. Loading.Visible = false;
  534. LoadingStaged.Visible = false;
  535. Commit.Enabled = true;
  536. CommitAndPush.Enabled = true;
  537. Amend.Enabled = true;
  538. Reset.Enabled = DoChangesExist();
  539. EnableStageButtons(true);
  540. workingToolStripMenuItem.Enabled = true;
  541. var inTheMiddleOfConflictedMerge = Module.InTheMiddleOfConflictedMerge();
  542. SolveMergeconflicts.Visible = inTheMiddleOfConflictedMerge;
  543. if (Staged.IsEmpty)
  544. {
  545. _currentFilesList = Unstaged;
  546. if (Staged.ContainsFocus)
  547. Unstaged.Focus();
  548. }
  549. else if (Unstaged.IsEmpty)
  550. {
  551. _currentFilesList = Staged;
  552. if (Unstaged.ContainsFocus)
  553. Staged.Focus();
  554. }
  555. RestoreSelectedFiles(unStagedFiles, stagedFiles, lastSelection);
  556. if (OnStageAreaLoaded != null)
  557. OnStageAreaLoaded();
  558. if (_loadUnstagedOutputFirstTime)
  559. {
  560. var fc = this.FindFocusedControl();
  561. if (fc == this.Ok)
  562. {
  563. if (Unstaged.GitItemStatuses.Any())
  564. Unstaged.Focus();
  565. else if (Staged.GitItemStatuses.Any())
  566. Message.Focus();
  567. else
  568. Amend.Focus();
  569. }
  570. _loadUnstagedOutputFirstTime = false;
  571. }
  572. }
  573. private void SelectStoredNextIndex()
  574. {
  575. Unstaged.SelectStoredNextIndex(0);
  576. if (Unstaged.GitItemStatuses.Any())
  577. {
  578. Staged.SelectStoredNextIndex();
  579. }
  580. else
  581. {
  582. Staged.SelectStoredNextIndex(0);
  583. }
  584. }
  585. private void RestoreSelectedFiles(IList<GitItemStatus> unStagedFiles, IList<GitItemStatus> stagedFiles, IList<GitItemStatus> lastSelection)
  586. {
  587. if (_currentFilesList == null || _currentFilesList.IsEmpty)
  588. {
  589. SelectStoredNextIndex();
  590. return;
  591. }
  592. var newItems = _currentFilesList == Staged ? stagedFiles : unStagedFiles;
  593. var names = lastSelection.ToHashSet(x => x.Name);
  594. var newSelection = newItems.Where(x => names.Contains(x.Name)).ToList();
  595. if (newSelection.Any())
  596. _currentFilesList.SelectedItems = newSelection;
  597. else
  598. SelectStoredNextIndex();
  599. }
  600. /// <summary>Returns if there are any changes at all, staged or unstaged.</summary>
  601. private bool DoChangesExist()
  602. {
  603. return Unstaged.AllItems.Any() || Staged.AllItems.Any();
  604. }
  605. private void ShowChanges(GitItemStatus item, bool staged)
  606. {
  607. _currentItem = item;
  608. _currentItemStaged = staged;
  609. if (item == null)
  610. return;
  611. long length = GetItemLength(item.Name);
  612. if (length < 5 * 1024 * 1024) // 5Mb
  613. SetSelectedDiff(item, staged);
  614. else
  615. {
  616. SelectedDiff.Clear();
  617. SelectedDiff.Refresh();
  618. llShowPreview.Show();
  619. }
  620. _stageSelectedLinesToolStripMenuItem.Text = staged ? _unstageSelectedLines.Text : _stageSelectedLines.Text;
  621. _stageSelectedLinesToolStripMenuItem.Image = staged ? toolUnstageItem.Image : toolStageItem.Image;
  622. _stageSelectedLinesToolStripMenuItem.ShortcutKeyDisplayString =
  623. GetShortcutKeys((int)(staged ? Commands.UnStageSelectedFile : Commands.StageSelectedFile)).ToShortcutKeyDisplayString();
  624. }
  625. private long GetItemLength(string fileName)
  626. {
  627. long length = -1;
  628. string path = fileName;
  629. if (!File.Exists(fileName))
  630. path = Path.Combine(Module.WorkingDir, fileName);
  631. if (File.Exists(path))
  632. {
  633. FileInfo fi = new FileInfo(path);
  634. length = fi.Length;
  635. }
  636. return length;
  637. }
  638. private void llShowPreview_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
  639. {
  640. llShowPreview.Hide();
  641. SetSelectedDiff(_currentItem, _currentItemStaged);
  642. }
  643. private void SetSelectedDiff(GitItemStatus item, bool staged)
  644. {
  645. if (item.Name.EndsWith(".png"))
  646. {
  647. SelectedDiff.ViewFile(item.Name);
  648. }
  649. else if (item.IsTracked)
  650. {
  651. SelectedDiff.ViewCurrentChanges(item, staged);
  652. }
  653. else
  654. {
  655. SelectedDiff.ViewFile(item.Name);
  656. }
  657. }
  658. private List<GitItemStatus> _currentSelection;
  659. private void ClearDiffViewIfNoFilesLeft()
  660. {
  661. llShowPreview.Hide();
  662. if (Staged.IsEmpty && Unstaged.IsEmpty)
  663. SelectedDiff.Clear();
  664. }
  665. private void CommitClick(object sender, EventArgs e)
  666. {
  667. ExecuteCommitCommand();
  668. }
  669. private void CheckForStagedAndCommit(bool amend, bool push)
  670. {
  671. if (amend)
  672. {
  673. // This is an amend commit. Confirm the user understands the implications. We don't want to prompt for an empty
  674. // commit, because amend may be used just to change the commit message or timestamp.
  675. if (!AppSettings.DontConfirmAmend)
  676. if (MessageBox.Show(this, _amendCommit.Text, _amendCommitCaption.Text, MessageBoxButtons.YesNo) != DialogResult.Yes)
  677. return;
  678. }
  679. else if (Staged.IsEmpty)
  680. {
  681. if (IsMergeCommit)
  682. {
  683. // it is a merge commit, so user can commit just for merging two branches even the changeset is empty,
  684. // but also user may forget to add files, so only ask for confirmation that user really wants to commit an empty changeset
  685. if (MessageBox.Show(this, _noFilesStagedAndConfirmAnEmptyMergeCommit.Text, _noStagedChanges.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes)
  686. return;
  687. }
  688. else
  689. {
  690. if (Unstaged.IsEmpty)
  691. {
  692. MessageBox.Show(this, _noFilesStagedAndNothingToCommit.Text, _noStagedChanges.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
  693. return;
  694. }
  695. // there are no staged files, but there are unstaged files. Most probably user forgot to stage them.
  696. if (MessageBox.Show(this, _noFilesStagedButSuggestToCommitAllUnstaged.Text, _noStagedChanges.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes)
  697. return;
  698. StageAll();
  699. // if staging failed (i.e. line endings conflict), user already got error message, don't try to commit empty changeset.
  700. if (Staged.IsEmpty)
  701. return;
  702. }
  703. }
  704. DoCommit(amend, push);
  705. }
  706. private void DoCommit(bool amend, bool push)
  707. {
  708. if (Module.InTheMiddleOfConflictedMerge())
  709. {
  710. MessageBox.Show(this, _mergeConflicts.Text, _mergeConflictsCaption.Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
  711. return;
  712. }
  713. if (_useFormCommitMessage && (string.IsNullOrEmpty(Message.Text) || Message.Text == _commitTemplate))
  714. {
  715. MessageBox.Show(this, _enterCommitMessage.Text, _enterCommitMessageCaption.Text, MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
  716. return;
  717. }
  718. if (_useFormCommitMessage && !ValidCommitMessage())
  719. return;
  720. if (Module.IsDetachedHead())
  721. {
  722. int idx = PSTaskDialog.cTaskDialog.ShowCommandBox(this,
  723. _notOnBranchCaption.Text,
  724. _notOnBranchMainInstruction.Text,
  725. _notOnBranch.Text,
  726. _notOnBranchButtons.Text,
  727. true);
  728. switch (idx)
  729. {
  730. case 0:
  731. string revision = _editedCommit != null ? _editedCommit.Guid : "";
  732. if (!UICommands.StartCheckoutBranch(this, new[] {revision}))
  733. return;
  734. break;
  735. case 1:
  736. if (!UICommands.StartCreateBranchDialog(this, _editedCommit))
  737. return;
  738. break;
  739. case -1:
  740. return;
  741. }
  742. }
  743. try
  744. {
  745. if (_useFormCommitMessage)
  746. {
  747. SetCommitMessageFromTextBox(Message.Text);
  748. }
  749. ScriptManager.RunEventScripts(this, ScriptEvent.BeforeCommit);
  750. var errorOccurred = !FormProcess.ShowDialog(this, Module.CommitCmd(amend, signOffToolStripMenuItem.Checked, toolAuthor.Text, _useFormCommitMessage));
  751. UICommands.RepoChangedNotifier.Notify();
  752. if (errorOccurred)
  753. return;
  754. Amend.Checked = false;
  755. ScriptManager.RunEventScripts(this, ScriptEvent.AfterCommit);
  756. Message.Text = string.Empty;
  757. CommitHelper.SetCommitMessage(Module, string.Empty);
  758. bool pushCompleted = true;
  759. if (push)
  760. {
  761. UICommands.StartPushDialog(this, true, out pushCompleted);
  762. }
  763. if (pushCompleted && Module.SuperprojectModule != null && AppSettings.StageInSuperprojectAfterCommit)
  764. Module.SuperprojectModule.StageFile(Module.SubmodulePath);
  765. if (AppSettings.CloseCommitDialogAfterCommit)
  766. {
  767. Close();
  768. return;
  769. }
  770. if (Unstaged.GitItemStatuses.Any())
  771. {
  772. InitializedStaged();
  773. return;
  774. }
  775. if (AppSettings.CloseCommitDialogAfterLastCommit)
  776. Close();
  777. else
  778. InitializedStaged();
  779. }
  780. catch (Exception e)
  781. {
  782. MessageBox.Show(this, string.Format("Exception: {0}", e.Message));
  783. }
  784. }
  785. private bool ValidCommitMessage()
  786. {
  787. if (AppSettings.CommitValidationMaxCntCharsFirstLine > 0)
  788. {
  789. var firstLine = Message.Text.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries)[0];
  790. if (firstLine.Length > AppSettings.CommitValidationMaxCntCharsFirstLine)
  791. {
  792. if (DialogResult.No == MessageBox.Show(this, _commitMsgFirstLineInvalid.Text, _commitValidationCaption.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Asterisk))
  793. return false;
  794. }
  795. }
  796. if (AppSettings.CommitValidationMaxCntCharsPerLine > 0)
  797. {
  798. var lines = Message.Text.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
  799. foreach (var line in lines)
  800. {
  801. if (line.Length > AppSettings.CommitValidationMaxCntCharsPerLine)
  802. {
  803. if (DialogResult.No == MessageBox.Show(this, String.Format(_commitMsgLineInvalid.Text, line), _commitValidationCaption.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Asterisk))
  804. return false;
  805. }
  806. }
  807. }
  808. if (AppSettings.CommitValidationSecondLineMustBeEmpty)
  809. {
  810. var lines = Message.Text.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None);
  811. if (lines.Length > 2)
  812. {
  813. if (lines[1].Length != 0)
  814. {
  815. if (DialogResult.No == MessageBox.Show(this, _commitMsgSecondLineNotEmpty.Text, _commitValidationCaption.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Asterisk))
  816. return false;
  817. }
  818. }
  819. }
  820. if (!AppSettings.CommitValidationRegEx.IsNullOrEmpty())
  821. {
  822. try
  823. {
  824. if (!Regex.IsMatch(Message.Text, AppSettings.CommitValidationRegEx))
  825. {
  826. if (DialogResult.No == MessageBox.Show(this, _commitMsgRegExNotMatched.Text, _commitValidationCaption.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Asterisk))
  827. return false;
  828. }
  829. }
  830. catch
  831. {
  832. }
  833. }
  834. return true;
  835. }
  836. private void RescanChanges()
  837. {
  838. if (_shouldRescanChanges)
  839. {
  840. toolRefreshItem.Enabled = false;
  841. Initialize();
  842. toolRefreshItem.Enabled = true;
  843. }
  844. }
  845. private void UnstageFilesClick(object sender, EventArgs e)
  846. {
  847. if (_currentFilesList != Staged)
  848. return;
  849. Unstage();
  850. }
  851. void Staged_DoubleClick(object sender, EventArgs e)
  852. {
  853. _currentFilesList = Staged;
  854. Unstage();
  855. }
  856. private void UnstageAllToolStripMenuItemClick(object sender, EventArgs e)
  857. {
  858. var lastSelection = new List<GitItemStatus>();
  859. if (_currentFilesList != null)
  860. lastSelection = _currentSelection;
  861. Action stageAreaLoaded = null;
  862. stageAreaLoaded = () =>
  863. {
  864. _currentFilesList = Unstaged;
  865. RestoreSelectedFiles(Unstaged.GitItemStatuses, Staged.GitItemStatuses, lastSelection);
  866. Unstaged.Focus();
  867. OnStageAreaLoaded -= stageAreaLoaded;
  868. };
  869. OnStageAreaLoaded += stageAreaLoaded;
  870. Module.ResetMixed("HEAD");
  871. Initialize();
  872. }
  873. private void UnstagedSelectionChanged(object sender, EventArgs e)
  874. {
  875. if (_currentFilesList != Unstaged || _skipUpdate)
  876. return;
  877. ClearDiffViewIfNoFilesLeft();
  878. Unstaged.ContextMenuStrip = null;
  879. if (!Unstaged.SelectedItems.Any())
  880. return;
  881. Staged.ClearSelected();
  882. _currentSelection = Unstaged.SelectedItems.ToList();
  883. GitItemStatus item = _currentSelection.FirstOrDefault();
  884. ShowChanges(item, false);
  885. if (!item.IsSubmodule)
  886. Unstaged.ContextMenuStrip = UnstagedFileContext;
  887. else
  888. Unstaged.ContextMenuStrip = UnstagedSubmoduleContext;
  889. }
  890. private void Unstaged_Enter(object sender, EventArgs e)
  891. {
  892. if (_currentFilesList != Unstaged)
  893. {
  894. _currentFilesList = Unstaged;
  895. _skipUpdate = false;
  896. UnstagedSelectionChanged(Unstaged, null);
  897. }
  898. }
  899. private void Unstage()
  900. {
  901. if (Staged.GitItemStatuses.Count() > 10 && Staged.SelectedItems.Count() == Staged.GitItemStatuses.Count())
  902. {
  903. UnstageAllToolStripMenuItemClick(null, null);
  904. return;
  905. }
  906. Cursor.Current = Cursors.WaitCursor;
  907. EnableStageButtons(false);
  908. try
  909. {
  910. var lastSelection = new List<GitItemStatus>();
  911. if (_currentFilesList != null)
  912. lastSelection = _currentSelection;
  913. toolStripProgressBar1.Visible = true;
  914. toolStripProgressBar1.Maximum = Staged.SelectedItems.Count() * 2;
  915. toolStripProgressBar1.Value = 0;
  916. Staged.StoreNextIndexToSelect();
  917. var files = new List<GitItemStatus>();
  918. var allFiles = new List<GitItemStatus>();
  919. foreach (var item in Staged.SelectedItems)
  920. {
  921. toolStripProgressBar1.Value = Math.Min(toolStripProgressBar1.Maximum - 1, toolStripProgressBar1.Value + 1);
  922. if (!item.IsNew)
  923. {
  924. toolStripProgressBar1.Value = Math.Min(toolStripProgressBar1.Maximum - 1, toolStripProgressBar1.Value + 1);
  925. Module.UnstageFileToRemove(item.Name);
  926. if (item.IsRenamed)
  927. Module.UnstageFileToRemove(item.OldName);
  928. }
  929. else
  930. {
  931. files.Add(item);
  932. }
  933. allFiles.Add(item);
  934. }
  935. Module.UnstageFiles(files);
  936. _skipUpdate = true;
  937. InitializedStaged();
  938. var stagedFiles = Staged.GitItemStatuses.ToList();
  939. var unStagedFiles = Unstaged.GitItemStatuses.ToList();
  940. foreach (var item in allFiles)
  941. {
  942. var item1 = item;
  943. if (stagedFiles.Exists(i => i.Name == item1.Name))
  944. continue;
  945. var item2 = item;
  946. if (unStagedFiles.Exists(i => i.Name == item2.Name))
  947. continue;
  948. if (item.IsNew && !item.IsChanged && !item.IsDeleted)
  949. item.IsTracked = false;
  950. else
  951. item.IsTracked = true;
  952. if (item.IsRenamed)
  953. {
  954. var clone = new GitItemStatus
  955. {
  956. Name = item.OldName,
  957. IsDeleted = true,
  958. IsTracked = true,
  959. IsStaged = false
  960. };
  961. unStagedFiles.Add(clone);
  962. item.IsRenamed = false;
  963. item.IsNew = true;
  964. item.IsTracked = false;
  965. item.OldName = string.Empty;
  966. }
  967. item.IsStaged = false;
  968. unStagedFiles.Add(item);
  969. }
  970. Staged.GitItemStatuses = stagedFiles;
  971. Unstaged.GitItemStatuses = unStagedFiles;
  972. _skipUpdate = false;
  973. Staged.SelectStoredNextIndex();
  974. toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
  975. toolStripProgressBar1.Visible = false;
  976. if (Staged.IsEmpty)
  977. {
  978. _currentFilesList = Unstaged;
  979. RestoreSelectedFiles(Unstaged.GitItemStatuses, Staged.GitItemStatuses, lastSelection);
  980. Unstaged.Focus();
  981. }
  982. }
  983. catch (Exception ex)
  984. {
  985. Trace.WriteLine(ex.Message);
  986. }
  987. EnableStageButtons(true);
  988. Cursor.Current = Cursors.Default;
  989. if (AppSettings.RevisionGraphShowWorkingDirChanges)
  990. UICommands.RepoChangedNotifier.Notify();
  991. }
  992. private void StageClick(object sender, EventArgs e)
  993. {
  994. if (_currentFilesList != Unstaged)
  995. return;
  996. Stage(Unstaged.SelectedItems.ToList());
  997. if (Unstaged.IsEmpty)
  998. Staged.Focus();
  999. }
  1000. void Unstaged_DoubleClick(object sender, EventArgs e)
  1001. {
  1002. _currentFilesList = Unstaged;
  1003. Stage(Unstaged.SelectedItems.ToList());
  1004. if (Unstaged.IsEmpty)
  1005. Staged.Focus();
  1006. }
  1007. private void StageAll()
  1008. {
  1009. Stage(Unstaged.GitItemStatuses);
  1010. Staged.Focus();
  1011. }
  1012. private void StageAllToolStripMenuItemClick(object sender, EventArgs e)
  1013. {
  1014. StageAll();
  1015. }
  1016. private void StagedSelectionChanged(object sender, EventArgs e)
  1017. {
  1018. if (_currentFilesList != Staged || _skipUpdate)
  1019. return;
  1020. ClearDiffViewIfNoFilesLeft();
  1021. if (!Staged.SelectedItems.Any())
  1022. return;
  1023. Unstaged.ClearSelected();
  1024. _currentSelection = Staged.SelectedItems.ToList();
  1025. GitItemStatus item = _currentSelection.FirstOrDefault();
  1026. ShowChanges(item, true);
  1027. }
  1028. private void Staged_Enter(object sender, EventArgs e)
  1029. {
  1030. if (_currentFilesList != Staged)
  1031. {
  1032. _currentFilesList = Staged;
  1033. _skipUpdate = false;
  1034. StagedSelectionChanged(Staged, null);
  1035. }
  1036. }
  1037. private void Stage(IList<GitItemStatus> gitItemStatusses)
  1038. {
  1039. EnableStageButtons(false);
  1040. try
  1041. {
  1042. var lastSelection = new List<GitItemStatus>();
  1043. if (_currentFilesList != null)
  1044. lastSelection = _currentSelection;
  1045. Cursor.Current = Cursors.WaitCursor;
  1046. Unstaged.StoreNextIndexToSelect();
  1047. toolStripProgressB

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