PageRenderTime 59ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/GitUI/FormCommit.cs

https://github.com/eisnerd/gitextensions
C# | 1919 lines | 1556 code | 323 blank | 40 comment | 218 complexity | 2176857fc91083aaeea0dff38245b846 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.Diagnostics;
  4. using System.Drawing;
  5. using System.IO;
  6. using System.Linq;
  7. using System.Text;
  8. using System.Text.RegularExpressions;
  9. using System.Threading;
  10. using System.Windows.Forms;
  11. using GitCommands;
  12. using GitCommands.Config;
  13. using GitUI.Hotkey;
  14. using GitUI.Script;
  15. using PatchApply;
  16. using ResourceManager.Translation;
  17. using Timer = System.Windows.Forms.Timer;
  18. namespace GitUI
  19. {
  20. public sealed partial class FormCommit : GitExtensionsForm //, IHotkeyable
  21. {
  22. #region Translation
  23. private readonly TranslationString _alsoDeleteUntrackedFiles =
  24. new TranslationString("Do you also want to delete the new files that are in the selection?" +
  25. Environment.NewLine + Environment.NewLine + "Choose 'No' to keep all new files.");
  26. private readonly TranslationString _alsoDeleteUntrackedFilesCaption = new TranslationString("Delete");
  27. private readonly TranslationString _amendCommit =
  28. new TranslationString("You are about to rewrite history." + Environment.NewLine +
  29. "Only use amend if the commit is not published yet!" + Environment.NewLine +
  30. Environment.NewLine + "Do you want to continue?");
  31. private readonly TranslationString _amendCommitCaption = new TranslationString("Amend commit");
  32. private readonly TranslationString _deleteFailed = new TranslationString("Delete file failed");
  33. private readonly TranslationString _deleteSelectedFiles =
  34. new TranslationString("Are you sure you want delete the selected file(s)?");
  35. private readonly TranslationString _deleteSelectedFilesCaption = new TranslationString("Delete");
  36. private readonly TranslationString _deleteUntrackedFiles =
  37. new TranslationString("Are you sure you want to delete all untracked files?");
  38. private readonly TranslationString _deleteUntrackedFilesCaption =
  39. new TranslationString("Delete untracked files.");
  40. private readonly TranslationString _enterCommitMessage = new TranslationString("Please enter commit message");
  41. private readonly TranslationString _enterCommitMessageCaption = new TranslationString("Commit message");
  42. private readonly TranslationString _enterCommitMessageHint = new TranslationString("Enter commit message");
  43. private readonly TranslationString _mergeConflicts =
  44. new TranslationString("There are unresolved mergeconflicts, solve mergeconflicts before committing.");
  45. private readonly TranslationString _mergeConflictsCaption = new TranslationString("Merge conflicts");
  46. private readonly TranslationString _noFilesStagedAndNothingToCommit =
  47. new TranslationString("There are no files staged for this commit.");
  48. private readonly TranslationString _noFilesStagedButSuggestToCommitAllUnstaged =
  49. new TranslationString("There are no files staged for this commit. Stage and commit all unstaged files?");
  50. private readonly TranslationString _noFilesStagedAndConfirmAnEmptyMergeCommit =
  51. new TranslationString("There are no files staged for this commit.\nAre you sure you want to commit?");
  52. private readonly TranslationString _noStagedChanges = new TranslationString("There are no staged changes");
  53. private readonly TranslationString _noUnstagedChanges = new TranslationString("There are no unstaged changes");
  54. private readonly TranslationString _notOnBranchMainInstruction = new TranslationString("You are not working on a branch");
  55. private readonly TranslationString _notOnBranch =
  56. new TranslationString("This commit will be unreferenced when switching to another branch and can be lost." +
  57. Environment.NewLine + "" + Environment.NewLine + "Do you want to continue?");
  58. private readonly TranslationString _notOnBranchButtons = new TranslationString("Checkout branch|Continue");
  59. private readonly TranslationString _notOnBranchCaption = new TranslationString("Not on a branch");
  60. private readonly TranslationString _onlyStageChunkOfSingleFileError =
  61. new TranslationString("You can only use this option when selecting a single file");
  62. private readonly TranslationString _resetChangesText =
  63. new TranslationString("Are you sure you want to reset the changes to the selected files?");
  64. private readonly TranslationString _resetChangesCaption = new TranslationString("Reset changes");
  65. private readonly TranslationString _resetSelectedChangesText =
  66. new TranslationString("Are you sure you want to reset all selected files?");
  67. private readonly TranslationString _stageChunkOfFileCaption = new TranslationString("Stage chunk of file");
  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. #endregion
  89. private readonly SynchronizationContext _syncContext;
  90. public bool NeedRefresh;
  91. private GitItemStatus _currentItem;
  92. private bool _currentItemStaged;
  93. private readonly CommitKind _commitKind;
  94. private readonly GitRevision _editedCommit;
  95. private readonly ToolStripItem _StageSelectedLinesToolStripMenuItem;
  96. private readonly ToolStripItem _ResetSelectedLinesToolStripMenuItem;
  97. private string commitTemplate;
  98. private bool IsMergeCommit { get; set; }
  99. private bool shouldRescanChanges = true;
  100. private bool _shouldReloadCommitTemplates = true;
  101. private AsyncLoader unstagedLoader = new AsyncLoader();
  102. public FormCommit()
  103. : this(CommitKind.Normal, null)
  104. { }
  105. public FormCommit(CommitKind commitKind, GitRevision editedCommit)
  106. {
  107. _syncContext = SynchronizationContext.Current;
  108. InitializeComponent();
  109. this.Loading.Image = global::GitUI.Properties.Resources.loadingpanel;
  110. splitRight.Panel2MinSize = 130;
  111. Translate();
  112. SolveMergeconflicts.Font = new Font(SystemFonts.MessageBoxFont, FontStyle.Bold);
  113. SelectedDiff.ExtraDiffArgumentsChanged += SelectedDiffExtraDiffArgumentsChanged;
  114. closeDialogAfterEachCommitToolStripMenuItem.Checked = Settings.CloseCommitDialogAfterCommit;
  115. closeDialogAfterAllFilesCommittedToolStripMenuItem.Checked = Settings.CloseCommitDialogAfterLastCommit;
  116. refreshDialogOnFormFocusToolStripMenuItem.Checked = Settings.RefreshCommitDialogOnFormFocus;
  117. Unstaged.SetNoFilesText(_noUnstagedChanges.Text);
  118. Staged.SetNoFilesText(_noStagedChanges.Text);
  119. Message.WatermarkText = _enterCommitMessageHint.Text;
  120. _commitKind = commitKind;
  121. _editedCommit = editedCommit;
  122. Unstaged.SelectedIndexChanged += UntrackedSelectionChanged;
  123. Staged.SelectedIndexChanged += TrackedSelectionChanged;
  124. Unstaged.DoubleClick += Unstaged_DoubleClick;
  125. Staged.DoubleClick += Staged_DoubleClick;
  126. SelectedDiff.AddContextMenuEntry(null, null);
  127. _StageSelectedLinesToolStripMenuItem = SelectedDiff.AddContextMenuEntry(_stageSelectedLines.Text, StageSelectedLinesToolStripMenuItemClick);
  128. _ResetSelectedLinesToolStripMenuItem = SelectedDiff.AddContextMenuEntry(_resetSelectedLines.Text, ResetSelectedLinesToolStripMenuItemClick);
  129. splitMain.SplitterDistance = Settings.CommitDialogSplitter;
  130. HotkeysEnabled = true;
  131. Hotkeys = HotkeySettingsManager.LoadHotkeys(HotkeySettingsName);
  132. SelectedDiff.ContextMenuOpening += SelectedDiff_ContextMenuOpening;
  133. }
  134. void SelectedDiff_ContextMenuOpening(object sender, System.ComponentModel.CancelEventArgs e)
  135. {
  136. _StageSelectedLinesToolStripMenuItem.Enabled = SelectedDiff.HasAnyPatches();
  137. }
  138. #region Hotkey commands
  139. public const string HotkeySettingsName = "Commit";
  140. internal enum Commands
  141. {
  142. AddToGitIgnore,
  143. DeleteSelectedFiles,
  144. FocusUnstagedFiles,
  145. FocusSelectedDiff,
  146. FocusStagedFiles,
  147. FocusCommitMessage,
  148. ResetSelectedFiles,
  149. StageSelectedFile,
  150. UnStageSelectedFile,
  151. ToggleSelectionFilter
  152. }
  153. private bool AddToGitIgnore()
  154. {
  155. if (Unstaged.Focused)
  156. {
  157. AddFileTogitignoreToolStripMenuItemClick(this, null);
  158. return true;
  159. }
  160. return false;
  161. }
  162. private bool DeleteSelectedFiles()
  163. {
  164. if (Unstaged.Focused)
  165. {
  166. DeleteFileToolStripMenuItemClick(this, null);
  167. return true;
  168. }
  169. return false;
  170. }
  171. private bool FocusStagedFiles()
  172. {
  173. FocusFileList(Staged);
  174. return true;
  175. }
  176. private bool FocusUnstagedFiles()
  177. {
  178. FocusFileList(Unstaged);
  179. return true;
  180. }
  181. /// <summary>Helper method that moves the focus to the supplied FileStatusList</summary>
  182. private void FocusFileList(FileStatusList fileStatusList)
  183. {
  184. fileStatusList.Focus();
  185. }
  186. private bool FocusSelectedDiff()
  187. {
  188. SelectedDiff.Focus();
  189. return true;
  190. }
  191. private bool FocusCommitMessage()
  192. {
  193. Message.Focus();
  194. return true;
  195. }
  196. private bool ResetSelectedFiles()
  197. {
  198. if (Unstaged.Focused)
  199. {
  200. ResetSoftClick(this, null);
  201. return true;
  202. }
  203. return false;
  204. }
  205. private bool StageSelectedFile()
  206. {
  207. if (Unstaged.Focused)
  208. {
  209. StageClick(this, null);
  210. return true;
  211. }
  212. return false;
  213. }
  214. private bool UnStageSelectedFile()
  215. {
  216. if (Staged.Focused)
  217. {
  218. UnstageFilesClick(this, null);
  219. return true;
  220. }
  221. return false;
  222. }
  223. private bool ToggleSelectionFilter()
  224. {
  225. selectionFilterToolStripMenuItem.Checked = !selectionFilterToolStripMenuItem.Checked;
  226. toolbarSelectionFilter.Visible = selectionFilterToolStripMenuItem.Checked;
  227. return true;
  228. }
  229. protected override bool ExecuteCommand(int cmd)
  230. {
  231. switch ((Commands)cmd)
  232. {
  233. case Commands.AddToGitIgnore: return AddToGitIgnore();
  234. case Commands.DeleteSelectedFiles: return DeleteSelectedFiles();
  235. case Commands.FocusStagedFiles: return FocusStagedFiles();
  236. case Commands.FocusUnstagedFiles: return FocusUnstagedFiles();
  237. case Commands.FocusSelectedDiff: return FocusSelectedDiff();
  238. case Commands.FocusCommitMessage: return FocusCommitMessage();
  239. case Commands.ResetSelectedFiles: return ResetSelectedFiles();
  240. case Commands.StageSelectedFile: return StageSelectedFile();
  241. case Commands.UnStageSelectedFile: return UnStageSelectedFile();
  242. case Commands.ToggleSelectionFilter: return ToggleSelectionFilter();
  243. //default: return false;
  244. default: ExecuteScriptCommand(cmd, Keys.None); return true;
  245. }
  246. }
  247. #endregion
  248. public void ShowWhenChanges()
  249. {
  250. ShowWhenChanges(null, false);
  251. }
  252. private void ComputeUnstagedFiles(Action<List<GitItemStatus>> onComputed)
  253. {
  254. unstagedLoader.Load(() =>
  255. Settings.Module.GetAllChangedFiles(
  256. !showIgnoredFilesToolStripMenuItem.Checked,
  257. showUntrackedFilesToolStripMenuItem.Checked),
  258. onComputed);
  259. }
  260. public void ShowWhenChanges(IWin32Window owner, bool blocking)
  261. {
  262. ComputeUnstagedFiles((allChangedFiles) =>
  263. {
  264. if (allChangedFiles.Count > 0)
  265. {
  266. LoadUnstagedOutput(allChangedFiles);
  267. Initialize(false);
  268. if (blocking)
  269. ShowDialog(owner);
  270. else
  271. Show();
  272. }
  273. else
  274. Close();
  275. }
  276. );
  277. }
  278. private void StageSelectedLinesToolStripMenuItemClick(object sender, EventArgs e)
  279. {
  280. // Prepare git command
  281. string args = "apply --cached --whitespace=nowarn";
  282. if (_currentItemStaged) //staged
  283. args += " --reverse";
  284. string patch = PatchManager.GetSelectedLinesAsPatch(SelectedDiff.GetText(), SelectedDiff.GetSelectionPosition(), SelectedDiff.GetSelectionLength(), _currentItemStaged);
  285. if (!string.IsNullOrEmpty(patch))
  286. {
  287. string output = Settings.Module.RunGitCmd(args, patch);
  288. if (!string.IsNullOrEmpty(output))
  289. {
  290. MessageBox.Show(this, output);
  291. }
  292. RescanChanges();
  293. }
  294. }
  295. private void ResetSelectedLinesToolStripMenuItemClick(object sender, EventArgs e)
  296. {
  297. if (MessageBox.Show(this, _resetSelectedLinesConfirmation.Text, _resetChangesCaption.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No)
  298. return;
  299. // Prepare git command
  300. string args = "apply --whitespace=nowarn --reverse";
  301. if (_currentItemStaged) //staged
  302. args += " --index";
  303. string patch = PatchManager.GetSelectedLinesAsPatch(SelectedDiff.GetText(), SelectedDiff.GetSelectionPosition(), SelectedDiff.GetSelectionLength(), _currentItemStaged);
  304. if (!string.IsNullOrEmpty(patch))
  305. {
  306. string output = Settings.Module.RunGitCmd(args, patch);
  307. if (!string.IsNullOrEmpty(output))
  308. {
  309. MessageBox.Show(this, output);
  310. }
  311. RescanChanges();
  312. }
  313. }
  314. private void EnableStageButtons(bool enable)
  315. {
  316. toolUnstageItem.Enabled = enable;
  317. toolUnstageAllItem.Enabled = enable;
  318. toolStageItem.Enabled = enable;
  319. toolStageAllItem.Enabled = enable;
  320. workingToolStripMenuItem.Enabled = enable;
  321. }
  322. private bool initialized = false;
  323. private void Initialize(bool loadUnstaged)
  324. {
  325. initialized = true;
  326. EnableStageButtons(false);
  327. Cursor.Current = Cursors.WaitCursor;
  328. if (loadUnstaged)
  329. ComputeUnstagedFiles(LoadUnstagedOutput);
  330. UpdateMergeHead();
  331. // Check if commit.template is used
  332. ConfigFile globalConfig = GitCommandHelpers.GetGlobalConfig();
  333. string fileName = globalConfig.GetValue("commit.template");
  334. if (!string.IsNullOrEmpty(fileName))
  335. {
  336. using (var commitReader = new StreamReader(fileName))
  337. {
  338. commitTemplate = commitReader.ReadToEnd().Replace("\r", "");
  339. }
  340. Message.Text = commitTemplate;
  341. }
  342. Loading.Visible = true;
  343. LoadingStaged.Visible = true;
  344. Commit.Enabled = false;
  345. CommitAndPush.Enabled = false;
  346. Amend.Enabled = false;
  347. Reset.Enabled = false;
  348. Cursor.Current = Cursors.Default;
  349. }
  350. private void Initialize()
  351. {
  352. Initialize(true);
  353. }
  354. private void UpdateMergeHead()
  355. {
  356. var mergeHead = Settings.Module.RevParse("MERGE_HEAD");
  357. IsMergeCommit = Regex.IsMatch(mergeHead, GitRevision.Sha1HashPattern);
  358. }
  359. private void InitializedStaged()
  360. {
  361. Cursor.Current = Cursors.WaitCursor;
  362. Staged.GitItemStatuses = null;
  363. SolveMergeconflicts.Visible = Settings.Module.InTheMiddleOfConflictedMerge();
  364. Staged.GitItemStatuses = Settings.Module.GetStagedFiles();
  365. Cursor.Current = Cursors.Default;
  366. }
  367. private bool LoadUnstagedOutputFirstTime = true;
  368. /// <summary>
  369. /// Loads the unstaged output.
  370. /// This method is passed in to the SetTextCallBack delegate
  371. /// to set the Text property of textBox1.
  372. /// </summary>
  373. private void LoadUnstagedOutput(List<GitItemStatus> allChangedFiles)
  374. {
  375. var unStagedFiles = new List<GitItemStatus>();
  376. var stagedFiles = new List<GitItemStatus>();
  377. foreach (var fileStatus in allChangedFiles)
  378. {
  379. if (fileStatus.IsStaged)
  380. stagedFiles.Add(fileStatus);
  381. else
  382. unStagedFiles.Add(fileStatus);
  383. }
  384. Unstaged.GitItemStatuses = null;
  385. Unstaged.GitItemStatuses = unStagedFiles;
  386. Staged.GitItemStatuses = null;
  387. Staged.GitItemStatuses = stagedFiles;
  388. Loading.Visible = false;
  389. LoadingStaged.Visible = false;
  390. Commit.Enabled = true;
  391. CommitAndPush.Enabled = true;
  392. Amend.Enabled = true;
  393. Reset.Enabled = DoChangesExist();
  394. EnableStageButtons(true);
  395. workingToolStripMenuItem.Enabled = true;
  396. var inTheMiddleOfConflictedMerge = Settings.Module.InTheMiddleOfConflictedMerge();
  397. SolveMergeconflicts.Visible = inTheMiddleOfConflictedMerge;
  398. Unstaged.SelectStoredNextIndex();
  399. if (LoadUnstagedOutputFirstTime)
  400. {
  401. if (Unstaged.GitItemStatuses.Count > 0)
  402. Unstaged.Focus();
  403. else if (Staged.GitItemStatuses.Count > 0)
  404. Message.Focus();
  405. else
  406. Amend.Focus();
  407. LoadUnstagedOutputFirstTime = false;
  408. }
  409. }
  410. /// <summary>Returns if there are any changes at all, staged or unstaged.</summary>
  411. private bool DoChangesExist()
  412. {
  413. return (Unstaged.AllItems.Count > 0) || (Staged.AllItems.Count > 0);
  414. }
  415. private void ShowChanges(GitItemStatus item, bool staged)
  416. {
  417. _currentItem = item;
  418. _currentItemStaged = staged;
  419. if (item == null)
  420. return;
  421. long length = GetItemLength(item.Name);
  422. if (length < 5 * 1024 * 1024) // 5Mb
  423. SetSelectedDiff(item, staged);
  424. else
  425. {
  426. SelectedDiff.Clear();
  427. SelectedDiff.Refresh();
  428. llShowPreview.Show();
  429. }
  430. _StageSelectedLinesToolStripMenuItem.Text = staged ? _unstageSelectedLines.Text : _stageSelectedLines.Text;
  431. _ResetSelectedLinesToolStripMenuItem.Enabled = staged;
  432. }
  433. private long GetItemLength(string fileName)
  434. {
  435. long length = -1;
  436. string path = fileName;
  437. if (!File.Exists(fileName))
  438. path = GitCommands.Settings.WorkingDir + fileName;
  439. if (File.Exists(path))
  440. {
  441. FileInfo fi = new FileInfo(path);
  442. length = fi.Length;
  443. }
  444. return length;
  445. }
  446. private void llShowPreview_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
  447. {
  448. llShowPreview.Hide();
  449. SetSelectedDiff(_currentItem, _currentItemStaged);
  450. }
  451. private void SetSelectedDiff(GitItemStatus item, bool staged)
  452. {
  453. if (item.Name.EndsWith(".png"))
  454. {
  455. SelectedDiff.ViewFile(item.Name);
  456. }
  457. else if (item.IsTracked)
  458. {
  459. if (!item.IsSubmodule)
  460. SelectedDiff.ViewCurrentChanges(item.Name, item.OldName, staged);
  461. else
  462. SelectedDiff.ViewSubmoduleChanges(item.Name, item.OldName, staged);
  463. }
  464. else
  465. {
  466. SelectedDiff.ViewFile(item.Name);
  467. }
  468. }
  469. private void TrackedSelectionChanged(object sender, EventArgs e)
  470. {
  471. ClearDiffViewIfNoFilesLeft();
  472. if (Staged.SelectedItems.Count == 0)
  473. return;
  474. Unstaged.SelectedItem = null;
  475. ShowChanges(Staged.SelectedItems[0], true);
  476. }
  477. private void UntrackedSelectionChanged(object sender, EventArgs e)
  478. {
  479. ClearDiffViewIfNoFilesLeft();
  480. Unstaged.ContextMenuStrip = null;
  481. if (Unstaged.SelectedItems.Count == 0)
  482. return;
  483. Staged.SelectedItem = null;
  484. ShowChanges(Unstaged.SelectedItems[0], false);
  485. GitItemStatus item = Unstaged.SelectedItems[0];
  486. if (!item.IsSubmodule)
  487. Unstaged.ContextMenuStrip = UnstagedFileContext;
  488. else
  489. Unstaged.ContextMenuStrip = UnstagedSubmoduleContext;
  490. }
  491. private void ClearDiffViewIfNoFilesLeft()
  492. {
  493. llShowPreview.Hide();
  494. if (Staged.IsEmpty && Unstaged.IsEmpty)
  495. SelectedDiff.Clear();
  496. }
  497. private void CommitClick(object sender, EventArgs e)
  498. {
  499. CheckForStagedAndCommit(false, false);
  500. }
  501. private void CheckForStagedAndCommit(bool amend, bool push)
  502. {
  503. if (Staged.IsEmpty)
  504. {
  505. if (IsMergeCommit)
  506. {
  507. // it is a merge commit, so user can commit just for merging two branches even the changeset is empty,
  508. // but also user may forget to add files, so only ask for confirmation that user really wants to commit an empty changeset
  509. if (MessageBox.Show(this, _noFilesStagedAndConfirmAnEmptyMergeCommit.Text, _noStagedChanges.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes)
  510. return;
  511. }
  512. else
  513. {
  514. if (Unstaged.IsEmpty)
  515. {
  516. MessageBox.Show(this, _noFilesStagedAndNothingToCommit.Text, _noStagedChanges.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
  517. return;
  518. }
  519. // there are no staged files, but there are unstaged files. Most probably user forgot to stage them.
  520. if (MessageBox.Show(this, _noFilesStagedButSuggestToCommitAllUnstaged.Text, _noStagedChanges.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes)
  521. return;
  522. StageAll();
  523. // if staging failed (i.e. line endings conflict), user already got error message, don't try to commit empty changeset.
  524. if (Staged.IsEmpty)
  525. return;
  526. }
  527. }
  528. DoCommit(amend, push);
  529. }
  530. private void DoCommit(bool amend, bool push)
  531. {
  532. if (Settings.Module.InTheMiddleOfConflictedMerge())
  533. {
  534. MessageBox.Show(this, _mergeConflicts.Text, _mergeConflictsCaption.Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
  535. return;
  536. }
  537. if (string.IsNullOrEmpty(Message.Text) || Message.Text == commitTemplate)
  538. {
  539. MessageBox.Show(this, _enterCommitMessage.Text, _enterCommitMessageCaption.Text, MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
  540. return;
  541. }
  542. if (!ValidCommitMessage())
  543. return;
  544. if (Settings.Module.GetSelectedBranch().Equals("(no branch)", StringComparison.OrdinalIgnoreCase))
  545. {
  546. int idx = PSTaskDialog.cTaskDialog.ShowCommandBox(this,
  547. _notOnBranchCaption.Text,
  548. _notOnBranchMainInstruction.Text,
  549. _notOnBranch.Text,
  550. _notOnBranchButtons.Text,
  551. true);
  552. switch (idx)
  553. {
  554. case 0:
  555. string revision = _editedCommit != null ? _editedCommit.Guid : "";
  556. if (!GitUICommands.Instance.StartCheckoutBranchDialog(revision))
  557. return;
  558. break;
  559. case -1:
  560. return;
  561. }
  562. }
  563. try
  564. {
  565. SetCommitMessageFromTextBox(Message.Text);
  566. ScriptManager.RunEventScripts(ScriptEvent.BeforeCommit);
  567. var errorOccurred = !FormProcess.ShowDialog(this, Settings.Module.CommitCmd(amend, signOffToolStripMenuItem.Checked, toolAuthor.Text));
  568. NeedRefresh = true;
  569. if (errorOccurred)
  570. return;
  571. ScriptManager.RunEventScripts(ScriptEvent.AfterCommit);
  572. Message.Text = string.Empty;
  573. GitCommands.Commit.SetCommitMessage(string.Empty);
  574. if (push)
  575. {
  576. GitUICommands.Instance.StartPushDialog(this, true);
  577. }
  578. if (Committed != null)
  579. Committed();
  580. if (Settings.CloseCommitDialogAfterCommit)
  581. {
  582. Close();
  583. return;
  584. }
  585. if (Unstaged.GitItemStatuses.Any(gitItemStatus => gitItemStatus.IsTracked))
  586. {
  587. InitializedStaged();
  588. return;
  589. }
  590. if (Settings.CloseCommitDialogAfterLastCommit)
  591. Close();
  592. else
  593. InitializedStaged();
  594. }
  595. catch (Exception e)
  596. {
  597. MessageBox.Show(this, string.Format("Exception: {0}", e.Message));
  598. }
  599. }
  600. public event Action Committed;
  601. private bool ValidCommitMessage()
  602. {
  603. if (Settings.CommitValidationMaxCntCharsFirstLine > 0)
  604. {
  605. var firstLine = Message.Text.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries)[0];
  606. if (firstLine.Length > Settings.CommitValidationMaxCntCharsFirstLine)
  607. {
  608. if (DialogResult.No == MessageBox.Show(this, _commitMsgFirstLineInvalid.Text, _commitValidationCaption.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Asterisk))
  609. return false;
  610. }
  611. }
  612. if (Settings.CommitValidationMaxCntCharsPerLine > 0)
  613. {
  614. var lines = Message.Text.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
  615. foreach (var line in lines)
  616. {
  617. if (line.Length > Settings.CommitValidationMaxCntCharsPerLine)
  618. {
  619. if (DialogResult.No == MessageBox.Show(this, String.Format(_commitMsgLineInvalid.Text, line), _commitValidationCaption.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Asterisk))
  620. return false;
  621. }
  622. }
  623. }
  624. if (Settings.CommitValidationSecondLineMustBeEmpty)
  625. {
  626. var lines = Message.Text.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None);
  627. if (lines.Length > 2)
  628. {
  629. if (lines[1].Length != 0)
  630. {
  631. if (DialogResult.No == MessageBox.Show(this, _commitMsgSecondLineNotEmpty.Text, _commitValidationCaption.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Asterisk))
  632. return false;
  633. }
  634. }
  635. }
  636. if (!Settings.CommitValidationRegEx.IsNullOrEmpty())
  637. {
  638. try
  639. {
  640. if (!Regex.IsMatch(Message.Text, Settings.CommitValidationRegEx))
  641. {
  642. if (DialogResult.No == MessageBox.Show(this, _commitMsgRegExNotMatched.Text, _commitValidationCaption.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Asterisk))
  643. return false;
  644. }
  645. }
  646. catch
  647. {
  648. }
  649. }
  650. return true;
  651. }
  652. private void RescanChanges()
  653. {
  654. if (shouldRescanChanges)
  655. {
  656. toolRefreshItem.Enabled = false;
  657. Initialize();
  658. toolRefreshItem.Enabled = true;
  659. }
  660. }
  661. private void StageClick(object sender, EventArgs e)
  662. {
  663. Stage(Unstaged.SelectedItems);
  664. }
  665. private void StageAll()
  666. {
  667. Stage(Unstaged.GitItemStatuses);
  668. }
  669. private void Stage(ICollection<GitItemStatus> gitItemStatusses)
  670. {
  671. EnableStageButtons(false);
  672. try
  673. {
  674. Cursor.Current = Cursors.WaitCursor;
  675. Unstaged.StoreNextIndexToSelect();
  676. toolStripProgressBar1.Visible = true;
  677. toolStripProgressBar1.Maximum = gitItemStatusses.Count * 2;
  678. toolStripProgressBar1.Value = 0;
  679. var files = new List<GitItemStatus>();
  680. foreach (var gitItemStatus in gitItemStatusses)
  681. {
  682. toolStripProgressBar1.Value = Math.Min(toolStripProgressBar1.Maximum - 1, toolStripProgressBar1.Value + 1);
  683. files.Add(gitItemStatus);
  684. }
  685. if (Settings.ShowErrorsWhenStagingFiles)
  686. {
  687. FormStatus.ProcessStart processStart =
  688. form =>
  689. {
  690. form.AddOutput(string.Format(_stageFiles.Text,
  691. files.Count));
  692. var output = GitCommandHelpers.StageFiles(files);
  693. form.AddOutput(output);
  694. form.Done(string.IsNullOrEmpty(output));
  695. };
  696. var process = new FormStatus(processStart, null) { Text = _stageDetails.Text };
  697. process.ShowDialogOnError(this);
  698. }
  699. else
  700. {
  701. GitCommandHelpers.StageFiles(files);
  702. }
  703. InitializedStaged();
  704. var stagedFiles = (List<GitItemStatus>)Staged.GitItemStatuses;
  705. var unStagedFiles = (List<GitItemStatus>)Unstaged.GitItemStatuses;
  706. Unstaged.GitItemStatuses = null;
  707. unStagedFiles.RemoveAll(item => stagedFiles.Exists(i => i.Name == item.Name || i.OldName == item.Name) && files.Exists(i => i.Name == item.Name));
  708. Unstaged.GitItemStatuses = unStagedFiles;
  709. Unstaged.SelectStoredNextIndex();
  710. toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
  711. toolStripProgressBar1.Visible = false;
  712. }
  713. catch (Exception ex)
  714. {
  715. Trace.WriteLine(ex.Message);
  716. }
  717. EnableStageButtons(true);
  718. Commit.Enabled = true;
  719. Amend.Enabled = true;
  720. AcceptButton = Commit;
  721. Cursor.Current = Cursors.Default;
  722. if (Settings.RevisionGraphShowWorkingDirChanges)
  723. NeedRefresh = true;
  724. }
  725. private void UnstageFilesClick(object sender, EventArgs e)
  726. {
  727. EnableStageButtons(false);
  728. try
  729. {
  730. Cursor.Current = Cursors.WaitCursor;
  731. if (Staged.GitItemStatuses.Count > 10 && Staged.SelectedItems.Count == Staged.GitItemStatuses.Count)
  732. {
  733. Loading.Visible = true;
  734. LoadingStaged.Visible = true;
  735. Commit.Enabled = false;
  736. CommitAndPush.Enabled = false;
  737. Amend.Enabled = false;
  738. Reset.Enabled = false;
  739. Settings.Module.ResetMixed("HEAD");
  740. Initialize();
  741. }
  742. else
  743. {
  744. toolStripProgressBar1.Visible = true;
  745. toolStripProgressBar1.Maximum = Staged.SelectedItems.Count * 2;
  746. toolStripProgressBar1.Value = 0;
  747. Staged.StoreNextIndexToSelect();
  748. var files = new List<GitItemStatus>();
  749. var allFiles = new List<GitItemStatus>();
  750. foreach (var item in Staged.SelectedItems)
  751. {
  752. toolStripProgressBar1.Value = Math.Min(toolStripProgressBar1.Maximum - 1, toolStripProgressBar1.Value + 1);
  753. if (!item.IsNew)
  754. {
  755. toolStripProgressBar1.Value = Math.Min(toolStripProgressBar1.Maximum - 1, toolStripProgressBar1.Value + 1);
  756. Settings.Module.UnstageFileToRemove(item.Name);
  757. if (item.IsRenamed)
  758. Settings.Module.UnstageFileToRemove(item.OldName);
  759. }
  760. else
  761. {
  762. files.Add(item);
  763. }
  764. allFiles.Add(item);
  765. }
  766. GitCommandHelpers.UnstageFiles(files);
  767. InitializedStaged();
  768. var stagedFiles = (List<GitItemStatus>)Staged.GitItemStatuses;
  769. var unStagedFiles = (List<GitItemStatus>)Unstaged.GitItemStatuses;
  770. Unstaged.GitItemStatuses = null;
  771. foreach (var item in allFiles)
  772. {
  773. var item1 = item;
  774. if (stagedFiles.Exists(i => i.Name == item1.Name))
  775. continue;
  776. var item2 = item;
  777. if (unStagedFiles.Exists(i => i.Name == item2.Name))
  778. continue;
  779. if (item.IsNew && !item.IsChanged && !item.IsDeleted)
  780. item.IsTracked = false;
  781. else
  782. item.IsTracked = true;
  783. if (item.IsRenamed)
  784. {
  785. var clone = new GitItemStatus
  786. {
  787. Name = item.OldName,
  788. IsDeleted = true,
  789. IsTracked = true,
  790. IsStaged = false
  791. };
  792. unStagedFiles.Add(clone);
  793. item.IsRenamed = false;
  794. item.IsNew = true;
  795. item.IsTracked = false;
  796. item.OldName = string.Empty;
  797. }
  798. item.IsStaged = false;
  799. unStagedFiles.Add(item);
  800. }
  801. Staged.GitItemStatuses = stagedFiles;
  802. Unstaged.GitItemStatuses = unStagedFiles;
  803. Staged.SelectStoredNextIndex();
  804. toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
  805. }
  806. toolStripProgressBar1.Visible = false;
  807. }
  808. catch (Exception ex)
  809. {
  810. Trace.WriteLine(ex.Message);
  811. }
  812. EnableStageButtons(true);
  813. Cursor.Current = Cursors.Default;
  814. if (Settings.RevisionGraphShowWorkingDirChanges)
  815. NeedRefresh = true;
  816. }
  817. private void ResetSoftClick(object sender, EventArgs e)
  818. {
  819. shouldRescanChanges = false;
  820. try
  821. {
  822. if (Unstaged.SelectedItem == null)
  823. return;
  824. // Show a form asking the user if they want to reset the changes.
  825. FormResetChanges.ResultType resetType = FormResetChanges.ShowResetDialog(this, Unstaged.SelectedItems.Any(item => !item.IsNew), Unstaged.SelectedItems.Any(item => item.IsNew));
  826. if (resetType == FormResetChanges.ResultType.CANCEL)
  827. return;
  828. //remember max selected index
  829. Unstaged.StoreNextIndexToSelect();
  830. var deleteNewFiles = Unstaged.SelectedItems.Any(item => item.IsNew) && (resetType == FormResetChanges.ResultType.RESET_AND_DELETE);
  831. var output = new StringBuilder();
  832. foreach (var item in Unstaged.SelectedItems)
  833. {
  834. if (item.IsNew)
  835. {
  836. if (deleteNewFiles)
  837. {
  838. try
  839. {
  840. File.Delete(Settings.WorkingDir + item.Name);
  841. }
  842. catch (System.IO.IOException)
  843. {
  844. }
  845. catch (System.UnauthorizedAccessException)
  846. {
  847. }
  848. }
  849. }
  850. else
  851. {
  852. output.Append(Settings.Module.ResetFile(item.Name));
  853. }
  854. }
  855. if (!string.IsNullOrEmpty(output.ToString()))
  856. MessageBox.Show(this, output.ToString(), _resetChangesCaption.Text);
  857. }
  858. finally
  859. {
  860. shouldRescanChanges = true;
  861. }
  862. Initialize();
  863. }
  864. private void DeleteFileToolStripMenuItemClick(object sender, EventArgs e)
  865. {
  866. try
  867. {
  868. SelectedDiff.Clear();
  869. if (Unstaged.SelectedItem == null ||
  870. MessageBox.Show(this, _deleteSelectedFiles.Text, _deleteSelectedFilesCaption.Text, MessageBoxButtons.YesNo) !=
  871. DialogResult.Yes)
  872. return;
  873. Unstaged.StoreNextIndexToSelect();
  874. foreach (var item in Unstaged.SelectedItems)
  875. File.Delete(Settings.WorkingDir + item.Name);
  876. Initialize();
  877. }
  878. catch (Exception ex)
  879. {
  880. MessageBox.Show(this, _deleteFailed.Text + Environment.NewLine + ex.Message);
  881. }
  882. }
  883. private void SolveMergeConflictsClick(object sender, EventArgs e)
  884. {
  885. if (GitUICommands.Instance.StartResolveConflictsDialog(this))
  886. Initialize();
  887. }
  888. private void DeleteSelectedFilesToolStripMenuItemClick(object sender, EventArgs e)
  889. {
  890. if (MessageBox.Show(this, _deleteSelectedFiles.Text, _deleteSelectedFilesCaption.Text, MessageBoxButtons.YesNo) !=
  891. DialogResult.Yes)
  892. return;
  893. try
  894. {
  895. foreach (var gitItemStatus in Unstaged.SelectedItems)
  896. File.Delete(Settings.WorkingDir + gitItemStatus.Name);
  897. }
  898. catch (Exception ex)
  899. {
  900. MessageBox.Show(this, _deleteFailed.Text + Environment.NewLine + ex);
  901. }
  902. Initialize();
  903. }
  904. private void ResetSelectedFilesToolStripMenuItemClick(object sender, EventArgs e)
  905. {
  906. if (MessageBox.Show(this, _resetSelectedChangesText.Text, _resetChangesCaption.Text, MessageBoxButtons.YesNo) !=
  907. DialogResult.Yes)
  908. return;
  909. foreach (var gitItemStatus in Unstaged.SelectedItems)
  910. {
  911. Settings.Module.ResetFile(gitItemStatus.Name);
  912. }
  913. Initialize();
  914. }
  915. private void ResetAlltrackedChangesToolStripMenuItemClick(object sender, EventArgs e)
  916. {
  917. ResetClick(null, null);
  918. }
  919. private void EditGitIgnoreToolStripMenuItemClick(object sender, EventArgs e)
  920. {
  921. GitUICommands.Instance.StartEditGitIgnoreDialog(this);
  922. Initialize();
  923. }
  924. private void StageAllToolStripMenuItemClick(object sender, EventArgs e)
  925. {
  926. StageAll();
  927. }
  928. private void UnstageAllToolStripMenuItemClick(object sender, EventArgs e)
  929. {
  930. Settings.Module.ResetMixed("HEAD");
  931. Initialize();
  932. }
  933. private void FormCommitShown(object sender, EventArgs e)
  934. {
  935. if (!initialized)
  936. Initialize();
  937. AcceptButton = Commit;
  938. string message;
  939. switch (_commitKind)
  940. {
  941. case CommitKind.Fixup:
  942. message = string.Format("fixup! {0}", _editedCommit.Message);
  943. break;
  944. case CommitKind.Squash:
  945. message = string.Format("squash! {0}", _editedCommit.Message);
  946. break;
  947. default:
  948. message = Settings.Module.GetMergeMessage();
  949. if (string.IsNullOrEmpty(message) && File.Exists(GitCommands.Commit.GetCommitMessagePath()))
  950. message = File.ReadAllText(GitCommands.Commit.GetCommitMessagePath(), Settings.CommitEncoding);
  951. break;
  952. }
  953. if (!string.IsNullOrEmpty(message))
  954. Message.Text = message;
  955. ThreadPool.QueueUserWorkItem(
  956. o =>
  957. {
  958. var text =
  959. string.Format(_formTitle.Text, Settings.Module.GetSelectedBranch(),
  960. Settings.WorkingDir);
  961. _syncContext.Post(state1 => Text = text, null);
  962. });
  963. }
  964. private void SetCommitMessageFromTextBox(string commitMessageText)
  965. {
  966. //Save last commit message in settings. This way it can be used in multiple repositories.
  967. Settings.LastCommitMessage = commitMessageText;
  968. var path = Settings.Module.WorkingDirGitDir() + Settings.PathSeparator.ToString() + "COMMITMESSAGE";
  969. //Commit messages are UTF-8 by default unless otherwise in the config file.
  970. //The git manual states:
  971. // git commit and git commit-tree issues a warning if the commit log message
  972. // given to it does not look like a valid UTF-8 string, unless you
  973. // explicitly say your project uses a legacy encoding. The way to say
  974. // this is to have i18n.commitencoding in .git/config file, like this:...
  975. Encoding encoding = Settings.CommitEncoding;
  976. using (var textWriter = new StreamWriter(path, false, encoding))
  977. {
  978. var lineNumber = 0;
  979. foreach (var line in commitMessageText.Split('\n'))
  980. {
  981. //When a committemplate is used, skip comments
  982. //otherwise: "#" is probably not used for comment but for issue number
  983. if (!line.StartsWith("#") ||
  984. string.IsNullOrEmpty(commitTemplate))
  985. {
  986. if (lineNumber == 1 && !String.IsNullOrEmpty(line))
  987. textWriter.WriteLine();
  988. textWriter.WriteLine(line);
  989. }
  990. lineNumber++;
  991. }
  992. }
  993. }
  994. private void FormCommitFormClosing(object sender, FormClosingEventArgs e)
  995. {
  996. // Do not remember commit message of fixup or squash commits, since they have
  997. // a special meaning, and can be dangerous if used inappropriately.
  998. if (CommitKind.Normal == _commitKind)
  999. GitCommands.Commit.SetCommitMessage(Message.Text);
  1000. SavePosition("commit");
  1001. Settings.CommitDialogSplitter = splitMain.SplitterDistance;
  1002. }
  1003. private void DeleteAllUntrackedFilesToolStripMenuItemClick(object sender, EventArgs e)
  1004. {
  1005. if (MessageBox.Show(this,
  1006. _deleteUntrackedFiles.Text,
  1007. _deleteUntrackedFilesCaption.Text,
  1008. MessageBoxButtons.YesNo) !=
  1009. DialogResult.Yes)
  1010. return;
  1011. FormProcess.ShowDialog(this, "clean -f");
  1012. Initialize();
  1013. }
  1014. private void StageChunkOfFileToolStripMenuItemClick(object sender, EventArgs e)
  1015. {
  1016. if (Unstaged.SelectedItems.Count != 1)
  1017. {
  1018. MessageBox.Show(_onlyStageChunkOfSingleFileError.Text, _stageChunkOfFileCaption.Text);
  1019. return;
  1020. }
  1021. foreach (var gitItemStatus in Unstaged.SelectedItems)
  1022. {
  1023. Settings.Module.RunRealCmd(Settings.GitCommand,
  1024. string.Format("add -p \"{0}\"", gitItemStatus.Name));
  1025. Initialize();
  1026. }
  1027. }
  1028. private void ShowIgnoredFilesToolStripMenuItemClick(object sender, EventArgs e)
  1029. {
  1030. showIgnoredFilesToolStripMenuItem.Checked = !showIgnoredFilesToolStripMenuItem.Checked;
  1031. RescanChanges();
  1032. }
  1033. private void CommitMessageToolStripMenuItemDropDownOpening(object sender, EventArgs e)
  1034. {
  1035. var items = commitMessageToolStripMenuItem.DropDownItems;
  1036. for (int i = 0; i < items.Count - 2; i++)
  1037. items.RemoveAt(0);
  1038. AddCommitMessageToMenu(Settings.LastCommitMessage);
  1039. string localLastCommitMessage = Settings.Module.GetPreviousCommitMessage(0);
  1040. if (!localLastCommitMessage.Trim().Equals(Settings.LastCommitMessage.Trim()))
  1041. AddCommitMessageToMenu(localLastCommitMessage);
  1042. AddCommitMessageToMenu(Settings.Module.GetPreviousCommitMessage(1));
  1043. AddCommitMessageToMenu(Settings.Module.GetPreviousCommitMessage(2));
  1044. AddCommitMessageToMenu(Settings.Module.GetPreviousCommitMessage(3));
  1045. }
  1046. private void AddCommitMessageToMenu(string commitMessage)
  1047. {
  1048. if (string.IsNullOrEmpty(commitMessage))
  1049. return;
  1050. var toolStripItem =
  1051. new ToolStripMenuItem
  1052. {
  1053. Tag = commitMessage,
  1054. Text =
  1055. commitMessage.Sub

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