• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

samsmithnz / RepoAutomation / 3730826048

pending completion
3730826048

Pull #198

github

GitHub
Merge 9cbe36303 into f7c251d6b
Pull Request #198: Bump samsmithnz/SamsDotNetSonarCloudAction from 1.0.6 to 1.1.1

294 of 428 branches covered (68.69%)

Branch coverage included in aggregate %.

708 of 807 relevant lines covered (87.73%)

4.93 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

90.7
/src/RepoAutomation.Core/APIAccess/GitHubAPIAccess.cs
1
using Newtonsoft.Json;
2
using RepoAutomation.Core.Models;
3
using System.Text;
4
using System.Web;
5

6
namespace RepoAutomation.Core.APIAccess;
7

8
public static class GitHubApiAccess
9
{
10

11
    //https://docs.github.com/en/enterprise-cloud@latest/rest/reference/repos
12
    /// <summary>
13
    /// Get a single repo
14
    /// </summary>
15
    /// <param name="clientId"></param>
16
    /// <param name="clientSecret"></param>
17
    /// <param name="owner"></param>
18
    /// <param name="repo"></param>
19
    /// <returns></returns>
20
    public async static Task<Repo?> GetRepo(string? clientId, string? clientSecret, string owner, string repo)
21
    {
8✔
22
        Repo? result = null;
8✔
23
        if (clientId != null && clientSecret != null)
8!
24
        {
8✔
25
            string url = $"https://api.github.com/repos/{owner}/{repo}";
8✔
26
            string? response = await BaseApiAccess.GetGitHubMessage(url, clientId, clientSecret, false);
8✔
27
            if (!string.IsNullOrEmpty(response) &&
8!
28
                response != @"{""message"":""Not Found"",""documentation_url"":""https://docs.github.com/rest/reference/repos#get-a-repository""}")
8✔
29
            {
5✔
30
                dynamic? jsonObj = JsonConvert.DeserializeObject(response);
5✔
31
                result = JsonConvert.DeserializeObject<Repo>(jsonObj?.ToString());
5!
32
                result.RawJSON = jsonObj?.ToString();
5!
33
            }
5✔
34
        }
8✔
35
        return result;
8✔
36
    }
8✔
37

38
    //https://docs.github.com/en/rest/reference/repos#list-repositories-for-a-user
39
    /// <summary>
40
    /// Get a list of repos
41
    /// </summary>
42
    /// <param name="clientId"></param>
43
    /// <param name="clientSecret"></param>
44
    /// <param name="owner"></param>
45
    /// <returns></returns>
46
    public async static Task<List<Repo>?> GetRepos(string? clientId, string? clientSecret, string owner)
47
    {
1✔
48
        List<Repo>? result = new();
1✔
49
        if (clientId != null && clientSecret != null)
1!
50
        {
1✔
51
            string url = $"https://api.github.com/users/{owner}/repos";
1✔
52
            string? response = await BaseApiAccess.GetGitHubMessage(url, clientId, clientSecret, false);
1✔
53
            if (!string.IsNullOrEmpty(response) &&
1!
54
                response != @"{""message"":""Not Found"",""documentation_url"":""https://docs.github.com/rest/reference/repos#get-a-repository""}")
1✔
55
            {
1✔
56
                dynamic? jsonObj = JsonConvert.DeserializeObject(response);
1✔
57
                string? jsonString = jsonObj?.ToString();
1!
58
                if (jsonString != null)
1✔
59
                {
1✔
60
                    result = JsonConvert.DeserializeObject<List<Repo>>(jsonString);
1✔
61
                }
1✔
62
                //Commented out because we are returning a list, and there is no RawJSON property on the list
63
                //result.RawJSON = jsonObj?.ToString();
64
            }
1✔
65
        }
1✔
66
        return result;
1✔
67
    }
1✔
68

69
    /// <summary>
70
    /// Create a new repo
71
    /// </summary>
72
    /// <param name="clientId"></param>
73
    /// <param name="clientSecret"></param>
74
    /// <param name="repo"></param>
75
    /// <param name="allowAutoMerge"></param>
76
    /// <param name="deleteBranchOnMerge"></param>
77
    /// <param name="allowRebaseMerge"></param>
78
    /// <param name="isPrivate"></param>
79
    /// <param name="gitIgnoreTemplate"></param>
80
    /// <returns></returns>
81
    public async static Task<bool> CreateRepo(string? clientId, string? clientSecret,
82
        string repo,
83
        bool allowAutoMerge,
84
        bool deleteBranchOnMerge,
85
        bool allowRebaseMerge,
86
        bool isPrivate,
87
        string gitIgnoreTemplate = "VisualStudio")
88
    {
1✔
89
        if (clientId != null && clientSecret != null)
1!
90
        {
1✔
91
            var body = new
1✔
92
            {
1✔
93
                name = repo,
1✔
94
                allow_auto_merge = allowAutoMerge,
1✔
95
                delete_branch_on_merge = deleteBranchOnMerge,
1✔
96
                allow_rebase_merge = allowRebaseMerge,
1✔
97
                @private = isPrivate,
1✔
98
                auto_init = true,
1✔
99
                gitignore_template = gitIgnoreTemplate,
1✔
100

1✔
101
            };
1✔
102
            StringContent content = new(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json");
1✔
103
            string url = $"https://api.github.com/user/repos";
1✔
104
            await BaseApiAccess.PostGitHubMessage(url, clientId, clientSecret, content);
1✔
105
        }
1✔
106
        return true;
1✔
107
    }
1✔
108

109
    /// <summary>
110
    /// Update a new repo
111
    /// </summary>
112
    /// <param name="clientId"></param>
113
    /// <param name="clientSecret"></param>
114
    /// <param name="owner"></param>
115
    /// <param name="repo"></param>
116
    /// <param name="allowAutoMerge"></param>
117
    /// <param name="deleteBranchOnMerge"></param>
118
    /// <param name="allowRebaseMerge"></param>
119
    /// <param name="isPrivate"></param>
120
    /// <returns></returns>
121
    public async static Task<bool> UpdateRepo(string? clientId, string? clientSecret,
122
        string owner,
123
        string repo,
124
        bool allowAutoMerge,
125
        bool deleteBranchOnMerge,
126
        bool allowRebaseMerge,
127
        bool isPrivate)
128
    {
1✔
129
        if (clientId != null && clientSecret != null)
1!
130
        {
1✔
131
            var body = new
1✔
132
            {
1✔
133
                name = repo,
1✔
134
                allow_auto_merge = allowAutoMerge,
1✔
135
                delete_branch_on_merge = deleteBranchOnMerge,
1✔
136
                allow_rebase_merge = allowRebaseMerge,
1✔
137
                @private = isPrivate,
1✔
138
                auto_init = true
1✔
139
            };
1✔
140
            StringContent content = new(JsonConvert.SerializeObject(body), Encoding.UTF8, "application/json");
1✔
141
            //https://docs.github.com/en/rest/repos/repos#update-a-repository
142
            string url = $"https://api.github.com/repos/{owner}/{repo}";
1✔
143
            await BaseApiAccess.PatchGitHubMessage(url, clientId, clientSecret, content);
1✔
144
        }
1✔
145
        return true;
1✔
146
    }
1✔
147

148

149

150
    /// <summary>
151
    /// Delete the repo
152
    /// </summary>
153
    /// <param name="clientId"></param>
154
    /// <param name="clientSecret"></param>
155
    /// <param name="owner"></param>
156
    /// <param name="repo"></param>
157
    /// <param name="processErrors"></param>
158
    /// <returns></returns>
159
    public async static Task<bool> DeleteRepo(string? clientId, string? clientSecret, string owner, string repo, bool processErrors = true)
160
    {
3✔
161
        if (clientId != null && clientSecret != null)
3!
162
        {
3✔
163
            string url = $"https://api.github.com/repos/{owner}/{repo}";
3✔
164
            string? response = await BaseApiAccess.DeleteGitHubMessage(url, clientId, clientSecret, processErrors);
3✔
165
            if (string.IsNullOrEmpty(response))
2✔
166
            {
1✔
167
                return false;
1✔
168
            }
169
        }
1✔
170
        return true;
1✔
171
    }
2✔
172

173
    /// <summary>
174
    /// Get a list of all files at a path
175
    /// </summary>
176
    /// <param name="clientId"></param>
177
    /// <param name="clientSecret"></param>
178
    /// <param name="owner"></param>
179
    /// <param name="repo"></param>
180
    /// <param name="path"></param>
181
    /// <returns></returns>
182
    public async static Task<GitHubFile[]?> GetFiles(string? clientId, string? clientSecret,
183
        string owner, string repo, string path)
184
    {
5✔
185
        GitHubFile[]? result = null;
5✔
186
        if (clientId != null && clientSecret != null)
5!
187
        {
5✔
188
            path = HttpUtility.UrlEncode(path);
5✔
189
            string url = $"https://api.github.com/repos/{owner}/{repo}/contents/{path}";
5✔
190
            string? response = await BaseApiAccess.GetGitHubMessage(url, clientId, clientSecret, false);
5✔
191
            if (!string.IsNullOrEmpty(response) &&
5!
192
                !response.Contains(@"""message"":""Not Found"""))
5✔
193
            {
5✔
194
                dynamic? jsonObj = JsonConvert.DeserializeObject(response);
5✔
195
                result = JsonConvert.DeserializeObject<GitHubFile[]>(jsonObj?.ToString());
5!
196
            }
5✔
197
        }
5✔
198
        return result;
5✔
199
    }
5✔
200

201
    /// <summary>
202
    /// Get a file and it's contents
203
    /// </summary>
204
    /// <param name="clientId"></param>
205
    /// <param name="clientSecret"></param>
206
    /// <param name="owner"></param>
207
    /// <param name="repo"></param>
208
    /// <param name="path"></param>
209
    /// <returns></returns>
210
    public async static Task<GitHubFile?> GetFile(string? clientId, string? clientSecret,
211
        string owner, string repo, string path)
212
    {
3✔
213
        GitHubFile? result = null;
3✔
214
        if (clientId != null && clientSecret != null)
3!
215
        {
3✔
216
            path = HttpUtility.UrlEncode(path);
3✔
217
            string url = $"https://api.github.com/repos/{owner}/{repo}/contents/{path}";
3✔
218
            string? response = await BaseApiAccess.GetGitHubMessage(url, clientId, clientSecret, false);
3✔
219
            if (!string.IsNullOrEmpty(response) &&
3!
220
                !response.Contains(@"""message"":""Not Found"""))
3✔
221
            {
2✔
222
                dynamic? jsonObj = JsonConvert.DeserializeObject(response);
2✔
223
                result = JsonConvert.DeserializeObject<GitHubFile>(jsonObj?.ToString());
2!
224

225
                //Decode the Base64 file contents result
226
                if (result != null && result.content != null)
2!
227
                {
2✔
228
                    byte[]? valueBytes = System.Convert.FromBase64String(result.content);
2✔
229
                    result.content = Encoding.UTF8.GetString(valueBytes);
2✔
230
                }
2✔
231
            }
2✔
232
        }
3✔
233
        return result;
3✔
234
    }
3✔
235

236
    /// <summary>
237
    /// Get the branch policy for a repo/branch
238
    /// </summary>
239
    /// <param name="clientId"></param>
240
    /// <param name="clientSecret"></param>
241
    /// <param name="owner"></param>
242
    /// <param name="repo"></param>
243
    /// <param name="branch"></param>
244
    /// <returns></returns>
245
    public async static Task<BranchProtectionPolicy?> GetBranchProtectionPolicy(string? clientId, string? clientSecret,
246
        string owner, string repo, string branch)
247
    {
3✔
248
        BranchProtectionPolicy? result = null;
3✔
249
        if (clientId != null && clientSecret != null)
3!
250
        {
3✔
251
            string url = $"https://api.github.com/repos/{owner}/{repo}/branches/{branch}/protection";
3✔
252
            string? response = await BaseApiAccess.GetGitHubMessage(url, clientId, clientSecret, false);
3✔
253
            if (!string.IsNullOrEmpty(response) &&
3!
254
                !response.Contains(@"""message"":""Branch not protected"""))
3✔
255
            {
3✔
256
                dynamic? jsonObj = JsonConvert.DeserializeObject(response);
3✔
257
                result = JsonConvert.DeserializeObject<BranchProtectionPolicy>(jsonObj?.ToString());
3!
258
                result.RawJSON = jsonObj?.ToString();
3!
259
            }
3✔
260
        }
3✔
261
        return result;
3✔
262
    }
3✔
263

264
    /// <summary>
265
    /// Update a branch policy for a repo/branch. Lots of assumptions/simplifications are made today. This definition WILL change.
266
    /// </summary>
267
    /// <param name="clientId"></param>
268
    /// <param name="clientSecret"></param>
269
    /// <param name="owner"></param>
270
    /// <param name="repo"></param>
271
    /// <param name="branch"></param>
272
    /// <param name="requiredStatusCheck"></param>
273
    /// <returns></returns>
274
    public async static Task<bool> UpdateBranchProtectionPolicy(string? clientId, string? clientSecret, string owner, string repo,
275
        string branch, RequiredStatusCheckPut? requiredStatusCheck)
276
    {
1✔
277
        if (clientId != null && clientSecret != null)
1!
278
        {
1✔
279
            BranchProtectionPolicyPut body = new()
1✔
280
            {
1✔
281
                required_status_checks = requiredStatusCheck,
1✔
282
                required_pull_request_reviews = new()
1✔
283
                {
1✔
284
                    dismiss_stale_reviews = false,
1✔
285
                    required_approving_review_count = 0,
1✔
286
                    require_code_owner_reviews = false
1✔
287
                },
1✔
288
                restrictions = null,
1✔
289
                required_conversation_resolution = true,
1✔
290
                required_linear_history = false,
1✔
291
                enforce_admins = true,
1✔
292
                allow_force_pushes = false,
1✔
293
                allow_deletions = false
1✔
294
            };
1✔
295
            string json = JsonConvert.SerializeObject(body);
1✔
296

297
            StringContent content = new(json, Encoding.UTF8, "application/json");
1✔
298
            string url = $"https://api.github.com/repos/{owner}/{repo}/branches/{branch}/protection";
1✔
299
            string? response = await BaseApiAccess.PutGitHubMessage(url, clientId, clientSecret, content);
1✔
300
            if (string.IsNullOrEmpty(response))
1!
301
            {
×
302
                return false;
×
303
            }
304
        }
1✔
305
        return true;
1✔
306
    }
1✔
307

308
    /// <summary>
309
    /// Get the latest release for a repo
310
    /// </summary>
311
    /// <param name="clientId"></param>
312
    /// <param name="clientSecret"></param>
313
    /// <param name="owner"></param>
314
    /// <param name="repo"></param>
315
    /// <returns></returns>
316
    public async static Task<Release?> GetReleaseLatest(string? clientId, string? clientSecret,
317
        string owner, string repo)
318
    {
2✔
319
        Release? result = null;
2✔
320
        if (clientId != null && clientSecret != null)
2!
321
        {
2✔
322
            string url = $"https://api.github.com/repos/{owner}/{repo}/releases/latest";
2✔
323
            string? response = await BaseApiAccess.GetGitHubMessage(url, clientId, clientSecret, false);
2✔
324
            if (!string.IsNullOrEmpty(response))
2✔
325
            {
2✔
326
                dynamic? jsonObj = JsonConvert.DeserializeObject(response);
2✔
327
                result = JsonConvert.DeserializeObject<Release>(jsonObj?.ToString());
2!
328
                result.RawJSON = jsonObj?.ToString();
2!
329
            }
2✔
330
            //Check if the release is effectively empty - if so, mark it as null
331
            if (result != null && result.name == null)
2!
332
            {
1✔
333
                result = null;
1✔
334
            }
1✔
335
        }
2✔
336
        return result;
2✔
337
    }
2✔
338

339
    //IMPORTANT: Note that search has a rate limit of 30 requests per minute: https://docs.github.com/en/rest/reference/search#rate-limit
340
    public async static Task<SearchResult?> SearchFiles(string? clientId, string? clientSecret,
341
        string owner, string repo, string? extension = null, string? fileName = null, int counter = 0)
342
    {
3✔
343
        SearchResult? result = new();
3✔
344
        if (clientId != null && clientSecret != null)
3!
345
        {
3✔
346
            string url = "";
3✔
347
            if (extension != null)
3✔
348
            {
2✔
349
                //"https://api.github.com/search/code?q=extension:js+repo:vnation/NewsAggregator";
350
                url = $"https://api.github.com/search/code?q=extension:{extension}+repo:{owner}/{repo}";
2✔
351
            }
2✔
352
            else if (fileName != null)
1✔
353
            {
1✔
354
                //https://github.com/search?q=user%3Asamsmithnz+ProjectVersion.txt+filename%3AProjectVersion.txt&type=Repositories&ref=advsearch&l=&l=
355
                url = $"https://api.github.com/search/code?q=filename%3A{fileName}+repo:{owner}/{repo}";
1✔
356
            }
1✔
357
            if (!string.IsNullOrEmpty(url))
3✔
358
            {
3✔
359
                string? response = await BaseApiAccess.GetGitHubMessage(url, clientId, clientSecret, false);
3✔
360
                if (!string.IsNullOrEmpty(response))
3✔
361
                {
3✔
362
                    dynamic? jsonObj = JsonConvert.DeserializeObject(response);
3✔
363
                    result = JsonConvert.DeserializeObject<SearchResult>(jsonObj?.ToString());
3!
364
                }
3✔
365
                result.RawJSON = response;
3✔
366
            }
3✔
367
        }
3✔
368
        if (result?.incomplete_results == true && counter < 3)
3!
369
        {
×
370
            counter++;
×
371
            result = await SearchFiles(clientId, clientSecret, owner, repo, extension, fileName, counter);
×
372
        }
×
373

374
        return result;
3✔
375
    }
3✔
376

377
    public async static Task<string?> GetLastCommit(string? clientId, string? clientSecret,
378
        string owner, string repo)
379
    {
1✔
380
        string? result = null;
1✔
381
        if (clientId != null && clientSecret != null)
1!
382
        {
1✔
383
            //https://api.github.com/repos/torvalds/linux/commits?per_page=1
384
            string url = $"https://api.github.com/repos/{owner}/{repo}/commits?per_page=1";
1✔
385
            string? response = await BaseApiAccess.GetGitHubMessage(url, clientId, clientSecret, false);
1✔
386
            if (!string.IsNullOrEmpty(response))
1✔
387
            {
1✔
388
                dynamic? jsonObj = JsonConvert.DeserializeObject(response);
1✔
389
                Commit[] commits = JsonConvert.DeserializeObject<Commit[]>(jsonObj?.ToString());
1!
390
                if (commits != null && commits.Length > 0)
1!
391
                {
1✔
392
                    result = commits[0].sha;
1✔
393
                }
1✔
394
            }
1✔
395
        }
1✔
396
        return result;
1✔
397
    }
1✔
398

399
    public async static Task<List<PullRequest>> GetPullRequests(string? clientId, string? clientSecret,
400
        string owner, string repo)
401
    {
1✔
402
        List<PullRequest>? pullRequests = new();
1✔
403
        if (clientId != null && clientSecret != null)
1!
404
        {
1✔
405
            //https://docs.github.com/en/rest/pulls/pulls#list-pull-requests (only first 30)
406
            string url = $"https://api.github.com/repos/{owner}/{repo}/pulls?state=open";
1✔
407
            string? response = await BaseApiAccess.GetGitHubMessage(url, clientId, clientSecret, false);
1✔
408
            if (!string.IsNullOrEmpty(response))
1✔
409
            {
1✔
410
                dynamic? jsonObj = JsonConvert.DeserializeObject(response);
1✔
411
                PR[] prs = JsonConvert.DeserializeObject<PR[]>(jsonObj?.ToString());
1!
412
                if (prs != null && prs.Length > 0)
1!
413
                {
1✔
414
                    foreach (PR pr in prs)
15✔
415
                    {
6✔
416
                        PullRequest newPullRequest = new()
6✔
417
                        {
6✔
418
                            Number = pr.number,
6✔
419
                            Title = pr.title,
6✔
420
                            State = pr.state
6✔
421
                        };
6✔
422
                        if (pr != null && pr.updated_at != null)
6!
423
                        {
6✔
424
                            newPullRequest.LastUpdated = DateTime.Parse(pr.updated_at);
6✔
425
                        }
6✔
426
                        //if (pr.auto_merge == null)
427
                        //{
428
                        //    newPullRequest.AutoMergeEnabled = false;
429
                        //}
430
                        //else
431
                        //{
432
                        //    newPullRequest.AutoMergeEnabled = bool.Parse(pr.auto_merge);
433
                        //}
434
                        if (pr != null && pr.labels != null)
6!
435
                        {
6✔
436
                            foreach (Label item in pr.labels)
38✔
437
                            {
10✔
438
                                if (item != null && item.name != null)
10!
439
                                {
10✔
440
                                    newPullRequest.Labels.Add(item.name);
10✔
441
                                    if (item.name == "dependencies")
10✔
442
                                    {
5✔
443
                                        newPullRequest.IsDependabotPR = true;
5✔
444
                                    }
5✔
445
                                }
10✔
446
                            }
10✔
447
                        }
6✔
448
                        pullRequests.Add(newPullRequest);
6✔
449
                    }
6✔
450
                }
1✔
451
            }
1✔
452
        }
1✔
453
        return pullRequests;
1✔
454
    }
1✔
455

456
    public async static Task<List<PRReview>> GetPullRequestReview(string? clientId, string? clientSecret,
457
        string owner, string repo, string pullRequestNumber)
458
    {
2✔
459
        List<PRReview> prReview = new();
2✔
460
        if (clientId != null && clientSecret != null)
2!
461
        {
2✔
462
            //https://docs.github.com/en/rest/pulls/reviews#submit-a-pull-request-review
463
            string url = $"https://api.github.com/repos/{owner}/{repo}/pulls/{pullRequestNumber}/reviews";
2✔
464
            string? response = await BaseApiAccess.GetGitHubMessage(url, clientId, clientSecret, false);
2✔
465
            if (!string.IsNullOrEmpty(response))
2✔
466
            {
2✔
467
                dynamic? jsonObj = JsonConvert.DeserializeObject(response);
2✔
468
                prReview = JsonConvert.DeserializeObject<List<PRReview>>(jsonObj?.ToString());
2!
469
            }
2✔
470

471
        }
2✔
472

473
        return prReview;
2✔
474
    }
2✔
475
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc