PageRenderTime 67ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 1ms

/GitUI/CommandsDialogs/FormCommit.cs

https://github.com/vbjay/gitextensions
C# | 2418 lines | 1982 code | 387 blank | 49 comment | 337 complexity | 5e44b918597f049a2c4e0d09a95b13e9 MD5 | raw file
Possible License(s): GPL-3.0, GPL-2.0
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Collections.ObjectModel;
  4. using System.Diagnostics;
  5. using System.Drawing;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Text;
  9. using System.Text.RegularExpressions;
  10. using System.Threading;
  11. using System.Threading.Tasks;
  12. using System.Windows.Forms;
  13. using GitCommands;
  14. using GitCommands.Utils;
  15. using GitUI.CommandsDialogs.CommitDialog;
  16. using GitUI.HelperDialogs;
  17. using GitUI.Hotkey;
  18. using GitUI.Script;
  19. using PatchApply;
  20. using ResourceManager;
  21. using Timer = System.Windows.Forms.Timer;
  22. namespace GitUI.CommandsDialogs
  23. {
  24. public sealed partial class FormCommit : GitModuleForm //, IHotkeyable
  25. {
  26. const string USER_NAME_KEY = "user.name";
  27. const string USER_EMAIL_KEY = "user.email";
  28. #region Translation
  29. private readonly TranslationString _amendCommit =
  30. new TranslationString("You are about to rewrite history." + Environment.NewLine +
  31. "Only use amend if the commit is not published yet!" + Environment.NewLine +
  32. Environment.NewLine + "Do you want to continue?");
  33. private readonly TranslationString _amendCommitCaption = new TranslationString("Amend commit");
  34. private readonly TranslationString _deleteFailed = new TranslationString("Delete file failed");
  35. private readonly TranslationString _deleteSelectedFiles =
  36. new TranslationString("Are you sure you want delete the selected file(s)?");
  37. private readonly TranslationString _deleteSelectedFilesCaption = new TranslationString("Delete");
  38. private readonly TranslationString _deleteUntrackedFiles =
  39. new TranslationString("Are you sure you want to delete all untracked files?");
  40. private readonly TranslationString _deleteUntrackedFilesCaption =
  41. new TranslationString("Delete untracked files.");
  42. private readonly TranslationString _enterCommitMessage = new TranslationString("Please enter commit message");
  43. private readonly TranslationString _enterCommitMessageCaption = new TranslationString("Commit message");
  44. private readonly TranslationString _commitMessageDisabled = new TranslationString("Commit Message is requested during commit");
  45. private readonly TranslationString _enterCommitMessageHint = new TranslationString("Enter commit message");
  46. private readonly TranslationString _mergeConflicts =
  47. new TranslationString("There are unresolved mergeconflicts, solve mergeconflicts before committing.");
  48. private readonly TranslationString _mergeConflictsCaption = new TranslationString("Merge conflicts");
  49. private readonly TranslationString _noFilesStagedAndNothingToCommit =
  50. new TranslationString("There are no files staged for this commit.");
  51. private readonly TranslationString _noFilesStagedButSuggestToCommitAllUnstaged =
  52. new TranslationString("There are no files staged for this commit. Stage and commit all unstaged files?");
  53. private readonly TranslationString _noFilesStagedAndConfirmAnEmptyMergeCommit =
  54. new TranslationString("There are no files staged for this commit.\nAre you sure you want to commit?");
  55. private readonly TranslationString _noStagedChanges = new TranslationString("There are no staged changes");
  56. private readonly TranslationString _noUnstagedChanges = new TranslationString("There are no unstaged changes");
  57. private readonly TranslationString _notOnBranchMainInstruction = new TranslationString("You are not working on a branch");
  58. private readonly TranslationString _notOnBranch =
  59. new TranslationString("This commit will be unreferenced when switching to another branch and can be lost." +
  60. Environment.NewLine + "" + Environment.NewLine + "Do you want to continue?");
  61. private readonly TranslationString _notOnBranchButtons = new TranslationString("Checkout branch|Create branch|Continue");
  62. private readonly TranslationString _notOnBranchCaption = new TranslationString("Not on a branch");
  63. private readonly TranslationString _onlyStageChunkOfSingleFileError =
  64. new TranslationString("You can only use this option when selecting a single file");
  65. private readonly TranslationString _resetChangesCaption = new TranslationString("Reset changes");
  66. private readonly TranslationString _resetSelectedChangesText =
  67. new TranslationString("Are you sure you want to reset all selected files?");
  68. private readonly TranslationString _resetStageChunkOfFileCaption = new TranslationString("Unstage chunk of file");
  69. private readonly TranslationString _stageDetails = new TranslationString("Stage Details");
  70. private readonly TranslationString _stageFiles = new TranslationString("Stage {0} files");
  71. private readonly TranslationString _selectOnlyOneFile = new TranslationString("You must have only one file selected.");
  72. private readonly TranslationString _selectOnlyOneFileCaption = new TranslationString("Error");
  73. private readonly TranslationString _stageSelectedLines = new TranslationString("Stage selected line(s)");
  74. private readonly TranslationString _unstageSelectedLines = new TranslationString("Unstage selected line(s)");
  75. private readonly TranslationString _resetSelectedLines = new TranslationString("Reset selected line(s)");
  76. private readonly TranslationString _resetSelectedLinesConfirmation = new TranslationString("Are you sure you want to reset the changes to the selected lines?");
  77. private readonly TranslationString _formTitle = new TranslationString("Commit to {0} ({1})");
  78. private readonly TranslationString _selectionFilterToolTip = new TranslationString("Enter a regular expression to select unstaged files.");
  79. private readonly TranslationString _selectionFilterErrorToolTip = new TranslationString("Error {0}");
  80. private readonly TranslationString _commitMsgFirstLineInvalid = new TranslationString("First line of commit message contains too many characters."
  81. + Environment.NewLine + "Do you want to continue?");
  82. private readonly TranslationString _commitMsgLineInvalid = new TranslationString("The following line of commit message contains too many characters:"
  83. + Environment.NewLine + Environment.NewLine + "{0}" + Environment.NewLine + Environment.NewLine + "Do you want to continue?");
  84. private readonly TranslationString _commitMsgSecondLineNotEmpty = new TranslationString("Second line of commit message is not empty." + Environment.NewLine + "Do you want to continue?");
  85. private readonly TranslationString _commitMsgRegExNotMatched = new TranslationString("Commit message does not match RegEx." + Environment.NewLine + "Do you want to continue?");
  86. private readonly TranslationString _commitValidationCaption = new TranslationString("Commit validation");
  87. private readonly TranslationString _commitTemplateSettings = new TranslationString("Settings");
  88. private readonly TranslationString _commitAuthorInfo = new TranslationString("Author ");
  89. private readonly TranslationString _commitCommitterInfo = new TranslationString("Committer ");
  90. private readonly TranslationString _commitCommitterToolTip = new TranslationString("Click to change committer information.");
  91. #endregion
  92. private FileStatusList _currentFilesList;
  93. private bool _skipUpdate;
  94. private readonly TaskScheduler _taskScheduler;
  95. private GitItemStatus _currentItem;
  96. private bool _currentItemStaged;
  97. private readonly CommitKind _commitKind;
  98. private readonly GitRevision _editedCommit;
  99. private readonly ToolStripMenuItem _stageSelectedLinesToolStripMenuItem;
  100. private readonly ToolStripMenuItem _resetSelectedLinesToolStripMenuItem;
  101. private string _commitTemplate;
  102. private bool IsMergeCommit { get; set; }
  103. private bool _shouldRescanChanges = true;
  104. private bool _shouldReloadCommitTemplates = true;
  105. private readonly AsyncLoader _unstagedLoader;
  106. private readonly bool _useFormCommitMessage;
  107. private CancellationTokenSource _interactiveAddBashCloseWaitCts = new CancellationTokenSource();
  108. private string _userName = "";
  109. private string _userEmail = "";
  110. private bool RepoUserSettings = false;
  111. /// <summary>
  112. /// For VS designer
  113. /// </summary>
  114. private FormCommit()
  115. : this(null)
  116. {
  117. }
  118. public FormCommit(GitUICommands aCommands)
  119. : this(aCommands, CommitKind.Normal, null)
  120. { }
  121. public FormCommit(GitUICommands aCommands, CommitKind commitKind, GitRevision editedCommit)
  122. : base(true, aCommands)
  123. {
  124. _taskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
  125. _unstagedLoader = new AsyncLoader(_taskScheduler);
  126. _useFormCommitMessage = AppSettings.UseFormCommitMessage;
  127. InitializeComponent();
  128. Message.TextChanged += Message_TextChanged;
  129. Message.TextAssigned += Message_TextAssigned;
  130. Loading.Image = Properties.Resources.loadingpanel;
  131. Translate();
  132. SolveMergeconflicts.Font = new Font(SystemFonts.MessageBoxFont, FontStyle.Bold);
  133. SelectedDiff.ExtraDiffArgumentsChanged += SelectedDiffExtraDiffArgumentsChanged;
  134. if (IsUICommandsInitialized)
  135. StageInSuperproject.Visible = Module.SuperprojectModule != null;
  136. StageInSuperproject.Checked = AppSettings.StageInSuperprojectAfterCommit;
  137. closeDialogAfterEachCommitToolStripMenuItem.Checked = AppSettings.CloseCommitDialogAfterCommit;
  138. closeDialogAfterAllFilesCommittedToolStripMenuItem.Checked = AppSettings.CloseCommitDialogAfterLastCommit;
  139. refreshDialogOnFormFocusToolStripMenuItem.Checked = AppSettings.RefreshCommitDialogOnFormFocus;
  140. Unstaged.SetNoFilesText(_noUnstagedChanges.Text);
  141. Staged.SetNoFilesText(_noStagedChanges.Text);
  142. Message.Enabled = _useFormCommitMessage;
  143. commitMessageToolStripMenuItem.Enabled = _useFormCommitMessage;
  144. commitTemplatesToolStripMenuItem.Enabled = _useFormCommitMessage;
  145. Message.WatermarkText = _useFormCommitMessage
  146. ? _enterCommitMessageHint.Text
  147. : _commitMessageDisabled.Text;
  148. _commitKind = commitKind;
  149. _editedCommit = editedCommit;
  150. HotkeysEnabled = true;
  151. Hotkeys = HotkeySettingsManager.LoadHotkeys(HotkeySettingsName);
  152. SelectedDiff.AddContextMenuSeparator();
  153. _stageSelectedLinesToolStripMenuItem = SelectedDiff.AddContextMenuEntry(_stageSelectedLines.Text, StageSelectedLinesToolStripMenuItemClick);
  154. _stageSelectedLinesToolStripMenuItem.ShortcutKeyDisplayString = GetShortcutKeys((int)Commands.StageSelectedFile).ToShortcutKeyDisplayString();
  155. _resetSelectedLinesToolStripMenuItem = SelectedDiff.AddContextMenuEntry(_resetSelectedLines.Text, ResetSelectedLinesToolStripMenuItemClick);
  156. _resetSelectedLinesToolStripMenuItem.ShortcutKeyDisplayString = GetShortcutKeys((int)Commands.ResetSelectedFiles).ToShortcutKeyDisplayString();
  157. _resetSelectedLinesToolStripMenuItem.Image = Reset.Image;
  158. resetChanges.ShortcutKeyDisplayString = _resetSelectedLinesToolStripMenuItem.ShortcutKeyDisplayString;
  159. commitAuthorStatus.ToolTipText = _commitCommitterToolTip.Text;
  160. toolAuthor.Control.PreviewKeyDown += ToolAuthor_PreviewKeyDown;
  161. }
  162. void ToolAuthor_PreviewKeyDown(object sender, PreviewKeyDownEventArgs e)
  163. {
  164. if (e.Alt)
  165. e.IsInputKey = true;
  166. }
  167. private void FormCommit_Load(object sender, EventArgs e)
  168. {
  169. if (AppSettings.CommitDialogSplitter != -1)
  170. splitMain.SplitterDistance = AppSettings.CommitDialogSplitter;
  171. if (AppSettings.CommitDialogRightSplitter != -1)
  172. splitRight.SplitterDistance = AppSettings.CommitDialogRightSplitter;
  173. Reset.Visible = AppSettings.ShowResetAllChanges;
  174. ResetUnStaged.Visible = AppSettings.ShowResetUnstagedChanges;
  175. CommitAndPush.Visible = AppSettings.ShowCommitAndPush;
  176. AdjustCommitButtonPanelHeight();
  177. }
  178. private void AdjustCommitButtonPanelHeight()
  179. {
  180. splitRight.Panel2MinSize = Math.Max(splitRight.Panel2MinSize, flowCommitButtons.PreferredSize.Height);
  181. splitRight.SplitterDistance = Math.Min(splitRight.SplitterDistance, splitRight.Height - splitRight.Panel2MinSize);
  182. }
  183. private void FormCommitFormClosing(object sender, FormClosingEventArgs e)
  184. {
  185. // Do not remember commit message of fixup or squash commits, since they have
  186. // a special meaning, and can be dangerous if used inappropriately.
  187. if (CommitKind.Normal == _commitKind)
  188. GitCommands.CommitHelper.SetCommitMessage(Module, Message.Text);
  189. AppSettings.CommitDialogSplitter = splitMain.SplitterDistance;
  190. AppSettings.CommitDialogRightSplitter = splitRight.SplitterDistance;
  191. }
  192. void SelectedDiff_ContextMenuOpening(object sender, System.ComponentModel.CancelEventArgs e)
  193. {
  194. _stageSelectedLinesToolStripMenuItem.Enabled = SelectedDiff.HasAnyPatches() || _currentItem != null && _currentItem.IsNew;
  195. _resetSelectedLinesToolStripMenuItem.Enabled = _stageSelectedLinesToolStripMenuItem.Enabled;
  196. }
  197. #region Hotkey commands
  198. public const string HotkeySettingsName = "Commit";
  199. internal enum Commands
  200. {
  201. AddToGitIgnore,
  202. DeleteSelectedFiles,
  203. FocusUnstagedFiles,
  204. FocusSelectedDiff,
  205. FocusStagedFiles,
  206. FocusCommitMessage,
  207. ResetSelectedFiles,
  208. StageSelectedFile,
  209. UnStageSelectedFile,
  210. ToggleSelectionFilter
  211. }
  212. private bool AddToGitIgnore()
  213. {
  214. if (Unstaged.Focused)
  215. {
  216. AddFileTogitignoreToolStripMenuItemClick(this, null);
  217. return true;
  218. }
  219. return false;
  220. }
  221. private bool DeleteSelectedFiles()
  222. {
  223. if (Unstaged.Focused)
  224. {
  225. DeleteFileToolStripMenuItemClick(this, null);
  226. return true;
  227. }
  228. return false;
  229. }
  230. private bool FocusStagedFiles()
  231. {
  232. Staged.Focus();
  233. return true;
  234. }
  235. private bool FocusUnstagedFiles()
  236. {
  237. Unstaged.Focus();
  238. return true;
  239. }
  240. private bool FocusSelectedDiff()
  241. {
  242. SelectedDiff.Focus();
  243. return true;
  244. }
  245. private bool FocusCommitMessage()
  246. {
  247. Message.Focus();
  248. return true;
  249. }
  250. private bool ResetSelectedFiles()
  251. {
  252. if (Unstaged.Focused)
  253. {
  254. ResetSoftClick(this, null);
  255. return true;
  256. }
  257. else if (SelectedDiff.ContainsFocus && _resetSelectedLinesToolStripMenuItem.Enabled)
  258. {
  259. ResetSelectedLinesToolStripMenuItemClick(this, null);
  260. return true;
  261. }
  262. return false;
  263. }
  264. private bool StageSelectedFile()
  265. {
  266. if (Unstaged.Focused)
  267. {
  268. StageClick(this, null);
  269. return true;
  270. }
  271. else if (SelectedDiff.ContainsFocus && !_currentItemStaged && _stageSelectedLinesToolStripMenuItem.Enabled)
  272. {
  273. StageSelectedLinesToolStripMenuItemClick(this, null);
  274. return true;
  275. }
  276. return false;
  277. }
  278. private bool UnStageSelectedFile()
  279. {
  280. if (Staged.Focused)
  281. {
  282. UnstageFilesClick(this, null);
  283. return true;
  284. }
  285. else if (SelectedDiff.ContainsFocus && _currentItemStaged && _stageSelectedLinesToolStripMenuItem.Enabled)
  286. {
  287. StageSelectedLinesToolStripMenuItemClick(this, null);
  288. return true;
  289. }
  290. return false;
  291. }
  292. private bool ToggleSelectionFilter()
  293. {
  294. selectionFilterToolStripMenuItem.Checked = !selectionFilterToolStripMenuItem.Checked;
  295. toolbarSelectionFilter.Visible = selectionFilterToolStripMenuItem.Checked;
  296. return true;
  297. }
  298. protected override bool ExecuteCommand(int cmd)
  299. {
  300. switch ((Commands)cmd)
  301. {
  302. case Commands.AddToGitIgnore: return AddToGitIgnore();
  303. case Commands.DeleteSelectedFiles: return DeleteSelectedFiles();
  304. case Commands.FocusStagedFiles: return FocusStagedFiles();
  305. case Commands.FocusUnstagedFiles: return FocusUnstagedFiles();
  306. case Commands.FocusSelectedDiff: return FocusSelectedDiff();
  307. case Commands.FocusCommitMessage: return FocusCommitMessage();
  308. case Commands.ResetSelectedFiles: return ResetSelectedFiles();
  309. case Commands.StageSelectedFile: return StageSelectedFile();
  310. case Commands.UnStageSelectedFile: return UnStageSelectedFile();
  311. case Commands.ToggleSelectionFilter: return ToggleSelectionFilter();
  312. default: return base.ExecuteCommand(cmd);
  313. }
  314. }
  315. #endregion
  316. public void ShowDialogWhenChanges()
  317. {
  318. ShowDialogWhenChanges(null);
  319. }
  320. private void ComputeUnstagedFiles(Action<IList<GitItemStatus>> onComputed, bool DoAsync)
  321. {
  322. Func<IList<GitItemStatus>> getAllChangedFilesWithSubmodulesStatus = () => Module.GetAllChangedFilesWithSubmodulesStatus(
  323. !showIgnoredFilesToolStripMenuItem.Checked,
  324. showUntrackedFilesToolStripMenuItem.Checked ? UntrackedFilesMode.Default : UntrackedFilesMode.No);
  325. if (DoAsync)
  326. _unstagedLoader.Load(getAllChangedFilesWithSubmodulesStatus, onComputed);
  327. else
  328. {
  329. _unstagedLoader.Cancel();
  330. onComputed(getAllChangedFilesWithSubmodulesStatus());
  331. }
  332. }
  333. public void ShowDialogWhenChanges(IWin32Window owner)
  334. {
  335. ComputeUnstagedFiles((allChangedFiles) =>
  336. {
  337. if (allChangedFiles.Count > 0)
  338. {
  339. LoadUnstagedOutput(allChangedFiles);
  340. Initialize(false);
  341. ShowDialog(owner);
  342. }
  343. else
  344. Close();
  345. #if !__MonoCS__ // animated GIFs are not supported in Mono/Linux
  346. //trying to properly dispose loading image issue #1037
  347. Loading.Image.Dispose();
  348. #endif
  349. }, false
  350. );
  351. }
  352. private bool _selectedDiffReloaded = true;
  353. private void StageSelectedLinesToolStripMenuItemClick(object sender, EventArgs e)
  354. {
  355. //to prevent multiple clicks
  356. if (!_selectedDiffReloaded)
  357. return;
  358. Debug.Assert(_currentItem != null);
  359. // Prepare git command
  360. string args = "apply --cached --whitespace=nowarn";
  361. if (_currentItemStaged) //staged
  362. args += " --reverse";
  363. byte[] patch;
  364. if (!_currentItemStaged && _currentItem.IsNew)
  365. patch = PatchManager.GetSelectedLinesAsNewPatch(Module, _currentItem.Name,
  366. SelectedDiff.GetText(), SelectedDiff.GetSelectionPosition(),
  367. SelectedDiff.GetSelectionLength(), SelectedDiff.Encoding, false);
  368. else
  369. patch = PatchManager.GetSelectedLinesAsPatch(Module, SelectedDiff.GetText(),
  370. SelectedDiff.GetSelectionPosition(), SelectedDiff.GetSelectionLength(),
  371. _currentItemStaged, SelectedDiff.Encoding, _currentItem.IsNew);
  372. if (patch != null && patch.Length > 0)
  373. {
  374. string output = Module.RunGitCmd(args, null, patch);
  375. ProcessApplyOutput(output, patch);
  376. }
  377. }
  378. private void ProcessApplyOutput(string output, byte[] patch)
  379. {
  380. if (!string.IsNullOrEmpty(output))
  381. {
  382. MessageBox.Show(this, output + "\n\n" + SelectedDiff.Encoding.GetString(patch));
  383. }
  384. if (_currentItemStaged)
  385. Staged.StoreNextIndexToSelect();
  386. else
  387. Unstaged.StoreNextIndexToSelect();
  388. ScheduleGoToLine();
  389. _selectedDiffReloaded = false;
  390. RescanChanges();
  391. }
  392. private void ScheduleGoToLine()
  393. {
  394. int selectedDifflineToSelect = SelectedDiff.GetText().Substring(0, SelectedDiff.GetSelectionPosition()).Count(c => c == '\n');
  395. int scrollPosition = SelectedDiff.ScrollPos;
  396. string selectedFileName = _currentItem.Name;
  397. Action stageAreaLoaded = null;
  398. stageAreaLoaded = () =>
  399. {
  400. EventHandler textLoaded = null;
  401. textLoaded = (a, b) =>
  402. {
  403. if (_currentItem != null && _currentItem.Name.Equals(selectedFileName))
  404. {
  405. SelectedDiff.GoToLine(selectedDifflineToSelect);
  406. SelectedDiff.ScrollPos = scrollPosition;
  407. }
  408. SelectedDiff.TextLoaded -= textLoaded;
  409. _selectedDiffReloaded = true;
  410. };
  411. SelectedDiff.TextLoaded += textLoaded;
  412. OnStageAreaLoaded -= stageAreaLoaded;
  413. };
  414. OnStageAreaLoaded += stageAreaLoaded;
  415. }
  416. private void ResetSelectedLinesToolStripMenuItemClick(object sender, EventArgs e)
  417. {
  418. //to prevent multiple clicks
  419. if (!_selectedDiffReloaded)
  420. return;
  421. if (MessageBox.Show(this, _resetSelectedLinesConfirmation.Text, _resetChangesCaption.Text,
  422. MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No)
  423. return;
  424. Debug.Assert(_currentItem != null);
  425. // Prepare git command
  426. string args = "apply --whitespace=nowarn";
  427. if (_currentItemStaged) //staged
  428. args += " --reverse --index";
  429. byte[] patch;
  430. if (_currentItemStaged)
  431. patch = PatchManager.GetSelectedLinesAsPatch(Module, SelectedDiff.GetText(),
  432. SelectedDiff.GetSelectionPosition(), SelectedDiff.GetSelectionLength(),
  433. _currentItemStaged, SelectedDiff.Encoding, _currentItem.IsNew);
  434. else if (_currentItem.IsNew)
  435. patch = PatchManager.GetSelectedLinesAsNewPatch(Module, _currentItem.Name,
  436. SelectedDiff.GetText(), SelectedDiff.GetSelectionPosition(), SelectedDiff.GetSelectionLength(),
  437. SelectedDiff.Encoding, true);
  438. else
  439. patch = PatchManager.GetResetUnstagedLinesAsPatch(Module, SelectedDiff.GetText(),
  440. SelectedDiff.GetSelectionPosition(), SelectedDiff.GetSelectionLength(),
  441. _currentItemStaged, SelectedDiff.Encoding);
  442. if (patch != null && patch.Length > 0)
  443. {
  444. string output = Module.RunGitCmd(args, null, patch);
  445. if (EnvUtils.RunningOnWindows())
  446. {
  447. //remove file mode warnings on windows
  448. Regex regEx = new Regex("warning: .*has type .* expected .*", RegexOptions.Compiled);
  449. output = output.RemoveLines(regEx.IsMatch);
  450. }
  451. ProcessApplyOutput(output, patch);
  452. }
  453. }
  454. private void EnableStageButtons(bool enable)
  455. {
  456. toolUnstageItem.Enabled = enable;
  457. toolUnstageAllItem.Enabled = enable;
  458. toolStageItem.Enabled = enable;
  459. toolStageAllItem.Enabled = enable;
  460. workingToolStripMenuItem.Enabled = enable;
  461. ResetUnStaged.Enabled = Unstaged.AllItems.Any();
  462. }
  463. private bool _initialized;
  464. private void Initialize(bool loadUnstaged)
  465. {
  466. _initialized = true;
  467. Cursor.Current = Cursors.WaitCursor;
  468. if (loadUnstaged)
  469. {
  470. Loading.Visible = true;
  471. LoadingStaged.Visible = true;
  472. Commit.Enabled = false;
  473. CommitAndPush.Enabled = false;
  474. Amend.Enabled = false;
  475. Reset.Enabled = false;
  476. ResetUnStaged.Enabled = false;
  477. EnableStageButtons(false);
  478. ComputeUnstagedFiles(LoadUnstagedOutput, true);
  479. }
  480. UpdateMergeHead();
  481. Message.TextBoxFont = AppSettings.CommitFont;
  482. // Check if commit.template is used
  483. string fileName = Module.GetEffectivePathSetting("commit.template");
  484. if (!string.IsNullOrEmpty(fileName))
  485. {
  486. using (var commitReader = new StreamReader(fileName))
  487. {
  488. _commitTemplate = commitReader.ReadToEnd().Replace("\r", "");
  489. }
  490. Message.Text = _commitTemplate;
  491. }
  492. Cursor.Current = Cursors.Default;
  493. }
  494. private void Initialize()
  495. {
  496. Initialize(true);
  497. }
  498. private void UpdateMergeHead()
  499. {
  500. var mergeHead = Module.RevParse("MERGE_HEAD");
  501. IsMergeCommit = Regex.IsMatch(mergeHead, GitRevision.Sha1HashPattern);
  502. }
  503. private void InitializedStaged()
  504. {
  505. Cursor.Current = Cursors.WaitCursor;
  506. SolveMergeconflicts.Visible = Module.InTheMiddleOfConflictedMerge();
  507. Staged.GitItemStatuses = Module.GetStagedFilesWithSubmodulesStatus();
  508. Cursor.Current = Cursors.Default;
  509. }
  510. private event Action OnStageAreaLoaded;
  511. private bool _loadUnstagedOutputFirstTime = true;
  512. /// <summary>
  513. /// Loads the unstaged output.
  514. /// This method is passed in to the SetTextCallBack delegate
  515. /// to set the Text property of textBox1.
  516. /// </summary>
  517. private void LoadUnstagedOutput(IList<GitItemStatus> allChangedFiles)
  518. {
  519. var lastSelection = new List<GitItemStatus>();
  520. if (_currentFilesList != null)
  521. lastSelection = _currentSelection;
  522. var unStagedFiles = new List<GitItemStatus>();
  523. var stagedFiles = new List<GitItemStatus>();
  524. foreach (var fileStatus in allChangedFiles)
  525. {
  526. if (fileStatus.IsStaged)
  527. stagedFiles.Add(fileStatus);
  528. else
  529. unStagedFiles.Add(fileStatus);
  530. }
  531. Unstaged.GitItemStatuses = unStagedFiles;
  532. Staged.GitItemStatuses = stagedFiles;
  533. Loading.Visible = false;
  534. LoadingStaged.Visible = false;
  535. Commit.Enabled = true;
  536. CommitAndPush.Enabled = true;
  537. Amend.Enabled = true;
  538. Reset.Enabled = DoChangesExist();
  539. EnableStageButtons(true);
  540. workingToolStripMenuItem.Enabled = true;
  541. var inTheMiddleOfConflictedMerge = Module.InTheMiddleOfConflictedMerge();
  542. SolveMergeconflicts.Visible = inTheMiddleOfConflictedMerge;
  543. if (Staged.IsEmpty)
  544. {
  545. _currentFilesList = Unstaged;
  546. if (Staged.ContainsFocus)
  547. Unstaged.Focus();
  548. }
  549. else if (Unstaged.IsEmpty)
  550. {
  551. _currentFilesList = Staged;
  552. if (Unstaged.ContainsFocus)
  553. Staged.Focus();
  554. }
  555. RestoreSelectedFiles(unStagedFiles, stagedFiles, lastSelection);
  556. if (OnStageAreaLoaded != null)
  557. OnStageAreaLoaded();
  558. if (_loadUnstagedOutputFirstTime)
  559. {
  560. var fc = this.FindFocusedControl();
  561. if (fc == this.Ok)
  562. {
  563. if (Unstaged.GitItemStatuses.Any())
  564. Unstaged.Focus();
  565. else if (Staged.GitItemStatuses.Any())
  566. Message.Focus();
  567. else
  568. Amend.Focus();
  569. }
  570. _loadUnstagedOutputFirstTime = false;
  571. }
  572. }
  573. private void SelectStoredNextIndex()
  574. {
  575. Unstaged.SelectStoredNextIndex(0);
  576. if (Unstaged.GitItemStatuses.Any())
  577. {
  578. Staged.SelectStoredNextIndex();
  579. }
  580. else
  581. {
  582. Staged.SelectStoredNextIndex(0);
  583. }
  584. }
  585. private void RestoreSelectedFiles(IList<GitItemStatus> unStagedFiles, IList<GitItemStatus> stagedFiles, IList<GitItemStatus> lastSelection)
  586. {
  587. if (_currentFilesList == null || _currentFilesList.IsEmpty)
  588. {
  589. SelectStoredNextIndex();
  590. return;
  591. }
  592. var newItems = _currentFilesList == Staged ? stagedFiles : unStagedFiles;
  593. var names = lastSelection.ToHashSet(x => x.Name);
  594. var newSelection = newItems.Where(x => names.Contains(x.Name)).ToList();
  595. if (newSelection.Any())
  596. _currentFilesList.SelectedItems = newSelection;
  597. else
  598. SelectStoredNextIndex();
  599. }
  600. /// <summary>Returns if there are any changes at all, staged or unstaged.</summary>
  601. private bool DoChangesExist()
  602. {
  603. return Unstaged.AllItems.Any() || Staged.AllItems.Any();
  604. }
  605. private void ShowChanges(GitItemStatus item, bool staged)
  606. {
  607. _currentItem = item;
  608. _currentItemStaged = staged;
  609. if (item == null)
  610. return;
  611. long length = GetItemLength(item.Name);
  612. if (length < 5 * 1024 * 1024) // 5Mb
  613. SetSelectedDiff(item, staged);
  614. else
  615. {
  616. SelectedDiff.Clear();
  617. SelectedDiff.Refresh();
  618. llShowPreview.Show();
  619. }
  620. _stageSelectedLinesToolStripMenuItem.Text = staged ? _unstageSelectedLines.Text : _stageSelectedLines.Text;
  621. _stageSelectedLinesToolStripMenuItem.Image = staged ? toolUnstageItem.Image : toolStageItem.Image;
  622. _stageSelectedLinesToolStripMenuItem.ShortcutKeyDisplayString =
  623. GetShortcutKeys((int)(staged ? Commands.UnStageSelectedFile : Commands.StageSelectedFile)).ToShortcutKeyDisplayString();
  624. }
  625. private long GetItemLength(string fileName)
  626. {
  627. long length = -1;
  628. string path = fileName;
  629. if (!File.Exists(fileName))
  630. path = Path.Combine(Module.WorkingDir, fileName);
  631. if (File.Exists(path))
  632. {
  633. FileInfo fi = new FileInfo(path);
  634. length = fi.Length;
  635. }
  636. return length;
  637. }
  638. private void llShowPreview_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
  639. {
  640. llShowPreview.Hide();
  641. SetSelectedDiff(_currentItem, _currentItemStaged);
  642. }
  643. private void SetSelectedDiff(GitItemStatus item, bool staged)
  644. {
  645. if (item.Name.EndsWith(".png"))
  646. {
  647. SelectedDiff.ViewFile(item.Name);
  648. }
  649. else if (item.IsTracked)
  650. {
  651. SelectedDiff.ViewCurrentChanges(item, staged);
  652. }
  653. else
  654. {
  655. SelectedDiff.ViewFile(item.Name);
  656. }
  657. }
  658. private List<GitItemStatus> _currentSelection;
  659. private void ClearDiffViewIfNoFilesLeft()
  660. {
  661. llShowPreview.Hide();
  662. if (Staged.IsEmpty && Unstaged.IsEmpty)
  663. SelectedDiff.Clear();
  664. }
  665. private void CommitClick(object sender, EventArgs e)
  666. {
  667. ExecuteCommitCommand();
  668. }
  669. private void CheckForStagedAndCommit(bool amend, bool push)
  670. {
  671. if (amend)
  672. {
  673. // This is an amend commit. Confirm the user understands the implications. We don't want to prompt for an empty
  674. // commit, because amend may be used just to change the commit message or timestamp.
  675. if (!AppSettings.DontConfirmAmend)
  676. if (MessageBox.Show(this, _amendCommit.Text, _amendCommitCaption.Text, MessageBoxButtons.YesNo) != DialogResult.Yes)
  677. return;
  678. }
  679. else if (Staged.IsEmpty)
  680. {
  681. if (IsMergeCommit)
  682. {
  683. // it is a merge commit, so user can commit just for merging two branches even the changeset is empty,
  684. // but also user may forget to add files, so only ask for confirmation that user really wants to commit an empty changeset
  685. if (MessageBox.Show(this, _noFilesStagedAndConfirmAnEmptyMergeCommit.Text, _noStagedChanges.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes)
  686. return;
  687. }
  688. else
  689. {
  690. if (Unstaged.IsEmpty)
  691. {
  692. MessageBox.Show(this, _noFilesStagedAndNothingToCommit.Text, _noStagedChanges.Text, MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
  693. return;
  694. }
  695. // there are no staged files, but there are unstaged files. Most probably user forgot to stage them.
  696. if (MessageBox.Show(this, _noFilesStagedButSuggestToCommitAllUnstaged.Text, _noStagedChanges.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Question) != DialogResult.Yes)
  697. return;
  698. StageAll();
  699. // if staging failed (i.e. line endings conflict), user already got error message, don't try to commit empty changeset.
  700. if (Staged.IsEmpty)
  701. return;
  702. }
  703. }
  704. DoCommit(amend, push);
  705. }
  706. private void DoCommit(bool amend, bool push)
  707. {
  708. if (Module.InTheMiddleOfConflictedMerge())
  709. {
  710. MessageBox.Show(this, _mergeConflicts.Text, _mergeConflictsCaption.Text, MessageBoxButtons.OK, MessageBoxIcon.Error);
  711. return;
  712. }
  713. if (_useFormCommitMessage && (string.IsNullOrEmpty(Message.Text) || Message.Text == _commitTemplate))
  714. {
  715. MessageBox.Show(this, _enterCommitMessage.Text, _enterCommitMessageCaption.Text, MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
  716. return;
  717. }
  718. if (_useFormCommitMessage && !ValidCommitMessage())
  719. return;
  720. if (Module.IsDetachedHead())
  721. {
  722. int idx = PSTaskDialog.cTaskDialog.ShowCommandBox(this,
  723. _notOnBranchCaption.Text,
  724. _notOnBranchMainInstruction.Text,
  725. _notOnBranch.Text,
  726. _notOnBranchButtons.Text,
  727. true);
  728. switch (idx)
  729. {
  730. case 0:
  731. string revision = _editedCommit != null ? _editedCommit.Guid : "";
  732. if (!UICommands.StartCheckoutBranch(this, new[] {revision}))
  733. return;
  734. break;
  735. case 1:
  736. if (!UICommands.StartCreateBranchDialog(this, _editedCommit))
  737. return;
  738. break;
  739. case -1:
  740. return;
  741. }
  742. }
  743. try
  744. {
  745. if (_useFormCommitMessage)
  746. {
  747. SetCommitMessageFromTextBox(Message.Text);
  748. }
  749. ScriptManager.RunEventScripts(this, ScriptEvent.BeforeCommit);
  750. var errorOccurred = !FormProcess.ShowDialog(this, Module.CommitCmd(amend, signOffToolStripMenuItem.Checked, toolAuthor.Text, _useFormCommitMessage));
  751. UICommands.RepoChangedNotifier.Notify();
  752. if (errorOccurred)
  753. return;
  754. Amend.Checked = false;
  755. ScriptManager.RunEventScripts(this, ScriptEvent.AfterCommit);
  756. Message.Text = string.Empty;
  757. CommitHelper.SetCommitMessage(Module, string.Empty);
  758. bool pushCompleted = true;
  759. if (push)
  760. {
  761. UICommands.StartPushDialog(this, true, out pushCompleted);
  762. }
  763. if (pushCompleted && Module.SuperprojectModule != null && AppSettings.StageInSuperprojectAfterCommit)
  764. Module.SuperprojectModule.StageFile(Module.SubmodulePath);
  765. if (AppSettings.CloseCommitDialogAfterCommit)
  766. {
  767. Close();
  768. return;
  769. }
  770. if (Unstaged.GitItemStatuses.Any())
  771. {
  772. InitializedStaged();
  773. return;
  774. }
  775. if (AppSettings.CloseCommitDialogAfterLastCommit)
  776. Close();
  777. else
  778. InitializedStaged();
  779. }
  780. catch (Exception e)
  781. {
  782. MessageBox.Show(this, string.Format("Exception: {0}", e.Message));
  783. }
  784. }
  785. private bool ValidCommitMessage()
  786. {
  787. if (AppSettings.CommitValidationMaxCntCharsFirstLine > 0)
  788. {
  789. var firstLine = Message.Text.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries)[0];
  790. if (firstLine.Length > AppSettings.CommitValidationMaxCntCharsFirstLine)
  791. {
  792. if (DialogResult.No == MessageBox.Show(this, _commitMsgFirstLineInvalid.Text, _commitValidationCaption.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Asterisk))
  793. return false;
  794. }
  795. }
  796. if (AppSettings.CommitValidationMaxCntCharsPerLine > 0)
  797. {
  798. var lines = Message.Text.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.RemoveEmptyEntries);
  799. foreach (var line in lines)
  800. {
  801. if (line.Length > AppSettings.CommitValidationMaxCntCharsPerLine)
  802. {
  803. if (DialogResult.No == MessageBox.Show(this, String.Format(_commitMsgLineInvalid.Text, line), _commitValidationCaption.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Asterisk))
  804. return false;
  805. }
  806. }
  807. }
  808. if (AppSettings.CommitValidationSecondLineMustBeEmpty)
  809. {
  810. var lines = Message.Text.Split(new string[] { "\r\n", "\n" }, StringSplitOptions.None);
  811. if (lines.Length > 2)
  812. {
  813. if (lines[1].Length != 0)
  814. {
  815. if (DialogResult.No == MessageBox.Show(this, _commitMsgSecondLineNotEmpty.Text, _commitValidationCaption.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Asterisk))
  816. return false;
  817. }
  818. }
  819. }
  820. if (!AppSettings.CommitValidationRegEx.IsNullOrEmpty())
  821. {
  822. try
  823. {
  824. if (!Regex.IsMatch(Message.Text, AppSettings.CommitValidationRegEx))
  825. {
  826. if (DialogResult.No == MessageBox.Show(this, _commitMsgRegExNotMatched.Text, _commitValidationCaption.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Asterisk))
  827. return false;
  828. }
  829. }
  830. catch
  831. {
  832. }
  833. }
  834. return true;
  835. }
  836. private void RescanChanges()
  837. {
  838. if (_shouldRescanChanges)
  839. {
  840. toolRefreshItem.Enabled = false;
  841. Initialize();
  842. toolRefreshItem.Enabled = true;
  843. }
  844. }
  845. private void UnstageFilesClick(object sender, EventArgs e)
  846. {
  847. if (_currentFilesList != Staged)
  848. return;
  849. Unstage();
  850. }
  851. void Staged_DoubleClick(object sender, EventArgs e)
  852. {
  853. _currentFilesList = Staged;
  854. Unstage();
  855. }
  856. private void UnstageAllToolStripMenuItemClick(object sender, EventArgs e)
  857. {
  858. var lastSelection = new List<GitItemStatus>();
  859. if (_currentFilesList != null)
  860. lastSelection = _currentSelection;
  861. Action stageAreaLoaded = null;
  862. stageAreaLoaded = () =>
  863. {
  864. _currentFilesList = Unstaged;
  865. RestoreSelectedFiles(Unstaged.GitItemStatuses, Staged.GitItemStatuses, lastSelection);
  866. Unstaged.Focus();
  867. OnStageAreaLoaded -= stageAreaLoaded;
  868. };
  869. OnStageAreaLoaded += stageAreaLoaded;
  870. Module.ResetMixed("HEAD");
  871. Initialize();
  872. }
  873. private void UnstagedSelectionChanged(object sender, EventArgs e)
  874. {
  875. if (_currentFilesList != Unstaged || _skipUpdate)
  876. return;
  877. ClearDiffViewIfNoFilesLeft();
  878. Unstaged.ContextMenuStrip = null;
  879. if (!Unstaged.SelectedItems.Any())
  880. return;
  881. Staged.ClearSelected();
  882. _currentSelection = Unstaged.SelectedItems.ToList();
  883. GitItemStatus item = _currentSelection.FirstOrDefault();
  884. ShowChanges(item, false);
  885. if (!item.IsSubmodule)
  886. Unstaged.ContextMenuStrip = UnstagedFileContext;
  887. else
  888. Unstaged.ContextMenuStrip = UnstagedSubmoduleContext;
  889. }
  890. private void Unstaged_Enter(object sender, EventArgs e)
  891. {
  892. if (_currentFilesList != Unstaged)
  893. {
  894. _currentFilesList = Unstaged;
  895. _skipUpdate = false;
  896. UnstagedSelectionChanged(Unstaged, null);
  897. }
  898. }
  899. private void Unstage()
  900. {
  901. if (Staged.GitItemStatuses.Count() > 10 && Staged.SelectedItems.Count() == Staged.GitItemStatuses.Count())
  902. {
  903. UnstageAllToolStripMenuItemClick(null, null);
  904. return;
  905. }
  906. Cursor.Current = Cursors.WaitCursor;
  907. EnableStageButtons(false);
  908. try
  909. {
  910. var lastSelection = new List<GitItemStatus>();
  911. if (_currentFilesList != null)
  912. lastSelection = _currentSelection;
  913. toolStripProgressBar1.Visible = true;
  914. toolStripProgressBar1.Maximum = Staged.SelectedItems.Count() * 2;
  915. toolStripProgressBar1.Value = 0;
  916. Staged.StoreNextIndexToSelect();
  917. var files = new List<GitItemStatus>();
  918. var allFiles = new List<GitItemStatus>();
  919. foreach (var item in Staged.SelectedItems)
  920. {
  921. toolStripProgressBar1.Value = Math.Min(toolStripProgressBar1.Maximum - 1, toolStripProgressBar1.Value + 1);
  922. if (!item.IsNew)
  923. {
  924. toolStripProgressBar1.Value = Math.Min(toolStripProgressBar1.Maximum - 1, toolStripProgressBar1.Value + 1);
  925. Module.UnstageFileToRemove(item.Name);
  926. if (item.IsRenamed)
  927. Module.UnstageFileToRemove(item.OldName);
  928. }
  929. else
  930. {
  931. files.Add(item);
  932. }
  933. allFiles.Add(item);
  934. }
  935. Module.UnstageFiles(files);
  936. _skipUpdate = true;
  937. InitializedStaged();
  938. var stagedFiles = Staged.GitItemStatuses.ToList();
  939. var unStagedFiles = Unstaged.GitItemStatuses.ToList();
  940. foreach (var item in allFiles)
  941. {
  942. var item1 = item;
  943. if (stagedFiles.Exists(i => i.Name == item1.Name))
  944. continue;
  945. var item2 = item;
  946. if (unStagedFiles.Exists(i => i.Name == item2.Name))
  947. continue;
  948. if (item.IsNew && !item.IsChanged && !item.IsDeleted)
  949. item.IsTracked = false;
  950. else
  951. item.IsTracked = true;
  952. if (item.IsRenamed)
  953. {
  954. var clone = new GitItemStatus
  955. {
  956. Name = item.OldName,
  957. IsDeleted = true,
  958. IsTracked = true,
  959. IsStaged = false
  960. };
  961. unStagedFiles.Add(clone);
  962. item.IsRenamed = false;
  963. item.IsNew = true;
  964. item.IsTracked = false;
  965. item.OldName = string.Empty;
  966. }
  967. item.IsStaged = false;
  968. unStagedFiles.Add(item);
  969. }
  970. Staged.GitItemStatuses = stagedFiles;
  971. Unstaged.GitItemStatuses = unStagedFiles;
  972. _skipUpdate = false;
  973. Staged.SelectStoredNextIndex();
  974. toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
  975. toolStripProgressBar1.Visible = false;
  976. if (Staged.IsEmpty)
  977. {
  978. _currentFilesList = Unstaged;
  979. RestoreSelectedFiles(Unstaged.GitItemStatuses, Staged.GitItemStatuses, lastSelection);
  980. Unstaged.Focus();
  981. }
  982. }
  983. catch (Exception ex)
  984. {
  985. Trace.WriteLine(ex.Message);
  986. }
  987. EnableStageButtons(true);
  988. Cursor.Current = Cursors.Default;
  989. if (AppSettings.RevisionGraphShowWorkingDirChanges)
  990. UICommands.RepoChangedNotifier.Notify();
  991. }
  992. private void StageClick(object sender, EventArgs e)
  993. {
  994. if (_currentFilesList != Unstaged)
  995. return;
  996. Stage(Unstaged.SelectedItems.ToList());
  997. if (Unstaged.IsEmpty)
  998. Staged.Focus();
  999. }
  1000. void Unstaged_DoubleClick(object sender, EventArgs e)
  1001. {
  1002. _currentFilesList = Unstaged;
  1003. Stage(Unstaged.SelectedItems.ToList());
  1004. if (Unstaged.IsEmpty)
  1005. Staged.Focus();
  1006. }
  1007. private void StageAll()
  1008. {
  1009. Stage(Unstaged.GitItemStatuses);
  1010. Staged.Focus();
  1011. }
  1012. private void StageAllToolStripMenuItemClick(object sender, EventArgs e)
  1013. {
  1014. StageAll();
  1015. }
  1016. private void StagedSelectionChanged(object sender, EventArgs e)
  1017. {
  1018. if (_currentFilesList != Staged || _skipUpdate)
  1019. return;
  1020. ClearDiffViewIfNoFilesLeft();
  1021. if (!Staged.SelectedItems.Any())
  1022. return;
  1023. Unstaged.ClearSelected();
  1024. _currentSelection = Staged.SelectedItems.ToList();
  1025. GitItemStatus item = _currentSelection.FirstOrDefault();
  1026. ShowChanges(item, true);
  1027. }
  1028. private void Staged_Enter(object sender, EventArgs e)
  1029. {
  1030. if (_currentFilesList != Staged)
  1031. {
  1032. _currentFilesList = Staged;
  1033. _skipUpdate = false;
  1034. StagedSelectionChanged(Staged, null);
  1035. }
  1036. }
  1037. private void Stage(IList<GitItemStatus> gitItemStatusses)
  1038. {
  1039. EnableStageButtons(false);
  1040. try
  1041. {
  1042. var lastSelection = new List<GitItemStatus>();
  1043. if (_currentFilesList != null)
  1044. lastSelection = _currentSelection;
  1045. Cursor.Current = Cursors.WaitCursor;
  1046. Unstaged.StoreNextIndexToSelect();
  1047. toolStripProgressBar1.Visible = true;
  1048. toolStripProgressBar1.Maximum = gitItemStatusses.Count() * 2;
  1049. toolStripProgressBar1.Value = 0;
  1050. var files = new List<GitItemStatus>();
  1051. foreach (var gitItemStatus in gitItemStatusses)
  1052. {
  1053. toolStripProgressBar1.Value = Math.Min(toolStripProgressBar1.Maximum - 1, toolStripProgressBar1.Value + 1);
  1054. if (gitItemStatus.Name.EndsWith("/"))
  1055. gitItemStatus.Name = gitItemStatus.Name.TrimEnd('/');
  1056. files.Add(gitItemStatus);
  1057. }
  1058. bool wereErrors = false;
  1059. if (AppSettings.ShowErrorsWhenStagingFiles)
  1060. {
  1061. FormStatus.ProcessStart processStart =
  1062. form =>
  1063. {
  1064. form.AddMessageLine(string.Format(_stageFiles.Text,
  1065. files.Count));
  1066. var output = Module.StageFiles(files, out wereErrors);
  1067. form.AddMessageLine(output);
  1068. form.Done(string.IsNullOrEmpty(output));
  1069. };
  1070. using (var process = new FormStatus(processStart, null) { Text = _stageDetails.Text })
  1071. process.ShowDialogOnError(this);
  1072. }
  1073. else
  1074. {
  1075. Module.StageFiles(files, out wereErrors);
  1076. }
  1077. if (wereErrors)
  1078. RescanChanges();
  1079. else
  1080. {
  1081. InitializedStaged();
  1082. var unStagedFiles = Unstaged.GitItemStatuses.ToList();
  1083. _skipUpdate = true;
  1084. var names = new HashSet<string>();
  1085. foreach (var item in files)
  1086. {
  1087. names.Add(item.Name);
  1088. names.Add(item.OldName);
  1089. }
  1090. var unstagedItems = new HashSet<GitItemStatus>();
  1091. foreach (var item in unStagedFiles)
  1092. {
  1093. if (names.Contains(item.Name))
  1094. unstagedItems.Add(item);
  1095. }
  1096. unStagedFiles.RemoveAll(item => !item.IsSubmodule && unstagedItems.Contains(item));
  1097. unStagedFiles.RemoveAll(item => item.IsSubmodule && item.SubmoduleStatus.IsCompleted &&
  1098. !item.SubmoduleStatus.Result.IsDirty && unstagedItems.Contains(item));
  1099. foreach (var item in unstagedItems.Where(item => item.IsSubmodule &&
  1100. item.SubmoduleStatus.IsCompleted && item.SubmoduleStatus.Result.IsDirty))
  1101. {
  1102. item.SubmoduleStatus.Result.Status = SubmoduleStatus.Unknown;
  1103. }
  1104. Unstaged.GitItemStatuses = unStagedFiles;
  1105. Unstaged.ClearSelected();
  1106. _skipUpdate = false;
  1107. Unstaged.SelectStoredNextIndex();
  1108. }
  1109. toolStripProgressBar1.Value = toolStripProgressBar1.Maximum;
  1110. toolStripProgressBar1.Visible = false;
  1111. if (Unstaged.IsEmpty)
  1112. {
  1113. _currentFilesList = Staged;
  1114. RestoreSelectedFiles(Unstaged.GitItemStatuses, Staged.GitItemStatuses, lastSelection);
  1115. }
  1116. }
  1117. catch (Exception ex)
  1118. {
  1119. Trace.WriteLine(ex.Message);
  1120. }
  1121. EnableStageButtons(true);
  1122. Commit.Enabled = true;
  1123. Amend.Enabled = true;
  1124. AcceptButton = Commit;
  1125. Cursor.Current = Cursors.Default;
  1126. if (AppSettings.RevisionGraphShowWorkingDirChanges)
  1127. UICommands.RepoChangedNotifier.Notify();
  1128. }
  1129. private void ResetSoftClick(object sender, EventArgs e)
  1130. {
  1131. _shouldRescanChanges = false;
  1132. try
  1133. {
  1134. if (Unstaged.SelectedItem == null)
  1135. return;
  1136. // Show a form asking the user if they want to reset the changes.
  1137. FormResetChanges.ActionEnum resetType = FormResetChanges.ShowResetDialog(this, Unstaged.SelectedItems.Any(item => !item.IsNew), Unstaged.SelectedItems.Any(item => item.IsNew));
  1138. if (resetType == FormResetChanges.ActionEnum.Cancel)
  1139. return;
  1140. //remember max selected index
  1141. Unstaged.StoreNextIndexToSelect();
  1142. var deleteNewFiles = Unstaged.SelectedItems.Any(item => item.IsNew) && (resetType == FormResetChanges.ActionEnum.ResetAndDelete);
  1143. var filesInUse = new List<string>();
  1144. var output = new StringBuilder();
  1145. foreach (var item in Unstaged.SelectedItems)
  1146. {
  1147. if (item.IsNew)
  1148. {
  1149. if (deleteNewFiles)
  1150. {
  1151. try
  1152. {
  1153. string path = Path.Combine(Module.WorkingDir, item.Name);
  1154. if (File.Exists(path))
  1155. File.Delete(path);
  1156. else
  1157. Directory.Delete(path, true);
  1158. }
  1159. catch (System.IO.IOException)
  1160. {
  1161. filesInUse.Add(item.Name);
  1162. }
  1163. catch (System.UnauthorizedAccessException)
  1164. {
  1165. }
  1166. }
  1167. }
  1168. else
  1169. {
  1170. output.Append(Module.ResetFile(item.Name));
  1171. }
  1172. }
  1173. if (AppSettings.RevisionGraphShowWorkingDirChanges)
  1174. UICommands.RepoChangedNotifier.Notify();
  1175. if (filesInUse.Count > 0)
  1176. MessageBox.Show(this, "The following files are currently in use and will not be reset:" + Environment.NewLine + "\u2022 " + string.Join(Environment.NewLine + "\u2022 ", filesInUse), "Files In Use");
  1177. if (!string.IsNullOrEmpty(output.ToString()))
  1178. MessageBox.Show(this, output.ToString(), _resetChangesCaption.Text);
  1179. }
  1180. finally
  1181. {
  1182. _shouldRescanChanges = true;
  1183. }
  1184. Initialize();
  1185. }
  1186. private void DeleteFileToolStripMenuItemClick(object sender, EventArgs e)
  1187. {
  1188. try
  1189. {
  1190. SelectedDiff.Clear();
  1191. if (Unstaged.SelectedItem == null ||
  1192. MessageBox.Show(this, _deleteSelectedFiles.Text, _deleteSelectedFilesCaption.Text, MessageBoxButtons.YesNo) !=
  1193. DialogResult.Yes)
  1194. return;
  1195. Unstaged.StoreNextIndexToSelect();
  1196. foreach (var item in Unstaged.SelectedItems)
  1197. File.Delete(Path.Combine(Module.WorkingDir, item.Name));
  1198. Initialize();
  1199. }
  1200. catch (Exception ex)
  1201. {
  1202. MessageBox.Show(this, _deleteFailed.Text + Environment.NewLine + ex.Message);
  1203. }
  1204. }
  1205. private void SolveMergeConflictsClick(object sender, EventArgs e)
  1206. {
  1207. if (UICommands.StartResolveConflictsDialog(this, false))
  1208. Initialize();
  1209. }
  1210. private void DeleteSelectedFilesToolStripMenuItemClick(object sender, EventArgs e)
  1211. {
  1212. if (MessageBox.Show(this, _deleteSelectedFiles.Text, _deleteSelectedFilesCaption.Text, MessageBoxButtons.YesNo) !=
  1213. DialogResult.Yes)
  1214. return;
  1215. try
  1216. {
  1217. foreach (var gitItemStatus in Unstaged.SelectedItems)
  1218. File.Delete(Path.Combine(Module.WorkingDir, gitItemStatus.Name));
  1219. }
  1220. catch (Exception ex)
  1221. {
  1222. MessageBox.Show(this, _deleteFailed.Text + Environment.NewLine + ex);
  1223. }
  1224. Initialize();
  1225. }
  1226. private void ResetSelectedFilesToolStripMenuItemClick(object sender, EventArgs e)
  1227. {
  1228. if (MessageBox.Show(this, _resetSelectedChangesText.Text, _resetChangesCaption.Text, MessageBoxButtons.YesNo) !=
  1229. DialogResult.Yes)
  1230. return;
  1231. foreach (var gitItemStatus in Unstaged.SelectedItems)
  1232. {
  1233. Module.ResetFile(gitItemStatus.Name);
  1234. }
  1235. Initialize();
  1236. }
  1237. private void ResetAlltrackedChangesToolStripMenuItemClick(object sender, EventArgs e)
  1238. {
  1239. ResetClick(null, null);
  1240. }
  1241. private void resetUnstagedChangesToolStripMenuItem_Click(object sender, EventArgs e)
  1242. {
  1243. ResetUnStagedClick(null, null);
  1244. }
  1245. private void EditGitIgnoreToolStripMenuItemClick(object sender, EventArgs e)
  1246. {
  1247. UICommands.StartEditGitIgnoreDialog(this);
  1248. Initialize();
  1249. }
  1250. private void FormCommitShown(object sender, EventArgs e)
  1251. {
  1252. if (!_initialized)
  1253. Initialize();
  1254. AcceptButton = Commit;
  1255. string message;
  1256. switch (_commitKind)
  1257. {
  1258. case CommitKind.Fixup:
  1259. message = string.Format("fixup! {0}", _editedCommit.Message);
  1260. break;
  1261. case CommitKind.Squash:
  1262. message = string.Format("squash! {0}", _editedCommit.Message);
  1263. break;
  1264. default:
  1265. message = Module.GetMergeMessage();
  1266. if (string.IsNullOrEmpty(message) && File.Exists(GitCommands.CommitHelper.GetCommitMessagePath(Module)))
  1267. message = File.ReadAllText(GitCommands.CommitHelper.GetCommitMessagePath(Module), Module.CommitEncoding);
  1268. break;
  1269. }
  1270. if (_useFormCommitMessage && !string.IsNullOrEmpty(message))
  1271. Message.Text = message;
  1272. Task.Factory.StartNew(() => string.Format(_formTitle.Text, Module.GetSelectedBranch(),
  1273. Module.WorkingDir))
  1274. .ContinueWith(task => Text = task.Result, _taskScheduler);
  1275. }
  1276. private void SetCommitMessageFromTextBox(string commitMessageText)
  1277. {
  1278. //Save last commit message in settings. This way it can be used in multiple repositories.
  1279. AppSettings.LastCommitMessage = commitMessageText;
  1280. var path = Path.Combine(Module.GetGitDirectory(), "COMMITMESSAGE");
  1281. //Commit messages are UTF-8 by default unless otherwise in the config file.
  1282. //The git manual states:
  1283. // git commit and git commit-tree issues a warning if the commit log message
  1284. // given to it does not look like a valid UTF-8 string, unless you
  1285. // explicitly say your project uses a legacy encoding. The way to say
  1286. // this is to have i18n.commitencoding in .git/config file, like this:...
  1287. Encoding encoding = Module.CommitEncoding;
  1288. using (var textWriter = new StreamWriter(path, false, encoding))
  1289. {
  1290. var lineNumber = 0;
  1291. foreach (var line in commitMessageText.Split('\n'))
  1292. {
  1293. //When a committemplate is used, skip comments
  1294. //otherwise: "#" is probably not used for comment but for issue number
  1295. if (!line.StartsWith("#") ||
  1296. string.IsNullOrEmpty(_commitTemplate))
  1297. {
  1298. if (lineNumber == 1 && !String.IsNullOrEmpty(line))
  1299. textWriter.WriteLine();
  1300. textWriter.WriteLine(line);
  1301. }
  1302. lineNumber++;
  1303. }
  1304. }
  1305. }
  1306. private void DeleteAllUntrackedFilesToolStripMenuItemClick(object sender, EventArgs e)
  1307. {
  1308. if (MessageBox.Show(this,
  1309. _deleteUntrackedFiles.Text,
  1310. _deleteUntrackedFilesCaption.Text,
  1311. MessageBoxButtons.YesNo) !=
  1312. DialogResult.Yes)
  1313. return;
  1314. FormProcess.ShowDialog(this, "clean -f");
  1315. Initialize();
  1316. }
  1317. private void ShowIgnoredFilesToolStripMenuItemClick(object sender, EventArgs e)
  1318. {
  1319. showIgnoredFilesToolStripMenuItem.Checked = !showIgnoredFilesToolStripMenuItem.Checked;
  1320. RescanChanges();
  1321. }
  1322. private void CommitMessageToolStripMenuItemDropDownOpening(object sender, EventArgs e)
  1323. {
  1324. commitMessageToolStripMenuItem.DropDownItems.Clear();
  1325. var msg = AppSettings.LastCommitMessage;
  1326. var prevMsgs = Module.GetPreviousCommitMessages(AppSettings.CommitDialogNumberOfPreviousMessages);
  1327. if (!prevMsgs.Contains(msg))
  1328. {
  1329. prevMsgs = new string[] { msg }.Concat(prevMsgs).Take(AppSettings.CommitDialogNumberOfPreviousMessages);
  1330. }
  1331. foreach (var localLastCommitMessage in prevMsgs)
  1332. {
  1333. AddCommitMessageToMenu(localLastCommitMessage);
  1334. }
  1335. commitMessageToolStripMenuItem.DropDownItems.AddRange(new ToolStripItem[]
  1336. {
  1337. toolStripMenuItem1,
  1338. generateListOfChangesInSubmodulesChangesToolStripMenuItem
  1339. });
  1340. }
  1341. private void AddCommitMessageToMenu(string commitMessage)
  1342. {
  1343. if (string.IsNullOrEmpty(commitMessage))
  1344. return;
  1345. var toolStripItem =
  1346. new ToolStripMenuItem
  1347. {
  1348. Tag = commitMessage,
  1349. Text =
  1350. commitMessage.Substring(0,
  1351. Math.Min(Math.Min(50, commitMessage.Length),
  1352. commitMessage.Contains("\n") ? commitMessage.IndexOf('\n') : 99)) +
  1353. "..."
  1354. };
  1355. commitMessageToolStripMenuItem.DropDownItems.Add(toolStripItem);
  1356. }
  1357. private void CommitMessageToolStripMenuItemDropDownItemClicked(object sender, ToolStripItemClickedEventArgs e)
  1358. {
  1359. if (e.ClickedItem.Tag != null)
  1360. Message.Text = ((string)e.ClickedItem.Tag).Trim();
  1361. }
  1362. private void generateListOfChangesInSubmodulesChangesToolStripMenuItem_Click(object sender, EventArgs e)
  1363. {
  1364. var stagedFiles = Staged.AllItems;
  1365. Dictionary<string, string> modules = stagedFiles.Where(it => it.IsSubmodule).
  1366. Select(item => item.Name).ToDictionary(item => Module.GetSubmoduleNameByPath(item));
  1367. if (modules.Count == 0)
  1368. return;
  1369. StringBuilder sb = new StringBuilder();
  1370. sb.AppendLine("Submodule" + (modules.Count == 1 ? " " : "s ") +
  1371. String.Join(", ", modules.Keys) + " updated");
  1372. sb.AppendLine();
  1373. foreach (var item in modules)
  1374. {
  1375. string diff = Module.RunGitCmd(
  1376. string.Format("diff --cached -z -- {0}", item.Value));
  1377. var lines = diff.Split(new[] { '\n' }, StringSplitOptions.RemoveEmptyEntries);
  1378. const string subprojCommit = "Subproject commit ";
  1379. var from = lines.Single(s => s.StartsWith("-" + subprojCommit)).Substring(subprojCommit.Length + 1);
  1380. var to = lines.Single(s => s.StartsWith("+" + subprojCommit)).Substring(subprojCommit.Length + 1);
  1381. if (!String.IsNullOrEmpty(from) && !String.IsNullOrEmpty(to))
  1382. {
  1383. sb.AppendLine("Submodule " + item.Key + ":");
  1384. GitModule module = new GitModule(Module.WorkingDir + item.Value.EnsureTrailingPathSeparator());
  1385. string log = module.RunGitCmd(
  1386. string.Format("log --pretty=format:\" %m %h - %s\" --no-merges {0}...{1}", from, to));
  1387. if (log.Length != 0)
  1388. sb.AppendLine(log);
  1389. else
  1390. sb.AppendLine(" * Revision changed to " + to.Substring(0, 7));
  1391. sb.AppendLine();
  1392. }
  1393. }
  1394. Message.Text = sb.ToString().TrimEnd();
  1395. }
  1396. private void AddFileTogitignoreToolStripMenuItemClick(object sender, EventArgs e)
  1397. {
  1398. if (!Unstaged.SelectedItems.Any())
  1399. return;
  1400. SelectedDiff.Clear();
  1401. var fileNames = Unstaged.SelectedItems.Select(item => item.Name).ToArray();
  1402. if (UICommands.StartAddToGitIgnoreDialog(this, fileNames))
  1403. Initialize();
  1404. }
  1405. private void SelectedDiffExtraDiffArgumentsChanged(object sender, EventArgs e)
  1406. {
  1407. ShowChanges(_currentItem, _currentItemStaged);
  1408. }
  1409. private void RescanChangesToolStripMenuItemClick(object sender, EventArgs e)
  1410. {
  1411. RescanChanges();
  1412. }
  1413. private void OpenToolStripMenuItemClick(object sender, EventArgs e)
  1414. {
  1415. FileStatusList list = sender as FileStatusList;
  1416. if (!SenderToFileStatusList(sender, out list))
  1417. return;
  1418. if (!list.SelectedItems.Any())
  1419. return;
  1420. var item = list.SelectedItem;
  1421. var fileName = item.Name;
  1422. Process.Start((Path.Combine(Module.WorkingDir, fileName)).ToNativePath());
  1423. }
  1424. private void OpenWithToolStripMenuItemClick(object sender, EventArgs e)
  1425. {
  1426. FileStatusList list;
  1427. if (!SenderToFileStatusList(sender, out list))
  1428. return;
  1429. if (!list.SelectedItems.Any())
  1430. return;
  1431. var item = list.SelectedItem;
  1432. var fileName = item.Name;
  1433. OsShellUtil.OpenAs(Module.WorkingDir + fileName.ToNativePath());
  1434. }
  1435. private void FilenameToClipboardToolStripMenuItemClick(object sender, EventArgs e)
  1436. {
  1437. FileStatusList list;
  1438. if (!SenderToFileStatusList(sender, out list))
  1439. return;
  1440. if (!list.SelectedItems.Any())
  1441. return;
  1442. var fileNames = new StringBuilder();
  1443. foreach (var item in list.SelectedItems)
  1444. {
  1445. //Only use append line when multiple items are selected.
  1446. //This to make it easier to use the text from clipboard when 1 file is selected.
  1447. if (fileNames.Length > 0)
  1448. fileNames.AppendLine();
  1449. fileNames.Append((Path.Combine(Module.WorkingDir, item.Name)).ToNativePath());
  1450. }
  1451. Clipboard.SetText(fileNames.ToString());
  1452. }
  1453. private void OpenWithDifftoolToolStripMenuItemClick(object sender, EventArgs e)
  1454. {
  1455. if (!Unstaged.SelectedItems.Any())
  1456. return;
  1457. var item = Unstaged.SelectedItem;
  1458. var fileName = item.Name;
  1459. var cmdOutput = Module.OpenWithDifftool(fileName);
  1460. if (!string.IsNullOrEmpty(cmdOutput))
  1461. MessageBox.Show(this, cmdOutput);
  1462. }
  1463. private void ResetPartOfFileToolStripMenuItemClick(object sender, EventArgs e)
  1464. {
  1465. if (Unstaged.SelectedItems.Count() != 1)
  1466. {
  1467. MessageBox.Show(this, _onlyStageChunkOfSingleFileError.Text, _resetStageChunkOfFileCaption.Text);
  1468. return;
  1469. }
  1470. foreach (var gitItemStatus in Unstaged.SelectedItems)
  1471. {
  1472. Module.RunExternalCmdShowConsole(AppSettings.GitCommand, string.Format("checkout -p \"{0}\"", gitItemStatus.Name));
  1473. Initialize();
  1474. }
  1475. }
  1476. private void ResetClick(object sender, EventArgs e)
  1477. {
  1478. UICommands.StartResetChangesDialog(this, Unstaged.AllItems, false);
  1479. Initialize();
  1480. }
  1481. private void ResetUnStagedClick(object sender, EventArgs e)
  1482. {
  1483. UICommands.StartResetChangesDialog(this, Unstaged.AllItems, true);
  1484. Initialize();
  1485. }
  1486. private void ShowUntrackedFilesToolStripMenuItemClick(object sender, EventArgs e)
  1487. {
  1488. showUntrackedFilesToolStripMenuItem.Checked = !showUntrackedFilesToolStripMenuItem.Checked;
  1489. RescanChanges();
  1490. }
  1491. private void editFileToolStripMenuItem_Click(object sender, EventArgs e)
  1492. {
  1493. FileStatusList list;
  1494. if (!SenderToFileStatusList(sender, out list))
  1495. return;
  1496. var item = list.SelectedItem;
  1497. var fileName = Path.Combine(Module.WorkingDir, item.Name);
  1498. UICommands.StartFileEditorDialog(fileName);
  1499. UnstagedSelectionChanged(null, null);
  1500. }
  1501. private void CommitAndPush_Click(object sender, EventArgs e)
  1502. {
  1503. CheckForStagedAndCommit(Amend.Checked, true);
  1504. }
  1505. private void FormCommitActivated(object sender, EventArgs e)
  1506. {
  1507. if (AppSettings.RefreshCommitDialogOnFormFocus)
  1508. RescanChanges();
  1509. updateAuthorInfo();
  1510. }
  1511. private void updateAuthorInfo()
  1512. {
  1513. GetUserSettings();
  1514. string author = "";
  1515. string committer = string.Format("{0} {1} <{2}>", _commitCommitterInfo.Text, _userName, _userEmail);
  1516. if (string.IsNullOrEmpty(toolAuthor.Text) || string.IsNullOrEmpty(toolAuthor.Text.Trim()))
  1517. {
  1518. author = string.Format("{0} {1} <{2}>", _commitAuthorInfo.Text, _userName, _userEmail);
  1519. }
  1520. else
  1521. {
  1522. author = string.Format("{0} {1}", _commitAuthorInfo.Text, toolAuthor.Text);
  1523. }
  1524. if (author != string.Format("{0} {1} <{2}>", _commitAuthorInfo.Text, _userName, _userEmail))
  1525. commitAuthorStatus.Text = string.Format("{0} {1}", committer, author);
  1526. else
  1527. commitAuthorStatus.Text = committer;
  1528. }
  1529. private void GetUserSettings()
  1530. {
  1531. _userName = Module.GetEffectiveSetting(USER_NAME_KEY);
  1532. _userEmail = Module.GetEffectiveSetting(USER_EMAIL_KEY);
  1533. }
  1534. private bool SenderToFileStatusList(object sender, out FileStatusList list)
  1535. {
  1536. list = null;
  1537. ToolStripMenuItem item = sender as ToolStripMenuItem;
  1538. if (item == null)
  1539. return false;
  1540. ContextMenuStrip menu = item.Owner as ContextMenuStrip;
  1541. if (menu == null)
  1542. return false;
  1543. ListView lv = menu.SourceControl as ListView;
  1544. if (lv == null)
  1545. return false;
  1546. list = lv.Parent as FileStatusList;
  1547. return (list != null);
  1548. }
  1549. private void ViewFileHistoryMenuItem_Click(object sender, EventArgs e)
  1550. {
  1551. FileStatusList list;
  1552. if (!SenderToFileStatusList(sender, out list))
  1553. return;
  1554. if (list.SelectedItems.Count() == 1)
  1555. {
  1556. UICommands.StartFileHistoryDialog(this, list.SelectedItem.Name, null);
  1557. }
  1558. else
  1559. MessageBox.Show(this, _selectOnlyOneFile.Text, _selectOnlyOneFileCaption.Text);
  1560. }
  1561. private void Message_KeyUp(object sender, KeyEventArgs e)
  1562. {
  1563. // Ctrl + Enter = Commit
  1564. if (e.Control && e.KeyCode == Keys.Enter)
  1565. {
  1566. ExecuteCommitCommand();
  1567. e.Handled = true;
  1568. }
  1569. }
  1570. private void ExecuteCommitCommand()
  1571. {
  1572. CheckForStagedAndCommit(Amend.Checked, false);
  1573. }
  1574. private void Message_KeyDown(object sender, KeyEventArgs e)
  1575. {
  1576. // Prevent adding a line break when all we want is to commit
  1577. if (e.Control && e.KeyCode == Keys.Enter)
  1578. e.Handled = true;
  1579. }
  1580. private void Message_TextChanged(object sender, EventArgs e)
  1581. {
  1582. //always format from 0 to handle pasted text
  1583. FormatAllText(0);
  1584. }
  1585. private void Message_TextAssigned(object sender, EventArgs e)
  1586. {
  1587. Message_TextChanged(sender, e);
  1588. }
  1589. private bool FormatLine(int line)
  1590. {
  1591. int limit1 = AppSettings.CommitValidationMaxCntCharsFirstLine;
  1592. int limitX = AppSettings.CommitValidationMaxCntCharsPerLine;
  1593. bool empty2 = AppSettings.CommitValidationSecondLineMustBeEmpty;
  1594. bool textHasChanged = false;
  1595. if (limit1 > 0 && line == 0)
  1596. {
  1597. ColorTextAsNecessary(line, limit1, false);
  1598. }
  1599. if (empty2 && line == 1)
  1600. {
  1601. // Ensure next line. Optionally add a bullet.
  1602. Message.EnsureEmptyLine(AppSettings.CommitValidationIndentAfterFirstLine, 1);
  1603. Message.ChangeTextColor(2, 0, Message.LineLength(2), Color.Black);
  1604. if (FormatLine(2))
  1605. {
  1606. textHasChanged = true;
  1607. }
  1608. }
  1609. if (limitX > 0 && line >= (empty2 ? 2 : 1))
  1610. {
  1611. if (AppSettings.CommitValidationAutoWrap)
  1612. {
  1613. if (WrapIfNecessary(line, limitX))
  1614. {
  1615. textHasChanged = true;
  1616. }
  1617. }
  1618. ColorTextAsNecessary(line, limitX, textHasChanged);
  1619. }
  1620. return textHasChanged;
  1621. }
  1622. private bool WrapIfNecessary(int line, int lineLimit)
  1623. {
  1624. if (Message.LineLength(line) > lineLimit)
  1625. {
  1626. var oldText = Message.Line(line);
  1627. var newText = WordWrapper.WrapSingleLine(oldText, lineLimit);
  1628. if (!String.Equals(oldText, newText))
  1629. {
  1630. Message.ReplaceLine(line, newText);
  1631. return true;
  1632. }
  1633. }
  1634. return false;
  1635. }
  1636. private void ColorTextAsNecessary(int line, int lineLimit, bool fullRefresh)
  1637. {
  1638. var lineLength = Message.LineLength(line);
  1639. int offset = 0;
  1640. bool textAppended = false;
  1641. if (!fullRefresh && formattedLines.Count > line)
  1642. {
  1643. offset = formattedLines[line].CommonPrefix(Message.Line(line)).Length;
  1644. textAppended = offset > 0 && offset == formattedLines[line].Length;
  1645. }
  1646. int len = Math.Min(lineLimit, lineLength) - offset;
  1647. if (!textAppended && 0 < len)
  1648. Message.ChangeTextColor(line, offset, len, Color.Black);
  1649. if (lineLength > lineLimit)
  1650. {
  1651. if (offset <= lineLimit || !textAppended)
  1652. {
  1653. offset = Math.Max(offset, lineLimit);
  1654. len = lineLength - offset;
  1655. if (len > 0)
  1656. Message.ChangeTextColor(line, offset, len, Color.Red);
  1657. }
  1658. }
  1659. }
  1660. private List<string> formattedLines = new List<string>();
  1661. private bool DidFormattedLineChange(int lineNumber)
  1662. {
  1663. //line not formated yet
  1664. if (formattedLines.Count <= lineNumber)
  1665. return true;
  1666. return !formattedLines[lineNumber].Equals(Message.Line(lineNumber), StringComparison.OrdinalIgnoreCase);
  1667. }
  1668. private void TrimFormattedLines(int lineCount)
  1669. {
  1670. if (formattedLines.Count > lineCount)
  1671. formattedLines.RemoveRange(lineCount, formattedLines.Count - lineCount);
  1672. }
  1673. private void SetFormattedLine(int lineNumber)
  1674. {
  1675. //line not formated yet
  1676. if (formattedLines.Count <= lineNumber)
  1677. {
  1678. Debug.Assert(formattedLines.Count == lineNumber, formattedLines.Count + ":" + lineNumber);
  1679. formattedLines.Add(Message.Line(lineNumber));
  1680. }
  1681. else
  1682. formattedLines[lineNumber] = Message.Line(lineNumber);
  1683. }
  1684. private void FormatAllText(int startLine)
  1685. {
  1686. var lineCount = Message.LineCount();
  1687. TrimFormattedLines(lineCount);
  1688. for (int line = startLine; line < lineCount; line++)
  1689. {
  1690. if (DidFormattedLineChange(line))
  1691. {
  1692. bool lineChanged = FormatLine(line);
  1693. SetFormattedLine(line);
  1694. if (lineChanged)
  1695. FormatAllText(line);
  1696. }
  1697. }
  1698. }
  1699. private void Message_SelectionChanged(object sender, EventArgs e)
  1700. {
  1701. commitCursorColumn.Text = Message.CurrentColumn.ToString();
  1702. commitCursorLine.Text = Message.CurrentLine.ToString();
  1703. }
  1704. private void closeDialogAfterEachCommitToolStripMenuItem_Click(object sender, EventArgs e)
  1705. {
  1706. closeDialogAfterEachCommitToolStripMenuItem.Checked = !closeDialogAfterEachCommitToolStripMenuItem.Checked;
  1707. AppSettings.CloseCommitDialogAfterCommit = closeDialogAfterEachCommitToolStripMenuItem.Checked;
  1708. }
  1709. private void closeDialogAfterAllFilesCommittedToolStripMenuItem_Click(object sender, EventArgs e)
  1710. {
  1711. closeDialogAfterAllFilesCommittedToolStripMenuItem.Checked = !closeDialogAfterAllFilesCommittedToolStripMenuItem.Checked;
  1712. AppSettings.CloseCommitDialogAfterLastCommit = closeDialogAfterAllFilesCommittedToolStripMenuItem.Checked;
  1713. }
  1714. private void refreshDialogOnFormFocusToolStripMenuItem_Click(object sender, EventArgs e)
  1715. {
  1716. refreshDialogOnFormFocusToolStripMenuItem.Checked = !refreshDialogOnFormFocusToolStripMenuItem.Checked;
  1717. AppSettings.RefreshCommitDialogOnFormFocus = refreshDialogOnFormFocusToolStripMenuItem.Checked;
  1718. }
  1719. protected override bool ProcessCmdKey(ref Message msg, Keys keyData)
  1720. {
  1721. if (keyData == Keys.F5)
  1722. RescanChanges();
  1723. return base.ProcessCmdKey(ref msg, keyData);
  1724. }
  1725. private void signOffToolStripMenuItem_Click(object snder, EventArgs e)
  1726. {
  1727. signOffToolStripMenuItem.Checked = !signOffToolStripMenuItem.Checked;
  1728. }
  1729. private void toolAuthor_TextChanged(object sender, EventArgs e)
  1730. {
  1731. toolAuthorLabelItem.Enabled = toolAuthorLabelItem.Checked = !string.IsNullOrEmpty(toolAuthor.Text);
  1732. }
  1733. private void toolAuthorLabelItem_Click(object sender, EventArgs e)
  1734. {
  1735. toolAuthor.Text = "";
  1736. toolAuthorLabelItem.Enabled = toolAuthorLabelItem.Checked = false;
  1737. updateAuthorInfo();
  1738. }
  1739. private long _lastUserInputTime;
  1740. private void FilterChanged(object sender, EventArgs e)
  1741. {
  1742. var currentTime = DateTime.Now.Ticks;
  1743. if (_lastUserInputTime == 0)
  1744. {
  1745. long timerLastChanged = currentTime;
  1746. var timer = new Timer { Interval = 250 };
  1747. timer.Tick += (s, a) =>
  1748. {
  1749. if (NoUserInput(timerLastChanged))
  1750. {
  1751. var selectionCount = 0;
  1752. try
  1753. {
  1754. selectionCount = Unstaged.SetSelectionFilter(selectionFilter.Text);
  1755. selectionFilter.ToolTipText = _selectionFilterToolTip.Text;
  1756. }
  1757. catch (ArgumentException ae)
  1758. {
  1759. selectionFilter.ToolTipText = string.Format(_selectionFilterErrorToolTip.Text, ae.Message);
  1760. }
  1761. if (selectionCount > 0)
  1762. {
  1763. AddToSelectionFilter(selectionFilter.Text);
  1764. }
  1765. timer.Stop();
  1766. _lastUserInputTime = 0;
  1767. }
  1768. timerLastChanged = _lastUserInputTime;
  1769. };
  1770. timer.Start();
  1771. }
  1772. _lastUserInputTime = currentTime;
  1773. }
  1774. private bool NoUserInput(long timerLastChanged)
  1775. {
  1776. return timerLastChanged == _lastUserInputTime;
  1777. }
  1778. private void AddToSelectionFilter(string filter)
  1779. {
  1780. if (!selectionFilter.Items.Cast<string>().Any(candiate => candiate == filter))
  1781. {
  1782. const int SelectionFilterMaxLength = 10;
  1783. if (selectionFilter.Items.Count == SelectionFilterMaxLength)
  1784. {
  1785. selectionFilter.Items.RemoveAt(SelectionFilterMaxLength - 1);
  1786. }
  1787. selectionFilter.Items.Insert(0, filter);
  1788. }
  1789. }
  1790. private void FilterIndexChanged(object sender, EventArgs e)
  1791. {
  1792. Unstaged.SetSelectionFilter(selectionFilter.Text);
  1793. }
  1794. private void ToogleShowSelectionFilter(object sender, EventArgs e)
  1795. {
  1796. toolbarSelectionFilter.Visible = selectionFilterToolStripMenuItem.Checked;
  1797. }
  1798. private void commitSubmoduleChanges_Click(object sender, EventArgs e)
  1799. {
  1800. GitUICommands submodulCommands = new GitUICommands(Module.WorkingDir + _currentItem.Name.EnsureTrailingPathSeparator());
  1801. submodulCommands.StartCommitDialog(this, false);
  1802. Initialize();
  1803. }
  1804. private void openSubmoduleMenuItem_Click(object sender, EventArgs e)
  1805. {
  1806. Process process = new Process();
  1807. process.StartInfo.FileName = Application.ExecutablePath;
  1808. process.StartInfo.Arguments = "browse";
  1809. process.StartInfo.WorkingDirectory = Module.WorkingDir + _currentItem.Name.EnsureTrailingPathSeparator();
  1810. process.Start();
  1811. }
  1812. private void resetSubmoduleChanges_Click(object sender, EventArgs e)
  1813. {
  1814. var unStagedFiles = Unstaged.SelectedItems.ToList();
  1815. if (unStagedFiles.Count == 0)
  1816. return;
  1817. // Show a form asking the user if they want to reset the changes.
  1818. FormResetChanges.ActionEnum resetType = FormResetChanges.ShowResetDialog(this, true, true);
  1819. if (resetType == FormResetChanges.ActionEnum.Cancel)
  1820. return;
  1821. foreach (var item in unStagedFiles.Where(it => it.IsSubmodule))
  1822. {
  1823. GitModule module = Module.GetSubmodule(item.Name);
  1824. // Reset all changes.
  1825. module.ResetHard("");
  1826. // Also delete new files, if requested.
  1827. if (resetType == FormResetChanges.ActionEnum.ResetAndDelete)
  1828. {
  1829. var unstagedFiles = module.GetUnstagedFiles();
  1830. foreach (var file in unstagedFiles.Where(file => file.IsNew))
  1831. {
  1832. try
  1833. {
  1834. string path = Path.Combine(module.WorkingDir, file.Name);
  1835. if (File.Exists(path))
  1836. File.Delete(path);
  1837. else
  1838. Directory.Delete(path, true);
  1839. }
  1840. catch (System.IO.IOException) { }
  1841. catch (System.UnauthorizedAccessException) { }
  1842. }
  1843. }
  1844. }
  1845. Initialize();
  1846. }
  1847. private void updateSubmoduleMenuItem_Click(object sender, EventArgs e)
  1848. {
  1849. var unStagedFiles = Unstaged.SelectedItems.ToList();
  1850. if (unStagedFiles.Count == 0)
  1851. return;
  1852. foreach (var item in unStagedFiles.Where(it => it.IsSubmodule))
  1853. {
  1854. FormProcess.ShowDialog(this, GitCommandHelpers.SubmoduleUpdateCmd(item.Name));
  1855. }
  1856. Initialize();
  1857. }
  1858. private void stashSubmoduleChangesToolStripMenuItem_Click(object sender, EventArgs e)
  1859. {
  1860. var unStagedFiles = Unstaged.SelectedItems.ToList();
  1861. if (unStagedFiles.Count == 0)
  1862. return;
  1863. foreach (var item in unStagedFiles.Where(it => it.IsSubmodule))
  1864. {
  1865. GitUICommands uiCmds = new GitUICommands(Module.GetSubmodule(item.Name));
  1866. uiCmds.StashSave(this, AppSettings.IncludeUntrackedFilesInManualStash);
  1867. }
  1868. Initialize();
  1869. }
  1870. private void submoduleSummaryMenuItem_Click(object sender, EventArgs e)
  1871. {
  1872. string summary = Module.GetSubmoduleSummary(_currentItem.Name);
  1873. using (var frm = new FormEdit(summary)) frm.ShowDialog(this);
  1874. }
  1875. private void viewHistoryMenuItem_Click(object sender, EventArgs e)
  1876. {
  1877. ViewFileHistoryMenuItem_Click(sender, e);
  1878. }
  1879. private void openFolderMenuItem_Click(object sender, EventArgs e)
  1880. {
  1881. OpenToolStripMenuItemClick(sender, e);
  1882. }
  1883. private void openDiffMenuItem_Click(object sender, EventArgs e)
  1884. {
  1885. OpenWithDifftoolToolStripMenuItemClick(sender, e);
  1886. }
  1887. private void copyFolderNameMenuItem_Click(object sender, EventArgs e)
  1888. {
  1889. FilenameToClipboardToolStripMenuItemClick(sender, e);
  1890. }
  1891. private void commitTemplatesConfigtoolStripMenuItem_Click(object sender, EventArgs e)
  1892. {
  1893. using (var frm = new FormCommitTemplateSettings()) frm.ShowDialog(this);
  1894. _shouldReloadCommitTemplates = true;
  1895. }
  1896. private void LoadCommitTemplates()
  1897. {
  1898. CommitTemplateItem[] commitTemplates = CommitTemplateItem.LoadFromSettings();
  1899. commitTemplatesToolStripMenuItem.DropDownItems.Clear();
  1900. if (null != commitTemplates)
  1901. {
  1902. for (int i = 0; i < commitTemplates.Length; i++)
  1903. {
  1904. if (!commitTemplates[i].Name.IsNullOrEmpty())
  1905. AddTemplateCommitMessageToMenu(commitTemplates[i], commitTemplates[i].Name);
  1906. }
  1907. }
  1908. commitTemplatesToolStripMenuItem.DropDownItems.Add(new ToolStripSeparator());
  1909. var toolStripItem = new ToolStripMenuItem(_commitTemplateSettings.Text);
  1910. toolStripItem.Click += commitTemplatesConfigtoolStripMenuItem_Click;
  1911. commitTemplatesToolStripMenuItem.DropDownItems.Add(toolStripItem);
  1912. }
  1913. private void AddTemplateCommitMessageToMenu(CommitTemplateItem item, string name)
  1914. {
  1915. if (string.IsNullOrEmpty(name))
  1916. return;
  1917. var toolStripItem =
  1918. new ToolStripMenuItem
  1919. {
  1920. Tag = item,
  1921. Text = name
  1922. };
  1923. toolStripItem.Click += commitTemplatesToolStripMenuItem_Clicked;
  1924. commitTemplatesToolStripMenuItem.DropDownItems.Add(toolStripItem);
  1925. }
  1926. private void commitTemplatesToolStripMenuItem_Clicked(object sender, EventArgs e)
  1927. {
  1928. try
  1929. {
  1930. ToolStripMenuItem item = (ToolStripMenuItem)sender;
  1931. CommitTemplateItem templateItem = (CommitTemplateItem)(item.Tag);
  1932. Message.Text = templateItem.Text;
  1933. Message.Focus();
  1934. }
  1935. catch
  1936. {
  1937. return;
  1938. }
  1939. }
  1940. private void commitTemplatesToolStripMenuItem_DropDownOpening(object sender, EventArgs e)
  1941. {
  1942. if (_shouldReloadCommitTemplates)
  1943. {
  1944. LoadCommitTemplates();
  1945. _shouldReloadCommitTemplates = false;
  1946. }
  1947. }
  1948. private void openContainingFolderToolStripMenuItem_Click(object sender, EventArgs e)
  1949. {
  1950. OpenContainingFolder(Unstaged);
  1951. }
  1952. private void OpenContainingFolder(FileStatusList list)
  1953. {
  1954. foreach (var item in list.SelectedItems)
  1955. {
  1956. var fileNames = new StringBuilder();
  1957. fileNames.Append((Path.Combine(Module.WorkingDir, item.Name)).ToNativePath());
  1958. string filePath = fileNames.ToString();
  1959. if (File.Exists(filePath))
  1960. {
  1961. OsShellUtil.SelectPathInFileExplorer(filePath);
  1962. }
  1963. }
  1964. }
  1965. private void toolStripMenuItem9_Click(object sender, EventArgs e)
  1966. {
  1967. foreach (var item in Staged.SelectedItems)
  1968. {
  1969. string output = Module.OpenWithDifftool(item.Name, null, null, null, "--cached");
  1970. if (!string.IsNullOrEmpty(output))
  1971. MessageBox.Show(this, output);
  1972. }
  1973. }
  1974. private void toolStripMenuItem10_Click(object sender, EventArgs e)
  1975. {
  1976. OpenContainingFolder(Staged);
  1977. }
  1978. private void toolStripMenuItem4_Click(object sender, EventArgs e)
  1979. {
  1980. if (Unstaged.SelectedItem == null)
  1981. return;
  1982. Process gitProcess = Module.RunExternalCmdDetachedShowConsole(AppSettings.GitCommand,
  1983. "add -p \"" + Unstaged.SelectedItem.Name + "\"");
  1984. if (gitProcess != null)
  1985. {
  1986. _interactiveAddBashCloseWaitCts.Cancel();
  1987. _interactiveAddBashCloseWaitCts = new CancellationTokenSource();
  1988. var formsTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
  1989. Task.Factory.StartNew(() =>
  1990. {
  1991. gitProcess.WaitForExit();
  1992. gitProcess.Dispose();
  1993. })
  1994. .ContinueWith(_ => RescanChanges(),
  1995. _interactiveAddBashCloseWaitCts.Token,
  1996. TaskContinuationOptions.OnlyOnRanToCompletion,
  1997. formsTaskScheduler);
  1998. }
  1999. }
  2000. private void Amend_CheckedChanged(object sender, EventArgs e)
  2001. {
  2002. if (string.IsNullOrEmpty(Message.Text) && Amend.Checked)
  2003. {
  2004. Message.Text = Module.GetPreviousCommitMessages(1).FirstOrDefault().Trim();
  2005. return;
  2006. }
  2007. }
  2008. private void StageInSuperproject_CheckedChanged(object sender, EventArgs e)
  2009. {
  2010. if (StageInSuperproject.Visible)
  2011. AppSettings.StageInSuperprojectAfterCommit = StageInSuperproject.Checked;
  2012. }
  2013. private void commitCommitter_Click(object sender, EventArgs e)
  2014. {
  2015. UICommands.StartSettingsDialog(this, SettingsDialog.Pages.GitConfigSettingsPage.GetPageReference());
  2016. }
  2017. private void toolAuthor_Leave(object sender, EventArgs e)
  2018. {
  2019. updateAuthorInfo();
  2020. }
  2021. }
  2022. /// <summary>
  2023. /// Indicates the kind of commit being prepared. Used for adjusting the behavior of FormCommit.
  2024. /// </summary>
  2025. public enum CommitKind
  2026. {
  2027. Normal,
  2028. Fixup,
  2029. Squash
  2030. }
  2031. }