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;
}
}
}
}