PageRenderTime 56ms CodeModel.GetById 18ms RepoModel.GetById 1ms app.codeStats 0ms

/GitUI/CommandsDialogs/FormPush.cs

https://github.com/PKRoma/gitextensions
C# | 1293 lines | 1047 code | 199 blank | 47 comment | 181 complexity | 49d7aa7daf984e27c9b741979f36865a MD5 | raw file
Possible License(s): GPL-3.0

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

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Data;
  4. using System.Diagnostics;
  5. using System.Drawing;
  6. using System.IO;
  7. using System.Linq;
  8. using System.Text.RegularExpressions;
  9. using System.Windows.Forms;
  10. using GitCommands;
  11. using GitCommands.Config;
  12. using GitCommands.Git;
  13. using GitCommands.Git.Commands;
  14. using GitCommands.Remotes;
  15. using GitCommands.Settings;
  16. using GitCommands.UserRepositoryHistory;
  17. using GitExtUtils.GitUI;
  18. using GitUI.HelperDialogs;
  19. using GitUI.Script;
  20. using GitUIPluginInterfaces;
  21. using GitUIPluginInterfaces.Settings;
  22. using Microsoft;
  23. using ResourceManager;
  24. namespace GitUI.CommandsDialogs
  25. {
  26. public partial class FormPush : GitModuleForm
  27. {
  28. private const string HeadText = "HEAD";
  29. private const string AllRefs = "[ All ]";
  30. private const string LocalColumnName = "Local";
  31. private const string RemoteColumnName = "Remote";
  32. private const string AheadColumnName = "New";
  33. private const string PushColumnName = "Push";
  34. private const string ForceColumnName = "Force";
  35. private const string DeleteColumnName = "Delete";
  36. private string? _currentBranchName;
  37. private ConfigFileRemote? _currentBranchRemote;
  38. private bool _candidateForRebasingMergeCommit;
  39. private string? _selectedBranch;
  40. private ConfigFileRemote? _selectedRemote;
  41. private string? _selectedRemoteBranchName;
  42. private IReadOnlyList<IGitRef>? _gitRefs;
  43. private readonly IConfigFileRemoteSettingsManager _remotesManager;
  44. public bool ErrorOccurred { get; private set; }
  45. private int _pushColumnIndex;
  46. #region Translation
  47. private readonly TranslationString _branchNewForRemote =
  48. new("The branch you are about to push seems to be a new branch for the remote." +
  49. Environment.NewLine + "Are you sure you want to push this branch?");
  50. private readonly TranslationString _pushCaption = new("Push");
  51. private readonly TranslationString _pushToCaption = new("Push to {0}");
  52. private readonly TranslationString _selectDestinationDirectory =
  53. new("Please select a destination directory");
  54. private readonly TranslationString _errorPushToRemoteCaption = new("Push to remote");
  55. private readonly TranslationString _configureRemote = new($"Please configure a remote repository first.{Environment.NewLine}Would you like to do it now?");
  56. private readonly TranslationString _selectTag =
  57. new("You need to select a tag to push or select \"Push all tags\".");
  58. private readonly TranslationString _updateTrackingReference =
  59. new("The branch {0} does not have a tracking reference. Do you want to add a tracking reference to {1}?");
  60. private readonly TranslationString _pullRepositoryMainMergeInstruction = new("Pull latest changes from remote repository");
  61. private readonly TranslationString _pullRepositoryMainForceInstruction = new("Push rejected");
  62. private readonly TranslationString _pullRepositoryMergeInstruction =
  63. new("The push was rejected because the tip of your current branch is behind its remote counterpart. " +
  64. "Merge the remote changes before pushing again.");
  65. private readonly TranslationString _pullRepositoryForceInstruction =
  66. new("The push was rejected because the tip of your current branch is behind its remote counterpart");
  67. private readonly TranslationString _pullDefaultButton = new("&Pull with the default pull action ({0})");
  68. private readonly TranslationString _pullRebaseButton = new("Pull with &rebase");
  69. private readonly TranslationString _pullMergeButton = new("Pull with &merge");
  70. private readonly TranslationString _pushForceButton = new("&Force push with lease");
  71. private readonly TranslationString _pullActionNone = new("none");
  72. private readonly TranslationString _pullActionFetch = new("fetch");
  73. private readonly TranslationString _pullActionRebase = new("rebase");
  74. private readonly TranslationString _pullActionMerge = new("merge");
  75. private readonly TranslationString _pullRepositoryCaption = new("Push was rejected from \"{0}\"");
  76. private readonly TranslationString _useForceWithLeaseInstead =
  77. new("Force push may overwrite changes since your last fetch. Do you want to use the safer force with lease instead?");
  78. private readonly TranslationString _forceWithLeaseTooltips =
  79. new("Force with lease is a safer way to force push. It ensures you only overwrite work that you have seen in your local repository");
  80. #endregion
  81. [Obsolete("For VS designer and translation test only. Do not remove.")]
  82. #pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
  83. private FormPush()
  84. #pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
  85. {
  86. InitializeComponent();
  87. }
  88. public FormPush(GitUICommands commands)
  89. : base(commands)
  90. {
  91. InitializeComponent();
  92. Push.Text = TranslatedStrings.ButtonPush;
  93. NewColumn.Width = DpiUtil.Scale(97);
  94. PushColumn.Width = DpiUtil.Scale(36);
  95. ForceColumn.Width = DpiUtil.Scale(101);
  96. DeleteColumn.Width = DpiUtil.Scale(108);
  97. InitializeComplete();
  98. ForcePushTags.DataBindings.Add("Checked", ckForceWithLease, "Checked",
  99. formattingEnabled: false, updateMode: DataSourceUpdateMode.OnPropertyChanged);
  100. toolTip1.SetToolTip(ckForceWithLease, _forceWithLeaseTooltips.Text);
  101. // can't be set in OnLoad, because after PushAndShowDialogWhenFailed()
  102. // they are reset to false
  103. _remotesManager = new ConfigFileRemoteSettingsManager(() => Module);
  104. Init();
  105. void Init()
  106. {
  107. _gitRefs = Module.GetRefs(RefsFilter.Heads | RefsFilter.Remotes);
  108. RecursiveSubmodules.SelectedIndex = AppSettings.RecursiveSubmodules;
  109. _currentBranchName = Module.GetSelectedBranch();
  110. // refresh registered git remotes
  111. UserGitRemotes = _remotesManager.LoadRemotes(false).ToList();
  112. BindRemotesDropDown(null);
  113. UpdateBranchDropDown();
  114. UpdateRemoteBranchDropDown();
  115. Push.Focus();
  116. if (AppSettings.AlwaysShowAdvOpt)
  117. {
  118. ShowOptions_LinkClicked(this, null!);
  119. }
  120. // Save the value because later the value for all the columns will be at '0'
  121. _pushColumnIndex = PushColumn.Index;
  122. PushColumn.HeaderCell.ContextMenuStrip = menuPushSelection;
  123. // Handle left button click to also open the context menu
  124. BranchGrid.ColumnHeaderMouseClick += BranchGrid_ColumnHeaderMouseClick;
  125. }
  126. }
  127. /// <summary>
  128. /// Gets the list of remotes configured in .git/config file.
  129. /// </summary>
  130. private List<ConfigFileRemote>? UserGitRemotes { get; set; }
  131. public DialogResult PushAndShowDialogWhenFailed(IWin32Window? owner = null)
  132. {
  133. if (!PushChanges(owner))
  134. {
  135. return ShowDialog(owner);
  136. }
  137. return DialogResult.OK;
  138. }
  139. private bool CheckIfRemoteExist()
  140. {
  141. Validates.NotNull(UserGitRemotes);
  142. if (UserGitRemotes.Count < 1)
  143. {
  144. if (MessageBox.Show(this, _configureRemote.Text, _errorPushToRemoteCaption.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Error) == DialogResult.Yes)
  145. {
  146. OpenRemotesDialogAndRefreshList(null);
  147. return UserGitRemotes.Count > 0;
  148. }
  149. return false;
  150. }
  151. return true;
  152. }
  153. public void CheckForceWithLease()
  154. {
  155. ckForceWithLease.Checked = true;
  156. }
  157. private void OpenRemotesDialogAndRefreshList(string? selectedRemoteName)
  158. {
  159. if (!UICommands.StartRemotesDialog(this, selectedRemoteName))
  160. {
  161. return;
  162. }
  163. UserGitRemotes = _remotesManager.LoadRemotes(false).ToList();
  164. BindRemotesDropDown(selectedRemoteName);
  165. }
  166. private void PushClick(object sender, EventArgs e)
  167. {
  168. DialogResult = PushChanges(this) ? DialogResult.OK : DialogResult.None;
  169. }
  170. private void BindRemotesDropDown(string? selectedRemoteName)
  171. {
  172. _NO_TRANSLATE_Remotes.SelectedIndexChanged -= RemotesUpdated;
  173. _NO_TRANSLATE_Remotes.TextUpdate -= RemotesUpdated;
  174. _NO_TRANSLATE_Remotes.Sorted = false;
  175. _NO_TRANSLATE_Remotes.DataSource = UserGitRemotes;
  176. _NO_TRANSLATE_Remotes.DisplayMember = nameof(ConfigFileRemote.Name);
  177. _NO_TRANSLATE_Remotes.SelectedIndex = -1;
  178. _NO_TRANSLATE_Remotes.SelectedIndexChanged += RemotesUpdated;
  179. _NO_TRANSLATE_Remotes.TextUpdate += RemotesUpdated;
  180. if (string.IsNullOrEmpty(selectedRemoteName))
  181. {
  182. selectedRemoteName = Module.GetSetting(string.Format(SettingKeyString.BranchRemote, _currentBranchName));
  183. }
  184. _currentBranchRemote = UserGitRemotes.FirstOrDefault(x => StringComparer.OrdinalIgnoreCase.Equals(x.Name, selectedRemoteName));
  185. if (_currentBranchRemote is not null)
  186. {
  187. _NO_TRANSLATE_Remotes.SelectedItem = _currentBranchRemote;
  188. }
  189. else if (UserGitRemotes.Any())
  190. {
  191. var defaultRemote = UserGitRemotes.FirstOrDefault(x => StringComparer.OrdinalIgnoreCase.Equals(x.Name, "origin"));
  192. // we couldn't find the default assigned remote for the selected branch
  193. // it is usually gets mapped via FormRemotes -> "default pull behavior" tab
  194. // so pick the default user remote
  195. if (defaultRemote is null)
  196. {
  197. _NO_TRANSLATE_Remotes.SelectedIndex = 0;
  198. }
  199. else
  200. {
  201. _NO_TRANSLATE_Remotes.SelectedItem = defaultRemote;
  202. }
  203. }
  204. else
  205. {
  206. _NO_TRANSLATE_Remotes.SelectedIndex = -1;
  207. }
  208. }
  209. private bool IsBranchKnownToRemote(string? remote, string branch)
  210. {
  211. var remoteRefs = GetRemoteBranches(remote).Where(r => r.LocalName == branch);
  212. if (remoteRefs.Any())
  213. {
  214. return true;
  215. }
  216. var localRefs = _gitRefs.Where(r => r.IsHead && r.Name == branch && r.TrackingRemote == remote);
  217. return localRefs.Any();
  218. }
  219. private bool PushChanges(IWin32Window? owner)
  220. {
  221. ErrorOccurred = false;
  222. if (PushToUrl.Checked && !Uri.IsWellFormedUriString(PushDestination.Text, UriKind.Absolute))
  223. {
  224. MessageBox.Show(owner, _selectDestinationDirectory.Text, TranslatedStrings.Error, MessageBoxButtons.OK, MessageBoxIcon.Error);
  225. return false;
  226. }
  227. if (/* PushToRemote.Checked */!CheckIfRemoteExist())
  228. {
  229. return false;
  230. }
  231. Validates.NotNull(_selectedRemote);
  232. var selectedRemoteName = _selectedRemote.Name;
  233. if (TabControlTagBranch.SelectedTab == TagTab && string.IsNullOrEmpty(TagComboBox.Text))
  234. {
  235. MessageBox.Show(owner, _selectTag.Text, TranslatedStrings.Error, MessageBoxButtons.OK, MessageBoxIcon.Error);
  236. return false;
  237. }
  238. // Extra check if the branch is already known to the remote, give a warning when not.
  239. // This is not possible when the remote is an URL, but this is ok since most users push to
  240. // known remotes anyway.
  241. if (TabControlTagBranch.SelectedTab == BranchTab && PushToRemote.Checked &&
  242. !Module.IsBareRepository())
  243. {
  244. // If the current branch is not the default push, and not known by the remote
  245. // (as far as we know since we are disconnected....)
  246. if (_NO_TRANSLATE_Branch.Text != AllRefs &&
  247. RemoteBranch.Text != _remotesManager.GetDefaultPushRemote(_selectedRemote, _NO_TRANSLATE_Branch.Text) &&
  248. !IsBranchKnownToRemote(selectedRemoteName, RemoteBranch.Text))
  249. {
  250. // Ask if this is really what the user wants
  251. if (!AppSettings.DontConfirmPushNewBranch &&
  252. MessageBox.Show(owner, _branchNewForRemote.Text, _pushCaption.Text, MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.No)
  253. {
  254. return false;
  255. }
  256. }
  257. }
  258. if (PushToUrl.Checked)
  259. {
  260. var path = PushDestination.Text;
  261. ThreadHelper.JoinableTaskFactory.Run(() => RepositoryHistoryManager.Remotes.AddAsMostRecentAsync(path));
  262. }
  263. AppSettings.RecursiveSubmodules = RecursiveSubmodules.SelectedIndex;
  264. var remote = "";
  265. string destination;
  266. if (PushToUrl.Checked)
  267. {
  268. destination = PushDestination.Text;
  269. }
  270. else
  271. {
  272. Validates.NotNull(selectedRemoteName);
  273. EnsurePageant(selectedRemoteName);
  274. destination = selectedRemoteName;
  275. remote = selectedRemoteName.Trim();
  276. }
  277. string pushCmd;
  278. if (TabControlTagBranch.SelectedTab == BranchTab)
  279. {
  280. bool track = ReplaceTrackingReference.Checked;
  281. if (!track && !string.IsNullOrWhiteSpace(RemoteBranch.Text))
  282. {
  283. GitRef? selectedLocalBranch = _NO_TRANSLATE_Branch.SelectedItem as GitRef;
  284. track = selectedLocalBranch is not null && string.IsNullOrEmpty(selectedLocalBranch.TrackingRemote) &&
  285. !UserGitRemotes.Any(x => _NO_TRANSLATE_Branch.Text.StartsWith(x.Name, StringComparison.OrdinalIgnoreCase));
  286. var autoSetupMerge = Module.EffectiveConfigFile.GetValue("branch.autoSetupMerge");
  287. if (!string.IsNullOrWhiteSpace(autoSetupMerge) && autoSetupMerge.ToLowerInvariant() == "false")
  288. {
  289. track = false;
  290. }
  291. if (track && !AppSettings.DontConfirmAddTrackingRef)
  292. {
  293. Validates.NotNull(selectedLocalBranch);
  294. var result = MessageBox.Show(owner,
  295. string.Format(_updateTrackingReference.Text, selectedLocalBranch.Name, RemoteBranch.Text),
  296. _pushCaption.Text,
  297. MessageBoxButtons.YesNoCancel,
  298. MessageBoxIcon.Question,
  299. MessageBoxDefaultButton.Button1);
  300. if (result == DialogResult.Cancel)
  301. {
  302. return false;
  303. }
  304. track = result == DialogResult.Yes;
  305. }
  306. }
  307. if (ForcePushBranches.Checked)
  308. {
  309. var choice = MessageBox.Show(owner,
  310. _useForceWithLeaseInstead.Text,
  311. "Question", MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question,
  312. MessageBoxDefaultButton.Button1);
  313. switch (choice)
  314. {
  315. case DialogResult.Yes:
  316. ForcePushBranches.Checked = false;
  317. ckForceWithLease.Checked = true;
  318. break;
  319. case DialogResult.Cancel:
  320. return false;
  321. }
  322. }
  323. if (_NO_TRANSLATE_Branch.Text == AllRefs)
  324. {
  325. pushCmd = Module.PushAllCmd(destination, GetForcePushOption(), track, RecursiveSubmodules.SelectedIndex);
  326. }
  327. else
  328. {
  329. pushCmd = Module.PushCmd(destination, _NO_TRANSLATE_Branch.Text, RemoteBranch.Text,
  330. GetForcePushOption(), track, RecursiveSubmodules.SelectedIndex);
  331. }
  332. }
  333. else if (TabControlTagBranch.SelectedTab == TagTab)
  334. {
  335. string tag = TagComboBox.Text;
  336. bool pushAllTags = false;
  337. if (tag == AllRefs)
  338. {
  339. tag = "";
  340. pushAllTags = true;
  341. }
  342. pushCmd = GitCommandHelpers.PushTagCmd(destination, tag, pushAllTags, GetForcePushOption());
  343. }
  344. else
  345. {
  346. // Push Multiple Branches Tab selected
  347. List<GitPushAction> pushActions = new();
  348. Validates.NotNull(_branchTable);
  349. foreach (DataRow row in _branchTable.Rows)
  350. {
  351. var push = Convert.ToBoolean(row[PushColumnName]);
  352. var force = Convert.ToBoolean(row[ForceColumnName]);
  353. var delete = Convert.ToBoolean(row[DeleteColumnName]);
  354. var localBranch = row[LocalColumnName].ToString();
  355. var remoteBranch = row[RemoteColumnName].ToString();
  356. if (string.IsNullOrWhiteSpace(remoteBranch))
  357. {
  358. remoteBranch = localBranch;
  359. }
  360. if (string.IsNullOrWhiteSpace(remoteBranch))
  361. {
  362. continue;
  363. }
  364. if (push || force)
  365. {
  366. pushActions.Add(new GitPushAction(localBranch, remoteBranch, force));
  367. }
  368. else if (delete)
  369. {
  370. pushActions.Add(GitPushAction.DeleteRemoteBranch(remoteBranch));
  371. }
  372. }
  373. pushCmd = GitCommandHelpers.PushMultipleCmd(destination, pushActions);
  374. }
  375. bool success = ScriptManager.RunEventScripts(this, ScriptEvent.BeforePush);
  376. if (!success)
  377. {
  378. return false;
  379. }
  380. // controls can be accessed only from UI thread
  381. _selectedBranch = _NO_TRANSLATE_Branch.Text;
  382. _candidateForRebasingMergeCommit = PushToRemote.Checked && (_selectedBranch != AllRefs) && TabControlTagBranch.SelectedTab == BranchTab;
  383. _selectedRemoteBranchName = RemoteBranch.Text;
  384. using FormRemoteProcess form = new(UICommands, pushCmd)
  385. {
  386. Remote = remote,
  387. Text = string.Format(_pushToCaption.Text, destination),
  388. HandleOnExitCallback = HandlePushOnExit
  389. };
  390. form.ShowDialog(owner);
  391. ErrorOccurred = form.ErrorOccurred();
  392. if (!Module.InTheMiddleOfAction() && !form.ErrorOccurred())
  393. {
  394. ScriptManager.RunEventScripts(this, ScriptEvent.AfterPush);
  395. if (_createPullRequestCB.Checked)
  396. {
  397. UICommands.StartCreatePullRequest(owner);
  398. }
  399. return true;
  400. }
  401. return false;
  402. }
  403. private ForcePushOptions GetForcePushOption()
  404. {
  405. if (ForcePushBranches.Checked || ForcePushTags.Checked /* tags cannot be pushed using --force-with-lease */)
  406. {
  407. return ForcePushOptions.Force;
  408. }
  409. if (ckForceWithLease.Checked)
  410. {
  411. return ForcePushOptions.ForceWithLease;
  412. }
  413. return ForcePushOptions.DoNotForce;
  414. }
  415. private bool IsRebasingMergeCommit()
  416. {
  417. if (AppSettings.DefaultPullAction == AppSettings.PullAction.Rebase &&
  418. _candidateForRebasingMergeCommit &&
  419. _selectedBranch == _currentBranchName &&
  420. _selectedRemote == _currentBranchRemote)
  421. {
  422. string remoteBranchName = _selectedRemote + "/" + _selectedRemoteBranchName;
  423. return Module.ExistsMergeCommit(remoteBranchName, _selectedBranch);
  424. }
  425. return false;
  426. }
  427. private bool HandlePushOnExit(ref bool isError, FormProcess form)
  428. {
  429. if (!isError)
  430. {
  431. return false;
  432. }
  433. // there is no way to pull to not current branch
  434. if (_selectedBranch != _currentBranchName)
  435. {
  436. return false;
  437. }
  438. // auto pull from URL not supported. See https://github.com/gitextensions/gitextensions/issues/1887
  439. if (!PushToRemote.Checked)
  440. {
  441. return false;
  442. }
  443. // if push was rejected, offer force push and for current branch also pull/merge
  444. // Note that the Git output contains color codes etc too
  445. Regex isRejected = new($"! \\[rejected\\] .* ((?<currBranch>{Regex.Escape(_currentBranchName)})|.*) -> ");
  446. Match match = isRejected.Match(form.GetOutputString());
  447. if (match.Success && !Module.IsBareRepository())
  448. {
  449. IWin32Window owner = form.Owner;
  450. (var onRejectedPullAction, var forcePush) = AskForAutoPullOnPushRejectedAction(owner, match.Groups["currBranch"].Success);
  451. if (forcePush)
  452. {
  453. if (!form.ProcessArguments.Contains(" -f ") && !form.ProcessArguments.Contains(" --force"))
  454. {
  455. Trace.Assert(form.ProcessArguments.StartsWith("push "), "Arguments should start with 'push' command");
  456. form.ProcessArguments = form.ProcessArguments.Insert("push".Length, " --force-with-lease");
  457. }
  458. form.Retry();
  459. return true;
  460. }
  461. if (onRejectedPullAction == AppSettings.PullAction.Default)
  462. {
  463. onRejectedPullAction = AppSettings.DefaultPullAction;
  464. }
  465. if (onRejectedPullAction == AppSettings.PullAction.None)
  466. {
  467. return false;
  468. }
  469. if (onRejectedPullAction is not (AppSettings.PullAction.Merge or AppSettings.PullAction.Rebase))
  470. {
  471. form.AppendOutput(Environment.NewLine +
  472. "Automatical pull can only be performed, when the default pull action is either set to Merge or Rebase." +
  473. Environment.NewLine + Environment.NewLine);
  474. return false;
  475. }
  476. if (IsRebasingMergeCommit())
  477. {
  478. form.AppendOutput(Environment.NewLine +
  479. "Can not perform automatical pull, when the pull action is set to Rebase " + Environment.NewLine +
  480. "and one of the commits that are about to be rebased is a merge commit." +
  481. Environment.NewLine + Environment.NewLine);
  482. return false;
  483. }
  484. Validates.NotNull(_selectedRemote);
  485. UICommands.StartPullDialogAndPullImmediately(
  486. out var pullCompleted,
  487. owner,
  488. _selectedRemoteBranchName,
  489. _selectedRemote.Name,
  490. onRejectedPullAction);
  491. if (pullCompleted)
  492. {
  493. form.Retry();
  494. return true;
  495. }
  496. }
  497. return false;
  498. }
  499. private (AppSettings.PullAction pullAction, bool forcePush) AskForAutoPullOnPushRejectedAction(IWin32Window owner, bool allOptions)
  500. {
  501. bool forcePush = false;
  502. AppSettings.PullAction? onRejectedPullAction = AppSettings.AutoPullOnPushRejectedAction;
  503. if (onRejectedPullAction is null)
  504. {
  505. string destination = _NO_TRANSLATE_Remotes.Text;
  506. string pullDefaultButtonText;
  507. switch (AppSettings.DefaultPullAction)
  508. {
  509. case AppSettings.PullAction.Fetch:
  510. case AppSettings.PullAction.FetchAll:
  511. case AppSettings.PullAction.FetchPruneAll:
  512. pullDefaultButtonText = string.Format(_pullDefaultButton.Text, _pullActionFetch.Text);
  513. break;
  514. case AppSettings.PullAction.Merge:
  515. pullDefaultButtonText = string.Format(_pullDefaultButton.Text, _pullActionMerge.Text);
  516. break;
  517. case AppSettings.PullAction.Rebase:
  518. pullDefaultButtonText = string.Format(_pullDefaultButton.Text, _pullActionRebase.Text);
  519. break;
  520. default:
  521. pullDefaultButtonText = string.Format(_pullDefaultButton.Text, _pullActionNone.Text);
  522. break;
  523. }
  524. TaskDialogPage page = new()
  525. {
  526. Text = allOptions ? _pullRepositoryMergeInstruction.Text : _pullRepositoryForceInstruction.Text,
  527. Heading = allOptions ? _pullRepositoryMainMergeInstruction.Text : _pullRepositoryMainForceInstruction.Text,
  528. Caption = string.Format(_pullRepositoryCaption.Text, destination),
  529. Buttons = { TaskDialogButton.Cancel },
  530. Icon = TaskDialogIcon.Error,
  531. Verification = new TaskDialogVerificationCheckBox
  532. {
  533. Text = TranslatedStrings.DontShowAgain
  534. },
  535. AllowCancel = true,
  536. SizeToContent = true
  537. };
  538. TaskDialogCommandLinkButton btnPullDefault = new(pullDefaultButtonText);
  539. TaskDialogCommandLinkButton btnPullRebase = new(_pullRebaseButton.Text);
  540. TaskDialogCommandLinkButton btnPullMerge = new(_pullMergeButton.Text);
  541. TaskDialogCommandLinkButton btnPushForce = new(_pushForceButton.Text);
  542. if (allOptions)
  543. {
  544. page.Buttons.Add(btnPullDefault);
  545. page.Buttons.Add(btnPullRebase);
  546. page.Buttons.Add(btnPullMerge);
  547. }
  548. page.Buttons.Add(btnPushForce);
  549. TaskDialogButton result = TaskDialog.ShowDialog(Handle, page);
  550. if (result == TaskDialogButton.Cancel)
  551. {
  552. onRejectedPullAction = AppSettings.PullAction.None;
  553. }
  554. else if (result == btnPullDefault)
  555. {
  556. onRejectedPullAction = AppSettings.PullAction.Default;
  557. }
  558. else if (result == btnPullRebase)
  559. {
  560. onRejectedPullAction = AppSettings.PullAction.Rebase;
  561. }
  562. else if (result == btnPullMerge)
  563. {
  564. onRejectedPullAction = AppSettings.PullAction.Merge;
  565. }
  566. else if (result == btnPushForce)
  567. {
  568. forcePush = true;
  569. }
  570. if (page.Verification.Checked)
  571. {
  572. AppSettings.AutoPullOnPushRejectedAction = onRejectedPullAction;
  573. }
  574. }
  575. return (onRejectedPullAction ?? AppSettings.PullAction.None, forcePush);
  576. }
  577. private void UpdateBranchDropDown()
  578. {
  579. var curBranch = _NO_TRANSLATE_Branch.Text;
  580. _NO_TRANSLATE_Branch.DisplayMember = nameof(IGitRef.Name);
  581. _NO_TRANSLATE_Branch.Items.Clear();
  582. _NO_TRANSLATE_Branch.Items.Add(AllRefs);
  583. _NO_TRANSLATE_Branch.Items.Add(HeadText);
  584. if (string.IsNullOrEmpty(curBranch))
  585. {
  586. Validates.NotNull(_currentBranchName);
  587. curBranch = _currentBranchName;
  588. if (curBranch.IndexOfAny("() ".ToCharArray()) != -1)
  589. {
  590. curBranch = HeadText;
  591. }
  592. }
  593. foreach (var head in GetLocalBranches())
  594. {
  595. _NO_TRANSLATE_Branch.Items.Add(head);
  596. }
  597. _NO_TRANSLATE_Branch.ResizeDropDownWidth(AppSettings.BranchDropDownMinWidth, AppSettings.BranchDropDownMaxWidth);
  598. _NO_TRANSLATE_Branch.Text = curBranch;
  599. }
  600. private IEnumerable<IGitRef> GetLocalBranches()
  601. {
  602. return _gitRefs.Where(r => r.IsHead);
  603. }
  604. private IEnumerable<IGitRef> GetRemoteBranches(string? remoteName)
  605. {
  606. return _gitRefs.Where(r => r.IsRemote && r.Remote == remoteName);
  607. }
  608. private void PullClick(object sender, EventArgs e)
  609. {
  610. UICommands.StartPullDialog(this);
  611. }
  612. private void UpdateRemoteBranchDropDown()
  613. {
  614. RemoteBranch.Items.Clear();
  615. if (!string.IsNullOrEmpty(_NO_TRANSLATE_Branch.Text))
  616. {
  617. RemoteBranch.Items.Add(_NO_TRANSLATE_Branch.Text);
  618. }
  619. if (_selectedRemote is not null)
  620. {
  621. foreach (var head in GetRemoteBranches(_selectedRemote.Name))
  622. {
  623. if (_NO_TRANSLATE_Branch.Text != head.LocalName)
  624. {
  625. RemoteBranch.Items.Add(head.LocalName);
  626. }
  627. }
  628. var remoteBranchesSet = GetRemoteBranches(_selectedRemote.Name).Select(b => b.LocalName).ToHashSet();
  629. var onlyLocalBranches = GetLocalBranches().Where(b => !remoteBranchesSet.Contains(b.LocalName));
  630. foreach (var head in onlyLocalBranches)
  631. {
  632. if (_NO_TRANSLATE_Branch.Text != head.LocalName)
  633. {
  634. RemoteBranch.Items.Add(head.LocalName);
  635. }
  636. }
  637. }
  638. RemoteBranch.ResizeDropDownWidth(AppSettings.BranchDropDownMinWidth, AppSettings.BranchDropDownMaxWidth);
  639. // Set text again as workaround for appearing focused after setting DropDownWidth
  640. RemoteBranch.Text = RemoteBranch.Text;
  641. }
  642. private void BranchSelectedValueChanged(object sender, EventArgs e)
  643. {
  644. if (_NO_TRANSLATE_Branch.Text == AllRefs)
  645. {
  646. RemoteBranch.Text = "";
  647. return;
  648. }
  649. if (_NO_TRANSLATE_Branch.Text != HeadText)
  650. {
  651. if (PushToRemote.Checked)
  652. {
  653. if (_NO_TRANSLATE_Branch.SelectedItem is GitRef branch)
  654. {
  655. if (_selectedRemote is not null)
  656. {
  657. string? defaultRemote = _remotesManager.GetDefaultPushRemote(_selectedRemote, branch.Name);
  658. if (!string.IsNullOrEmpty(defaultRemote))
  659. {
  660. RemoteBranch.Text = defaultRemote;
  661. return;
  662. }
  663. if (branch.TrackingRemote.Equals(_selectedRemote.Name, StringComparison.OrdinalIgnoreCase))
  664. {
  665. RemoteBranch.Text = branch.MergeWith;
  666. if (!string.IsNullOrEmpty(RemoteBranch.Text))
  667. {
  668. return;
  669. }
  670. }
  671. }
  672. }
  673. }
  674. RemoteBranch.Text = _NO_TRANSLATE_Branch.Text;
  675. }
  676. }
  677. private void FormPushLoad(object sender, EventArgs e)
  678. {
  679. _NO_TRANSLATE_Remotes.Select();
  680. Text = string.Concat(_pushCaption.Text, " (", Module.WorkingDir, ")");
  681. var gitHoster = PluginRegistry.TryGetGitHosterForModule(Module);
  682. _createPullRequestCB.Enabled = gitHoster is not null;
  683. }
  684. private void AddRemoteClick(object sender, EventArgs e)
  685. {
  686. OpenRemotesDialogAndRefreshList(_selectedRemote?.Name);
  687. }
  688. private void PushToUrlCheckedChanged(object sender, EventArgs e)
  689. {
  690. PushDestination.Enabled = PushToUrl.Checked;
  691. folderBrowserButton1.Enabled = PushToUrl.Checked;
  692. _NO_TRANSLATE_Remotes.Enabled = PushToRemote.Checked;
  693. AddRemote.Enabled = PushToRemote.Checked;
  694. if (PushToUrl.Checked)
  695. {
  696. ThreadHelper.JoinableTaskFactory.Run(async () =>
  697. {
  698. var repositoryHistory = await RepositoryHistoryManager.Remotes.LoadRecentHistoryAsync();
  699. await this.SwitchToMainThreadAsync();
  700. string prevUrl = PushDestination.Text;
  701. PushDestination.DataSource = repositoryHistory;
  702. PushDestination.DisplayMember = nameof(Repository.Path);
  703. PushDestination.Text = prevUrl;
  704. BranchSelectedValueChanged(this, EventArgs.Empty);
  705. });
  706. }
  707. else
  708. {
  709. RemotesUpdated(sender, e);
  710. }
  711. }
  712. private void RemotesUpdated(object sender, EventArgs e)
  713. {
  714. _selectedRemote = _NO_TRANSLATE_Remotes.SelectedItem as ConfigFileRemote;
  715. if (_selectedRemote is null)
  716. {
  717. return;
  718. }
  719. if (TabControlTagBranch.SelectedTab == MultipleBranchTab)
  720. {
  721. UpdateMultiBranchView();
  722. }
  723. EnableLoadSshButton();
  724. // update the text box of the Remote Url combobox to show the URL of selected remote
  725. string? pushUrl = _selectedRemote.PushUrl;
  726. if (string.IsNullOrEmpty(pushUrl))
  727. {
  728. pushUrl = _selectedRemote.Url;
  729. }
  730. PushDestination.Text = pushUrl;
  731. if (string.IsNullOrEmpty(_NO_TRANSLATE_Branch.Text))
  732. {
  733. // Doing this makes it pretty easy to accidentally create a branch on the remote.
  734. // But leaving it blank will do the 'default' thing, meaning all branches are pushed.
  735. // Solution: when pushing a branch that doesn't exist on the remote, ask what to do
  736. Validates.NotNull(_currentBranchName);
  737. Validates.NotNull(_selectedRemote.Name);
  738. GitRef currentBranch = new(Module, null, _currentBranchName, _selectedRemote.Name);
  739. _NO_TRANSLATE_Branch.Items.Add(currentBranch);
  740. _NO_TRANSLATE_Branch.SelectedItem = currentBranch;
  741. }
  742. BranchSelectedValueChanged(this, EventArgs.Empty);
  743. }
  744. private void EnableLoadSshButton()
  745. {
  746. LoadSSHKey.Visible = !string.IsNullOrWhiteSpace(_selectedRemote?.PuttySshKey);
  747. }
  748. private void LoadSshKeyClick(object sender, EventArgs e)
  749. {
  750. StartPageant(_selectedRemote?.Name);
  751. }
  752. private void StartPageant(string? remote)
  753. {
  754. if (!File.Exists(AppSettings.Pageant))
  755. {
  756. MessageBoxes.PAgentNotFound(this);
  757. }
  758. else
  759. {
  760. Module.StartPageantForRemote(remote);
  761. }
  762. }
  763. private void EnsurePageant(string? remote)
  764. {
  765. if (GitSshHelpers.Plink())
  766. {
  767. StartPageant(remote);
  768. }
  769. }
  770. private void RemotesValidated(object sender, EventArgs e)
  771. {
  772. EnableLoadSshButton();
  773. }
  774. private void FillTagDropDown()
  775. {
  776. // var tags = Module.GetTagHeads(GitModule.GetTagHeadsOption.OrderByCommitDateDescending); // comment out to sort by commit date
  777. List<string> tags = Module.GetRefs(RefsFilter.Tags)
  778. .Select(tag => tag.Name)
  779. .ToList();
  780. tags.Insert(0, AllRefs);
  781. TagComboBox.DataSource = tags;
  782. TagComboBox.ResizeDropDownWidth(AppSettings.BranchDropDownMinWidth, AppSettings.BranchDropDownMaxWidth);
  783. }
  784. private void ForcePushBranchesCheckedChanged(object sender, EventArgs e)
  785. {
  786. if (ForcePushBranches.Checked)
  787. {
  788. ckForceWithLease.Checked = false;
  789. }
  790. }
  791. #region Multi-Branch Methods
  792. private DataTable? _branchTable;
  793. private void UpdateMultiBranchView()
  794. {
  795. _branchTable = new DataTable();
  796. _branchTable.Columns.Add(LocalColumnName, typeof(string));
  797. _branchTable.Columns.Add(RemoteColumnName, typeof(string));
  798. _branchTable.Columns.Add(AheadColumnName, typeof(string));
  799. _branchTable.Columns.Add(PushColumnName, typeof(bool));
  800. _branchTable.Columns.Add(ForceColumnName, typeof(bool));
  801. _branchTable.Columns.Add(DeleteColumnName, typeof(bool));
  802. _branchTable.ColumnChanged += BranchTable_ColumnChanged;
  803. LocalColumn.DataPropertyName = LocalColumnName;
  804. RemoteColumn.DataPropertyName = RemoteColumnName;
  805. NewColumn.DataPropertyName = AheadColumnName;
  806. PushColumn.DataPropertyName = PushColumnName;
  807. ForceColumn.DataPropertyName = ForceColumnName;
  808. DeleteColumn.DataPropertyName = DeleteColumnName;
  809. BranchGrid.DataSource = new BindingSource { DataSource = _branchTable };
  810. if (_selectedRemote is null || _selectedRemote.Name is null)
  811. {
  812. return;
  813. }
  814. LoadMultiBranchViewData(_selectedRemote.Name);
  815. }
  816. private void LoadMultiBranchViewData(string remote)
  817. {
  818. using (WaitCursorScope.Enter(Cursors.AppStarting))
  819. {
  820. IReadOnlyList<IGitRef> remoteHeads;
  821. IDetailedSettings detailedSettings = Module.GetEffectiveSettings()
  822. .Detailed();
  823. if (detailedSettings.GetRemoteBranchesDirectlyFromRemote)
  824. {
  825. EnsurePageant(remote);
  826. FormRemoteProcess formProcess = new(UICommands, $"ls-remote --heads \"{remote}\"")
  827. {
  828. Remote = remote
  829. };
  830. using (formProcess)
  831. {
  832. formProcess.ShowDialog(this);
  833. if (formProcess.ErrorOccurred())
  834. {
  835. return;
  836. }
  837. var refList = CleanCommandOutput(formProcess.GetOutputString());
  838. remoteHeads = Module.ParseRefs(refList);
  839. }
  840. }
  841. else
  842. {
  843. // use remote branches from git's local database if there were problems with receiving branches from the remote server
  844. remoteHeads = Module.GetRemoteBranches().Where(r => r.Remote == remote).ToList();
  845. }
  846. ProcessHeads(remoteHeads);
  847. }
  848. return;
  849. string CleanCommandOutput(string processOutput)
  850. {
  851. // Command output consists of lines of format:
  852. //
  853. // <SHA1> \t <full-ref>
  854. //
  855. // Such as:
  856. //
  857. // fa77791d780a01a06d1f7d4ccad4ef93ed0ae2fd\trefs/heads/branchName
  858. int firstTabIdx = processOutput.IndexOf('\t');
  859. return firstTabIdx == 40
  860. ? processOutput
  861. : firstTabIdx > 40
  862. ? processOutput.Substring(firstTabIdx - 40)
  863. : string.Empty;
  864. }
  865. void ProcessHeads(IReadOnlyList<IGitRef> remoteHeads)
  866. {
  867. var localHeads = GetLocalBranches().ToList();
  868. var remoteBranches = remoteHeads.ToDictionary(h => h.LocalName, h => h);
  869. Validates.NotNull(_branchTable);
  870. _branchTable.BeginLoadData();
  871. AheadBehindDataProvider aheadBehindDataProvider = new(() => Module.GitExecutable);
  872. var aheadBehindData = aheadBehindDataProvider.GetData();
  873. // Add all the local branches.
  874. foreach (var head in localHeads)
  875. {
  876. var remoteName = head.Remote == remote
  877. ? head.MergeWith ?? head.Name
  878. : string.Empty;
  879. var isKnownAtRemote = remoteBranches.ContainsKey(head.Name);
  880. var row = _branchTable.NewRow();
  881. // Check if aheadBehind is relevant for this branch
  882. var isAheadRemote = (aheadBehindData?.ContainsKey(head.Name) ?? false)
  883. && GitRefName.GetRemoteName(aheadBehindData[head.Name].RemoteRef) == remote;
  884. row[ForceColumnName] = false;
  885. row[DeleteColumnName] = false;
  886. row[LocalColumnName] = head.Name;
  887. row[RemoteColumnName] = isAheadRemote
  888. ? GitRefName.GetRemoteBranch(aheadBehindData![head.Name].RemoteRef)
  889. : remoteName;
  890. row[AheadColumnName] = isAheadRemote
  891. ? aheadBehindData![head.Name].ToDisplay()
  892. : !isKnownAtRemote
  893. ? string.Empty
  894. : head.ObjectId == remoteBranches[head.Name].ObjectId
  895. ? "="
  896. : "<>";
  897. row[PushColumnName] = false;
  898. _branchTable.Rows.Add(row);
  899. }
  900. // Offer to delete all the left over remote branches.
  901. foreach (var remoteHead in remoteHeads)
  902. {
  903. if (localHeads.All(h => h.Name != remoteHead.LocalName))
  904. {
  905. var row = _branchTable.NewRow();
  906. row[LocalColumnName] = null;
  907. row[RemoteColumnName] = remoteHead.LocalName;
  908. row[AheadColumnName] = string.Empty;
  909. row[PushColumnName] = false;
  910. row[ForceColumnName] = false;
  911. row[DeleteColumnName] = false;
  912. _branchTable.Rows.Add(row);
  913. }
  914. }
  915. _branchTable.EndLoadData();
  916. BranchGrid.Enabled = true;
  917. }
  918. }
  919. private static void BranchTable_ColumnChanged(object sender, DataColumnChangeEventArgs e)
  920. {
  921. switch (e.Column.ColumnName)
  922. {
  923. case PushColumnName:
  924. {
  925. if ((bool)e.ProposedValue)
  926. {
  927. e.Row[ForceColumnName] = false;
  928. e.Row[DeleteColumnName] = false;
  929. }
  930. break;
  931. }
  932. case ForceColumnName:
  933. {
  934. if ((bool)e.ProposedValue)
  935. {
  936. e.Row[PushColumnName] = false;
  937. e.Row[DeleteColumnName] = false;
  938. }
  939. break;
  940. }
  941. case DeleteColumnName:
  942. {
  943. if ((bool)e.ProposedValue)
  944. {
  945. e.Row[PushColumnName] = false;
  946. e.Row[ForceColumnName] = false;
  947. }
  948. break;
  949. }
  950. }
  951. }
  952. private void TabControlTagBranch_Selected(object sender, TabControlEventArgs e)
  953. {
  954. if (TabControlTagBranch.SelectedTab == MultipleBranchTab)
  955. {
  956. UpdateMultiBranchView();
  957. }
  958. else if (TabControlTagBranch.SelectedTab == TagTab)
  959. {
  960. FillTagDropDown();
  961. }
  962. else
  963. {
  964. UpdateBranchDropDown();
  965. UpdateRemoteBranchDropDown();
  966. }
  967. }
  968. private void BranchGrid_CurrentCellDirtyStateChanged(object sender, EventArgs e)
  969. {
  970. // Push grid checkbox changes immediately into the underlying data table.
  971. if (BranchGrid.CurrentCell is DataGridViewCheckBoxCell)
  972. {
  973. BranchGrid.EndEdit();
  974. ((BindingSource)BranchGrid.DataSource).EndEdit();
  975. }
  976. }
  977. private void BranchGrid_DataBindingComplete(object sender, DataGridViewBindingCompleteEventArgs e)
  978. {
  979. foreach (DataGridViewRow row in BranchGrid.Rows)
  980. {
  981. if (row.Cells[0].Value == DBNull.Value)
  982. {
  983. row.Cells[3].ReadOnly = true;
  984. row.Cells[4].ReadOnly = true;
  985. }
  986. }
  987. }
  988. private void BranchGrid_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
  989. {
  990. if (e.RowIndex < 0)
  991. {
  992. return;
  993. }
  994. if ((e.ColumnIndex == 3 || e.ColumnIndex == 4) && BranchGrid.Rows[e.RowIndex].Cells[0].Value == DBNull.Value)
  995. {
  996. e.PaintBackground(e.ClipBounds, true);
  997. e.Handled = true;
  998. }
  999. }
  1000. #endregion
  1001. private void ShowOptions_LinkClicked(object sender, LinkLabelLinkClickedEventArgs e)
  1002. {
  1003. PushOptionsPanel.Visible = true;
  1004. ShowOptions.Visible = false;
  1005. SetFormSizeToFitAllItems();
  1006. }
  1007. private void SetFormSizeToFitAllItems()
  1008. {
  1009. if (Height < MinimumSize.Height + 50)
  1010. {
  1011. Height = MinimumSize.Height + 50;
  1012. }
  1013. }
  1014. private void _NO_TRANSLATE_Branch_SelectedIndexChanged(object sender, EventArgs e)
  1015. {
  1016. RemoteBranch.Enabled = _NO_TRANSLATE_Branch.Text != AllRefs;
  1017. }
  1018. /// <summary>
  1019. /// Clean up any resources being used.
  1020. /// </summary>
  1021. /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
  1022. protected override void Dispose(bool disposing)
  1023. {
  1024. if (disposing)
  1025. {
  1026. components?.Dispose();
  1027. }
  1028. base.Dispose(disposing);
  1029. }
  1030. private void ForceWithLeaseCheckedChanged(object sender, EventArgs e)
  1031. {
  1032. if (ckForceWithLease.Checked)
  1033. {
  1034. ForcePushBranches.Checked = false;
  1035. }
  1036. }
  1037. private void BranchGrid_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
  1038. {
  1039. if (e.ColumnIndex == _pushColumnIndex && e.Button == MouseButtons.Left)
  1040. {
  1041. var locationWhereToOpenContextMenu = BranchGrid.PointToScreen(BranchGrid.Location)
  1042. + new Size(BranchGrid.GetCellDisplayRectangle(_pushColumnIndex, -1, true).Location)
  1043. + new Size(e.Location);
  1044. menuPushSelection.Show(locationWhereToOpenContextMenu);
  1045. }
  1046. }
  1047. private void unselectAllToolStripMenuItem_Click(object sender, EventArgs e)
  1048. {
  1049. SetBranchesPushCheckboxesState(_ => false);
  1050. }
  1051. private void selectAllToolStripMenuItem_Click(object sender, EventArgs e)
  1052. {
  1053. SetBranchesPushCheckboxesState(_ => true);
  1054. }
  1055. private void selectTrackedToolStripMenuItem_Click(object sender, EventArgs e)
  1056. {
  1057. SetBranchesPushCheckboxesState(row =>
  1058. {
  1059. // Check if the branch is tracked (i.e. not new)
  1060. return row.Cells[LocalColumn.Name] is DataGridViewTextBoxCell localColumn &&
  1061. row.Cells[RemoteColumn.Name] is DataGridViewTextBoxCell remoteColumn &&
  1062. !string.IsNullOrEmpty(localColumn.Value.ToString()) &&
  1063. !string.IsNullOrEmpty(remoteColumn.Value.ToString());
  1064. });
  1065. }
  1066. private void SetBranchesPushCheckboxesState(Func<DataGridViewRow, bool> willPush)
  1067. {

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