PageRenderTime 41ms CodeModel.GetById 23ms RepoModel.GetById 1ms 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
  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.Substring(0,
  1056. Math.Min(Math.Min(50, commitMessage.Length),
  1057. commitMessage.Contains("\n") ? commitMessage.IndexOf('\n') : 99)) +
  1058. "..."
  1059. };
  1060. int count = commitMessageToolStripMenuItem.DropDownItems.Count;
  1061. commitMessageToolStripMenuItem.DropDownItems.Insert(count - 2, toolStripItem);
  1062. }
  1063. private void CommitMessageToolStripMenuItemDropDownItemClicked(object sender, ToolStripItemClickedEventArgs e)
  1064. {
  1065. if (e.ClickedItem.Tag != null)
  1066. Message.Text = ((string)e.ClickedItem.Tag).Trim();
  1067. }
  1068. private void generateListOfChangesInSubmodulesChangesToolStripMenuItem_Click(object sender, EventArgs e)
  1069. {
  1070. var stagedFiles = (List<GitItemStatus>)Staged.AllItems;
  1071. List<string> modules = new List<string>();
  1072. foreach (var item in stagedFiles.Where(it => it.IsSubmodule))
  1073. modules.Add(item.Name);
  1074. if (modules.Count == 0)
  1075. return;
  1076. StringBuilder sb = new StringBuilder();
  1077. sb.AppendLine("Submodule" + (modules.Count == 1 ? " " : "s ") +
  1078. String.Join(", ", modules.ToArray()) + " updated.");
  1079. sb.AppendLine();
  1080. foreach (var item in modules)
  1081. {
  1082. string diff = Settings.Module.RunGitCmd(
  1083. string.Format("diff --cached -z -- {0}", item));
  1084. var lines = diff.Split(new char[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
  1085. const string subprojCommit = "Subproject commit ";
  1086. var from = lines.Single(s => s.StartsWith("-" + subprojCommit)).Substring(subprojCommit.Length + 1);
  1087. var to = lines.Single(s => s.StartsWith("+" + subprojCommit)).Substring(subprojCommit.Length + 1);
  1088. if (!String.IsNullOrEmpty(from) && !String.IsNullOrEmpty(to))
  1089. {
  1090. sb.AppendLine("Submodule " + item + ":");
  1091. GitModule module = new GitModule(Settings.WorkingDir + item + Settings.PathSeparator.ToString());
  1092. string log = module.RunGitCmd(
  1093. string.Format("log --pretty=format:\" %m %h - %s\" --no-merges {0}...{1}", from, to));
  1094. if (log.Length != 0)
  1095. sb.AppendLine(log);
  1096. else
  1097. sb.AppendLine(" * Revision changed to " + to.Substring(0, 7));
  1098. sb.AppendLine();
  1099. }
  1100. }
  1101. Message.Text = sb.ToString().TrimEnd();
  1102. }
  1103. private void AddFileTogitignoreToolStripMenuItemClick(object sender, EventArgs e)
  1104. {
  1105. if (Unstaged.SelectedItems.Count == 0)
  1106. return;
  1107. SelectedDiff.Clear();
  1108. var fileNames = Unstaged.SelectedItems.Select(item => item.Name).ToArray();
  1109. new FormAddToGitIgnore(fileNames).ShowDialog(this);
  1110. Initialize();
  1111. }
  1112. private void SelectedDiffExtraDiffArgumentsChanged(object sender, EventArgs e)
  1113. {
  1114. ShowChanges(_currentItem, _currentItemStaged);
  1115. }
  1116. private void RescanChangesToolStripMenuItemClick(object sender, EventArgs e)
  1117. {
  1118. RescanChanges();
  1119. }
  1120. private void OpenToolStripMenuItemClick(object sender, EventArgs e)
  1121. {
  1122. FileStatusList list = sender as FileStatusList;
  1123. if (!SenderToFileStatusList(sender, out list))
  1124. return;
  1125. if (list.SelectedItems.Count == 0)
  1126. return;
  1127. var item = list.SelectedItem;
  1128. var fileName = item.Name;
  1129. Process.Start((Settings.WorkingDir + fileName).Replace(Settings.PathSeparatorWrong, Settings.PathSeparator));
  1130. }
  1131. private void OpenWithToolStripMenuItemClick(object sender, EventArgs e)
  1132. {
  1133. FileStatusList list;
  1134. if (!SenderToFileStatusList(sender, out list))
  1135. return;
  1136. if (list.SelectedItems.Count == 0)
  1137. return;
  1138. var item = list.SelectedItem;
  1139. var fileName = item.Name;
  1140. OpenWith.OpenAs(Settings.WorkingDir + fileName.Replace(Settings.PathSeparatorWrong, Settings.PathSeparator));
  1141. }
  1142. private void FilenameToClipboardToolStripMenuItemClick(object sender, EventArgs e)
  1143. {
  1144. FileStatusList list;
  1145. if (!SenderToFileStatusList(sender, out list))
  1146. return;
  1147. if (list.SelectedItems.Count == 0)
  1148. return;
  1149. var fileNames = new StringBuilder();
  1150. foreach (var item in list.SelectedItems)
  1151. {
  1152. //Only use appendline when multiple items are selected.
  1153. //This to make it easier to use the text from clipboard when 1 file is selected.
  1154. if (fileNames.Length > 0)
  1155. fileNames.AppendLine();
  1156. fileNames.Append((Settings.WorkingDir + item.Name).Replace(Settings.PathSeparatorWrong, Settings.PathSeparator));
  1157. }
  1158. Clipboard.SetText(fileNames.ToString());
  1159. }
  1160. private void OpenWithDifftoolToolStripMenuItemClick(object sender, EventArgs e)
  1161. {
  1162. if (Unstaged.SelectedItems.Count == 0)
  1163. return;
  1164. var item = Unstaged.SelectedItem;
  1165. var fileName = item.Name;
  1166. var cmdOutput = Settings.Module.OpenWithDifftool(fileName);
  1167. if (!string.IsNullOrEmpty(cmdOutput))
  1168. MessageBox.Show(this, cmdOutput);
  1169. }
  1170. private void ResetPartOfFileToolStripMenuItemClick(object sender, EventArgs e)
  1171. {
  1172. if (Unstaged.SelectedItems.Count != 1)
  1173. {
  1174. MessageBox.Show(this, _onlyStageChunkOfSingleFileError.Text, _resetStageChunkOfFileCaption.Text);
  1175. return;
  1176. }
  1177. foreach (var gitItemStatus in Unstaged.SelectedItems)
  1178. {
  1179. Settings.Module.RunGitRealCmd(
  1180. string.Format("checkout -p \"{0}\"", gitItemStatus.Name));
  1181. Initialize();
  1182. }
  1183. }
  1184. private void FormCommitLoad(object sender, EventArgs e)
  1185. {
  1186. RestorePosition("commit");
  1187. }
  1188. private void ResetClick(object sender, EventArgs e)
  1189. {
  1190. // Show a form asking the user if they want to reset the changes.
  1191. FormResetChanges.ResultType resetType = FormResetChanges.ShowResetDialog(this, Unstaged.AllItems.Any(item => !item.IsNew), Unstaged.AllItems.Any(item => item.IsNew));
  1192. if (resetType == FormResetChanges.ResultType.CANCEL)
  1193. return;
  1194. // Reset all changes.
  1195. Settings.Module.ResetHard("");
  1196. // Also delete new files, if requested.
  1197. if (resetType == FormResetChanges.ResultType.RESET_AND_DELETE)
  1198. {
  1199. foreach (var item in Unstaged.AllItems.Where(item => item.IsNew))
  1200. {
  1201. try
  1202. {
  1203. File.Delete(Settings.WorkingDir + item.Name);
  1204. }
  1205. catch (System.IO.IOException) { }
  1206. catch (System.UnauthorizedAccessException) { }
  1207. }
  1208. }
  1209. Initialize();
  1210. NeedRefresh = true;
  1211. }
  1212. private void AmendClick(object sender, EventArgs e)
  1213. {
  1214. if (string.IsNullOrEmpty(Message.Text))
  1215. {
  1216. Message.Text = Settings.Module.GetPreviousCommitMessage(0).Trim();
  1217. return;
  1218. }
  1219. DoCommit(true, false);
  1220. }
  1221. private void ShowUntrackedFilesToolStripMenuItemClick(object sender, EventArgs e)
  1222. {
  1223. showUntrackedFilesToolStripMenuItem.Checked = !showUntrackedFilesToolStripMenuItem.Checked;
  1224. RescanChanges();
  1225. }
  1226. private void editFileToolStripMenuItem_Click(object sender, EventArgs e)
  1227. {
  1228. FileStatusList list;
  1229. if (!SenderToFileStatusList(sender, out list))
  1230. return;
  1231. var item = list.SelectedItem;
  1232. var fileName = Settings.WorkingDir + item.Name;
  1233. new FormEditor(fileName).ShowDialog(this);
  1234. UntrackedSelectionChanged(null, null);
  1235. }
  1236. private void CommitAndPush_Click(object sender, EventArgs e)
  1237. {
  1238. CheckForStagedAndCommit(false, true);
  1239. }
  1240. private void FormCommitActivated(object sender, EventArgs e)
  1241. {
  1242. if (Settings.RefreshCommitDialogOnFormFocus)
  1243. RescanChanges();
  1244. }
  1245. private bool SenderToFileStatusList(object sender, out FileStatusList list)
  1246. {
  1247. if (sender is ToolStripMenuItem)
  1248. {
  1249. ToolStripMenuItem item = sender as ToolStripMenuItem;
  1250. if (item.Owner is ContextMenuStrip)
  1251. {
  1252. ContextMenuStrip menu = item.Owner as ContextMenuStrip;
  1253. if (menu.SourceControl is ListBox)
  1254. {
  1255. ListBox lb = menu.SourceControl as ListBox;
  1256. if (lb.Parent is FileStatusList)
  1257. {
  1258. list = lb.Parent as FileStatusList;
  1259. return true;
  1260. }
  1261. }
  1262. }
  1263. }
  1264. list = null;
  1265. return false;
  1266. }
  1267. private void ViewFileHistoryMenuItem_Click(object sender, EventArgs e)
  1268. {
  1269. FileStatusList list;
  1270. if (!SenderToFileStatusList(sender, out list))
  1271. return;
  1272. if (list.SelectedItems.Count == 1)
  1273. {
  1274. GitUICommands.Instance.StartFileHistoryDialog(this, list.SelectedItem.Name, null);
  1275. }
  1276. else
  1277. MessageBox.Show(this, _selectOnlyOneFile.Text, _selectOnlyOneFileCaption.Text);
  1278. }
  1279. void Unstaged_DoubleClick(object sender, EventArgs e)
  1280. {
  1281. StageClick(sender, e);
  1282. }
  1283. void Staged_DoubleClick(object sender, EventArgs e)
  1284. {
  1285. UnstageFilesClick(sender, e);
  1286. }
  1287. private void Message_KeyUp(object sender, KeyEventArgs e)
  1288. {
  1289. // Ctrl + Enter = Commit
  1290. if (e.Control && e.KeyCode == Keys.Enter)
  1291. {
  1292. CheckForStagedAndCommit(false, false);
  1293. e.Handled = true;
  1294. }
  1295. }
  1296. private void Message_KeyDown(object sender, KeyEventArgs e)
  1297. {
  1298. // Prevent adding a line break when all we want is to commit
  1299. if (e.Control && e.KeyCode == Keys.Enter)
  1300. e.Handled = true;
  1301. }
  1302. private void Message_KeyPress(object sender, KeyPressEventArgs e)
  1303. {
  1304. int limit1 = Settings.CommitValidationMaxCntCharsFirstLine;
  1305. int limitX = Settings.CommitValidationMaxCntCharsPerLine;
  1306. bool empty2 = Settings.CommitValidationSecondLineMustBeEmpty;
  1307. if (limit1 > 0 && Message.CurrentLine == 1 && Message.CurrentColumn > limit1)
  1308. {
  1309. // TODO: I don't really know what to do in this case.
  1310. }
  1311. if (empty2 && Message.CurrentLine == 2)
  1312. {
  1313. // Force next line and add a bullet.
  1314. Message.ForceNextLine(true);
  1315. }
  1316. if (limitX > 0 && Message.CurrentLine >= (empty2 ? 3 : 2) && Message.CurrentColumn > limitX)
  1317. {
  1318. Message.WrapWord();
  1319. }
  1320. }
  1321. private void Message_SelectionChanged(object sender, EventArgs e)
  1322. {
  1323. commitCursorColumn.Text = Message.CurrentColumn.ToString();
  1324. commitCursorLine.Text = Message.CurrentLine.ToString();
  1325. }
  1326. private void closeDialogAfterEachCommitToolStripMenuItem_Click(object sender, EventArgs e)
  1327. {
  1328. closeDialogAfterEachCommitToolStripMenuItem.Checked = !closeDialogAfterEachCommitToolStripMenuItem.Checked;
  1329. Settings.CloseCommitDialogAfterCommit = closeDialogAfterEachCommitToolStripMenuItem.Checked;
  1330. }
  1331. private void closeDialogAfterAllFilesCommittedToolStripMenuItem_Click(object sender, EventArgs e)
  1332. {
  1333. closeDialogAfterAllFilesCommittedToolStripMenuItem.Checked = !closeDialogAfterAllFilesCommittedToolStripMenuItem.Checked;
  1334. Settings.CloseCommitDialogAfterLastCommit = closeDialogAfterAllFilesCommittedToolStripMenuItem.Checked;
  1335. }
  1336. private void refreshDialogOnFormFocusToolStripMenuItem_Click(object sender, EventArgs e)
  1337. {
  1338. refreshDialogOnFormFocusToolStripMenuItem.Checked = !refreshDialogOnFormFocusToolStripMenuItem.Checked;
  1339. Settings.RefreshCommitDialogOnFormFocus = refreshDialogOnFormFocusToolStripMenuItem.Checked;
  1340. }
  1341. protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
  1342. {
  1343. if (keyData == Keys.F5)
  1344. RescanChanges();
  1345. return base.ProcessCmdKey(ref msg, keyData);
  1346. }
  1347. private void signOffToolStripMenuItem_Click(object snder, EventArgs e)
  1348. {
  1349. signOffToolStripMenuItem.Checked = !signOffToolStripMenuItem.Checked;
  1350. }
  1351. private void toolAuthor_TextChanged(object sender, EventArgs e)
  1352. {
  1353. toolAuthorLabelItem.Enabled = toolAuthorLabelItem.Checked = !string.IsNullOrEmpty(toolAuthor.Text);
  1354. }
  1355. private void toolAuthorLabelItem_Click(object sender, EventArgs e)
  1356. {
  1357. toolAuthor.Text = "";
  1358. toolAuthorLabelItem.Enabled = toolAuthorLabelItem.Checked = false;
  1359. }
  1360. private long lastUserInputTime;
  1361. private void FilterChanged(object sender, EventArgs e)
  1362. {
  1363. var currentTime = DateTime.Now.Ticks;
  1364. if (lastUserInputTime == 0)
  1365. {
  1366. long timerLastChanged = currentTime;
  1367. var timer = new Timer { Interval = 250 };
  1368. timer.Tick += (s, a) =>
  1369. {
  1370. if (NoUserInput(timerLastChanged))
  1371. {
  1372. var selectionCount = 0;
  1373. try
  1374. {
  1375. selectionCount = Unstaged.SetSelectionFilter(selectionFilter.Text);
  1376. selectionFilter.ToolTipText = _selectionFilterToolTip.Text;
  1377. }
  1378. catch (ArgumentException ae)
  1379. {
  1380. selectionFilter.ToolTipText = string.Format(_selectionFilterErrorToolTip.Text, ae.Message);
  1381. }
  1382. if (selectionCount > 0)
  1383. {
  1384. AddToSelectionFilter(selectionFilter.Text);
  1385. }
  1386. timer.Stop();
  1387. lastUserInputTime = 0;
  1388. }
  1389. timerLastChanged = lastUserInputTime;
  1390. };
  1391. timer.Start();
  1392. }
  1393. lastUserInputTime = currentTime;
  1394. }
  1395. private bool NoUserInput(long timerLastChanged)
  1396. {
  1397. return timerLastChanged == lastUserInputTime;
  1398. }
  1399. private void AddToSelectionFilter(string filter)
  1400. {
  1401. if (!selectionFilter.Items.Cast<string>().Any(candiate => candiate == filter))
  1402. {
  1403. const int SelectionFilterMaxLength = 10;
  1404. if (selectionFilter.Items.Count == SelectionFilterMaxLength)
  1405. {
  1406. selectionFilter.Items.RemoveAt(SelectionFilterMaxLength - 1);
  1407. }
  1408. selectionFilter.Items.Insert(0, filter);
  1409. }
  1410. }
  1411. private void FilterIndexChanged(object sender, EventArgs e)
  1412. {
  1413. Unstaged.SetSelectionFilter(selectionFilter.Text);
  1414. }
  1415. private void ToogleShowSelectionFilter(object sender, EventArgs e)
  1416. {
  1417. toolbarSelectionFilter.Visible = selectionFilterToolStripMenuItem.Checked;
  1418. }
  1419. private void commitSubmoduleChanges_Click(object sender, EventArgs e)
  1420. {
  1421. Process process = new Process();
  1422. process.StartInfo.FileName = Application.ExecutablePath;
  1423. process.StartInfo.Arguments = "commit";
  1424. process.StartInfo.WorkingDirectory = Settings.WorkingDir + _currentItem.Name + Settings.PathSeparator.ToString();
  1425. if (process.Start())
  1426. {
  1427. process.WaitForExit();
  1428. Initialize();
  1429. }
  1430. }
  1431. private void openSubmoduleMenuItem_Click(object sender, EventArgs e)
  1432. {
  1433. Process process = new Process();
  1434. process.StartInfo.FileName = Application.ExecutablePath;
  1435. process.StartInfo.Arguments = "browse";
  1436. process.StartInfo.WorkingDirectory = Settings.WorkingDir + _currentItem.Name + Settings.PathSeparator.ToString();
  1437. process.Start();
  1438. }
  1439. private void resetSubmoduleChanges_Click(object sender, EventArgs e)
  1440. {
  1441. var unStagedFiles = (List<GitItemStatus>)Unstaged.SelectedItems;
  1442. if (unStagedFiles.Count == 0)
  1443. return;
  1444. if (!Abort.ShowAbortMessage())
  1445. return;
  1446. foreach (var item in unStagedFiles.Where(it => it.IsSubmodule))
  1447. {
  1448. GitModule module = new GitModule(Settings.WorkingDir + item.Name + Settings.PathSeparator.ToString());
  1449. module.ResetHard("");
  1450. }
  1451. Initialize();
  1452. }
  1453. private void updateSubmoduleMenuItem_Click(object sender, EventArgs e)
  1454. {
  1455. var unStagedFiles = (List<GitItemStatus>)Unstaged.SelectedItems;
  1456. if (unStagedFiles.Count == 0)
  1457. return;
  1458. foreach (var item in unStagedFiles.Where(it => it.IsSubmodule))
  1459. {
  1460. FormProcess.ShowDialog(this, GitCommandHelpers.SubmoduleUpdateCmd(item.Name));
  1461. }
  1462. Initialize();
  1463. }
  1464. private void stashSubmoduleChangesToolStripMenuItem_Click(object sender, EventArgs e)
  1465. {
  1466. var unStagedFiles = (List<GitItemStatus>)Unstaged.SelectedItems;
  1467. if (unStagedFiles.Count == 0)
  1468. return;
  1469. var arguments = GitCommandHelpers.StashSaveCmd(Settings.IncludeUntrackedFilesInManualStash);
  1470. foreach (var item in unStagedFiles.Where(it => it.IsSubmodule))
  1471. {
  1472. GitModule module = new GitModule(Settings.WorkingDir + item.Name + Settings.PathSeparator.ToString());
  1473. FormProcess.ShowDialog(this, module, arguments);
  1474. }
  1475. Initialize();
  1476. }
  1477. private void submoduleSummaryMenuItem_Click(object sender, EventArgs e)
  1478. {
  1479. string summary = Settings.Module.GetSubmoduleSummary(_currentItem.Name);
  1480. new FormEdit(summary).ShowDialog(this);
  1481. }
  1482. private void viewHistoryMenuItem_Click(object sender, EventArgs e)
  1483. {
  1484. ViewFileHistoryMenuItem_Click(sender, e);
  1485. }
  1486. private void openFolderMenuItem_Click(object sender, EventArgs e)
  1487. {
  1488. OpenToolStripMenuItemClick(sender, e);
  1489. }
  1490. private void openDiffMenuItem_Click(object sender, EventArgs e)
  1491. {
  1492. OpenWithDifftoolToolStripMenuItemClick(sender, e);
  1493. }
  1494. private void copyFolderNameMenuItem_Click(object sender, EventArgs e)
  1495. {
  1496. FilenameToClipboardToolStripMenuItemClick(sender, e);
  1497. }
  1498. private void commitTemplatesConfigtoolStripMenuItem_Click(object sender, EventArgs e)
  1499. {
  1500. new FormCommitTemplateSettings().ShowDialog(this);
  1501. _shouldReloadCommitTemplates = true;
  1502. }
  1503. private void LoadCommitTemplates()
  1504. {
  1505. CommitTemplateItem[] commitTemplates =
  1506. CommitTemplateItem.DeserializeCommitTemplates(Settings.CommitTemplates);
  1507. commitTemplatesToolStripMenuItem.DropDownItems.Clear();
  1508. if (null != commitTemplates)
  1509. {
  1510. for (int i = 0; i < commitTemplates.Length; i++)
  1511. {
  1512. if (!commitTemplates[i].Name.IsNullOrEmpty())
  1513. AddTemplateCommitMessageToMenu(commitTemplates[i], commitTemplates[i].Name);
  1514. }
  1515. }
  1516. commitTemplatesToolStripMenuItem.DropDownItems.Add(new ToolStripSeparator());
  1517. var toolStripItem = new ToolStripMenuItem(_commitTemplateSettings.Text);
  1518. toolStripItem.Click += commitTemplatesConfigtoolStripMenuItem_Click;
  1519. commitTemplatesToolStripMenuItem.DropDownItems.Add(toolStripItem);
  1520. }
  1521. private void AddTemplateCommitMessageToMenu(CommitTemplateItem item, string name)
  1522. {
  1523. if (string.IsNullOrEmpty(name))
  1524. return;
  1525. var toolStripItem =
  1526. new ToolStripMenuItem
  1527. {
  1528. Tag = item,
  1529. Text = name
  1530. };
  1531. toolStripItem.Click += commitTemplatesToolStripMenuItem_Clicked;
  1532. commitTemplatesToolStripMenuItem.DropDownItems.Add(toolStripItem);
  1533. }
  1534. private void commitTemplatesToolStripMenuItem_Clicked(object sender, EventArgs e)
  1535. {
  1536. try
  1537. {
  1538. ToolStripMenuItem item = (ToolStripMenuItem)sender;
  1539. CommitTemplateItem templateItem = (CommitTemplateItem)(item.Tag);
  1540. Message.Text = templateItem.Text;
  1541. Message.Focus();
  1542. }
  1543. catch
  1544. {
  1545. return;
  1546. }
  1547. }
  1548. private void commitTemplatesToolStripMenuItem_DropDownOpening(object sender, EventArgs e)
  1549. {
  1550. if (_shouldReloadCommitTemplates)
  1551. {
  1552. LoadCommitTemplates();
  1553. _shouldReloadCommitTemplates = false;
  1554. }
  1555. }
  1556. private void openContainingFolderToolStripMenuItem_Click(object sender, EventArgs e)
  1557. {
  1558. openContainingFolder(Unstaged);
  1559. }
  1560. private void openContainingFolder(FileStatusList list)
  1561. {
  1562. foreach (var item in list.SelectedItems)
  1563. {
  1564. var fileNames = new StringBuilder();
  1565. fileNames.Append((Settings.WorkingDir + item.Name).Replace(Settings.PathSeparatorWrong, Settings.PathSeparator));
  1566. string filePath = fileNames.ToString();
  1567. if (File.Exists(filePath))
  1568. {
  1569. Process.Start("explorer.exe", "/select, " + filePath);
  1570. }
  1571. }
  1572. }
  1573. private void toolStripMenuItem9_Click(object sender, EventArgs e)
  1574. {
  1575. foreach (var item in Staged.SelectedItems)
  1576. {
  1577. string output = Settings.Module.OpenWithDifftool(item.Name, null, null, "--cached");
  1578. if (!string.IsNullOrEmpty(output))
  1579. MessageBox.Show(this, output);
  1580. }
  1581. }
  1582. private void toolStripMenuItem10_Click(object sender, EventArgs e)
  1583. {
  1584. openContainingFolder(Staged);
  1585. }
  1586. }
  1587. /// <summary>
  1588. /// Indicates the kind of commit being prepared. Used for adjusting the behavior of FormCommit.
  1589. /// </summary>
  1590. public enum CommitKind
  1591. {
  1592. Normal,
  1593. Fixup,
  1594. Squash
  1595. }
  1596. }