diff --git a/docs/mdsource/_plugins.azuredevops.action.include.md b/docs/mdsource/_plugins.azuredevops.action.include.md index 385e4647..46322fa9 100644 --- a/docs/mdsource/_plugins.azuredevops.action.include.md +++ b/docs/mdsource/_plugins.azuredevops.action.include.md @@ -1,3 +1,12 @@ +## azure-devops-create-prs@1 + +This action results in zero or one item in the contextmenu. This action makes it possible to create a pullrequest for the given repository, it will show an action to go to the specific PullRequest in your favorite webbrowser. +The AzureDevOps plugin is required. + +Example: + +snippet: RepositoryActionsAzureDevopsCreatePrs01 + ## azure-devops-get-prs@1 This action results in zero or more items in the contextmenu. For each open pullrequest for the given repository, it will show an action to go to the specific PullRequest in your favorite webbrowser. diff --git a/src/RepoM.Api/Git/Repository.cs b/src/RepoM.Api/Git/Repository.cs index cde0412f..1241402c 100644 --- a/src/RepoM.Api/Git/Repository.cs +++ b/src/RepoM.Api/Git/Repository.cs @@ -82,6 +82,13 @@ public string[] ReadAllBranches() (LocalRemoved ?? 0) > 0 || (StashCount ?? 0) > 0; + public bool HasLocalChanges => (LocalUntracked ?? 0) > 0 || + (LocalModified ?? 0) > 0 || + (LocalMissing ?? 0) > 0 || + (LocalAdded ?? 0) > 0 || + (LocalStaged ?? 0) > 0 || + (LocalRemoved ?? 0) > 0; + public int? AheadBy { get; set; } public int? BehindBy { get; set; } diff --git a/src/RepoM.Plugin.AzureDevOps/ActionProvider/ActionAzureDevOpsCreatePullRequestsV1Deserializer.cs b/src/RepoM.Plugin.AzureDevOps/ActionProvider/ActionAzureDevOpsCreatePullRequestsV1Deserializer.cs new file mode 100644 index 00000000..38d96b54 --- /dev/null +++ b/src/RepoM.Plugin.AzureDevOps/ActionProvider/ActionAzureDevOpsCreatePullRequestsV1Deserializer.cs @@ -0,0 +1,29 @@ +namespace RepoM.Plugin.AzureDevOps.ActionProvider; + +using System; +using JetBrains.Annotations; +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; +using RepoM.Api.IO.ModuleBasedRepositoryActionProvider; +using RepoM.Api.IO.ModuleBasedRepositoryActionProvider.ActionDeserializers; +using RepoM.Plugin.AzureDevOps.ActionProvider.Options; +using RepositoryAction = Api.IO.ModuleBasedRepositoryActionProvider.Data.RepositoryAction; + +[UsedImplicitly] +internal class ActionAzureDevOpsCreatePullRequestsV1Deserializer : IActionDeserializer +{ + public bool CanDeserialize(string type) + { + return "azure-devops-create-prs@1".Equals(type, StringComparison.CurrentCultureIgnoreCase); + } + + RepositoryAction? IActionDeserializer.Deserialize(JToken jToken, ActionDeserializerComposition actionDeserializer, JsonSerializer jsonSerializer) + { + return Deserialize(jToken, jsonSerializer); + } + + private static RepositoryActionAzureDevOpsCreatePullRequestsV1? Deserialize(JToken jToken, JsonSerializer jsonSerializer) + { + return jToken.ToObject(jsonSerializer); + } +} \ No newline at end of file diff --git a/src/RepoM.Plugin.AzureDevOps/ActionProvider/ActionAzureDevOpsCreatePullRequestsV1Mapper.cs b/src/RepoM.Plugin.AzureDevOps/ActionProvider/ActionAzureDevOpsCreatePullRequestsV1Mapper.cs new file mode 100644 index 00000000..ba625bc2 --- /dev/null +++ b/src/RepoM.Plugin.AzureDevOps/ActionProvider/ActionAzureDevOpsCreatePullRequestsV1Mapper.cs @@ -0,0 +1,109 @@ +namespace RepoM.Plugin.AzureDevOps.ActionProvider; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using JetBrains.Annotations; +using LibGit2Sharp; +using Microsoft.Extensions.Logging; +using RepoM.Api.Git; +using RepoM.Api.IO; +using RepoM.Api.IO.ModuleBasedRepositoryActionProvider; +using RepoM.Api.IO.ModuleBasedRepositoryActionProvider.ActionMappers; +using RepoM.Core.Plugin.Expressions; +using RepoM.Core.Plugin.RepositoryActions.Actions; +using RepoM.Plugin.AzureDevOps.ActionProvider.Options; +using RepoM.Plugin.AzureDevOps.Internal; +using RepositoryAction = Api.IO.ModuleBasedRepositoryActionProvider.Data.RepositoryAction; + +[UsedImplicitly] +internal class ActionAzureDevOpsCreatePullRequestsV1Mapper : IActionToRepositoryActionMapper +{ + private readonly IAzureDevOpsPullRequestService _service; + private readonly IRepositoryExpressionEvaluator _expressionEvaluator; + private readonly ILogger _logger; + + public ActionAzureDevOpsCreatePullRequestsV1Mapper(IAzureDevOpsPullRequestService service, IRepositoryExpressionEvaluator expressionEvaluator, ILogger logger) + { + _service = service ?? throw new ArgumentNullException(nameof(service)); + _expressionEvaluator = expressionEvaluator ?? throw new ArgumentNullException(nameof(expressionEvaluator)); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + } + + public bool CanMap(RepositoryAction action) + { + return action is RepositoryActionAzureDevOpsCreatePullRequestsV1; + } + + public bool CanHandleMultipleRepositories() + { + return false; + } + + public IEnumerable Map(RepositoryAction action, IEnumerable repository, ActionMapperComposition actionMapperComposition) + { + return Map(action as RepositoryActionAzureDevOpsCreatePullRequestsV1, repository.First()); + } + + private IEnumerable Map(RepositoryActionAzureDevOpsCreatePullRequestsV1? action, Api.Git.Repository repository) + { + if (action == null) + { + return Array.Empty(); + } + + if (!_expressionEvaluator.EvaluateBooleanExpression(action.Active, repository)) + { + return Array.Empty(); + } + + if (repository.HasLocalChanges || repository.CurrentBranch.Equals(action.ToBranch, StringComparison.OrdinalIgnoreCase)) + { + return Array.Empty(); + } + + if (string.IsNullOrWhiteSpace(action.ProjectId)) + { + return Array.Empty(); + } + + var projectId = _expressionEvaluator.EvaluateStringExpression(action.ProjectId, repository); + + if (string.IsNullOrWhiteSpace(projectId)) + { + return Array.Empty(); + } + + if (!string.IsNullOrWhiteSpace(action.ToBranch)) + { + // check if branch exists! + if (!repository.Branches.Contains(action.ToBranch)) + { + return Array.Empty(); + } + } + else + { + return Array.Empty(); + } + + return new List() + { + new(action.Title ?? $"Create Pull Request {(action.AutoComplete.Enabled ? "(with auto-complete)" : string.Empty)}", repository) + { + Action = new DelegateAction((_, _) => + { + if (action.AutoComplete.Enabled) + { + _service.CreatePullRequestWithAutoCompleteAsync(repository, projectId, action.ReviewerIds, action.ToBranch, (int)action.AutoComplete.MergeStrategy, action.PrTitle, action.DraftPr, action.IncludeWorkItems, action.OpenInBrowser, action.AutoComplete.DeleteSourceBranch, action.AutoComplete.TransitionWorkItems).GetAwaiter().GetResult(); + } + else + { + _service.CreatePullRequestAsync(repository, projectId, action.ReviewerIds, action.ToBranch, action.PrTitle, action.DraftPr, action.IncludeWorkItems, action.OpenInBrowser).GetAwaiter().GetResult(); + } + }), + }, + }; + } +} \ No newline at end of file diff --git a/src/RepoM.Plugin.AzureDevOps/ActionProvider/ActionAzureDevOpsPullRequestsV1Deserializer.cs b/src/RepoM.Plugin.AzureDevOps/ActionProvider/ActionAzureDevOpsGetPullRequestsV1Deserializer.cs similarity index 85% rename from src/RepoM.Plugin.AzureDevOps/ActionProvider/ActionAzureDevOpsPullRequestsV1Deserializer.cs rename to src/RepoM.Plugin.AzureDevOps/ActionProvider/ActionAzureDevOpsGetPullRequestsV1Deserializer.cs index 2afa8fcb..b46d9176 100644 --- a/src/RepoM.Plugin.AzureDevOps/ActionProvider/ActionAzureDevOpsPullRequestsV1Deserializer.cs +++ b/src/RepoM.Plugin.AzureDevOps/ActionProvider/ActionAzureDevOpsGetPullRequestsV1Deserializer.cs @@ -6,10 +6,11 @@ namespace RepoM.Plugin.AzureDevOps.ActionProvider; using Newtonsoft.Json.Linq; using RepoM.Api.IO.ModuleBasedRepositoryActionProvider; using RepoM.Api.IO.ModuleBasedRepositoryActionProvider.ActionDeserializers; +using RepoM.Plugin.AzureDevOps.ActionProvider.Options; using RepositoryAction = Api.IO.ModuleBasedRepositoryActionProvider.Data.RepositoryAction; [UsedImplicitly] -internal class ActionAzureDevOpsPullRequestsV1Deserializer : IActionDeserializer +internal class ActionAzureDevOpsGetPullRequestsV1Deserializer : IActionDeserializer { public bool CanDeserialize(string type) { @@ -21,9 +22,9 @@ public bool CanDeserialize(string type) return Deserialize(jToken, jsonSerializer); } - private static RepositoryActionAzureDevOpsPullRequestsV1? Deserialize(JToken jToken, JsonSerializer jsonSerializer) + private static RepositoryActionAzureDevOpsGetPullRequestsV1? Deserialize(JToken jToken, JsonSerializer jsonSerializer) { - RepositoryActionAzureDevOpsPullRequestsV1? result = jToken.ToObject(jsonSerializer); + RepositoryActionAzureDevOpsGetPullRequestsV1? result = jToken.ToObject(jsonSerializer); JToken? showWhenEmpty = jToken["show-when-empty"]; if (showWhenEmpty != null && result != null) diff --git a/src/RepoM.Plugin.AzureDevOps/ActionProvider/ActionAzureDevOpsPullRequestsV1Mapper.cs b/src/RepoM.Plugin.AzureDevOps/ActionProvider/ActionAzureDevOpsGetPullRequestsV1Mapper.cs similarity index 88% rename from src/RepoM.Plugin.AzureDevOps/ActionProvider/ActionAzureDevOpsPullRequestsV1Mapper.cs rename to src/RepoM.Plugin.AzureDevOps/ActionProvider/ActionAzureDevOpsGetPullRequestsV1Mapper.cs index 2a4edb03..b2e5f9e6 100644 --- a/src/RepoM.Plugin.AzureDevOps/ActionProvider/ActionAzureDevOpsPullRequestsV1Mapper.cs +++ b/src/RepoM.Plugin.AzureDevOps/ActionProvider/ActionAzureDevOpsGetPullRequestsV1Mapper.cs @@ -11,17 +11,18 @@ namespace RepoM.Plugin.AzureDevOps.ActionProvider; using RepoM.Api.IO.ModuleBasedRepositoryActionProvider.ActionMappers; using RepoM.Core.Plugin.Expressions; using RepoM.Core.Plugin.RepositoryActions.Actions; +using RepoM.Plugin.AzureDevOps.ActionProvider.Options; using RepoM.Plugin.AzureDevOps.Internal; using RepositoryAction = Api.IO.ModuleBasedRepositoryActionProvider.Data.RepositoryAction; [UsedImplicitly] -internal class ActionAzureDevOpsPullRequestsV1Mapper : IActionToRepositoryActionMapper +internal class ActionAzureDevOpsGetPullRequestsV1Mapper : IActionToRepositoryActionMapper { private readonly IAzureDevOpsPullRequestService _service; private readonly IRepositoryExpressionEvaluator _expressionEvaluator; private readonly ILogger _logger; - public ActionAzureDevOpsPullRequestsV1Mapper(IAzureDevOpsPullRequestService service, IRepositoryExpressionEvaluator expressionEvaluator, ILogger logger) + public ActionAzureDevOpsGetPullRequestsV1Mapper(IAzureDevOpsPullRequestService service, IRepositoryExpressionEvaluator expressionEvaluator, ILogger logger) { _service = service ?? throw new ArgumentNullException(nameof(service)); _expressionEvaluator = expressionEvaluator ?? throw new ArgumentNullException(nameof(expressionEvaluator)); @@ -30,7 +31,7 @@ public ActionAzureDevOpsPullRequestsV1Mapper(IAzureDevOpsPullRequestService serv public bool CanMap(RepositoryAction action) { - return action is RepositoryActionAzureDevOpsPullRequestsV1; + return action is RepositoryActionAzureDevOpsGetPullRequestsV1; } public bool CanHandleMultipleRepositories() @@ -40,10 +41,10 @@ public bool CanHandleMultipleRepositories() public IEnumerable Map(RepositoryAction action, IEnumerable repository, ActionMapperComposition actionMapperComposition) { - return Map(action as RepositoryActionAzureDevOpsPullRequestsV1, repository.First()); + return Map(action as RepositoryActionAzureDevOpsGetPullRequestsV1, repository.First()); } - private Api.Git.RepositoryAction[] Map(RepositoryActionAzureDevOpsPullRequestsV1? action, Repository repository) + private Api.Git.RepositoryAction[] Map(RepositoryActionAzureDevOpsGetPullRequestsV1? action, Repository repository) { if (action == null) { diff --git a/src/RepoM.Plugin.AzureDevOps/ActionProvider/Options/RepositoryActionAzureDevOpsBase.cs b/src/RepoM.Plugin.AzureDevOps/ActionProvider/Options/RepositoryActionAzureDevOpsBase.cs new file mode 100644 index 00000000..5e1710ed --- /dev/null +++ b/src/RepoM.Plugin.AzureDevOps/ActionProvider/Options/RepositoryActionAzureDevOpsBase.cs @@ -0,0 +1,8 @@ +namespace RepoM.Plugin.AzureDevOps.ActionProvider.Options; + +using RepoM.Api.IO.ModuleBasedRepositoryActionProvider.Data; + +public abstract class RepositoryActionAzureDevOpsBase : RepositoryAction +{ + public string? ProjectId { get; set; } +} \ No newline at end of file diff --git a/src/RepoM.Plugin.AzureDevOps/ActionProvider/Options/RepositoryActionAzureDevOpsCreatePullRequestsV1.cs b/src/RepoM.Plugin.AzureDevOps/ActionProvider/Options/RepositoryActionAzureDevOpsCreatePullRequestsV1.cs new file mode 100644 index 00000000..5f31ef5d --- /dev/null +++ b/src/RepoM.Plugin.AzureDevOps/ActionProvider/Options/RepositoryActionAzureDevOpsCreatePullRequestsV1.cs @@ -0,0 +1,54 @@ +namespace RepoM.Plugin.AzureDevOps.ActionProvider.Options; + +using System.Collections.Generic; + +public class RepositoryActionAzureDevOpsCreatePullRequestsV1 : RepositoryActionAzureDevOpsBase +{ + public string? Title { get; set; } + public string? PrTitle { get; set; } + public string ToBranch { get; set; } + public List ReviewerIds { get; set; } + public bool DraftPr { get; set; } + public bool IncludeWorkItems { get; set; } = true; + public bool OpenInBrowser { get; set; } + public RepositoryActionAzureDevOpsCreatePullRequestsAutoCompleteOptionsV1 AutoComplete { get; set; } + + public RepositoryActionAzureDevOpsCreatePullRequestsV1() + { + ToBranch = string.Empty; + ReviewerIds = new(); + AutoComplete = new(); + } +} + +public class RepositoryActionAzureDevOpsCreatePullRequestsAutoCompleteOptionsV1 +{ + public bool Enabled { get; set; } + public RepositoryActionAzureDevOpsCreatePullRequestsMergeStrategyV1 MergeStrategy { get; set; } = RepositoryActionAzureDevOpsCreatePullRequestsMergeStrategyV1.NoFastForward; + public bool DeleteSourceBranch { get; set; } = true; + public bool TransitionWorkItems { get; set; } = true; +} + +public enum RepositoryActionAzureDevOpsCreatePullRequestsMergeStrategyV1 +{ + // + // Summary: + // A two-parent, no-fast-forward merge. The source branch is unchanged. This is + // the default behavior. + NoFastForward = 1, + // + // Summary: + // Put all changes from the pull request into a single-parent commit. + Squash, + // + // Summary: + // Rebase the source branch on top of the target branch HEAD commit, and fast-forward + // the target branch. The source branch is updated during the rebase operation. + Rebase, + // + // Summary: + // Rebase the source branch on top of the target branch HEAD commit, and create + // a two-parent, no-fast-forward merge. The source branch is updated during the + // rebase operation. + RebaseMerge +} \ No newline at end of file diff --git a/src/RepoM.Plugin.AzureDevOps/ActionProvider/Options/RepositoryActionAzureDevOpsGetPullRequestsV1.cs b/src/RepoM.Plugin.AzureDevOps/ActionProvider/Options/RepositoryActionAzureDevOpsGetPullRequestsV1.cs new file mode 100644 index 00000000..297e888a --- /dev/null +++ b/src/RepoM.Plugin.AzureDevOps/ActionProvider/Options/RepositoryActionAzureDevOpsGetPullRequestsV1.cs @@ -0,0 +1,9 @@ +namespace RepoM.Plugin.AzureDevOps.ActionProvider.Options; + +using RepoM.Api.IO.ModuleBasedRepositoryActionProvider.Data; + +public class RepositoryActionAzureDevOpsGetPullRequestsV1 : RepositoryActionAzureDevOpsBase +{ + public string? RepoId { get; set; } + public string? ShowWhenEmpty { get; set; } +} \ No newline at end of file diff --git a/src/RepoM.Plugin.AzureDevOps/AzureDevOpsPackage.cs b/src/RepoM.Plugin.AzureDevOps/AzureDevOpsPackage.cs index d4c98d96..fe8939fa 100644 --- a/src/RepoM.Plugin.AzureDevOps/AzureDevOpsPackage.cs +++ b/src/RepoM.Plugin.AzureDevOps/AzureDevOpsPackage.cs @@ -1,5 +1,6 @@ namespace RepoM.Plugin.AzureDevOps; +using System.Net.Http; using JetBrains.Annotations; using RepoM.Api.IO.ModuleBasedRepositoryActionProvider; using RepoM.Core.Plugin; @@ -15,8 +16,10 @@ public class AzureDevOpsPackage : IPackage { public void RegisterServices(Container container) { - container.Collection.Append(Lifestyle.Singleton); - container.Collection.Append(Lifestyle.Singleton); + container.Collection.Append(Lifestyle.Singleton); + container.Collection.Append(Lifestyle.Singleton); + container.Collection.Append(Lifestyle.Singleton); + container.Collection.Append(Lifestyle.Singleton); container.Register(Lifestyle.Singleton); diff --git a/src/RepoM.Plugin.AzureDevOps/Internal/AzureDevOpsPullRequestService.cs b/src/RepoM.Plugin.AzureDevOps/Internal/AzureDevOpsPullRequestService.cs index e236fc66..bf34a108 100644 --- a/src/RepoM.Plugin.AzureDevOps/Internal/AzureDevOpsPullRequestService.cs +++ b/src/RepoM.Plugin.AzureDevOps/Internal/AzureDevOpsPullRequestService.cs @@ -3,22 +3,34 @@ namespace RepoM.Plugin.AzureDevOps.Internal; using System; using System.Collections.Concurrent; using System.Collections.Generic; +using System.Dynamic; using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Text.Json; +using System.Text.Json.Serialization; +using System.Text.RegularExpressions; using System.Threading; using System.Threading.Tasks; using Microsoft.Extensions.Logging; +using Microsoft.Graph; +using Microsoft.Graph.Models; using Microsoft.TeamFoundation.SourceControl.WebApi; using Microsoft.VisualStudio.Services.Common; using Microsoft.VisualStudio.Services.WebApi; +using Newtonsoft.Json; using RepoM.Api.Common; +using RepoM.Api.IO; using RepoM.Core.Plugin.Repository; internal sealed class AzureDevOpsPullRequestService : IAzureDevOpsPullRequestService, IDisposable { + private readonly HttpClient _httpClient; private readonly IAppSettingsService _appSettingsService; private readonly ILogger _logger; private readonly VssConnection? _connection; - private GitHttpClient? _gitClient; + private GitHttpClient? _azureDevopsGitClient; private readonly List _emptyList = new(0); private Timer? _updateTimer1; @@ -30,6 +42,7 @@ internal sealed class AzureDevOpsPullRequestService : IAzureDevOpsPullRequestSer public AzureDevOpsPullRequestService(IAppSettingsService appSettingsService, ILogger logger) { + _httpClient = new(); _appSettingsService = appSettingsService ?? throw new ArgumentNullException(nameof(appSettingsService)); _logger = logger ?? throw new ArgumentNullException(nameof(logger)); @@ -37,9 +50,13 @@ public AzureDevOpsPullRequestService(IAppSettingsService appSettingsService, ILo try { + Uri baseUrl = new(_appSettingsService.AzureDevOpsBaseUrl); _connection = new VssConnection( - new Uri(_appSettingsService.AzureDevOpsBaseUrl), + baseUrl, new VssBasicCredential(string.Empty, token)); + _httpClient.BaseAddress = baseUrl; + _httpClient.DefaultRequestHeaders.Accept.Add(new("application/json")); + _httpClient.DefaultRequestHeaders.Authorization = new("Basic", Convert.ToBase64String(Encoding.ASCII.GetBytes($":{_appSettingsService.AzureDevOpsPersonalAccessToken}"))); } catch (Exception e) { @@ -64,7 +81,7 @@ public Task InitializeAsync() try { - _gitClient = _connection.GetClient(); + _azureDevopsGitClient = _connection.GetClient(); } catch (Exception e) { @@ -72,12 +89,119 @@ public Task InitializeAsync() return Task.CompletedTask; } - _updateTimer1 = new Timer(async _ => await UpdatePullRequests(_gitClient!), null, TimeSpan.FromSeconds(5), TimeSpan.FromMinutes(4)); - _updateTimer2 = new Timer(async _ => await UpdateProjects(_gitClient!), null, TimeSpan.FromSeconds(7), TimeSpan.FromMinutes(10)); + _updateTimer1 = new Timer(async _ => await UpdatePullRequests(_azureDevopsGitClient!), null, TimeSpan.FromSeconds(5), TimeSpan.FromMinutes(4)); + _updateTimer2 = new Timer(async _ => await UpdateProjects(_azureDevopsGitClient!), null, TimeSpan.FromSeconds(7), TimeSpan.FromMinutes(10)); return Task.CompletedTask; } + public async Task CreatePullRequestWithAutoCompleteAsync(IRepository repository, string projectId, List reviewersIds, string toBranch, int mergeStrategy, string? title = null, bool isDraft = false, bool includeWorkItems = true, bool openInBrowser = false, bool deleteSourceBranch = true, bool transitionWorkItems = true, CancellationToken cancellationToken = default) + { + GitPullRequest pr = await CreatePullRequestInternalAsync(repository, projectId, reviewersIds, toBranch, title, isDraft, includeWorkItems, cancellationToken); + + Guid repoId = FindRepositoryGuid(repository); + + GitPullRequest prBody = new() + { + AutoCompleteSetBy = pr.CreatedBy, + CompletionOptions = new() + { + DeleteSourceBranch = deleteSourceBranch, + MergeStrategy = (GitPullRequestMergeStrategy)mergeStrategy, + TransitionWorkItems = transitionWorkItems, + MergeCommitMessage = $"Merged PR {pr.PullRequestId}: {pr.Title}" + } + }; + + string prBodyJson = JsonConvert.SerializeObject(prBody); + StringContent httpContent = new(prBodyJson, new MediaTypeHeaderValue("application/json")); + HttpResponseMessage response = await _httpClient.PatchAsync($"{projectId}/_apis/git/repositories/{repoId}/pullrequests/{pr.PullRequestId}?api-version=7.0", httpContent); + _ = response.EnsureSuccessStatusCode(); + + if (openInBrowser) + { + ProcessHelper.StartProcess(CreatePullRequestUrl(pr.Repository.WebUrl, pr.PullRequestId), string.Empty); + } + } + + public async Task CreatePullRequestAsync(IRepository repository, string projectId, List reviewersIds, string toBranch, string? title = null, bool isDraft = false, bool includeWorkItems = true, bool openInBrowser = false, CancellationToken cancellationToken = default) + { + GitPullRequest pr = await CreatePullRequestInternalAsync(repository, projectId, reviewersIds, toBranch, title, isDraft, includeWorkItems, cancellationToken); + + if (openInBrowser) + { + ProcessHelper.StartProcess(CreatePullRequestUrl(pr.Repository.WebUrl, pr.PullRequestId), string.Empty); + } + } + + private async Task CreatePullRequestInternalAsync(IRepository repository, string projectId, List reviewersIds, string toBranch, string? title = null, bool isDraft = false, bool includeWorkItems = true, CancellationToken cancellationToken = default) + { + title ??= repository.CurrentBranch.Substring(repository.CurrentBranch.IndexOf('/') + 1); + + Guid repoId = FindRepositoryGuid(repository); + + if (repoId == Guid.Empty) + { + repoId = await FindRepositoryGuidByProjectId(repository, projectId); + } + + HashSet workItems = new(); + + if (includeWorkItems) + { + using var repo = new LibGit2Sharp.Repository(repository.Path); + + Regex workItemRegex = new(@"\#(\d+)", RegexOptions.Compiled); + + var commitMessages = repo.Commits + .QueryBy(new LibGit2Sharp.CommitFilter() + { + ExcludeReachableFrom = repo.Branches[toBranch].UpstreamBranchCanonicalName + }) + .Select(c => c.Message).ToList(); + + foreach (var commitMessage in commitMessages) + { + Match match = workItemRegex.Match(commitMessage); + if (match.Success) + { + foreach (System.Text.RegularExpressions.Group group in match.Groups.Values.Skip(1)) + { + _ = workItems.Add(new ResourceRef() + { + Id = group.Value + }); + } + } + } + } + + GitPullRequest prBody = new() + { + Title = title, + IsDraft = isDraft, + SourceRefName = $"refs/heads/{repository.CurrentBranch}", + TargetRefName = $"refs/heads/{toBranch}", + Reviewers = reviewersIds + .Select(reviewerId => + new IdentityRefWithVote() + { + Id = reviewerId, + }) + .ToArray(), + SupportsIterations = true, + WorkItemRefs = workItems.ToArray() + }; + + string prBodyJson = JsonConvert.SerializeObject(prBody); + StringContent httpContent = new(prBodyJson, new MediaTypeHeaderValue("application/json")); + HttpResponseMessage response = await _httpClient.PostAsync($"{projectId}/_apis/git/repositories/{repoId}/pullrequests?api-version=7.0", httpContent, cancellationToken); + _ = response.EnsureSuccessStatusCode(); + + string? responseContent = await response.Content.ReadAsStringAsync() ?? throw new Exception("Invalid return type"); + return JsonConvert.DeserializeObject(responseContent); + } + public int CountPullRequests(IRepository repository) { var isRepositoryKnown = _repositoryDirectoryDevOpsRepoIdMapping.TryGetValue(repository.SafePath, out Guid repoIdGuid); @@ -112,7 +236,7 @@ public List GetPullRequests(IRepository repository, string projectI RegisterProjectId(projectId); if (_gitRepositoriesPerProject.IsEmpty) { - _ = UpdateProjects(_gitClient!); + _ = UpdateProjects(_azureDevopsGitClient!); } return Task.Run(() => GetPullRequestsTask(repository, projectId, repoId)).GetAwaiter().GetResult(); @@ -122,7 +246,7 @@ public void Dispose() { _updateTimer1?.Dispose(); _updateTimer2?.Dispose(); - _gitClient?.Dispose(); + _azureDevopsGitClient?.Dispose(); _connection?.Dispose(); } @@ -136,13 +260,13 @@ private void RegisterProjectId(string? projectId) if (!_gitRepositoriesPerProject.ContainsKey(projectId)) { _gitRepositoriesPerProject.AddOrUpdate(projectId, _ => Array.Empty(), (_, gitRepositories) => gitRepositories); - _ = UpdateProjects(_gitClient!); + _ = UpdateProjects(_azureDevopsGitClient!); } if (!_pullRequestsPerProject.ContainsKey(projectId)) { _pullRequestsPerProject.AddOrUpdate(projectId, _ => Array.Empty(), (_, prs) => prs); - _ = UpdatePullRequests(_gitClient!); + _ = UpdatePullRequests(_azureDevopsGitClient!); } } @@ -243,7 +367,7 @@ private async Task UpdatePullRequests(GitHttpClient gitClient) private async Task> GetPullRequestsTask(IRepository repository, string projectId, string? repoId) { - if (_gitClient == null) + if (_azureDevopsGitClient == null) { return _emptyList; } @@ -251,7 +375,7 @@ private async Task> GetPullRequestsTask(IRepository repository Guid repoIdGuid = Guid.Empty; // first get repo id - if (repoIdGuid == Guid.Empty && !string.IsNullOrWhiteSpace(repoId)) + if (!string.IsNullOrWhiteSpace(repoId)) { _ = Guid.TryParse(repoId, out repoIdGuid); } @@ -297,11 +421,11 @@ private async Task FindRepositoryGuidByProjectId(IRepository repository, s { try { - if (_gitClient != null) + if (_azureDevopsGitClient != null) { // yes it is possible due to a race condition that the _gitClient is null when executing this request. // don't care for now as we catch the exceptions. - List repos = await _gitClient!.GetRepositoriesAsync(projectId, includeLinks: true, includeAllUrls: true, includeHidden: true); + List repos = await _azureDevopsGitClient.GetRepositoriesAsync(projectId, includeLinks: true, includeAllUrls: true, includeHidden: true); foreach (GitRepository r in repos) { @@ -351,7 +475,12 @@ private Guid FindRepositoryGuid(IRepository repository) private static string CreatePullRequestUrl(GitRepository repo, GitPullRequest pr) { - return repo.WebUrl + "/pullrequest/" + pr.PullRequestId; + return CreatePullRequestUrl(repo.WebUrl, pr.PullRequestId); + } + + private static string CreatePullRequestUrl(string webUrl, int prId) + { + return $"{webUrl}/pullrequest/{prId}"; } private static string GetRepositorySearchUrl(IRepository repository) diff --git a/src/RepoM.Plugin.AzureDevOps/Internal/IAzureDevOpsPullRequestService.cs b/src/RepoM.Plugin.AzureDevOps/Internal/IAzureDevOpsPullRequestService.cs index aeee8c69..7aa1175a 100644 --- a/src/RepoM.Plugin.AzureDevOps/Internal/IAzureDevOpsPullRequestService.cs +++ b/src/RepoM.Plugin.AzureDevOps/Internal/IAzureDevOpsPullRequestService.cs @@ -1,7 +1,10 @@ namespace RepoM.Plugin.AzureDevOps.Internal; using System.Collections.Generic; +using System.Net.Http; +using System.Threading; using System.Threading.Tasks; +using Microsoft.TeamFoundation.SourceControl.WebApi; using RepoM.Core.Plugin.Repository; internal interface IAzureDevOpsPullRequestService @@ -10,5 +13,9 @@ internal interface IAzureDevOpsPullRequestService int CountPullRequests(IRepository repository); + Task CreatePullRequestWithAutoCompleteAsync(IRepository repository, string projectId, List reviewersIds, string toBranch, int mergeStrategy, string? title = null, bool isDraft = false, bool includeWorkItems = true, bool openInBrowser = false, bool deleteSourceBranch = true, bool transitionWorkItems = true, CancellationToken cancellationToken = default); + + Task CreatePullRequestAsync(IRepository repository, string projectId, List reviewersIds, string toBranch, string? title = null, bool isDraft = false, bool includeWorkItems = true, bool openInBrowser = false, CancellationToken cancellationToken = default); + List GetPullRequests(IRepository repository, string projectId, string? repoId); } \ No newline at end of file diff --git a/src/RepoM.Plugin.AzureDevOps/RepoM.Plugin.AzureDevOps.csproj b/src/RepoM.Plugin.AzureDevOps/RepoM.Plugin.AzureDevOps.csproj index 9654b1eb..73028479 100644 --- a/src/RepoM.Plugin.AzureDevOps/RepoM.Plugin.AzureDevOps.csproj +++ b/src/RepoM.Plugin.AzureDevOps/RepoM.Plugin.AzureDevOps.csproj @@ -6,6 +6,7 @@ + diff --git a/tests/RepoM.Plugin.AzureDevOps.Tests/ActionProvider/ActionAzureDevOpsCreatePullRequestsV1MapperTests.cs b/tests/RepoM.Plugin.AzureDevOps.Tests/ActionProvider/ActionAzureDevOpsCreatePullRequestsV1MapperTests.cs new file mode 100644 index 00000000..7e18ada0 --- /dev/null +++ b/tests/RepoM.Plugin.AzureDevOps.Tests/ActionProvider/ActionAzureDevOpsCreatePullRequestsV1MapperTests.cs @@ -0,0 +1,189 @@ +namespace RepoM.Plugin.AzureDevOps.Tests.ActionProvider; + +using System; +using System.Collections.Generic; +using System.Threading; +using System.Threading.Tasks; +using FakeItEasy; +using FluentAssertions; +using Microsoft.Extensions.Logging.Abstractions; +using RepoM.Api.Git; +using RepoM.Api.IO.ModuleBasedRepositoryActionProvider; +using RepoM.Api.IO.ModuleBasedRepositoryActionProvider.ActionMappers; +using RepoM.Core.Plugin.Expressions; +using RepoM.Core.Plugin.Repository; +using RepoM.Plugin.AzureDevOps.ActionProvider; +using RepoM.Plugin.AzureDevOps.ActionProvider.Options; +using RepoM.Plugin.AzureDevOps.Internal; +using Xunit; +using RepositoryAction = RepoM.Api.IO.ModuleBasedRepositoryActionProvider.Data.RepositoryAction; + +public class ActionAzureDevOpsCreatePullRequestsV1MapperTests +{ + private readonly IAzureDevOpsPullRequestService _service; + private readonly IRepositoryExpressionEvaluator _evaluator; + private readonly ActionAzureDevOpsCreatePullRequestsV1Mapper _sut; + private readonly RepositoryActionAzureDevOpsCreatePullRequestsV1 _action; + private readonly IEnumerable _repositories; + private readonly Repository _repository; + private readonly ActionMapperComposition _composition; + + public ActionAzureDevOpsCreatePullRequestsV1MapperTests() + { + _service = A.Fake(); + _evaluator = A.Fake(); + _sut = new ActionAzureDevOpsCreatePullRequestsV1Mapper(_service, _evaluator, NullLogger.Instance); + + _action = new RepositoryActionAzureDevOpsCreatePullRequestsV1() + { + ToBranch = "main" + }; + _repository = new Repository("") + { + Branches = new string[1] + { + "main" + } + }; + _repositories = new [] { _repository, }; + _composition = new ActionMapperComposition(Array.Empty(), _evaluator); + + // default test behavior. + _action.Active = "dummy-Active-property"; + _action.ProjectId = "dummy-project-id"; + A.CallTo(() => _evaluator.EvaluateBooleanExpression("dummy-Active-property", _repository)).Returns(true); + A.CallTo(() => _evaluator.EvaluateStringExpression("dummy-project-id", A._)).Returns("real-project-id"); + } + + [Fact] + public void CanHandleMultipleRepositories_ShouldReturnFalse() + { + // arrange + + // act + var result = _sut.CanHandleMultipleRepositories(); + + // assert + result.Should().BeFalse(); + A.CallTo(_service).MustNotHaveHappened(); + } + + [Fact] + public void CanMap_ShouldReturnFalse_WhenInputIsNotValidType() + { + // arrange + var action = new DummyRepositoryAction(); + + // act + var result = _sut.CanMap(action); + + // assert + result.Should().BeFalse(); + A.CallTo(_service).MustNotHaveHappened(); + } + + [Fact] + public void CanMap_ShouldReturnTrue_WhenInputIsValidType() + { + // arrange + var action = new RepositoryActionAzureDevOpsCreatePullRequestsV1(); + + // act + var result = _sut.CanMap(action); + + // assert + result.Should().BeTrue(); + A.CallTo(_service).MustNotHaveHappened(); + } + + [Fact] + public void Map_ShouldReturnEmptySet_WhenWrongActionType() + { + // arrange + + // act + IEnumerable result = _sut.Map(new DummyRepositoryAction(), _repositories, _composition); + + // assert + result.Should().BeEmpty(); + A.CallTo(_service).MustNotHaveHappened(); + } + + [Fact] + public void Map_ShouldReturnEmptySet_WhenActionNotActive() + { + // arrange + _action.Active = "dummy"; + A.CallTo(() => _evaluator.EvaluateBooleanExpression("dummy", _repository)).Returns(false); + + // act + IEnumerable result = _sut.Map(_action, _repositories, _composition); + + // assert + result.Should().BeEmpty(); + A.CallTo(_service).MustNotHaveHappened(); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void Map_ShouldReturnEmptySet_WhenProjectIdNotSet(string? projectId) + { + // arrange + _action.ProjectId = projectId; + + // act + IEnumerable result = _sut.Map(_action, _repositories, _composition); + + // assert + result.Should().BeEmpty(); + A.CallTo(_service).MustNotHaveHappened(); + } + + [Theory] + [InlineData(null)] + [InlineData("")] + [InlineData(" ")] + public void Map_ShouldReturnEmptySet_WhenProjectIdIsNotValidAfterEvaluation(string? projectId) + { + // arrange + _action.ProjectId = "dummy-project-id"; + A.CallTo(() => _evaluator.EvaluateStringExpression("dummy-project-id", A._)).Returns(projectId!); + + // act + IEnumerable result = _sut.Map(_action, _repositories, _composition); + + // assert + result.Should().BeEmpty(); + A.CallTo(_service).MustNotHaveHappened(); + } + + [Fact] + public void Map_ShouldReturnRepositoryActions() + { + // arrange + + // act + IEnumerable result = _sut.Map(_action, _repositories, _composition); + + // assert + result.Should().HaveCount(1).And.AllBeOfType(); + } + + [Fact] + public void Map_ShouldReturnRepositoryActions_WithAutoComplete() + { + // arrange + + // act + IEnumerable result = _sut.Map(_action, _repositories, _composition); + + // assert + result.Should().HaveCount(1).And.AllBeOfType(); + } +} + +file class DummyRepositoryAction : RepositoryAction +{ +} \ No newline at end of file diff --git a/tests/RepoM.Plugin.AzureDevOps.Tests/ActionProvider/ActionAzureDevOpsPullRequestsV1MapperTests.cs b/tests/RepoM.Plugin.AzureDevOps.Tests/ActionProvider/ActionAzureDevOpsGetPullRequestsV1MapperTests.cs similarity index 91% rename from tests/RepoM.Plugin.AzureDevOps.Tests/ActionProvider/ActionAzureDevOpsPullRequestsV1MapperTests.cs rename to tests/RepoM.Plugin.AzureDevOps.Tests/ActionProvider/ActionAzureDevOpsGetPullRequestsV1MapperTests.cs index 6ae3df45..9b5b4c17 100644 --- a/tests/RepoM.Plugin.AzureDevOps.Tests/ActionProvider/ActionAzureDevOpsPullRequestsV1MapperTests.cs +++ b/tests/RepoM.Plugin.AzureDevOps.Tests/ActionProvider/ActionAzureDevOpsGetPullRequestsV1MapperTests.cs @@ -11,27 +11,28 @@ namespace RepoM.Plugin.AzureDevOps.Tests.ActionProvider; using RepoM.Core.Plugin.Expressions; using RepoM.Core.Plugin.Repository; using RepoM.Plugin.AzureDevOps.ActionProvider; +using RepoM.Plugin.AzureDevOps.ActionProvider.Options; using RepoM.Plugin.AzureDevOps.Internal; using Xunit; using RepositoryAction = RepoM.Api.IO.ModuleBasedRepositoryActionProvider.Data.RepositoryAction; -public class ActionAzureDevOpsPullRequestsV1MapperTests +public class ActionAzureDevOpsGetPullRequestsV1MapperTests { private readonly IAzureDevOpsPullRequestService _service; private readonly IRepositoryExpressionEvaluator _evaluator; - private readonly ActionAzureDevOpsPullRequestsV1Mapper _sut; - private readonly RepositoryActionAzureDevOpsPullRequestsV1 _action; + private readonly ActionAzureDevOpsGetPullRequestsV1Mapper _sut; + private readonly RepositoryActionAzureDevOpsGetPullRequestsV1 _action; private readonly IEnumerable _repositories; private readonly Repository _repository; private readonly ActionMapperComposition _composition; - public ActionAzureDevOpsPullRequestsV1MapperTests() + public ActionAzureDevOpsGetPullRequestsV1MapperTests() { _service = A.Fake(); _evaluator = A.Fake(); - _sut = new ActionAzureDevOpsPullRequestsV1Mapper(_service, _evaluator, NullLogger.Instance); + _sut = new ActionAzureDevOpsGetPullRequestsV1Mapper(_service, _evaluator, NullLogger.Instance); - _action = new RepositoryActionAzureDevOpsPullRequestsV1(); + _action = new RepositoryActionAzureDevOpsGetPullRequestsV1(); _repository = new Repository(""); _repositories = new [] { _repository, }; _composition = new ActionMapperComposition(Array.Empty(), _evaluator); @@ -75,7 +76,7 @@ public void CanMap_ShouldReturnFalse_WhenInputIsNotValidType() public void CanMap_ShouldReturnTrue_WhenInputIsValidType() { // arrange - var action = new RepositoryActionAzureDevOpsPullRequestsV1(); + var action = new RepositoryActionAzureDevOpsGetPullRequestsV1(); // act var result = _sut.CanMap(action); diff --git a/tests/RepoM.Plugin.AzureDevOps.Tests/ActionProvider/AzureDevOpsPullRequestsV1Test.cs b/tests/RepoM.Plugin.AzureDevOps.Tests/ActionProvider/AzureDevOpsGetPullRequestsV1Test.cs similarity index 90% rename from tests/RepoM.Plugin.AzureDevOps.Tests/ActionProvider/AzureDevOpsPullRequestsV1Test.cs rename to tests/RepoM.Plugin.AzureDevOps.Tests/ActionProvider/AzureDevOpsGetPullRequestsV1Test.cs index ad36beee..e2808562 100644 --- a/tests/RepoM.Plugin.AzureDevOps.Tests/ActionProvider/AzureDevOpsPullRequestsV1Test.cs +++ b/tests/RepoM.Plugin.AzureDevOps.Tests/ActionProvider/AzureDevOpsGetPullRequestsV1Test.cs @@ -11,6 +11,7 @@ namespace RepoM.Plugin.AzureDevOps.Tests.ActionProvider; using RepoM.Api.IO.ModuleBasedRepositoryActionProvider.Deserialization; using RepoM.Api.Tests.IO.ModuleBasedRepositoryActionProvider; using RepoM.Plugin.AzureDevOps.ActionProvider; +using RepoM.Plugin.AzureDevOps.ActionProvider.Options; using VerifyTests; using VerifyXunit; using Xunit; @@ -18,16 +19,16 @@ namespace RepoM.Plugin.AzureDevOps.Tests.ActionProvider; [UsesEasyTestFile] [UsesVerify] -public class AzureDevOpsPullRequestsV1Test +public class AzureDevOpsGetPullRequestsV1Test { private readonly JsonDynamicRepositoryActionDeserializer _sutJson; private readonly EasyTestFileSettings _testFileSettings; private readonly VerifySettings _verifySettings; private readonly YamlDynamicRepositoryActionDeserializer _sutYaml; - public AzureDevOpsPullRequestsV1Test() + public AzureDevOpsGetPullRequestsV1Test() { - _sutJson = CreateWithDeserializer(new ActionAzureDevOpsPullRequestsV1Deserializer()); + _sutJson = CreateWithDeserializer(new ActionAzureDevOpsGetPullRequestsV1Deserializer()); _sutYaml = new YamlDynamicRepositoryActionDeserializer(_sutJson); _testFileSettings = new EasyTestFileSettings(); @@ -63,7 +64,7 @@ public async Task Deserialize_ShouldBeOfExpectedType() RepositoryActionConfiguration result = SutDeserialize(content, SerializationType.Json); // assert - _ = result.ActionsCollection.Actions.Should().AllBeOfType(); + _ = result.ActionsCollection.Actions.Should().AllBeOfType(); } private RepositoryActionConfiguration SutDeserialize(string rawContent, SerializationType type) diff --git a/tests/RepoM.Plugin.AzureDevOps.Tests/ActionProvider/TestFiles/AzureDevOpsPullRequestsV1Test.Deserialize.testfile.json b/tests/RepoM.Plugin.AzureDevOps.Tests/ActionProvider/TestFiles/AzureDevOpsGetPullRequestsV1Test.Deserialize.testfile.json similarity index 100% rename from tests/RepoM.Plugin.AzureDevOps.Tests/ActionProvider/TestFiles/AzureDevOpsPullRequestsV1Test.Deserialize.testfile.json rename to tests/RepoM.Plugin.AzureDevOps.Tests/ActionProvider/TestFiles/AzureDevOpsGetPullRequestsV1Test.Deserialize.testfile.json diff --git a/tests/RepoM.Plugin.AzureDevOps.Tests/ActionProvider/TestFiles/AzureDevOpsPullRequestsV1Test.Deserialize.testfile.yaml b/tests/RepoM.Plugin.AzureDevOps.Tests/ActionProvider/TestFiles/AzureDevOpsGetPullRequestsV1Test.Deserialize.testfile.yaml similarity index 100% rename from tests/RepoM.Plugin.AzureDevOps.Tests/ActionProvider/TestFiles/AzureDevOpsPullRequestsV1Test.Deserialize.testfile.yaml rename to tests/RepoM.Plugin.AzureDevOps.Tests/ActionProvider/TestFiles/AzureDevOpsGetPullRequestsV1Test.Deserialize.testfile.yaml diff --git a/tests/RepoM.Plugin.AzureDevOps.Tests/ActionProvider/Verified/AzureDevOpsPullRequestsV1Test.Deserialize.verified.txt b/tests/RepoM.Plugin.AzureDevOps.Tests/ActionProvider/Verified/AzureDevOpsGetPullRequestsV1Test.Deserialize.verified.txt similarity index 69% rename from tests/RepoM.Plugin.AzureDevOps.Tests/ActionProvider/Verified/AzureDevOpsPullRequestsV1Test.Deserialize.verified.txt rename to tests/RepoM.Plugin.AzureDevOps.Tests/ActionProvider/Verified/AzureDevOpsGetPullRequestsV1Test.Deserialize.verified.txt index 72ee7f19..037a0480 100644 --- a/tests/RepoM.Plugin.AzureDevOps.Tests/ActionProvider/Verified/AzureDevOpsPullRequestsV1Test.Deserialize.verified.txt +++ b/tests/RepoM.Plugin.AzureDevOps.Tests/ActionProvider/Verified/AzureDevOpsGetPullRequestsV1Test.Deserialize.verified.txt @@ -3,20 +3,20 @@ ActionsCollection: { Actions: [ { - $type: RepositoryActionAzureDevOpsPullRequestsV1, - ProjectId: {var.AzureDevOpsProjectId}, + $type: RepositoryActionAzureDevOpsGetPullRequestsV1, RepoId: repo_id_guid, ShowWhenEmpty: true, + ProjectId: {var.AzureDevOpsProjectId}, Type: azure-devops-get-prs@1 }, { - $type: RepositoryActionAzureDevOpsPullRequestsV1, - ProjectId: abc, + $type: RepositoryActionAzureDevOpsGetPullRequestsV1, ShowWhenEmpty: false, + ProjectId: abc, Type: azure-devops-get-prs@1 }, { - $type: RepositoryActionAzureDevOpsPullRequestsV1, + $type: RepositoryActionAzureDevOpsGetPullRequestsV1, Type: azure-devops-get-prs@1, Active: false } diff --git a/tests/RepoM.Plugin.AzureDevOps.Tests/DocumentationFiles/AzureDevopsCreatePrs.testfile.yaml b/tests/RepoM.Plugin.AzureDevOps.Tests/DocumentationFiles/AzureDevopsCreatePrs.testfile.yaml new file mode 100644 index 00000000..f4df11b7 --- /dev/null +++ b/tests/RepoM.Plugin.AzureDevOps.Tests/DocumentationFiles/AzureDevopsCreatePrs.testfile.yaml @@ -0,0 +1,46 @@ +version: 1 + +# begin-snippet: RepositoryActionsAzureDevopsCreatePrs01 + +repository-actions: + actions: + # Create PR + - type: azure-devops-create-prs@1 + projectId: '' + toBranch: develop + reviewerIds: [ + "GUID" + ] + + # Create PR with auto-complete enabled + - type: azure-devops-create-prs@1 + projectId: '' + toBranch: develop + reviewerIds: [ + "GUID" + ] + autoComplete: { + enabled: true, + mergeStrategy: "Squash" + } + + # Create PR with all settings + - type: azure-devops-create-prs@1 + projectId: '' + title: 'Create PR' + prTitle: 'PR title' # if not provided it is generated based on the value after the /, for example: feature/testBranch. It then uses the last part of the branch name for the PR title, so "testBranch" in this example. + toBranch: develop + reviewerIds: [ + "GUID" + ] + draftPr: true + includeWorkItems: true + openInBrowser: false + autoComplete: { + enabled: true, + mergeStrategy: "NoFastForward", # You can choose from: "NoFastForward", "Squash", "Rebase" and "RebaseMerge" + deleteSourceBranch: true, + transitionWorkItems: true + } + +# end-snippet diff --git a/tests/RepoM.Plugin.AzureDevOps.Tests/DocumentationFilesVerified/DocumentationTests.Deserialize_Documentation_AzureDevopsCreatePrs.verified.txt b/tests/RepoM.Plugin.AzureDevOps.Tests/DocumentationFilesVerified/DocumentationTests.Deserialize_Documentation_AzureDevopsCreatePrs.verified.txt new file mode 100644 index 00000000..4f709f92 --- /dev/null +++ b/tests/RepoM.Plugin.AzureDevOps.Tests/DocumentationFilesVerified/DocumentationTests.Deserialize_Documentation_AzureDevopsCreatePrs.verified.txt @@ -0,0 +1,63 @@ +{ + TagsCollection: {}, + ActionsCollection: { + Actions: [ + { + $type: RepositoryActionAzureDevOpsCreatePullRequestsV1, + ToBranch: develop, + ReviewerIds: [ + GUID + ], + DraftPr: false, + IncludeWorkItems: true, + OpenInBrowser: false, + AutoComplete: { + Enabled: false, + MergeStrategy: NoFastForward, + DeleteSourceBranch: true, + TransitionWorkItems: true + }, + ProjectId: , + Type: azure-devops-create-prs@1 + }, + { + $type: RepositoryActionAzureDevOpsCreatePullRequestsV1, + ToBranch: develop, + ReviewerIds: [ + GUID + ], + DraftPr: false, + IncludeWorkItems: true, + OpenInBrowser: false, + AutoComplete: { + Enabled: true, + MergeStrategy: Squash, + DeleteSourceBranch: true, + TransitionWorkItems: true + }, + ProjectId: , + Type: azure-devops-create-prs@1 + }, + { + $type: RepositoryActionAzureDevOpsCreatePullRequestsV1, + Title: Create PR, + PrTitle: PR title, + ToBranch: develop, + ReviewerIds: [ + GUID + ], + DraftPr: true, + IncludeWorkItems: true, + OpenInBrowser: false, + AutoComplete: { + Enabled: true, + MergeStrategy: NoFastForward, + DeleteSourceBranch: true, + TransitionWorkItems: true + }, + ProjectId: , + Type: azure-devops-create-prs@1 + } + ] + } +} \ No newline at end of file diff --git a/tests/RepoM.Plugin.AzureDevOps.Tests/DocumentationFilesVerified/DocumentationTests.Deserialize_Documentation_AzureDevopsGetPrs.verified.txt b/tests/RepoM.Plugin.AzureDevOps.Tests/DocumentationFilesVerified/DocumentationTests.Deserialize_Documentation_AzureDevopsGetPrs.verified.txt index 68cedf1f..fe435f55 100644 --- a/tests/RepoM.Plugin.AzureDevOps.Tests/DocumentationFilesVerified/DocumentationTests.Deserialize_Documentation_AzureDevopsGetPrs.verified.txt +++ b/tests/RepoM.Plugin.AzureDevOps.Tests/DocumentationFilesVerified/DocumentationTests.Deserialize_Documentation_AzureDevopsGetPrs.verified.txt @@ -3,15 +3,15 @@ ActionsCollection: { Actions: [ { - $type: RepositoryActionAzureDevOpsPullRequestsV1, - ProjectId: , + $type: RepositoryActionAzureDevOpsGetPullRequestsV1, RepoId: , ShowWhenEmpty: true, + ProjectId: , Type: azure-devops-get-prs@1, Active: true }, { - $type: RepositoryActionAzureDevOpsPullRequestsV1, + $type: RepositoryActionAzureDevOpsGetPullRequestsV1, RepoId: , Type: azure-devops-get-prs@1 } diff --git a/tests/RepoM.Plugin.AzureDevOps.Tests/DocumentationTests.cs b/tests/RepoM.Plugin.AzureDevOps.Tests/DocumentationTests.cs index da10ea21..7e4d983f 100644 --- a/tests/RepoM.Plugin.AzureDevOps.Tests/DocumentationTests.cs +++ b/tests/RepoM.Plugin.AzureDevOps.Tests/DocumentationTests.cs @@ -1,4 +1,4 @@ -namespace RepoM.Plugin.Heidi.Tests; +namespace RepoM.Plugin.AzureDevOps.Tests; using System.Threading.Tasks; using EasyTestFile; @@ -31,6 +31,7 @@ public DocumentationTests() } [Theory] + [InlineData("AzureDevopsCreatePrs")] [InlineData("AzureDevopsGetPrs")] public async Task Deserialize_Documentation(string filename) { diff --git a/tests/RepoM.Plugin.AzureDevOps.Tests/RepoM.Plugin.AzureDevOps.Tests.csproj b/tests/RepoM.Plugin.AzureDevOps.Tests/RepoM.Plugin.AzureDevOps.Tests.csproj index 11dd511f..ee03d322 100644 --- a/tests/RepoM.Plugin.AzureDevOps.Tests/RepoM.Plugin.AzureDevOps.Tests.csproj +++ b/tests/RepoM.Plugin.AzureDevOps.Tests/RepoM.Plugin.AzureDevOps.Tests.csproj @@ -4,6 +4,10 @@ net7.0 + + + + all @@ -21,6 +25,7 @@ + diff --git a/tests/RepoM.Plugin.AzureDevOps.Tests/TestFramework/DynamicRepositoryActionDeserializerFactory.cs b/tests/RepoM.Plugin.AzureDevOps.Tests/TestFramework/DynamicRepositoryActionDeserializerFactory.cs index f87cf000..f4e2d616 100644 --- a/tests/RepoM.Plugin.AzureDevOps.Tests/TestFramework/DynamicRepositoryActionDeserializerFactory.cs +++ b/tests/RepoM.Plugin.AzureDevOps.Tests/TestFramework/DynamicRepositoryActionDeserializerFactory.cs @@ -13,7 +13,8 @@ public static JsonDynamicRepositoryActionDeserializer Create() new ActionDeserializerComposition( new IActionDeserializer[] { - new ActionAzureDevOpsPullRequestsV1Deserializer(), + new ActionAzureDevOpsCreatePullRequestsV1Deserializer(), + new ActionAzureDevOpsGetPullRequestsV1Deserializer(), })); } diff --git a/tests/RepoM.Plugin.AzureDevOps.Tests/TestFramework/VerifierInitializer.cs b/tests/RepoM.Plugin.AzureDevOps.Tests/TestFramework/VerifierInitializer.cs index 2303643f..940f7bbc 100644 --- a/tests/RepoM.Plugin.AzureDevOps.Tests/TestFramework/VerifierInitializer.cs +++ b/tests/RepoM.Plugin.AzureDevOps.Tests/TestFramework/VerifierInitializer.cs @@ -11,5 +11,6 @@ public static void Initialize() { VerifierSettings.DisableRequireUniquePrefix(); VerifierSettings.AddExtraSettings(serializerSettings => serializerSettings.TypeNameHandling = TypeNameHandling.Auto); + ClipboardAccept.Enable(); } } \ No newline at end of file diff --git a/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenNameSet_a b c2.verified.txt b/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenNameSet_a b c2.verified.txt index 2a0ea6ac..7f139e32 100644 --- a/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenNameSet_a b c2.verified.txt +++ b/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenNameSet_a b c2.verified.txt @@ -17,6 +17,7 @@ CurrentBranchIsOnTag: false, WasFound: true, HasUnpushedChanges: false, + HasLocalChanges: false, SafePath: dummy }, ExecutionCausesSynchronizing: false, diff --git a/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenNameSet_empty.verified.txt b/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenNameSet_empty.verified.txt index 64ec09a5..e5ced40c 100644 --- a/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenNameSet_empty.verified.txt +++ b/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenNameSet_empty.verified.txt @@ -17,6 +17,7 @@ CurrentBranchIsOnTag: false, WasFound: true, HasUnpushedChanges: false, + HasLocalChanges: false, SafePath: dummy }, ExecutionCausesSynchronizing: false, diff --git a/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenNameSet_null.verified.txt b/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenNameSet_null.verified.txt index 64ec09a5..e5ced40c 100644 --- a/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenNameSet_null.verified.txt +++ b/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenNameSet_null.verified.txt @@ -17,6 +17,7 @@ CurrentBranchIsOnTag: false, WasFound: true, HasUnpushedChanges: false, + HasLocalChanges: false, SafePath: dummy }, ExecutionCausesSynchronizing: false, diff --git a/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenNameSet_text12313.verified.txt b/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenNameSet_text12313.verified.txt index 94f0d644..d686cfc6 100644 --- a/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenNameSet_text12313.verified.txt +++ b/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenNameSet_text12313.verified.txt @@ -17,6 +17,7 @@ CurrentBranchIsOnTag: false, WasFound: true, HasUnpushedChanges: false, + HasLocalChanges: false, SafePath: dummy }, ExecutionCausesSynchronizing: false, diff --git a/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenTextSet_a b c2.verified.txt b/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenTextSet_a b c2.verified.txt index a48aae66..0d1bd627 100644 --- a/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenTextSet_a b c2.verified.txt +++ b/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenTextSet_a b c2.verified.txt @@ -17,6 +17,7 @@ CurrentBranchIsOnTag: false, WasFound: true, HasUnpushedChanges: false, + HasLocalChanges: false, SafePath: dummy }, ExecutionCausesSynchronizing: false, diff --git a/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenTextSet_empty.verified.txt b/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenTextSet_empty.verified.txt index 0704d52c..5e238c0a 100644 --- a/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenTextSet_empty.verified.txt +++ b/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenTextSet_empty.verified.txt @@ -17,6 +17,7 @@ CurrentBranchIsOnTag: false, WasFound: true, HasUnpushedChanges: false, + HasLocalChanges: false, SafePath: dummy }, ExecutionCausesSynchronizing: false, diff --git a/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenTextSet_null.verified.txt b/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenTextSet_null.verified.txt index 0704d52c..5e238c0a 100644 --- a/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenTextSet_null.verified.txt +++ b/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenTextSet_null.verified.txt @@ -17,6 +17,7 @@ CurrentBranchIsOnTag: false, WasFound: true, HasUnpushedChanges: false, + HasLocalChanges: false, SafePath: dummy }, ExecutionCausesSynchronizing: false, diff --git a/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenTextSet_text12313.verified.txt b/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenTextSet_text12313.verified.txt index a341bce9..a7e84ab7 100644 --- a/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenTextSet_text12313.verified.txt +++ b/tests/RepoM.Plugin.Clipboard.Tests/ActionProvider/Verified/ActionClipboardCopyV1MapperTests.Map_ShouldMap_WhenTextSet_text12313.verified.txt @@ -17,6 +17,7 @@ CurrentBranchIsOnTag: false, WasFound: true, HasUnpushedChanges: false, + HasLocalChanges: false, SafePath: dummy }, ExecutionCausesSynchronizing: false, diff --git a/tests/RepoM.Plugin.Clipboard.Tests/RepoM.Plugin.Clipboard.Tests.csproj b/tests/RepoM.Plugin.Clipboard.Tests/RepoM.Plugin.Clipboard.Tests.csproj index 13427964..120a3753 100644 --- a/tests/RepoM.Plugin.Clipboard.Tests/RepoM.Plugin.Clipboard.Tests.csproj +++ b/tests/RepoM.Plugin.Clipboard.Tests/RepoM.Plugin.Clipboard.Tests.csproj @@ -21,6 +21,7 @@ + diff --git a/tests/RepoM.Plugin.Clipboard.Tests/TestFramework/VerifierInitializer.cs b/tests/RepoM.Plugin.Clipboard.Tests/TestFramework/VerifierInitializer.cs index 3697e609..3c9e84a0 100644 --- a/tests/RepoM.Plugin.Clipboard.Tests/TestFramework/VerifierInitializer.cs +++ b/tests/RepoM.Plugin.Clipboard.Tests/TestFramework/VerifierInitializer.cs @@ -11,5 +11,6 @@ public static void Initialize() { VerifierSettings.DisableRequireUniquePrefix(); VerifierSettings.AddExtraSettings(serializerSettings => serializerSettings.TypeNameHandling = TypeNameHandling.Auto); + ClipboardAccept.Enable(); } } \ No newline at end of file diff --git a/tests/RepoM.Plugin.Heidi.Tests/ActionProvider/Verified/ActionHeidiDatabasesV1MapperTests.Map_ShouldReturnActions_WhenNameIsNullOrEmptyAndDatabasesFound.verified.txt b/tests/RepoM.Plugin.Heidi.Tests/ActionProvider/Verified/ActionHeidiDatabasesV1MapperTests.Map_ShouldReturnActions_WhenNameIsNullOrEmptyAndDatabasesFound.verified.txt index 1e4dbdd5..7aa06d6b 100644 --- a/tests/RepoM.Plugin.Heidi.Tests/ActionProvider/Verified/ActionHeidiDatabasesV1MapperTests.Map_ShouldReturnActions_WhenNameIsNullOrEmptyAndDatabasesFound.verified.txt +++ b/tests/RepoM.Plugin.Heidi.Tests/ActionProvider/Verified/ActionHeidiDatabasesV1MapperTests.Map_ShouldReturnActions_WhenNameIsNullOrEmptyAndDatabasesFound.verified.txt @@ -21,6 +21,7 @@ CurrentBranchIsOnTag: false, WasFound: true, HasUnpushedChanges: false, + HasLocalChanges: false, SafePath: dummy }, ExecutionCausesSynchronizing: false, @@ -48,6 +49,7 @@ CurrentBranchIsOnTag: false, WasFound: true, HasUnpushedChanges: false, + HasLocalChanges: false, SafePath: dummy }, ExecutionCausesSynchronizing: false, diff --git a/tests/RepoM.Plugin.Heidi.Tests/ActionProvider/Verified/ActionHeidiDatabasesV1MapperTests.Map_ShouldReturnFolderWithDatabaseActions_WhenNameIsSetAndDatabasesFound.verified.txt b/tests/RepoM.Plugin.Heidi.Tests/ActionProvider/Verified/ActionHeidiDatabasesV1MapperTests.Map_ShouldReturnFolderWithDatabaseActions_WhenNameIsSetAndDatabasesFound.verified.txt index 11abbf8d..d3a681c5 100644 --- a/tests/RepoM.Plugin.Heidi.Tests/ActionProvider/Verified/ActionHeidiDatabasesV1MapperTests.Map_ShouldReturnFolderWithDatabaseActions_WhenNameIsSetAndDatabasesFound.verified.txt +++ b/tests/RepoM.Plugin.Heidi.Tests/ActionProvider/Verified/ActionHeidiDatabasesV1MapperTests.Map_ShouldReturnFolderWithDatabaseActions_WhenNameIsSetAndDatabasesFound.verified.txt @@ -16,6 +16,7 @@ CurrentBranchIsOnTag: false, WasFound: true, HasUnpushedChanges: false, + HasLocalChanges: false, SafePath: dummy }, ExecutionCausesSynchronizing: false, @@ -44,6 +45,7 @@ CurrentBranchIsOnTag: false, WasFound: true, HasUnpushedChanges: false, + HasLocalChanges: false, SafePath: dummy }, ExecutionCausesSynchronizing: false, @@ -70,6 +72,7 @@ CurrentBranchIsOnTag: false, WasFound: true, HasUnpushedChanges: false, + HasLocalChanges: false, SafePath: dummy }, ExecutionCausesSynchronizing: false, diff --git a/tests/RepoM.Plugin.Heidi.Tests/ActionProvider/Verified/ActionHeidiDatabasesV1MapperTests.Map_ShouldReturnFolderWithNoDatabasesFoundAction_WhenNameIsSetAndNoDatabasesFound.verified.txt b/tests/RepoM.Plugin.Heidi.Tests/ActionProvider/Verified/ActionHeidiDatabasesV1MapperTests.Map_ShouldReturnFolderWithNoDatabasesFoundAction_WhenNameIsSetAndNoDatabasesFound.verified.txt index 0af004bc..719b2c52 100644 --- a/tests/RepoM.Plugin.Heidi.Tests/ActionProvider/Verified/ActionHeidiDatabasesV1MapperTests.Map_ShouldReturnFolderWithNoDatabasesFoundAction_WhenNameIsSetAndNoDatabasesFound.verified.txt +++ b/tests/RepoM.Plugin.Heidi.Tests/ActionProvider/Verified/ActionHeidiDatabasesV1MapperTests.Map_ShouldReturnFolderWithNoDatabasesFoundAction_WhenNameIsSetAndNoDatabasesFound.verified.txt @@ -16,6 +16,7 @@ CurrentBranchIsOnTag: false, WasFound: true, HasUnpushedChanges: false, + HasLocalChanges: false, SafePath: dummy }, ExecutionCausesSynchronizing: false, @@ -39,6 +40,7 @@ CurrentBranchIsOnTag: false, WasFound: true, HasUnpushedChanges: false, + HasLocalChanges: false, SafePath: dummy }, ExecutionCausesSynchronizing: false, diff --git a/tests/RepoM.Plugin.Heidi.Tests/ActionProvider/Verified/ActionHeidiDatabasesV1MapperTests.Map_ShouldReturnNoDatabasesFoundAction_WhenNameIsNullOrEmptyAndNoDatabasesFound.verified.txt b/tests/RepoM.Plugin.Heidi.Tests/ActionProvider/Verified/ActionHeidiDatabasesV1MapperTests.Map_ShouldReturnNoDatabasesFoundAction_WhenNameIsNullOrEmptyAndNoDatabasesFound.verified.txt index 278a86f5..cc9776f1 100644 --- a/tests/RepoM.Plugin.Heidi.Tests/ActionProvider/Verified/ActionHeidiDatabasesV1MapperTests.Map_ShouldReturnNoDatabasesFoundAction_WhenNameIsNullOrEmptyAndNoDatabasesFound.verified.txt +++ b/tests/RepoM.Plugin.Heidi.Tests/ActionProvider/Verified/ActionHeidiDatabasesV1MapperTests.Map_ShouldReturnNoDatabasesFoundAction_WhenNameIsNullOrEmptyAndNoDatabasesFound.verified.txt @@ -16,6 +16,7 @@ CurrentBranchIsOnTag: false, WasFound: true, HasUnpushedChanges: false, + HasLocalChanges: false, SafePath: dummy }, ExecutionCausesSynchronizing: false, diff --git a/tests/RepoM.Plugin.Heidi.Tests/RepoM.Plugin.Heidi.Tests.csproj b/tests/RepoM.Plugin.Heidi.Tests/RepoM.Plugin.Heidi.Tests.csproj index 506d09b2..3986bc85 100644 --- a/tests/RepoM.Plugin.Heidi.Tests/RepoM.Plugin.Heidi.Tests.csproj +++ b/tests/RepoM.Plugin.Heidi.Tests/RepoM.Plugin.Heidi.Tests.csproj @@ -21,6 +21,7 @@ + diff --git a/tests/RepoM.Plugin.Heidi.Tests/TestFramework/VerifierInitializer.cs b/tests/RepoM.Plugin.Heidi.Tests/TestFramework/VerifierInitializer.cs index aa6deea0..e45943f0 100644 --- a/tests/RepoM.Plugin.Heidi.Tests/TestFramework/VerifierInitializer.cs +++ b/tests/RepoM.Plugin.Heidi.Tests/TestFramework/VerifierInitializer.cs @@ -11,5 +11,6 @@ public static void Initialize() { VerifierSettings.DisableRequireUniquePrefix(); VerifierSettings.AddExtraSettings(serializerSettings => serializerSettings.TypeNameHandling = TypeNameHandling.Auto); + ClipboardAccept.Enable(); } } \ No newline at end of file