Skip to content

Commit 93889b4

Browse files
committed
Changes to prevent breaking change to IRepositoryClient.GetArchive
Adds a new overload to IRepository.GetArchive which accepts a query object in order to allow additional parameters to be passed to the the archive endpoint without breaking the existing implementation.
1 parent b8d48da commit 93889b4

File tree

8 files changed

+190
-32
lines changed

8 files changed

+190
-32
lines changed

NGitLab.Mock/Clients/RepositoryClient.cs

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,12 @@ public void GetRawBlob(string sha, Action<Stream> parser)
8080
throw new NotImplementedException();
8181
}
8282

83-
public void GetArchive(Action<Stream> parser, string sha = null, string format = null)
83+
public void GetArchive(Action<Stream> parser)
84+
{
85+
throw new NotImplementedException();
86+
}
87+
88+
public void GetArchive(Action<Stream> parser, FileArchiveQuery fileArchiveQuery)
8489
{
8590
throw new NotImplementedException();
8691
}

NGitLab.Tests/RepositoryClient/RepositoryClientTests.cs

Lines changed: 76 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -356,10 +356,10 @@ public async Task GetCommitRefs(CommitRefType type)
356356

357357
[Test]
358358
[NGitLabRetry]
359-
public async Task GetArchiveWithoutOptionalParameters()
359+
public async Task GetArchive()
360360
{
361361
// Arrange
362-
using var context = await RepositoryClientTestsContext.CreateAsync(commitCount: 2);
362+
using var context = await RepositoryClientTestsContext.CreateAsync(commitCount: 2).ConfigureAwait(false);
363363

364364
// Act
365365
context.RepositoryClient.GetArchive((stream) => { });
@@ -376,78 +376,134 @@ public async Task GetArchiveWithoutOptionalParameters()
376376

377377
[Test]
378378
[NGitLabRetry]
379-
public async Task GetArchiveAcceptsShaParameter()
379+
public async Task GetArchiveWithNullQueryPassesNoParameters()
380380
{
381381
// Arrange
382-
using var context = await RepositoryClientTestsContext.CreateAsync(commitCount: 2);
382+
using var context = await RepositoryClientTestsContext.CreateAsync(commitCount: 2).ConfigureAwait(false);
383383
var firstCommitId = context.Commits[0].Id.ToString();
384384

385385
// Act
386-
context.RepositoryClient.GetArchive((stream) => { }, sha: firstCommitId);
386+
context.RepositoryClient.GetArchive((stream) => { }, fileArchiveQuery: null);
387387

388388
// Assert
389389
var requestPathAndQuery = context.Context.LastRequest.RequestUri.PathAndQuery;
390390

391391
Assert.Multiple(() =>
392392
{
393393
Assert.That(requestPathAndQuery, Is.Not.Null);
394-
Assert.That(requestPathAndQuery.Contains($"?sha={firstCommitId}", StringComparison.OrdinalIgnoreCase), Is.True);
394+
Assert.That(requestPathAndQuery.EndsWith("/archive", StringComparison.OrdinalIgnoreCase), Is.True);
395395
});
396396
}
397397

398-
[Test]
398+
[TestCase(null, "")]
399+
[TestCase(FileArchiveFormat.Bz2, ".bz2")]
400+
[TestCase(FileArchiveFormat.Gz, ".gz")]
401+
[TestCase(FileArchiveFormat.Tar, ".tar")]
402+
[TestCase(FileArchiveFormat.TarBz2, ".tar.bz2")]
403+
[TestCase(FileArchiveFormat.TarGz, ".tar.gz")]
404+
[TestCase(FileArchiveFormat.Tb2, ".tb2")]
405+
[TestCase(FileArchiveFormat.Tbz2, ".tbz2")]
406+
[TestCase(FileArchiveFormat.Zip, ".zip")]
399407
[NGitLabRetry]
400-
public async Task GetArchiveAcceptsFormatParameter()
408+
public async Task GetArchiveFormatValuePassedCorrectly(FileArchiveFormat? archiveFormat, string expectedExtension)
401409
{
402410
// Arrange
403411
using var context = await RepositoryClientTestsContext.CreateAsync(commitCount: 2);
404-
var format = ".zip";
412+
var fileArchiveQuery = new FileArchiveQuery
413+
{
414+
Format = archiveFormat,
415+
};
405416

406417
// Act
407-
context.RepositoryClient.GetArchive((stream) => { }, format: format);
418+
context.RepositoryClient.GetArchive((stream) => { }, fileArchiveQuery);
408419

409420
// Assert
410421
var requestPathAndQuery = context.Context.LastRequest.RequestUri.PathAndQuery;
411422

412423
Assert.Multiple(() =>
413424
{
414425
Assert.That(requestPathAndQuery, Is.Not.Null);
415-
Assert.That(requestPathAndQuery.Contains($"/archive{format}", StringComparison.OrdinalIgnoreCase), Is.True);
426+
Assert.That(requestPathAndQuery.EndsWith($"/archive{expectedExtension}", StringComparison.OrdinalIgnoreCase), Is.True);
416427
});
417428
}
418429

419430
[Test]
420431
[NGitLabRetry]
421-
public async Task GetArchiveAcceptsShaAndFormatParametersTogether()
432+
public async Task GetArchiveShaValuePassedCorrectly()
422433
{
423434
// Arrange
424435
using var context = await RepositoryClientTestsContext.CreateAsync(commitCount: 2);
425-
var format = ".zip";
426436
var firstCommitId = context.Commits[0].Id.ToString();
437+
var fileArchiveQuery = new FileArchiveQuery
438+
{
439+
Ref = firstCommitId,
440+
};
427441

428442
// Act
429-
context.RepositoryClient.GetArchive((stream) => { }, sha: firstCommitId, format: format);
443+
context.RepositoryClient.GetArchive((stream) => { }, fileArchiveQuery);
430444

431445
// Assert
432446
var requestPathAndQuery = context.Context.LastRequest.RequestUri.PathAndQuery;
433447

434448
Assert.Multiple(() =>
435449
{
436450
Assert.That(requestPathAndQuery, Is.Not.Null);
437-
Assert.That(requestPathAndQuery.Contains($"/archive{format}", StringComparison.OrdinalIgnoreCase), Is.True);
438-
Assert.That(requestPathAndQuery.Contains($"?sha={firstCommitId}", StringComparison.OrdinalIgnoreCase), Is.True);
451+
Assert.That(requestPathAndQuery.Contains($"sha={firstCommitId}", StringComparison.OrdinalIgnoreCase), Is.True);
439452
});
440453
}
441454

442455
[Test]
443456
[NGitLabRetry]
444-
public async Task GetArchiveThrowsExceptionWhenFormatDoesNotStartWithDot()
457+
public async Task GetArchivePathValuePassedCorrectly()
445458
{
446459
// Arrange
447460
using var context = await RepositoryClientTestsContext.CreateAsync(commitCount: 2);
448-
var format = "zip";
461+
var path = RepositoryClientTestsContext.SubfolderName;
462+
var fileArchiveQuery = new FileArchiveQuery
463+
{
464+
Path = path,
465+
};
449466

450-
// Act and Assert
451-
Assert.Throws<ArgumentException>(() => context.RepositoryClient.GetArchive((stream) => { }, format: format));
467+
// Act
468+
context.RepositoryClient.GetArchive((stream) => { }, fileArchiveQuery);
469+
470+
// Assert
471+
var requestPathAndQuery = context.Context.LastRequest.RequestUri.PathAndQuery;
472+
473+
Assert.Multiple(() =>
474+
{
475+
Assert.That(requestPathAndQuery, Is.Not.Null);
476+
Assert.That(requestPathAndQuery.Contains($"path={path}", StringComparison.OrdinalIgnoreCase), Is.True);
477+
});
478+
}
479+
480+
[Test]
481+
[NGitLabRetry]
482+
public async Task GetArchiveCombinationOfValuesPassedCorrectly()
483+
{
484+
// Arrange
485+
using var context = await RepositoryClientTestsContext.CreateAsync(commitCount: 2);
486+
var firstCommitId = context.Commits[0].Id.ToString();
487+
var path = RepositoryClientTestsContext.SubfolderName;
488+
var fileArchiveQuery = new FileArchiveQuery
489+
{
490+
Format = FileArchiveFormat.Zip,
491+
Path = path,
492+
Ref = firstCommitId,
493+
};
494+
495+
// Act
496+
context.RepositoryClient.GetArchive((stream) => { }, fileArchiveQuery);
497+
498+
// Assert
499+
var requestPathAndQuery = context.Context.LastRequest.RequestUri.PathAndQuery;
500+
501+
Assert.Multiple(() =>
502+
{
503+
Assert.That(requestPathAndQuery, Is.Not.Null);
504+
Assert.That(requestPathAndQuery.Contains($"/archive.zip", StringComparison.OrdinalIgnoreCase), Is.True);
505+
Assert.That(requestPathAndQuery.Contains($"path={path}", StringComparison.OrdinalIgnoreCase), Is.True);
506+
Assert.That(requestPathAndQuery.Contains($"sha={firstCommitId}", StringComparison.OrdinalIgnoreCase), Is.True);
507+
});
452508
}
453509
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System;
2+
using System.Linq;
3+
using System.Reflection;
4+
using System.Runtime.Serialization;
5+
6+
namespace NGitLab.Extensions;
7+
8+
internal static class TypeExtensions
9+
{
10+
public static string GetEnumMemberAttributeValue<TEnum>(this TEnum value)
11+
where TEnum : Enum
12+
{
13+
return typeof(TEnum)
14+
.GetTypeInfo()
15+
.DeclaredMembers
16+
.SingleOrDefault(x => string.Equals(x.Name, value.ToString(), StringComparison.Ordinal))
17+
?.GetCustomAttribute<EnumMemberAttribute>(inherit: false)
18+
?.Value;
19+
}
20+
}

NGitLab/IRepositoryClient.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,9 @@ public interface IRepositoryClient
2121

2222
void GetRawBlob(string sha, Action<Stream> parser);
2323

24-
void GetArchive(Action<Stream> parser, string sha = null, string format = null);
24+
void GetArchive(Action<Stream> parser);
25+
26+
void GetArchive(Action<Stream> parser, FileArchiveQuery fileArchiveQuery);
2527

2628
IEnumerable<Commit> Commits { get; }
2729

NGitLab/Impl/RepositoryClient.cs

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,17 +55,27 @@ public void GetRawBlob(string sha, Action<Stream> parser)
5555
_api.Get().Stream(_repoPath + "/raw_blobs/" + sha, parser);
5656
}
5757

58-
public void GetArchive(Action<Stream> parser, string sha = null, string format = null)
59-
{
60-
if (!string.IsNullOrEmpty(format) && !format.StartsWith(".", StringComparison.Ordinal))
61-
throw new ArgumentException($"Format must include the '.' as part of extension", nameof(format));
58+
public void GetArchive(Action<Stream> parser) => GetArchive(parser, fileArchiveQuery: null);
6259

63-
var relativePath = $"/archive{format}";
60+
public void GetArchive(Action<Stream> parser, FileArchiveQuery fileArchiveQuery)
61+
{
62+
var url = $"{_repoPath}/archive";
6463

65-
if (!string.IsNullOrEmpty(sha))
66-
relativePath += $"?sha={sha}";
64+
if (fileArchiveQuery != null)
65+
{
66+
// If a particular archive file format is requested, it is appended to the path directly as follows:
67+
// /project/123/repository/archive.zip
68+
// /project/123/repository/archive.tar
69+
if (fileArchiveQuery.Format.HasValue)
70+
{
71+
url += fileArchiveQuery.Format.Value.GetEnumMemberAttributeValue();
72+
}
73+
74+
url = Utils.AddParameter(url, "path", fileArchiveQuery.Path);
75+
url = Utils.AddParameter(url, "sha", fileArchiveQuery.Ref);
76+
}
6777

68-
_api.Get().Stream(_repoPath + relativePath, parser);
78+
_api.Get().Stream(url, parser);
6979
}
7080

7181
public IEnumerable<Commit> Commits => _api.Get().GetAll<Commit>(_repoPath + $"/commits?per_page={GetCommitsRequest.DefaultPerPage}");
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
using System.Runtime.Serialization;
2+
3+
namespace NGitLab.Models;
4+
5+
public enum FileArchiveFormat
6+
{
7+
[EnumMember(Value = ".bz2")]
8+
Bz2,
9+
10+
[EnumMember(Value = ".gz")]
11+
Gz,
12+
13+
[EnumMember(Value = ".tar")]
14+
Tar,
15+
16+
[EnumMember(Value = ".tar.bz2")]
17+
TarBz2,
18+
19+
[EnumMember(Value = ".tar.gz")]
20+
TarGz,
21+
22+
[EnumMember(Value = ".tb2")]
23+
Tb2,
24+
25+
[EnumMember(Value = ".tbz")]
26+
Tbz,
27+
28+
[EnumMember(Value = ".tbz2")]
29+
Tbz2,
30+
31+
[EnumMember(Value = ".zip")]
32+
Zip,
33+
}

NGitLab/Models/FileArchiveQuery.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
namespace NGitLab.Models;
2+
3+
public sealed class FileArchiveQuery
4+
{
5+
public FileArchiveFormat? Format { get; set; }
6+
7+
// This property is named Ref because even though the query string parameter key is 'sha' it accepts any ref
8+
// i.e. branch name, sha, tag
9+
public string Ref { get; set; }
10+
11+
public string Path { get; set; }
12+
}

NGitLab/PublicAPI.Unshipped.txt

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -869,7 +869,8 @@ NGitLab.Impl.RepositoryClient.Commits.get -> System.Collections.Generic.IEnumera
869869
NGitLab.Impl.RepositoryClient.Compare(NGitLab.Models.CompareQuery query) -> NGitLab.Models.CompareResults
870870
NGitLab.Impl.RepositoryClient.Contributors.get -> NGitLab.IContributorClient
871871
NGitLab.Impl.RepositoryClient.Files.get -> NGitLab.IFilesClient
872-
NGitLab.Impl.RepositoryClient.GetArchive(System.Action<System.IO.Stream> parser, string sha = null, string format = null) -> void
872+
NGitLab.Impl.RepositoryClient.GetArchive(System.Action<System.IO.Stream> parser) -> void
873+
NGitLab.Impl.RepositoryClient.GetArchive(System.Action<System.IO.Stream> parser, NGitLab.Models.FileArchiveQuery fileArchiveQuery) -> void
873874
NGitLab.Impl.RepositoryClient.GetCommit(NGitLab.Sha1 sha) -> NGitLab.Models.Commit
874875
NGitLab.Impl.RepositoryClient.GetCommitDiff(NGitLab.Sha1 sha) -> System.Collections.Generic.IEnumerable<NGitLab.Models.Diff>
875876
NGitLab.Impl.RepositoryClient.GetCommitRefs(NGitLab.Sha1 sha, NGitLab.Models.CommitRefType type = NGitLab.Models.CommitRefType.All) -> System.Collections.Generic.IEnumerable<NGitLab.Models.Ref>
@@ -1086,7 +1087,8 @@ NGitLab.IRepositoryClient.Commits.get -> System.Collections.Generic.IEnumerable<
10861087
NGitLab.IRepositoryClient.Compare(NGitLab.Models.CompareQuery query) -> NGitLab.Models.CompareResults
10871088
NGitLab.IRepositoryClient.Contributors.get -> NGitLab.IContributorClient
10881089
NGitLab.IRepositoryClient.Files.get -> NGitLab.IFilesClient
1089-
NGitLab.IRepositoryClient.GetArchive(System.Action<System.IO.Stream> parser, string sha = null, string format = null) -> void
1090+
NGitLab.IRepositoryClient.GetArchive(System.Action<System.IO.Stream> parser) -> void
1091+
NGitLab.IRepositoryClient.GetArchive(System.Action<System.IO.Stream> parser, NGitLab.Models.FileArchiveQuery fileArchiveQuery) -> void
10901092
NGitLab.IRepositoryClient.GetCommit(NGitLab.Sha1 sha) -> NGitLab.Models.Commit
10911093
NGitLab.IRepositoryClient.GetCommitDiff(NGitLab.Sha1 sha) -> System.Collections.Generic.IEnumerable<NGitLab.Models.Diff>
10921094
NGitLab.IRepositoryClient.GetCommitRefs(NGitLab.Sha1 sha, NGitLab.Models.CommitRefType type = NGitLab.Models.CommitRefType.All) -> System.Collections.Generic.IEnumerable<NGitLab.Models.Ref>
@@ -1768,6 +1770,24 @@ NGitLab.Models.EventTargetType.Note = 4 -> NGitLab.Models.EventTargetType
17681770
NGitLab.Models.EventTargetType.Project = 5 -> NGitLab.Models.EventTargetType
17691771
NGitLab.Models.EventTargetType.Snippet = 6 -> NGitLab.Models.EventTargetType
17701772
NGitLab.Models.EventTargetType.User = 7 -> NGitLab.Models.EventTargetType
1773+
NGitLab.Models.FileArchiveFormat
1774+
NGitLab.Models.FileArchiveFormat.Bz2 = 0 -> NGitLab.Models.FileArchiveFormat
1775+
NGitLab.Models.FileArchiveFormat.Gz = 1 -> NGitLab.Models.FileArchiveFormat
1776+
NGitLab.Models.FileArchiveFormat.Tar = 2 -> NGitLab.Models.FileArchiveFormat
1777+
NGitLab.Models.FileArchiveFormat.TarBz2 = 3 -> NGitLab.Models.FileArchiveFormat
1778+
NGitLab.Models.FileArchiveFormat.TarGz = 4 -> NGitLab.Models.FileArchiveFormat
1779+
NGitLab.Models.FileArchiveFormat.Tb2 = 5 -> NGitLab.Models.FileArchiveFormat
1780+
NGitLab.Models.FileArchiveFormat.Tbz = 6 -> NGitLab.Models.FileArchiveFormat
1781+
NGitLab.Models.FileArchiveFormat.Tbz2 = 7 -> NGitLab.Models.FileArchiveFormat
1782+
NGitLab.Models.FileArchiveFormat.Zip = 8 -> NGitLab.Models.FileArchiveFormat
1783+
NGitLab.Models.FileArchiveQuery
1784+
NGitLab.Models.FileArchiveQuery.FileArchiveQuery() -> void
1785+
NGitLab.Models.FileArchiveQuery.Format.get -> NGitLab.Models.FileArchiveFormat?
1786+
NGitLab.Models.FileArchiveQuery.Format.set -> void
1787+
NGitLab.Models.FileArchiveQuery.Path.get -> string
1788+
NGitLab.Models.FileArchiveQuery.Path.set -> void
1789+
NGitLab.Models.FileArchiveQuery.Ref.get -> string
1790+
NGitLab.Models.FileArchiveQuery.Ref.set -> void
17711791
NGitLab.Models.FileData
17721792
NGitLab.Models.FileData.BlobId -> string
17731793
NGitLab.Models.FileData.CommitId -> string

0 commit comments

Comments
 (0)