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

samsmithnz / RepoAutomation / 3851196580

pending completion
3851196580

push

github

GitHub
Merge pull request #210 from samsmithnz/ConvertedPercentsToWholeNumber

314 of 450 branches covered (69.78%)

Branch coverage included in aggregate %.

767 of 866 relevant lines covered (88.57%)

7.16 hits per line

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

90.88
/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
    {
4✔
213
        GitHubFile? result = null;
4✔
214
        path = HttpUtility.UrlEncode(path);
4✔
215
        string url = $"https://api.github.com/repos/{owner}/{repo}/contents/{path}";
4✔
216
        string? response = await BaseApiAccess.GetGitHubMessage(url, clientId, clientSecret, false);
4✔
217
        if (!string.IsNullOrEmpty(response) &&
4!
218
            !response.Contains(@"""message"":""Not Found"""))
4✔
219
        {
3✔
220
            dynamic? jsonObj = JsonConvert.DeserializeObject(response);
3✔
221
            result = JsonConvert.DeserializeObject<GitHubFile>(jsonObj?.ToString());
3!
222

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

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

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

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

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

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

371
        return result;
3✔
372
    }
3✔
373

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

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

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

469
        return prReview;
2✔
470
    }
2✔
471

472
    public async static Task<Dictionary<string, int>> GetRepoLanguages(string? clientId, string? clientSecret,
473
        string owner, string repo)
474
    {
2✔
475
        Dictionary<string, int> languages = new();
2✔
476
        if (clientId != null && clientSecret != null)
2!
477
        {
2✔
478
            //https://docs.github.com/en/rest/repos/repos?apiVersion=2022-11-28#list-repository-languages
479
            string url = $"https://api.github.com/repos/{owner}/{repo}/languages";
2✔
480
            string? response = await BaseApiAccess.GetGitHubMessage(url, clientId, clientSecret, false);
2✔
481
            if (!string.IsNullOrEmpty(response))
2✔
482
            {
2✔
483
                dynamic? jsonObj = JsonConvert.DeserializeObject(response);
2✔
484
                languages = JsonConvert.DeserializeObject<Dictionary<string, int>>(jsonObj?.ToString());
2!
485
            }
2✔
486
        }
2✔
487

488
        return languages;
2✔
489
    }
2✔
490

491
}
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