PageRenderTime 35ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/GitUI/CommandsDialogs/FormCommit.cs

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