Skip to content
Open
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
12 changes: 9 additions & 3 deletions docs/RepoM.Plugin.AzureDevOps.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,13 @@ The following default configuration is used:

```json
{
"Version": 1,
"Version": 2,
"Settings": {
"PersonalAccessToken": null,
"BaseUrl": null
"BaseUrl": null,
"DefaultProjectId": null,
"IntervalUpdatePullRequests": "00:04:00",
"IntervalUpdateProjects": "00:10:00"
}
}
```
Expand All @@ -24,7 +27,10 @@ Properties:

- `PersonalAccessToken`: Personal access token (PAT) to access Azure Devops. The PAT should be granted access to `todo` rights.
To create a PAT, goto `https://dev.azure.com/[my-organisation]/_usersSettings/tokens`.
- `BaseUrl`: The base url of azure devops for your organisation (ie. `https://dev.azure.com/[my-organisation]/`).<!-- endInclude -->
- `BaseUrl`: The base url of azure devops for your organisation (ie. `https://dev.azure.com/[my-organisation]/`).
- `DefaultProjectId`: Default project id to use when no project id provided in the repository action. Should be a GUID.
- `IntervalUpdatePullRequests`: Interval RepoM should update the list of open pull requests from Azure DevOps. Defaults to `4` minutes (ie. `00:04:00`).
- `IntervalUpdateProjects`: Interval RepoM should update the list of projects from Azure DevOps. Defaults to `10` minutes (ie. `00:10:00`).<!-- endInclude -->

## azure-devops-create-prs@1<!-- include: _plugins.azuredevops.action. path: /docs/mdsource/_plugins.azuredevops.action.include.md -->

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
namespace RepoM.Plugin.AzureDevOps.ActionProvider;

using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using Microsoft.Extensions.Logging;
using RepoM.Api.Git;
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 ActionAzureDevOpsCreatePullRequestsV2Mapper : IActionToRepositoryActionMapper
{
private readonly IAzureDevOpsPullRequestService _service;
private readonly IRepositoryExpressionEvaluator _expressionEvaluator;
private readonly ILogger _logger;

public ActionAzureDevOpsCreatePullRequestsV2Mapper(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 RepositoryActionAzureDevOpsCreatePullRequestsV2;
}

public bool CanHandleMultipleRepositories()
{
return false;
}

public IEnumerable<RepositoryActionBase> Map(RepositoryAction action, IEnumerable<Repository> repository, ActionMapperComposition actionMapperComposition)
{
return Map(action as RepositoryActionAzureDevOpsCreatePullRequestsV2, repository.First());
}

private IEnumerable<Api.Git.RepositoryAction> Map(RepositoryActionAzureDevOpsCreatePullRequestsV2? action, 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))
{
_logger.LogInformation("Branch {branch} does not exist", action.ToBranch);
return Array.Empty<Api.Git.RepositoryAction>();
}
}
else
{
return Array.Empty<Api.Git.RepositoryAction>();
}

bool hasAutoComplete = action.AutoComplete != null;
bool isDraft = _expressionEvaluator.EvaluateBooleanExpression(action.IsDraft, repository); // not oke
bool includeWorkItems = _expressionEvaluator.EvaluateBooleanExpression(action.IncludeWorkItems, repository); // not oke
bool openInBrowser = _expressionEvaluator.EvaluateBooleanExpression(action.OpenInBrowser, repository); // not oke

return new List<Api.Git.RepositoryAction>()
{
new(action.Name ?? $"Create Pull Request {(hasAutoComplete ? "(with auto-complete)" : string.Empty)}", repository)
{
Action = new DelegateAction((_, _) =>
{
if (hasAutoComplete)
{
_service.CreatePullRequestWithAutoCompleteAsync(repository, projectId, action.ReviewerIds, action.ToBranch, (int)action.AutoComplete.MergeStrategy, action.PrTitle, isDraft, includeWorkItems, openInBrowser, action.AutoComplete.DeleteSourceBranch, action.AutoComplete.TransitionWorkItems).GetAwaiter().GetResult();
}
else
{
_service.CreatePullRequestAsync(repository, projectId, action.ReviewerIds, action.ToBranch, action.PrTitle, isDraft, isDraft, openInBrowser).GetAwaiter().GetResult();
}
}),
},
};
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
namespace RepoM.Plugin.AzureDevOps.ActionProvider.Options;

/// <summary>
/// Merge strategies Azure Devops supports.
/// </summary>
public enum RepositoryActionAzureDevOpsCreatePullRequestsMergeStrategy
{
/// <summary>
/// A two-parent, no-fast-forward merge. The source branch is unchanged. This is the default behavior.
/// </summary>
NoFastForward = 1,

/// <summary>
/// Put all changes from the pull request into a single-parent commit.
/// </summary>
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.
/// </summary>
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.
/// </summary>
RebaseMerge,
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,9 +109,9 @@ public class RepositoryActionAzureDevOpsCreatePullRequestsAutoCompleteOptionsV1
/// The merge strategy. Possible values are `NoFastForward`, `Squash` and `Rebase`, and `RebaseMerge`.
/// </summary>
[Required]
[PropertyType(typeof(RepositoryActionAzureDevOpsCreatePullRequestsMergeStrategyV1))]
[PropertyDefaultTypedValueAttribute<RepositoryActionAzureDevOpsCreatePullRequestsMergeStrategyV1>(RepositoryActionAzureDevOpsCreatePullRequestsMergeStrategyV1.NoFastForward)]
public RepositoryActionAzureDevOpsCreatePullRequestsMergeStrategyV1 MergeStrategy { get; set; } = RepositoryActionAzureDevOpsCreatePullRequestsMergeStrategyV1.NoFastForward;
[PropertyType(typeof(RepositoryActionAzureDevOpsCreatePullRequestsMergeStrategy))]
[PropertyDefaultTypedValueAttribute<RepositoryActionAzureDevOpsCreatePullRequestsMergeStrategy>(RepositoryActionAzureDevOpsCreatePullRequestsMergeStrategy.NoFastForward)]
public RepositoryActionAzureDevOpsCreatePullRequestsMergeStrategy MergeStrategy { get; set; } = RepositoryActionAzureDevOpsCreatePullRequestsMergeStrategy.NoFastForward;

/// <summary>
/// Boolean specifying if the source branche should be deleted afer completion.
Expand All @@ -128,29 +128,4 @@ public class RepositoryActionAzureDevOpsCreatePullRequestsAutoCompleteOptionsV1
[PropertyType(typeof(bool))]
[PropertyDefaultBoolValue(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.
/// </summary>
NoFastForward = 1,

/// <summary>
/// Put all changes from the pull request into a single-parent commit.
/// </summary>
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.
/// </summary>
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.
/// </summary>
RebaseMerge,
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
namespace RepoM.Plugin.AzureDevOps.ActionProvider.Options;

using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using RepoM.Api.IO.ModuleBasedRepositoryActionProvider.Data;

/// <summary>
/// Action menu item to create a pull request in Azure Devops.
/// </summary>
[RepositoryAction(TYPE)]
public sealed class RepositoryActionAzureDevOpsCreatePullRequestsV2 : RepositoryAction
{
/// <summary>
/// RepositoryAction type.
/// </summary>
public const string TYPE = "azure-devops-create-prs@2";

/// <summary>
/// The azure devops project id. If none given, the default project id, set in the plugin configuration, will be used. At least one is required.
/// </summary>
[EvaluatedProperty]
[PropertyType(typeof(string))]
public string? ProjectId { get; set; }

/// <summary>
/// Pull Request title. When not provided, the title will be defined based on the branch name.
/// Title will be the last part of the branchname split on `/`, so `feature/123-testBranch` will result in title `123-testBranch`
/// </summary>
[EvaluatedProperty]
[PropertyType(typeof(string))]
public string? Title { get; set; }

/// <summary>
/// Name of the branch the pull request should be merged into. For instance `develop`, or `main`.
/// </summary>
[Required]
[EvaluatedProperty]
[PropertyType(typeof(string))]
public string? ToBranch { get; set; } = string.Empty;

/// <summary>
/// Required reviewers. An id should be a valid Azure DevOps user id (ie. GUID), or email address known in Azure Devops.
/// </summary>
[EvaluatedProperty]
[PropertyType(typeof(string))]
public string? Reviewer { get; set; }

/// <summary>
/// List of required reviewers. An id should be a valid Azure DevOps user id (ie. GUID), or email address known in Azure Devops.
/// </summary>
[EvaluatedProperty]
[PropertyType(typeof(List<string>))]
public List<string> Reviewers { get; set; } = new();

/// <summary>
/// Boolean specifying if th PR should be marked as draft.
/// </summary>
[Required]
[EvaluatedProperty]
[PropertyType(typeof(bool))]
[PropertyDefaultBoolValue(false)]
public string? IsDraft { get; set; } = "false";

/// <summary>
/// Boolean specifying if workitems should be included in the PR. The workitems will be found by using the commit messages.
/// </summary>
[Required]
[EvaluatedProperty]
[PropertyType(typeof(bool))]
[PropertyDefaultBoolValue(true)]
public string? IncludeWorkItems { get; set; } = "true";

/// <summary>
/// Boolean specifying if the Pull request should be opened in the browser after creation.
/// </summary>
[Required]
[EvaluatedProperty]
[PropertyType(typeof(bool))]
[PropertyDefaultBoolValue(default)]
public string? OpenInBrowser { get; set; }

/// <summary>
/// Auto complete options for the pull request. If not set, autocomplete will be `off`.
/// </summary>
[PropertyType(typeof(RepositoryActionAzureDevOpsCreatePullRequestsAutoCompleteOptionsV2))]
public RepositoryActionAzureDevOpsCreatePullRequestsAutoCompleteOptionsV2? AutoComplete { get; set; }
}

/// <summary>
/// Auto complete options.
/// </summary>
public class RepositoryActionAzureDevOpsCreatePullRequestsAutoCompleteOptionsV2
{
/// <summary>
/// The merge strategy. Possible values are `NoFastForward`, `Squash` and `Rebase`, and `RebaseMerge`.
/// </summary>
[PropertyType(typeof(RepositoryActionAzureDevOpsCreatePullRequestsMergeStrategy))]
[PropertyDefaultTypedValueAttribute<RepositoryActionAzureDevOpsCreatePullRequestsMergeStrategy>(RepositoryActionAzureDevOpsCreatePullRequestsMergeStrategy.NoFastForward)]
public RepositoryActionAzureDevOpsCreatePullRequestsMergeStrategy MergeStrategy { get; set; } = RepositoryActionAzureDevOpsCreatePullRequestsMergeStrategy.NoFastForward;

/// <summary>
/// Boolean specifying if the source branche should be deleted afer completion.
/// </summary>
[EvaluatedProperty]
[PropertyType(typeof(bool))]
[PropertyDefaultBoolValue(true)]
public string? DeleteSourceBranch { get; set; } = "true";

/// <summary>
/// Boolean specifying if related workitems should be transitioned to the next state.
/// </summary>
[EvaluatedProperty]
[PropertyType(typeof(bool))]
[PropertyDefaultBoolValue(true)]
public string? TransitionWorkItems { get; set; } = "true";
}
Loading
Loading