Skip to content

Commit 78bb2b8

Browse files
authored
Added create commit API (#222)
1 parent 65de4f3 commit 78bb2b8

File tree

9 files changed

+424
-117
lines changed

9 files changed

+424
-117
lines changed

src/GitLabApiClient/CommitsClient.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
using System;
22
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
35
using System.Threading.Tasks;
46
using GitLabApiClient.Internal.Http;
57
using GitLabApiClient.Internal.Paths;
68
using GitLabApiClient.Internal.Queries;
79
using GitLabApiClient.Models.Commits.Requests;
10+
using GitLabApiClient.Models.Commits.Requests.CreateCommitRequest;
811
using GitLabApiClient.Models.Commits.Responses;
912
using GitLabApiClient.Models.Projects.Responses;
1013

@@ -92,5 +95,24 @@ public async Task<IList<CommitStatuses>> GetStatusesAsync(ProjectId projectId, s
9295
string url = _commitStatusesQueryBuilder.Build($"projects/{projectId}/repository/commits/{sha}/statuses", queryOptions);
9396
return await _httpFacade.GetPagedList<CommitStatuses>(url);
9497
}
98+
99+
/// <summary>
100+
/// Creates a commit with multiple files and actions.
101+
/// </summary>
102+
/// <param name="projectId">The ID, path or <see cref="Project"/> of the project.</param>
103+
/// <param name="request">Create commit request.</param>
104+
/// <param name="autoEncodeToBase64">Automatically encode contents to base64 (default false).</param>
105+
public async Task<Commit> CreateAsync(ProjectId projectId, CreateCommitRequest request, bool autoEncodeToBase64 = false)
106+
{
107+
if (autoEncodeToBase64)
108+
{
109+
foreach (var action in request.Actions.Where(action => !string.IsNullOrEmpty(action.Content)))
110+
{
111+
action.Encoding = CreateCommitRequestActionEncoding.Base64;
112+
action.Content = Convert.ToBase64String(Encoding.UTF8.GetBytes(action.Content));
113+
}
114+
}
115+
return await _httpFacade.Post<Commit>($"projects/{projectId}/repository/commits", request);
116+
}
95117
}
96118
}

src/GitLabApiClient/ICommitsClient.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
using System.Threading.Tasks;
44
using GitLabApiClient.Internal.Paths;
55
using GitLabApiClient.Models.Commits.Requests;
6+
using GitLabApiClient.Models.Commits.Requests.CreateCommitRequest;
67
using GitLabApiClient.Models.Commits.Responses;
78
using GitLabApiClient.Models.Projects.Responses;
89

