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
7 changes: 3 additions & 4 deletions Apps.GitLab/Actions/BranchActions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
using Blackbird.Applications.Sdk.Common;
using Blackbird.Applications.Sdk.Common.Actions;
using Blackbird.Applications.Sdk.Common.Invocation;
using Blackbird.Applications.SDK.Extensions.FileManagement.Interfaces;
using GitLabApiClient.Models.Branches.Responses;
using RestSharp;

Expand All @@ -17,7 +16,7 @@ public class BranchActions(InvocationContext invocationContext)
: GitLabActions(invocationContext)
{

[Action("List branches", Description = "List respository branches")]
[Action("Search branches", Description = "Search repository branches")]
public async Task<ListRepositoryBranchesResponse> ListRepositoryBranches([ActionParameter] GetRepositoryRequest input)
{
var projectId = ParseProjectId(input.RepositoryId);
Expand All @@ -30,7 +29,7 @@ public async Task<ListRepositoryBranchesResponse> ListRepositoryBranches([Action
};
}

[Action("Get branch", Description = "Get branch by name")]
[Action("Get branch", Description = "Get branch details by name")]
public async Task<BranchDto> GetBranch(
[ActionParameter] GetRepositoryRequest repositoryRequest,
[ActionParameter] GetBranchRequest input)
Expand All @@ -44,7 +43,7 @@ public async Task<BranchDto> GetBranch(
return new BranchDto(branch);
}

[Action("Create branch", Description = "Create branch")]
[Action("Create branch", Description = "Create branch from a base branch")]
public async Task<BranchDto> CreateBranch(
[ActionParameter] GetRepositoryRequest repositoryRequest,
[ActionParameter] Models.Branch.Requests.CreateBranchRequest input)
Expand Down
176 changes: 162 additions & 14 deletions Apps.GitLab/Actions/CommitActions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ namespace Apps.Gitlab.Actions;
[ActionList("Commit")]
public class CommitActions : GitLabActions
{
private const int CommitsPageSize = 100;
private readonly IFileManagementClient _fileManagementClient;

public CommitActions(InvocationContext invocationContext, IFileManagementClient fileManagementClient)
Expand All @@ -30,26 +31,124 @@ public CommitActions(InvocationContext invocationContext, IFileManagementClient
_fileManagementClient = fileManagementClient;
}

[Action("List commits", Description = "List respository commits")]
[Action("Search commits", Description = "Search commits in a repository")]
public async Task<ListRepositoryCommitsResponse> ListRepositoryCommits(
[ActionParameter] GetRepositoryRequest repositoryRequest,
[ActionParameter] GetOptionalBranchRequest branchRequest)
[ActionParameter] GetOptionalBranchRequest branchRequest,
[ActionParameter] ListCommitsRequest searchRequest)
{
var commits = await SearchRepositoryCommits(repositoryRequest, branchRequest, searchRequest);

return new()
{
Count = commits.Count,
Commits = commits.Select(commit => new CommitResponse(commit))
};
}

[Action("Find commit", Description = "Find first commit that matches search filters in a repository")]
public async Task<CommitResponse> FindCommit(
[ActionParameter] GetRepositoryRequest repositoryRequest,
[ActionParameter] GetOptionalBranchRequest branchRequest,
[ActionParameter] SearchCommitsRequest searchRequest)
{
var commit = await FindRepositoryCommit(repositoryRequest, branchRequest, searchRequest)
?? throw new PluginApplicationException("No matching commit was found.");

return new(commit);
}

private async Task<List<Commit>> SearchRepositoryCommits(
GetRepositoryRequest repositoryRequest,
GetOptionalBranchRequest branchRequest,
ListCommitsRequest searchRequest)
{
var projectId = ParseProjectId(repositoryRequest.RepositoryId);
var includedAuthors = NormalizeFilterValues(searchRequest.AuthorsToInclude).ToList();
var maximumResults = GetMaximumResults(searchRequest);
var commits = new List<Commit>();
var page = 1;

while (true)
{
var pageCommits = await GetRepositoryCommitsPage(projectId, branchRequest, searchRequest, includedAuthors, page);
if (pageCommits.Count == 0)
break;

var matchingCommits = FilterCommits(pageCommits, searchRequest, includedAuthors)
.Take(maximumResults - commits.Count)
.ToList();

commits.AddRange(matchingCommits);
if (commits.Count >= maximumResults)
break;

if (pageCommits.Count < CommitsPageSize)
break;

page++;
}

return commits;
}

private async Task<Commit?> FindRepositoryCommit(
GetRepositoryRequest repositoryRequest,
GetOptionalBranchRequest branchRequest,
SearchCommitsRequest searchRequest)
{
var projectId = ParseProjectId(repositoryRequest.RepositoryId);
var includedAuthors = NormalizeFilterValues(searchRequest.AuthorsToInclude).ToList();
var page = 1;

while (true)
{
var pageCommits = await GetRepositoryCommitsPage(projectId, branchRequest, searchRequest, includedAuthors, page);
if (pageCommits.Count == 0)
return null;

var commit = FilterCommits(pageCommits, searchRequest, includedAuthors).FirstOrDefault();
if (commit is not null)
return commit;

if (pageCommits.Count < CommitsPageSize)
return null;

page++;
}
}

private async Task<List<Commit>> GetRepositoryCommitsPage(
int projectId,
GetOptionalBranchRequest branchRequest,
SearchCommitsRequest searchRequest,
IReadOnlyCollection<string> includedAuthors,
int page)
{
var request = RestClient.CreateRequest($"/projects/{projectId}/repository/commits", Method.Get);
request.AddQueryParameter("per_page", CommitsPageSize.ToString());
request.AddQueryParameter("page", page.ToString());

if (!string.IsNullOrWhiteSpace(branchRequest.Name))
request.AddQueryParameter("ref_name", branchRequest.Name);

var commits = await RestClient.ExecuteWithErrorHandling<List<Commit>>(request);
return new()
{
Commits = commits
};
if (searchRequest.CommitAfter.HasValue)
request.AddQueryParameter("since", FormatGitLabDate(searchRequest.CommitAfter.Value));

if (searchRequest.CommitBefore.HasValue)
request.AddQueryParameter("until", FormatGitLabDate(searchRequest.CommitBefore.Value));

if (!string.IsNullOrWhiteSpace(searchRequest.FilePath))
request.AddQueryParameter("path", searchRequest.FilePath);

if (includedAuthors.Count == 1)
request.AddQueryParameter("author", includedAuthors.First());

return await RestClient.ExecuteWithErrorHandling<List<Commit>>(request);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

[Action("Get commit", Description = "Get commit by id")]
public async Task<Commit> GetCommit(
[Action("Get commit", Description = "Get commit details by commit ID")]
public async Task<CommitResponse> GetCommit(
[ActionParameter] GetRepositoryRequest repositoryRequest,
[ActionParameter] GetCommitRequest input)
{
Expand All @@ -58,10 +157,10 @@ public async Task<Commit> GetCommit(
$"/projects/{projectId}/repository/commits/{Uri.EscapeDataString(input.CommitId)}",
Method.Get);

return await RestClient.ExecuteWithErrorHandling<Commit>(request);
return new(await RestClient.ExecuteWithErrorHandling<Commit>(request));
}

[Action("List added or modified files in X hours", Description = "List added or modified files in X hours")]
[Action("Search added or modified files in X hours", Description = "Search files added or modified during specified number of hours")]
public async Task<ListAddedOrModifiedInHoursResponse> ListAddedOrModifiedInHours(
[ActionParameter] GetRepositoryRequest repositoryRequest,
[ActionParameter] GetOptionalBranchRequest branchRequest,
Expand Down Expand Up @@ -103,7 +202,7 @@ public async Task<ListAddedOrModifiedInHoursResponse> ListAddedOrModifiedInHours
};
}

[Action("Create or update file", Description = "Create or update file")]
[Action("Create or update file", Description = "Create file or update existing file in a repository")]
public async Task<CommitDto> PushFile(
[ActionParameter] GetRepositoryRequest repositoryRequest,
[ActionParameter] GetOptionalBranchRequest branchRequest,
Expand Down Expand Up @@ -142,7 +241,7 @@ public async Task<CommitDto> PushFile(
return new(pushFileResult);
}

[Action("Update file", Description = "Update file in repository")]
[Action("Update file", Description = "Update existing file in a repository")]
public async Task<CommitDto> UpdateFile(
[ActionParameter] GetRepositoryRequest repositoryRequest,
[ActionParameter] GetOptionalBranchRequest branchRequest,
Expand All @@ -166,7 +265,7 @@ public async Task<CommitDto> UpdateFile(
return new(fileUpload);
}

[Action("Delete file", Description = "Delete file from repository")]
[Action("Delete file", Description = "Delete file from a repository")]
public async Task<DeleteFileResponse> DeleteFile(
[ActionParameter] GetRepositoryRequest repositoryRequest,
[ActionParameter] GetOptionalBranchRequest branchRequest,
Expand All @@ -183,4 +282,53 @@ public async Task<DeleteFileResponse> DeleteFile(
Message = fileDelete.Message
};
}

private static IEnumerable<Commit> FilterCommits(
IEnumerable<Commit> commits,
SearchCommitsRequest searchRequest,
IReadOnlyCollection<string> includedAuthors)
{
var excludedAuthors = NormalizeFilterValues(searchRequest.AuthorsToExclude).ToList();
var messageFilter = searchRequest.CommitMessageContains?.Trim();

return commits
.Where(commit => !searchRequest.CommitAfter.HasValue ||
commit.CreatedAt.ToUniversalTime() > searchRequest.CommitAfter.Value.ToUniversalTime())
.Where(commit => !searchRequest.CommitBefore.HasValue ||
commit.CreatedAt.ToUniversalTime() < searchRequest.CommitBefore.Value.ToUniversalTime())
.Where(commit => includedAuthors.Count == 0 || AuthorMatches(commit, includedAuthors))
.Where(commit => excludedAuthors.Count == 0 || !AuthorMatches(commit, excludedAuthors))
.Where(commit => string.IsNullOrWhiteSpace(messageFilter) || CommitMessageMatches(commit, messageFilter));
}

private static IEnumerable<string> NormalizeFilterValues(IEnumerable<string>? values)
=> values?
.Where(value => !string.IsNullOrWhiteSpace(value))
.Select(value => value.Trim())
?? Enumerable.Empty<string>();

private static int GetMaximumResults(ListCommitsRequest searchRequest)
{
var maximumResults = searchRequest.MaximumResults ?? 100;
if (maximumResults <= 0)
throw new PluginMisconfigurationException("Maximum results must be greater than 0.");

return maximumResults;
}

private static bool AuthorMatches(Commit commit, IEnumerable<string> authors)
=> authors.Any(author =>
ContainsIgnoreCase(commit.AuthorName, author) ||
ContainsIgnoreCase(commit.AuthorEmail, author));

private static bool CommitMessageMatches(Commit commit, string messageFilter)
=> ContainsIgnoreCase(commit.Message, messageFilter) ||
ContainsIgnoreCase(commit.Title, messageFilter);

private static bool ContainsIgnoreCase(string? value, string searchValue)
=> !string.IsNullOrWhiteSpace(value) &&
value.Contains(searchValue, StringComparison.OrdinalIgnoreCase);

private static string FormatGitLabDate(DateTime date)
=> date.ToUniversalTime().ToString("O");
}
8 changes: 4 additions & 4 deletions Apps.GitLab/Actions/PullRequestActions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,12 @@

namespace Apps.Gitlab.Actions;

[ActionList("Pull request")]
[ActionList("Merge request")]
public class PullRequestActions(InvocationContext invocationContext)
: GitLabActions(invocationContext)
{

[Action("List merge requests", Description = "List merge requests")]
[Action("Search merge requests", Description = "Search merge requests in a repository")]
public async Task<ListPullRequestsResponse> ListPullRequests(
[ActionParameter] GetRepositoryRequest repositoryRequest)
{
Expand All @@ -30,7 +30,7 @@ public async Task<ListPullRequestsResponse> ListPullRequests(
};
}

[Action("Get merge request", Description = "Get merge request")]
[Action("Get merge request", Description = "Get merge request details")]
public async Task<MergeRequest> GetPullRequest(
[ActionParameter] GetRepositoryRequest repositoryRequest,
[ActionParameter] GetPullRequest input)
Expand Down Expand Up @@ -62,7 +62,7 @@ public async Task<MergeRequest> CreatePullRequest(
return await RestClient.ExecuteWithErrorHandling<MergeRequest>(request);
}

[Action("Complete merge request", Description = "Complete merge request")]
[Action("Complete merge request", Description = "Complete merge request by merging it")]
public async Task<MergeRequest> MergePullRequest(
[ActionParameter] GetRepositoryRequest repositoryRequest,
[ActionParameter] GetPullRequest mergeRequest,
Expand Down
20 changes: 10 additions & 10 deletions Apps.GitLab/Actions/RepositoryActions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public RepositoryActions(InvocationContext invocationContext, IFileManagementCli
_fileManagementClient = fileManagementClient;
}

[Action("Create new repository", Description = "Create new repository")]
[Action("Create new repository", Description = "Create repository with selected settings")]
public async Task<RepositoryResponse> CreateRepository([ActionParameter] CreateRepositoryInput input)
{
var endpoint = "/projects";
Expand All @@ -50,7 +50,7 @@ public async Task<RepositoryResponse> CreateRepository([ActionParameter] CreateR
return RepositoryResponse.FromProject(project);
}

[Action("Get repository file", Description = "Get repository file by path")]
[Action("Get repository file", Description = "Get file from a repository by file path")]
public async Task<GetFileResponse> GetFile(
[ActionParameter] GetRepositoryRequest repositoryRequest,
[ActionParameter] GetOptionalBranchRequest branchRequest,
Expand Down Expand Up @@ -87,7 +87,7 @@ public async Task<GetFileResponse> GetFile(
};
}

[Action("Get all files in folder", Description = "Get all files in folder")]
[Action("Get all files in folder", Description = "Get files from a repository folder")]
Comment thread
coderabbitai[bot] marked this conversation as resolved.
public async Task<GetRepositoryFilesFromFilepathsResponse> GetAllFilesInFolder(
[ActionParameter] GetRepositoryRequest repositoryRequest,
[ActionParameter] GetOptionalBranchRequest branchRequest,
Expand Down Expand Up @@ -143,14 +143,14 @@ public async Task<GetRepositoryFilesFromFilepathsResponse> GetAllFilesInFolder(
return new GetRepositoryFilesFromFilepathsResponse { Files = resultFiles };
}

[Action("Get repository", Description = "Get repository info")]
[Action("Get repository", Description = "Get repository details")]
public async Task<RepositoryResponse> GetRepositoryById([ActionParameter] GetRepositoryRequest input)
{
var project = await GetProject(ParseProjectId(input.RepositoryId));
return RepositoryResponse.FromProject(project);
}

[Action("Get repository issues", Description = "Get opened issues against repository")]
[Action("Search repository issues", Description = "Get open issues in a repository")]
public async Task<GetIssuesResponse> GetIssuesInRepository([ActionParameter] RepositoryRequest input)
{
var projectId = ParseProjectId(input.RepositoryId);
Expand All @@ -163,7 +163,7 @@ public async Task<GetIssuesResponse> GetIssuesInRepository([ActionParameter] Rep
};
}

[Action("Get repository merge requests", Description = "Get opened merge requests in a repository")]
[Action("Search repository merge requests", Description = "Get open merge requests in a repository")]
public async Task<GetPullRequestsResponse> GetPullRequestsInRepository([ActionParameter] RepositoryRequest input)
{
var projectId = ParseProjectId(input.RepositoryId);
Expand All @@ -176,7 +176,7 @@ public async Task<GetPullRequestsResponse> GetPullRequestsInRepository([ActionPa
};
}

[Action("List repository folder content", Description = "List repository folder content")]
[Action("Search repository folder content", Description = "Search folder content in a repository")]
public async Task<RepositoryContentResponse> ListRepositoryContent(
[ActionParameter] GetRepositoryRequest repositoryRequest,
[ActionParameter] GetOptionalBranchRequest branchRequest,
Expand All @@ -200,7 +200,7 @@ public async Task<RepositoryContentResponse> ListRepositoryContent(
};
}

[Action("List repositories", Description = "List all repositories")]
[Action("Search repositories", Description = "Search repositories available to connection")]
public async Task<ListRepositoriesResponse> ListRepositories()
{
var request = RestClient.CreateRequest("/projects", Method.Get);
Expand All @@ -210,7 +210,7 @@ public async Task<ListRepositoriesResponse> ListRepositories()
return new(projects.ToArray());
}

[Action("Get files by filepaths", Description = "Get files by filepaths from webhooks")]
[Action("Search files by filepaths", Description = "Get files from a repository by file paths")]
public async Task<GetRepositoryFilesFromFilepathsResponse> GetRepositoryFilesFromFilepaths(
[ActionParameter] GetRepositoryRequest repositoryRequest,
[ActionParameter] GetOptionalBranchRequest branchRequest,
Expand All @@ -237,7 +237,7 @@ public async Task<GetRepositoryFilesFromFilepathsResponse> GetRepositoryFilesFro
};
}

[Action("Branch exists", Description = "Branch exists in specified repository")]
[Action("Check if branch exists", Description = "Check whether branch exists in a repository")]
public async Task<bool> BranchExists(
[ActionParameter] GetRepositoryRequest repositoryRequest,
[ActionParameter][Display("Branch name")] string branchNameRequest)
Expand Down
Loading