Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions docs/mdsource/_plugins.azuredevops.action.include.md
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
7 changes: 7 additions & 0 deletions src/RepoM.Api/Git/Repository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
Expand Down
Original file line number Diff line number Diff line change
@@ -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<RepositoryActionAzureDevOpsCreatePullRequestsV1>(jsonSerializer);
}
}
Original file line number Diff line number Diff line change
@@ -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<RepositoryActionBase> Map(RepositoryAction action, IEnumerable<Api.Git.Repository> repository, ActionMapperComposition actionMapperComposition)
{
return Map(action as RepositoryActionAzureDevOpsCreatePullRequestsV1, repository.First());
}

private IEnumerable<Api.Git.RepositoryAction> Map(RepositoryActionAzureDevOpsCreatePullRequestsV1? action, Api.Git.Repository repository)
{
if (action == null)
{
return Array.Empty<Api.Git.RepositoryAction>();
}

if (!_expressionEvaluator.EvaluateBooleanExpression(action.Active, repository))
{
return Array.Empty<Api.Git.RepositoryAction>();
}

if (repository.HasLocalChanges || repository.CurrentBranch.Equals(action.ToBranch, StringComparison.OrdinalIgnoreCase))
{
return Array.Empty<Api.Git.RepositoryAction>();
}

if (string.IsNullOrWhiteSpace(action.ProjectId))
{
return Array.Empty<Api.Git.RepositoryAction>();
}

var projectId = _expressionEvaluator.EvaluateStringExpression(action.ProjectId, repository);

if (string.IsNullOrWhiteSpace(projectId))
{
return Array.Empty<Api.Git.RepositoryAction>();
}

if (!string.IsNullOrWhiteSpace(action.ToBranch))
{
// check if branch exists!
if (!repository.Branches.Contains(action.ToBranch))
{
return Array.Empty<Api.Git.RepositoryAction>();
}
}
else
{
return Array.Empty<Api.Git.RepositoryAction>();
}

return new List<Api.Git.RepositoryAction>()
{
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();
}
}),
},
};
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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<RepositoryActionAzureDevOpsPullRequestsV1>(jsonSerializer);
RepositoryActionAzureDevOpsGetPullRequestsV1? result = jToken.ToObject<RepositoryActionAzureDevOpsGetPullRequestsV1>(jsonSerializer);

JToken? showWhenEmpty = jToken["show-when-empty"];
if (showWhenEmpty != null && result != null)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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));
Expand All @@ -30,7 +31,7 @@ public ActionAzureDevOpsPullRequestsV1Mapper(IAzureDevOpsPullRequestService serv

public bool CanMap(RepositoryAction action)
{
return action is RepositoryActionAzureDevOpsPullRequestsV1;
return action is RepositoryActionAzureDevOpsGetPullRequestsV1;
}

public bool CanHandleMultipleRepositories()
Expand All @@ -40,10 +41,10 @@ public bool CanHandleMultipleRepositories()

public IEnumerable<RepositoryActionBase> Map(RepositoryAction action, IEnumerable<Repository> 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)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -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; }
}
Original file line number Diff line number Diff line change
@@ -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<string> 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
}
Original file line number Diff line number Diff line change
@@ -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; }
}
7 changes: 5 additions & 2 deletions src/RepoM.Plugin.AzureDevOps/AzureDevOpsPackage.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
namespace RepoM.Plugin.AzureDevOps;

using System.Net.Http;
using JetBrains.Annotations;
using RepoM.Api.IO.ModuleBasedRepositoryActionProvider;
using RepoM.Core.Plugin;
Expand All @@ -15,8 +16,10 @@ public class AzureDevOpsPackage : IPackage
{
public void RegisterServices(Container container)
{
container.Collection.Append<IActionDeserializer, ActionAzureDevOpsPullRequestsV1Deserializer>(Lifestyle.Singleton);
container.Collection.Append<IActionToRepositoryActionMapper, ActionAzureDevOpsPullRequestsV1Mapper>(Lifestyle.Singleton);
container.Collection.Append<IActionDeserializer, ActionAzureDevOpsCreatePullRequestsV1Deserializer>(Lifestyle.Singleton);
container.Collection.Append<IActionDeserializer, ActionAzureDevOpsGetPullRequestsV1Deserializer>(Lifestyle.Singleton);
container.Collection.Append<IActionToRepositoryActionMapper, ActionAzureDevOpsCreatePullRequestsV1Mapper>(Lifestyle.Singleton);
container.Collection.Append<IActionToRepositoryActionMapper, ActionAzureDevOpsGetPullRequestsV1Mapper>(Lifestyle.Singleton);

container.Register<IAzureDevOpsPullRequestService, AzureDevOpsPullRequestService>(Lifestyle.Singleton);

Expand Down
Loading