@@ -51,5 +52,13 @@ public interface ICommitsClient
5152
/// <param name="sha">The commit hash</param>
5253
/// <returns></returns>
5354
Task<IList<CommitStatuses>> GetStatusesAsync(ProjectId projectId, string sha, Action<CommitStatusesQueryOptions> options = null);
55+
56+
/// <summary>
57+
/// Creates a commit with multiple files and actions.
58+
/// </summary>
59+
/// <param name="projectId">The ID, path or <see cref="Project"/> of the project.</param>
60+
/// <param name="request">Create commit request.</param>
61+
/// <param name="autoEncodeToBase64">Automatically encode contents to base64 (default false).</param>
62+
Task<Commit> CreateAsync(ProjectId projectId, CreateCommitRequest request, bool autoEncodeToBase64 = false);
5463
}
5564
}
Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
using System.Collections.Generic;
2+
using GitLabApiClient.Internal.Paths;
3+
using GitLabApiClient.Internal.Utilities;
4+
using Newtonsoft.Json;
5+
6+
namespace GitLabApiClient.Models.Commits.Requests.CreateCommitRequest
7+
{
8+
public class CreateCommitRequest
9+
{
10+
/// <summary>
11+
/// Initializes a new instance of the <see cref="CreateCommitRequest"/> class.
12+
/// </summary>
13+
/// <param name="branch">Name of the branch</param>
14+
/// <param name="commitMessage">Commit message</param>
15+
/// <param name="actions">File actions, see <see cref="CreateCommitRequestAction"/></param>
16+
public CreateCommitRequest(string branch, string commitMessage, List<CreateCommitRequestAction> actions)
17+
{
18+
Guard.NotEmpty(branch, nameof(branch));
19+
Guard.NotEmpty(commitMessage, nameof(commitMessage));
20+
Guard.NotEmpty(actions, nameof(actions));
21+
22+
Branch = branch;
23+
CommitMessage = commitMessage;
24+
Actions = actions;
25+
//Default of GitLab
26+
Stats = true;
27+
}
28+
29+
/// <summary>
30+
/// Name of the branch to commit into. To create a new branch, also provide either StartBranch or StartSha, and optionally StartProject.
31+
/// </summary>
32+
[JsonProperty("branch")]
33+
public string Branch { get; set; }
34+
35+
/// <summary>
36+
/// Commit message
37+
/// </summary>
38+
[JsonProperty("commit_message")]
39+
public string CommitMessage { get; set; }
40+
41+
/// <summary>
42+
/// Name of the branch to start the new branch from.
43+
/// </summary>
44+
[JsonProperty("start_branch")]
45+
public string StartBranch { get; set; }
46+
47+
/// <summary>
48+
/// SHA of the commit to start the new branch from.
49+
/// </summary>
50+
[JsonProperty("start_sha")]
51+
public string StartSha { get; set; }
52+
53+
/// <summary>
54+
/// The project ID or URL-encoded path of the project to start the new branch from. Defaults to the value of id.
55+
/// See <see cref="ProjectId"/>.
56+
/// </summary>
57+
[JsonProperty("start_project")]
58+
public ProjectId StartProjectId { get; set; }
59+
60+
/// <summary>
61+
/// A list of action hashes to commit as a batch. See <see cref="CreateCommitRequestAction"/>.
62+
/// </summary>
63+
[JsonProperty("actions")]
64+
public List<CreateCommitRequestAction> Actions { get; set; }
65+
66+
/// <summary>
67+
/// Specify the commit author’s email address.
68+
/// </summary>
69+
[JsonProperty("author_email")]
70+
public string AuthorEmail { get; set; }
71+
72+
/// <summary>
73+
/// Specify the commit author’s name.
74+
/// </summary>
75+
[JsonProperty("author_name")]
76+
public string AuthorName { get; set; }
77+
78+
/// <summary>
79+
/// Include commit stats. Default is true.
80+
/// </summary>
81+
[JsonProperty("stats")]
82+
public bool? Stats { get; set; }
83+
84+
/// <summary>
85+
/// When true overwrites the target branch with a new commit based on the StartBranch or StartSha.
86+
/// </summary>
87+
[JsonProperty("force")]
88+
public bool? Force { get; set; }
89+
}
90+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
using GitLabApiClient.Internal.Utilities;
2+
using Newtonsoft.Json;
3+
4+
namespace GitLabApiClient.Models.Commits.Requests.CreateCommitRequest
5+
{
6+
public class CreateCommitRequestAction
7+
{
8+
/// <summary>
9+
/// Initializes a new instance of the <see cref="CreateCommitRequestAction"/> class.
10+
/// </summary>
11+
/// <param name="action">The action to perform, see <see cref="CreateCommitRequestActionType"/>.</param>
12+
/// <param name="filePath">Full path to the file.</param>
13+
public CreateCommitRequestAction(CreateCommitRequestActionType action, string filePath)
14+
{
15+
Guard.NotEmpty(filePath, nameof(filePath));
16+
17+
Action = action;
18+
FilePath = filePath;
19+
//Default of GitLab
20+
Encoding = CreateCommitRequestActionEncoding.Text;
21+
}
22+
23+
/// <summary>
24+
/// The action to perform, see <see cref="CreateCommitRequestActionType"/>.
25+
/// </summary>
26+
[JsonProperty("action")]
27+
public CreateCommitRequestActionType Action { get; set; }
28+
29+
/// <summary>
30+
/// Full path to the file. Ex. lib/class.rb
31+
/// </summary>
32+
[JsonProperty("file_path")]
33+
public string FilePath { get; set; }
34+
35+
/// <summary>
36+
/// Original full path to the file being moved. Ex. lib/class1.rb. Only considered for move action.
37+
/// </summary>
38+
[JsonProperty("previous_path")]
39+
public string PreviousPath { get; set; }
40+
41+
/// <summary>
42+
/// File content, required for all except delete, chmod, and move. Move actions that do not specify content
43+
/// preserve the existing file content, and any other value of content overwrites the file content.
44+
/// </summary>
45+
[JsonProperty("content")]
46+
public string Content { get; set; }
47+
48+
/// <summary>
49+
/// text or base64. text is default. See <see cref="CreateCommitRequestActionEncoding"/>.
50+
/// </summary>
51+
[JsonProperty("encoding")]
52+
public CreateCommitRequestActionEncoding Encoding { get; set; }
53+
54+
/// <summary>
55+
/// Last known file commit ID. Only considered in update, move, and delete actions.
56+
/// </summary>
57+
[JsonProperty("last_commit_id")]
58+
public string LastCommitId { get; set; }
59+
60+
/// <summary>
61+
/// When true/false enables/disables the execute flag on the file. Only considered for chmod action.
62+
/// </summary>
63+
// ReSharper disable once StringLiteralTypo
64+
[JsonProperty("execute_filemode")]
65+
public bool? ExecuteFileMode { get; set; }
66+
}
67+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using System.Runtime.Serialization;
2+
3+
namespace GitLabApiClient.Models.Commits.Requests.CreateCommitRequest
4+
{
5+
public enum CreateCommitRequestActionEncoding
6+
{
7+
[EnumMember(Value = "text")]
8+
Text,
9+
[EnumMember(Value = "base64")]
10+
Base64
11+
}
12+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using System.Runtime.Serialization;
2+
3+
namespace GitLabApiClient.Models.Commits.Requests.CreateCommitRequest
4+
{
5+
public enum CreateCommitRequestActionType
6+
{
7+
[EnumMember(Value = "create")]
8+
Create,
9+
[EnumMember(Value = "delete")]
10+
Delete,
11+
[EnumMember(Value = "move")]
12+
Move,
13+
[EnumMember(Value = "update")]
14+
Update,
15+
[EnumMember(Value = "chmod")]
16+
Chmod
17+
}
18+
}
Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
using System;
2+
using System.Diagnostics.CodeAnalysis;
3+
using System.Net.Http;
4+
using FakeItEasy;
5+
using FluentAssertions;
6+
using GitLabApiClient.Internal.Http;
7+
using GitLabApiClient.Internal.Http.Serialization;
8+
using GitLabApiClient.Internal.Queries;
9+
using GitLabApiClient.Test.TestUtilities;
10+
using Xunit;
11+
12+
namespace GitLabApiClient.Test
13+
{
14+
[ExcludeFromCodeCoverage]
15+
public class CommitsClientMockedTest
16+
{
17+
[Fact]
18+
public async void GetCommitBySha()
19+
{
20+
string gitlabServer = "http://fake-gitlab.com/";
21+
string projectId = "id";
22+
string sha = "6104942438c14ec7bd21c6cd5bd995272b3faff6";
23+
string url = $"/projects/{projectId}/repository/commits/{sha}";
24+
25+
var handler = A.Fake<MockHandler>(opt => opt.CallsBaseMethods());
26+
A.CallTo(() => handler.SendAsync(HttpMethod.Get, url))
27+
.ReturnsLazily(() => HttpResponseMessageProducer.Success(
28+
$"{{\"id\": \"{sha}\", }}"));
29+
using (var client = new HttpClient(handler) { BaseAddress = new Uri(gitlabServer) })
30+
{
31+
var gitlabHttpFacade = new GitLabHttpFacade(new RequestsJsonSerializer(), client);
32+
var commitsClient = new CommitsClient(gitlabHttpFacade, new CommitQueryBuilder(), new CommitRefsQueryBuilder(), new CommitStatusesQueryBuilder());
33+
34+
var commitFromClient = await commitsClient.GetAsync(projectId, sha);
35+
commitFromClient.Id.Should().BeEquivalentTo(sha);
36+
}
37+
}
38+
39+
[Fact]
40+
public async void GetCommitsByRefName()
41+
{
42+
string gitlabServer = "http://fake-gitlab.com/";
43+
string projectId = "id";
44+
string refName = "6104942438c14ec7bd21c6cd5bd995272b3faff6";
45+
string url = $"/projects/id/repository/commits?ref_name={refName}&per_page=100&page=1";
46+
47+
var handler = A.Fake<MockHandler>(opt => opt.CallsBaseMethods());
48+
A.CallTo(() => handler.SendAsync(HttpMethod.Get, url))
49+
.ReturnsLazily(() => HttpResponseMessageProducer.Success(
50+
$"[ {{ \"id\": \"id1\",}},\n {{\"id\": \"id2\",}}]"));
51+
using (var client = new HttpClient(handler) { BaseAddress = new Uri(gitlabServer) })
52+
{
53+
var gitlabHttpFacade = new GitLabHttpFacade(new RequestsJsonSerializer(), client);
54+
var commitsClient = new CommitsClient(gitlabHttpFacade, new CommitQueryBuilder(), new CommitRefsQueryBuilder(), new CommitStatusesQueryBuilder());
55+
56+
var commitsFromClient = await commitsClient.GetAsync(projectId, o => o.RefName = refName);
57+
commitsFromClient[0].Id.Should().BeEquivalentTo("id1");
58+
commitsFromClient[1].Id.Should().BeEquivalentTo("id2");
59+
}
60+
61+
}
62+
63+
[Fact]
64+
public async void GetDiffsForCommit()
65+
{
66+
string gitlabServer = "http://fake-gitlab.com/";
67+
string projectId = "id";
68+
string sha = "6104942438c14ec7bd21c6cd5bd995272b3faff6";
69+
string url = $"/projects/id/repository/commits/{sha}/diff?per_page=100&page=1";
70+
71+
var handler = A.Fake<MockHandler>(opt => opt.CallsBaseMethods());
72+
A.CallTo(() => handler.SendAsync(HttpMethod.Get, url))
73+
.ReturnsLazily(() => HttpResponseMessageProducer.Success(
74+
$"[ {{ \"diff\": \"diff1\", \"new_path\": \"new_path1\", \"old_path\": \"old_path1\", \"a_mode\": \"a_mode1\", \"b_mode\": \"b_mode1\", \"new_file\": \"true\", \"renamed_file\": \"false\", \"deleted_file\": \"false\" }},\n {{\"diff\": \"diff2\", \"new_path\": \"new_path2\", \"old_path\": \"old_path2\", \"a_mode\": \"a_mode2\", \"b_mode\": \"b_mode2\", \"new_file\": \"false\", \"renamed_file\": \"true\", \"deleted_file\": \"true\"}}]"));
75+
using (var client = new HttpClient(handler) { BaseAddress = new Uri(gitlabServer) })
76+
{
77+
var gitlabHttpFacade = new GitLabHttpFacade(new RequestsJsonSerializer(), client);
78+
var commitsClient = new CommitsClient(gitlabHttpFacade, new CommitQueryBuilder(), new CommitRefsQueryBuilder(), new CommitStatusesQueryBuilder());
79+
80+
var diffsFromClient = await commitsClient.GetDiffsAsync(projectId, sha);
81+
diffsFromClient[0].DiffText.Should().BeEquivalentTo("diff1");
82+
diffsFromClient[0].NewPath.Should().BeEquivalentTo("new_path1");
83+
diffsFromClient[0].OldPath.Should().BeEquivalentTo("old_path1");
84+
diffsFromClient[0].AMode.Should().BeEquivalentTo("a_mode1");
85+
diffsFromClient[0].BMode.Should().BeEquivalentTo("b_mode1");
86+
diffsFromClient[0].IsNewFile.Should().BeTrue();
87+
diffsFromClient[0].IsRenamedFile.Should().BeFalse();
88+
diffsFromClient[0].IsDeletedFile.Should().BeFalse();
89+
90+
diffsFromClient[1].DiffText.Should().BeEquivalentTo("diff2");
91+
diffsFromClient[1].NewPath.Should().BeEquivalentTo("new_path2");
92+
diffsFromClient[1].OldPath.Should().BeEquivalentTo("old_path2");
93+
diffsFromClient[1].AMode.Should().BeEquivalentTo("a_mode2");
94+
diffsFromClient[1].BMode.Should().BeEquivalentTo("b_mode2");
95+
diffsFromClient[1].IsNewFile.Should().BeFalse();
96+
diffsFromClient[1].IsRenamedFile.Should().BeTrue();
97+
diffsFromClient[1].IsDeletedFile.Should().BeTrue();
98+
99+
}
100+
}
101+
102+
[Fact]
103+
public async void GetStatusesForCommit()
104+
{
105+
string gitlabServer = "http://fake-gitlab.com/";
106+
string projectId = "id";
107+
string sha = "6104942438c14ec7bd21c6cd5bd995272b3faff6";
108+
string Name = "name1";
109+
string url = $"/projects/id/repository/commits/{sha}/statuses?name={Name}&per_page=100&page=1";
110+
111+
var handler = A.Fake<MockHandler>(opt => opt.CallsBaseMethods());
112+
A.CallTo(() => handler.SendAsync(HttpMethod.Get, url))
113+
.ReturnsLazily(() => HttpResponseMessageProducer.Success(
114+
$"[ {{\"id\":1,\"sha\":\"{sha}\",\"ref \":\"\",\"status\":\"success\",\"name\":\"name1\",\"target_url\":\"target_url1\",\"description\":\"success\",\"created_at\":\"2020-04-08T11:57:49.136+05:30\",\"started_at\":\"2020-04-08T11:58:00.362+05:30\",\"finished_at\":\"2020-04-08T11:58:06.121+05:30\",\"allow_failure\":false,\"coverage\":null,\"author\":{{\"id\":1,\"name\":\"name\",\"username\":\"username\",\"state\":\"active\",\"avatar_url\":\"avatar_url1\",\"web_url\":\"web_url1\"}} }},{{\"id\":2,\"sha\":\"{sha}\",\"ref \":\"\",\"status\":\"success\",\"name\":\"name2\",\"target_url\":\"target_url2\",\"description\":\"success\",\"created_at\":\"2020-04-08T11:57:49.136+05:30\",\"started_at\":\"2020-04-08T11:58:00.362+05:30\",\"finished_at\":\"2020-04-08T11:58:06.121+05:30\",\"allow_failure\":false,\"coverage\":null,\"author\":{{\"id\":2,\"name\":\"name2\",\"username\":\"username2\",\"state\":\"activ2\",\"avatar_url2\":\"avatar_url2\",\"web_url\":\"web_url2\"}} }}]"));
115+
using (var client = new HttpClient(handler) { BaseAddress = new Uri(gitlabServer) })
116+
{
117+
var gitlabHttpFacade = new GitLabHttpFacade(new RequestsJsonSerializer(), client);
118+
var commitsClient = new CommitsClient(gitlabHttpFacade, new CommitQueryBuilder(), new CommitRefsQueryBuilder(), new CommitStatusesQueryBuilder());
119+
120+
var statusesFromClient = await commitsClient.GetStatusesAsync(projectId, sha, o => o.Name = Name);
121+
statusesFromClient[0].Status.Should().BeEquivalentTo("success");
122+
statusesFromClient[0].Name.Should().BeEquivalentTo("name1");
123+
statusesFromClient[0].TargetUrl.Should().BeEquivalentTo("target_url1");
124+
statusesFromClient[0].Id.Should().BeEquivalentTo("1");
125+
126+
statusesFromClient[1].Status.Should().BeEquivalentTo("success");
127+
statusesFromClient[1].Name.Should().BeEquivalentTo("name2");
128+
statusesFromClient[1].TargetUrl.Should().BeEquivalentTo("target_url2");
129+
statusesFromClient[1].Id.Should().BeEquivalentTo("2");
130+
131+
}
132+
}
133+
}
134+
}

0 commit comments

Comments
 (0)