using Octokit; using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; using System.Windows.Forms; using TGS.Interface; using TGS.Interface.Components; namespace TGS.ControlPanel { sealed partial class TestMergeManager : ServerOpForm { /// /// Error message format used when fails /// const string MergedPullsError = "Error retrieving currently merged pull requests: {0}"; /// /// The connected to an to handle the pull requests for /// readonly IServerInterface currentInterface; /// /// The to use to read PR lists /// readonly GitHubClient client; /// /// The owner of the target /// string repoOwner; /// /// The name of the target /// string repoName; /// /// Construct a /// /// The to use for managing the /// The to use for getting pull request information public TestMergeManager(IServerInterface interfaceToUse, GitHubClient clientToUse) { InitializeComponent(); DialogResult = DialogResult.Cancel; UpdateToRemoteRadioButton.Checked = true; currentInterface = interfaceToUse; client = clientToUse; Load += PullRequestManager_Load; PullRequestListBox.ItemCheck += PullRequestListBox_ItemCheck; } /// /// Called when an item in is checked or unchecked. Unchecks opposing PRs /// /// The sender of the event /// The void PullRequestListBox_ItemCheck(object sender, ItemCheckEventArgs e) { if (e.NewValue != CheckState.Checked) return; //let's uncheck the opposing PR# if this is one of the outdated/updated testmerge var item = (string)PullRequestListBox.Items[e.Index]; var prefix = item.Split(' ')[0]; for (var I = 0; I < PullRequestListBox.Items.Count; ++I) { var S = (string)PullRequestListBox.Items[I]; if (S.Split(' ')[0] == prefix && S != item) { PullRequestListBox.SetItemChecked(I, false); break; } } } /// /// Populate with from github and check off ones that are currently merged. Prompts the user to login to GitHub if they hit the rate limit /// async void LoadPullRequests() { try { Enabled = false; UseWaitCursor = true; try { PullRequestListBox.Items.Clear(); var repo = currentInterface.GetComponent(); string error = null; List pulls = null; //get started on this while we're processing here var pullsRequest = Task.Run(() => pulls = repo.MergedPullRequests(out error)); //Search for open PRs Enabled = false; UseWaitCursor = true; SearchIssuesResult result; try { result = await client.Search.SearchIssues(new SearchIssuesRequest { Repos = new RepositoryCollection { { repoOwner, repoName } }, State = ItemState.Open, Type = IssueTypeQualifier.PullRequest }); } finally { Enabled = true; UseWaitCursor = false; } //now we need to know what's merged await pullsRequest; if (pulls == null) MessageBox.Show(String.Format(MergedPullsError, error)); //insert the open pull requests, checking already merged once foreach (var I in result.Items) { bool alreadyMerged = false; var pull = pulls.Where(x => x.Number == I.Number).FirstOrDefault(); if (pull != null) { //we need the full info for this PR alreadyMerged = (await client.PullRequest.Get(repoOwner, repoName, I.Number)).Head.Sha == pull.Sha; if (alreadyMerged) pulls.Remove(pull); } InsertPullRequest(I, false, alreadyMerged); } //insert remaining merged pulls foreach (var I in pulls) InsertItem(String.Format("#{0} - {1} - OUTDATED: {2}", I.Number, I.Title, I.Sha), true, true); } finally { Enabled = true; UseWaitCursor = false; } } catch (ForbiddenException) { if (client.Credentials.AuthenticationType == AuthenticationType.Anonymous) //assume request limit hit { if(Program.RateLimitPrompt(client)) LoadPullRequests(); } else throw; } } /// /// Format an entry for and insert it into /// /// The to format, must contain a /// If this or is , will be inserted at the top of as opposed to the bottom /// If the item should be checked void InsertPullRequest(Issue issue, bool prioritize, bool isChecked) { bool needsTesting = false; foreach (var J in issue.Labels) if (J.Name.ToLower().Contains("test")) { needsTesting = true; break; } var itemString = String.Format("#{0} - {1}{2}", issue.Number, issue.Title, issue.PullRequest != null && issue.PullRequest.Merged ? " - MERGED ON REMOTE" : needsTesting ? " - TESTING REQUESTED" : ""); InsertItem(itemString, prioritize || needsTesting, isChecked); } /// /// Insert an into /// /// The to insert /// If this or is , will be inserted at the top of as opposed to the bottom /// If the item should be checked void InsertItem(string itemString, bool prioritize, bool isChecked) { prioritize = prioritize || isChecked; if (prioritize) { PullRequestListBox.Items.Insert(0, itemString); if (isChecked) PullRequestListBox.SetItemChecked(0, true); } else PullRequestListBox.Items.Add(itemString, isChecked); } /// /// Called when the is loaded. Sets and /// /// The sender of the event /// The void PullRequestManager_Load(object sender, EventArgs e) { Enabled = false; var repo = currentInterface.GetComponent(); if(!Program.GetRepositoryRemote(repo, out repoOwner, out repoName)) { Close(); return; } LoadPullRequests(); } /// /// Calls and shows the user an error prompt if it fails /// /// The to call on async void GenerateChangelog(ITGRepository repo) { string error = null; await WrapServerOp(() => repo.GenerateChangelog(out error)); if (error != null) MessageBox.Show(String.Format("Error generating changelog: {0}", error)); } /// /// Called when the is clicked. Calls if necessary, merge pull requests, and call . Closes the if appropriate /// /// The sender of the event /// The async void ApplyButton_Click(object sender, EventArgs e) { Enabled = false; UseWaitCursor = true; ApplyingPullRequestsLabel.Visible = true; ApplyingPullRequestsProgressBar.Visible = true; PullRequestListBox.Visible = false; try { //so first collect a list of pulls that are checked var pulls = new List(); foreach (var I in PullRequestListBox.CheckedItems) { var S = (string)I; string mergedSha = null; var splits = S.Split(' '); if(S.Contains(" - OUTDATED: ")) mergedSha = splits[splits.Length - 1]; var key = Convert.ToInt32((splits[0].Substring(1))); try { pulls.Add(new PullRequestInfo(key, mergedSha)); } catch { MessageBox.Show(String.Format("Checked both keep and update option for #{0}", key), "Error"); return; } } var repo = currentInterface.GetComponent(); string error = null; //Do standard repo updates if (UpdateToRemoteRadioButton.Checked) await WrapServerOp(() => error = repo.Update(true)); else if (UpdateToOriginRadioButton.Checked) await WrapServerOp(() => error = repo.Reset(true)); if (error != null) { MessageBox.Show(String.Format("Error updating repository: {0}", error)); return; } if (UpdateToRemoteRadioButton.Checked) { GenerateChangelog(repo); error = await Task.Run(() => repo.SynchronizePush()); } //Merge the PRs, collect errors var errors = await Task.Run(() => repo.MergePullRequests(pulls, false)); //Show any errors for (var I = 0; I < errors.Count(); ++I) { var err = errors.ElementAt(I); if (err != null) MessageBox.Show(err, String.Format("Error merging PR #{0}", pulls[I].Number)); } if (errors.Count() != 0) return; if (pulls.Count > 0) //regen the changelog GenerateChangelog(repo); //Start the compile var compileStarted = await Task.Run(() => currentInterface.GetComponent().Compile(pulls.Count == 1)); if (error != null) MessageBox.Show(String.Format("Error sychronizing repo: {0}", error)); if (!compileStarted) MessageBox.Show("Could not start compilation!"); else MessageBox.Show("Test merges updated and compilation started!"); } finally { Enabled = true; UseWaitCursor = false; ApplyingPullRequestsLabel.Visible = false; ApplyingPullRequestsProgressBar.Visible = false; PullRequestListBox.Visible = true; } } /// /// Called when the is clicked /// /// The sender of the event /// The void RefreshButton_Click(object sender, EventArgs e) { LoadPullRequests(); } /// /// Called when the is clicked. Adds the PR with the number in to or shows an error prompt if it doesn't/already exists /// /// The sender of the event /// The async void AddPRButton_Click(object sender, EventArgs e) { Enabled = false; UseWaitCursor = true; try { IList pulls = null; string error = null; var mergedPullsTask = WrapServerOp(() => pulls = currentInterface.GetComponent().MergedPullRequests(out error)); int PRNumber; try { PRNumber = Convert.ToInt32(AddPRNumericUpDown.Value); } catch { MessageBox.Show("Invalid PR number!"); return; } var found = false; var asString = PRNumber.ToString(); foreach (var I in PullRequestListBox.Items) if (((string)I).Split(' ')[0].Substring(1) == asString) { found = true; break; } if (found) { MessageBox.Show("That PR is already in the list!"); return; } await mergedPullsTask; if (pulls == null) MessageBox.Show(String.Format(MergedPullsError, error)); //get the PR in question var PR = await client.Issue.Get(repoName, repoOwner, PRNumber); if(PR == null ||PR.PullRequest == null) { MessageBox.Show("That doesn't seem to be a valid PR!"); return; } InsertPullRequest(PR, true, pulls == null || pulls.Any(x => x.Number == PRNumber)); } finally { UseWaitCursor = false; Enabled = true; } } } }