From 74dc51a6f567395d0c46d97f7270f959d671573e Mon Sep 17 00:00:00 2001 From: William Quelho Ferreira Date: Sun, 24 Jun 2018 09:33:33 -0300 Subject: [PATCH] [WIP] Fixes #1718 Implement Repository Transfer functionality (#1813) * Add "transfer repository" accept header * Create RepositoryTransfer class This will be used to send the POST request to initiate the transfer. * Create Ensure method to check for empty or null arrays * Change arg name in Ensure for nonempty arrays array -> value * Add xmldoc for ArgumentNotNullOrEmptyArray * Create Transfer method in IRepositoriesClient * Implement Transfer method in RepositoriesClient * Fix typo in xmldoc for Transfer * Add to Transfer xmldoc * Create Transfer method in IObservableRepositoriesClient * Implement Transfer in ObservableRepositoriesClient * Add DebuggerDIsplayAttribute do RepositoryTransfer * Add unit tests for RepositoryTransfer constructors * Change TeamId property type to IReadOnlyList * Rewrite DebuggerDisplay property into something more succint * Make new Ensure method into an IEnumerable checker * Add XmlDoc to RepositoryTransfer * Tweaks to first ctor XmlDoc * Create basic unit tests for Transfer * Create ApiUrls.RepositoryTransfer * Use ApiUrls.RepositoryTransfer to get URI in Transfer Previous implementation used wrong URI * Start implementing RepositoriesClientTests.TheTransferMethod * Implement org -> user transfer integration test * Implement user -> org transfer integration test * [WIP] Implement user -> org transfer with teams Implementation doesn't work, API usage seems correct. * Mark transfer user -> org w/ teams integration test with FIXME * Add second end point URI to ApiUrls * Add other Transfer overload to RepositoriesClient for other end point * Create unit tests for other Transfer endpoint * Add overload to IRepositoriesClient * Add integration tests for overload * Reorganize unit tests for TheTransferMethod * Rename id to repositoryId * Reorganize unit tests for RepositoriesClientTests.Transfer * Add second endpoint to IObservableRepositoriesClient * Add XmlDoc to second Transfer endpoint * Add XmlDoc to second Transfer endpoint in RepositoriesClient * Reimplement "with teams" integration tests using TeamContext * Rename integration test for consistency * Add asserts to actual ownership transfer * Rename RepositoryTransfer.TeamId property to TeamIds * Add awaiit to ThrowsAsync in RepositoriesClientTests * Put await in right places for unit tests * Add Ensures for Transfer method in RepositoriesClient * Add XmlDoc to ApiUrls.RepositoryTransfer with repo id * Update XmlDoc for RepositoryTransfer constructor and teamIds property * Rename currentOwner to owner * Add Ensure guards to ObservableRepositoriesClient.Transfer methods * Add unit tests for ObservableRepositoriesClient --- .../Clients/IObservableRepositoriesClient.cs | 23 +++ .../Clients/ObservableRepositoriesClient.cs | 35 ++++ .../Clients/RepositoriesClientTests.cs | 122 ++++++++++++++ .../Clients/RepositoriesClientTests.cs | 152 ++++++++++++++++++ .../Models/RepositoryTransferTest.cs | 86 ++++++++++ .../ObservableRepositoriesClientTests.cs | 65 ++++++++ Octokit/Clients/IRepositoriesClient.cs | 23 +++ Octokit/Clients/RepositoriesClient.cs | 35 ++++ Octokit/Helpers/AcceptHeaders.cs | 2 + Octokit/Helpers/ApiUrls.cs | 21 +++ Octokit/Helpers/Ensure.cs | 15 ++ Octokit/Models/Request/RepositoryTransfer.cs | 58 +++++++ 12 files changed, 637 insertions(+) create mode 100644 Octokit.Tests/Models/RepositoryTransferTest.cs create mode 100644 Octokit/Models/Request/RepositoryTransfer.cs diff --git a/Octokit.Reactive/Clients/IObservableRepositoriesClient.cs b/Octokit.Reactive/Clients/IObservableRepositoriesClient.cs index bbe9ca16b2..2843a46cb4 100644 --- a/Octokit.Reactive/Clients/IObservableRepositoriesClient.cs +++ b/Octokit.Reactive/Clients/IObservableRepositoriesClient.cs @@ -38,6 +38,29 @@ public interface IObservableRepositoriesClient /// An for the operation IObservable Delete(long repositoryId); + /// + /// Transfers the ownership of the specified repository. + /// + /// + /// See the API documentation for more information. + /// + /// The current owner of the repository + /// The name of the repository + /// Repository transfer information + /// A + IObservable Transfer(string owner, string name, RepositoryTransfer repositoryTransfer); + + /// + /// Transfers the ownership of the specified repository. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository + /// Repository transfer information + /// A + IObservable Transfer(long repositoryId, RepositoryTransfer repositoryTransfer); + /// /// Retrieves the for the specified owner and name. /// diff --git a/Octokit.Reactive/Clients/ObservableRepositoriesClient.cs b/Octokit.Reactive/Clients/ObservableRepositoriesClient.cs index e12e92dce1..2b0061d8e0 100644 --- a/Octokit.Reactive/Clients/ObservableRepositoriesClient.cs +++ b/Octokit.Reactive/Clients/ObservableRepositoriesClient.cs @@ -95,6 +95,41 @@ public IObservable Delete(long repositoryId) { return _client.Delete(repositoryId).ToObservable(); } + + /// + /// Transfers the ownership of the specified repository. + /// + /// + /// See the API documentation for more information. + /// + /// The current owner of the repository + /// The name of the repository + /// Repository transfer information + /// A + public IObservable Transfer(string owner, string name, RepositoryTransfer repositoryTransfer) + { + Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner)); + Ensure.ArgumentNotNullOrEmptyString(name, nameof(name)); + Ensure.ArgumentNotNull(repositoryTransfer, nameof(repositoryTransfer)); + + return _client.Transfer(owner, name, repositoryTransfer).ToObservable(); + } + + /// + /// Transfers the ownership of the specified repository. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository + /// Repository transfer information + /// A + public IObservable Transfer(long repositoryId, RepositoryTransfer repositoryTransfer) + { + Ensure.ArgumentNotNull(repositoryTransfer, nameof(repositoryTransfer)); + + return _client.Transfer(repositoryId, repositoryTransfer).ToObservable(); + } /// /// Retrieves the for the specified owner and name. diff --git a/Octokit.Tests.Integration/Clients/RepositoriesClientTests.cs b/Octokit.Tests.Integration/Clients/RepositoriesClientTests.cs index baa6c39c4e..4784be35b8 100644 --- a/Octokit.Tests.Integration/Clients/RepositoriesClientTests.cs +++ b/Octokit.Tests.Integration/Clients/RepositoriesClientTests.cs @@ -1681,4 +1681,126 @@ public async Task ReturnsLicenseContentWithRepositoryId() Assert.Equal("MIT License", license.License.Name); } } + + public class TheTransferMethod + { + [IntegrationTest] + public async Task TransfersFromOrgToUser() + { + var github = Helper.GetAuthenticatedClient(); + var newRepo = new NewRepository(Helper.MakeNameWithTimestamp("transferred-repo")); + var newOwner = Helper.UserName; + using (var context = await github.CreateRepositoryContext(Helper.Organization, newRepo)) + { + var transfer = new RepositoryTransfer(newOwner); + await github.Repository.Transfer(context.RepositoryOwner, context.RepositoryName, transfer); + var transferred = await github.Repository.Get(newOwner, context.RepositoryName); + + Assert.Equal(newOwner, transferred.Owner.Login); + } + } + + [IntegrationTest] + public async Task TransfersFromOrgToUserById() + { + var github = Helper.GetAuthenticatedClient(); + var newRepo = new NewRepository(Helper.MakeNameWithTimestamp("transferred-repo")); + var newOwner = Helper.UserName; + using (var context = await github.CreateRepositoryContext(Helper.Organization, newRepo)) + { + var transfer = new RepositoryTransfer(newOwner); + await github.Repository.Transfer(context.RepositoryId, transfer); + var transferred = await github.Repository.Get(context.RepositoryId); + + Assert.Equal(newOwner, transferred.Owner.Login); + } + } + + [IntegrationTest] + public async Task TransfersFromUserToOrg() + { + var github = Helper.GetAuthenticatedClient(); + var newRepo = new NewRepository(Helper.MakeNameWithTimestamp("transferred-repo")); + var newOwner = Helper.Organization; + using (var context = await github.CreateRepositoryContext(newRepo)) + { + var transfer = new RepositoryTransfer(newOwner); + await github.Repository.Transfer(context.RepositoryOwner, context.RepositoryName, transfer); + var transferred = await github.Repository.Get(newOwner, context.RepositoryName); + + Assert.Equal(newOwner, transferred.Owner.Login); + } + } + + [IntegrationTest] + public async Task TransfersFromUserToOrgById() + { + var github = Helper.GetAuthenticatedClient(); + var newRepo = new NewRepository(Helper.MakeNameWithTimestamp("transferred-repo")); + var newOwner = Helper.Organization; + using (var context = await github.CreateRepositoryContext(newRepo)) + { + var transfer = new RepositoryTransfer(newOwner); + await github.Repository.Transfer(context.RepositoryId, transfer); + var transferred = await github.Repository.Get(context.RepositoryId); + + Assert.Equal(newOwner, transferred.Owner.Login); + } + } + + [IntegrationTest] + public async Task TransfersFromUserToOrgWithTeams() + { + // FIXME API doesn't add teams when transferring to an organization + var github = Helper.GetAuthenticatedClient(); + var newRepo = new NewRepository(Helper.MakeNameWithTimestamp("transferred-repo")); + var newOwner = Helper.Organization; + + using (var repositoryContext = await github.CreateRepositoryContext(newRepo)) + { + NewTeam team = new NewTeam(Helper.MakeNameWithTimestamp("transfer-team")); + using (var teamContext = await github.CreateTeamContext(Helper.Organization, team)) { + var transferTeamIds = new int[] { teamContext.TeamId }; + var transfer = new RepositoryTransfer(newOwner, transferTeamIds); + await github.Repository.Transfer( + repositoryContext.RepositoryOwner, repositoryContext.RepositoryName, transfer); + var transferred = await github.Repository.Get(repositoryContext.RepositoryId); + var repoTeams = await github.Repository.GetAllTeams(repositoryContext.RepositoryId); + + Assert.Equal(newOwner, transferred.Owner.Login); + // transferTeamIds is a subset of repoTeams + Assert.Empty( + transferTeamIds.Except( + repoTeams.Select(t => t.Id))); + } + } + } + + [IntegrationTest] + public async Task TransfersFromUserToOrgWithTeamsById() + { + // FIXME API doesn't add teams when transferring to an organization + var github = Helper.GetAuthenticatedClient(); + var newRepo = new NewRepository(Helper.MakeNameWithTimestamp("transferred-repo")); + var newOwner = Helper.Organization; + + using (var repositoryContext = await github.CreateRepositoryContext(newRepo)) + { + NewTeam team = new NewTeam(Helper.MakeNameWithTimestamp("transfer-team")); + using (var teamContext = await github.CreateTeamContext(Helper.Organization, team)) { + var transferTeamIds = new int[] { teamContext.TeamId }; + var transfer = new RepositoryTransfer(newOwner, transferTeamIds); + await github.Repository.Transfer(repositoryContext.RepositoryId, transfer); + var transferred = await github.Repository.Get(repositoryContext.RepositoryId); + var repoTeams = await github.Repository.GetAllTeams(repositoryContext.RepositoryId); + + Assert.Equal(newOwner, transferred.Owner.Login); + // transferTeamIds is a subset of repoTeams + Assert.Empty( + transferTeamIds.Except( + repoTeams.Select(t => t.Id))); + } + } + } + } } diff --git a/Octokit.Tests/Clients/RepositoriesClientTests.cs b/Octokit.Tests/Clients/RepositoriesClientTests.cs index a59156c395..2ca658c42d 100644 --- a/Octokit.Tests/Clients/RepositoriesClientTests.cs +++ b/Octokit.Tests/Clients/RepositoriesClientTests.cs @@ -211,6 +211,158 @@ public async Task ThrowsRepositoryExistsExceptionForEnterpriseInstance() } } + public class TheTransferMethod + { + [Fact] + public async Task EnsuresNonNullArguments() + { + var connection = Substitute.For(); + var client = new RepositoriesClient(connection); + var transfer = new RepositoryTransfer("newOwner"); + + await Assert.ThrowsAsync( + () => client.Transfer(null, "name", transfer)); + await Assert.ThrowsAsync( + () => client.Transfer("owner", null, transfer)); + await Assert.ThrowsAsync( + () => client.Transfer("owner", "name", null)); + } + + [Fact] + public async Task EnsuresNonNullArgumentsById() + { + var connection = Substitute.For(); + var client = new RepositoriesClient(connection); + var transfer = new RepositoryTransfer("newOwner"); + var repositoryId = 1; + + await Assert.ThrowsAsync( + () => client.Transfer(repositoryId, null)); + } + + [Fact] + public async Task EnsuresNonEmptyArguments() + { + var connection = Substitute.For(); + var client = new RepositoriesClient(connection); + var transfer = new RepositoryTransfer("newOwner"); + + await Assert.ThrowsAsync( + () => client.Transfer("", "name", transfer)); + await Assert.ThrowsAsync( + () => client.Transfer("owner", "", transfer)); + } + + [Fact] + public async Task RequestsCorrectUrl() + { + var connection = Substitute.For(); + var client = new RepositoriesClient(connection); + var teamId = new int[2] {35, 42}; + var transfer = new RepositoryTransfer("newOwner", teamId); + + await client.Transfer("owner", "name", transfer); + + connection.Received() + .Post( + Arg.Is(u => u.ToString() == "repos/owner/name/transfer"), + Arg.Any(), + Arg.Any()); + } + + [Fact] + public async Task RequestsCorrectUrlById() + { + var connection = Substitute.For(); + var client = new RepositoriesClient(connection); + var teamId = new int[2] {35, 42}; + var transfer = new RepositoryTransfer("newOwner", teamId); + var repositoryId = 1; + + await client.Transfer(repositoryId, transfer); + + connection.Received() + .Post( + Arg.Is(u => u.ToString() == "repositories/1/transfer"), + Arg.Any(), + Arg.Any()); + } + + [Fact] + public async Task SendsCorrectRequest() + { + var connection = Substitute.For(); + var client = new RepositoriesClient(connection); + var teamId = new int[2] {35, 42}; + var transfer = new RepositoryTransfer("newOwner", teamId); + + await client.Transfer("owner", "name", transfer); + + connection.Received() + .Post( + Arg.Any(), + Arg.Is( + t => t.NewOwner == "newOwner" && object.Equals(teamId, t.TeamIds)), + Arg.Any()); + } + + [Fact] + public async Task SendsCorrectRequestById() + { + var connection = Substitute.For(); + var client = new RepositoriesClient(connection); + var teamId = new int[2] {35, 42}; + var transfer = new RepositoryTransfer("newOwner", teamId); + var repositoryId = 1; + + await client.Transfer(repositoryId, transfer); + + connection.Received() + .Post( + Arg.Any(), + Arg.Is( + t => t.NewOwner == "newOwner" && object.Equals(teamId, t.TeamIds)), + Arg.Any()); + } + + [Fact] + public async Task SendsPreviewHeader() + { + var connection = Substitute.For(); + var client = new RepositoriesClient(connection); + var teamId = new int[2] {35, 42}; + var transfer = new RepositoryTransfer("newOwner", teamId); + + await client.Transfer("owner", "name", transfer); + + connection.Received() + .Post( + Arg.Any(), + Arg.Any(), + Arg.Is( + s => s.Contains(AcceptHeaders.RepositoryTransferPreview))); + } + + [Fact] + public async Task SendsPreviewHeaderById() + { + var connection = Substitute.For(); + var client = new RepositoriesClient(connection); + var teamId = new int[2] {35, 42}; + var transfer = new RepositoryTransfer("newOwner", teamId); + var repositoryId = 1; + + await client.Transfer(repositoryId, transfer); + + connection.Received() + .Post( + Arg.Any(), + Arg.Any(), + Arg.Is( + s => s.Contains(AcceptHeaders.RepositoryTransferPreview))); + } + } + public class TheDeleteMethod { [Fact] diff --git a/Octokit.Tests/Models/RepositoryTransferTest.cs b/Octokit.Tests/Models/RepositoryTransferTest.cs new file mode 100644 index 0000000000..300923f97b --- /dev/null +++ b/Octokit.Tests/Models/RepositoryTransferTest.cs @@ -0,0 +1,86 @@ +using Xunit; +using System; + +namespace Octokit.Tests.Models +{ + public class RepositoryTransferTest + { + public static readonly string emptyName = ""; + public static readonly string nonemptyName = "name"; + public static readonly int[] emptyTeamId = new int[] {}; + public static readonly int[] nonemptyTeamId = new int[] {1, 2, 3}; + + public class TheSingleArgumentConstructor + { + [Fact] + public void ChecksForEmptyName() + { + Assert.Throws(() => {new RepositoryTransfer(emptyName);}); + } + + [Fact] + public void ChecksForNullName() + { + Assert.Throws(() => {new RepositoryTransfer(null);}); + } + + [Fact] + public void StoresGivenName() + { + string testName = nonemptyName; + RepositoryTransfer repositoryTransfer = new RepositoryTransfer(testName); + Assert.Equal(repositoryTransfer.NewOwner, testName); + } + + [Fact] + public void SetsTeamIdToNull() + { + RepositoryTransfer repositoryTransfer = new RepositoryTransfer(nonemptyName); + Assert.Null(repositoryTransfer.TeamIds); + } + } + + public class TheFullConstructor + { + [Fact] + public void ChecksForEmptyName() + { + Assert.Throws(() => {new RepositoryTransfer(emptyName, nonemptyTeamId);}); + } + + [Fact] + public void ChecksForNullName() + { + Assert.Throws(() => {new RepositoryTransfer(null, nonemptyTeamId);}); + } + + [Fact] + public void ChecksForEmptyTeamId() + { + Assert.Throws(() => {new RepositoryTransfer(nonemptyName, emptyTeamId);}); + } + + [Fact] + public void ChecksForNullTeamId() + { + Assert.Throws(() => {new RepositoryTransfer(nonemptyName, null);}); + } + + [Fact] + public void StoresGivenName() + { + string testName = nonemptyName; + RepositoryTransfer repositoryTransfer = new RepositoryTransfer(testName, nonemptyTeamId); + Assert.Equal(repositoryTransfer.NewOwner, testName); + } + + [Fact] + public void StoresGivenTeamId() + { + int[] testTeamId = nonemptyTeamId; + RepositoryTransfer repositoryTransfer = new RepositoryTransfer(nonemptyName, testTeamId); + Assert.Equal(repositoryTransfer.TeamIds, testTeamId); + } + } + } +} \ No newline at end of file diff --git a/Octokit.Tests/Reactive/ObservableRepositoriesClientTests.cs b/Octokit.Tests/Reactive/ObservableRepositoriesClientTests.cs index 29f6b04d6e..7bbfc4e867 100644 --- a/Octokit.Tests/Reactive/ObservableRepositoriesClientTests.cs +++ b/Octokit.Tests/Reactive/ObservableRepositoriesClientTests.cs @@ -21,6 +21,71 @@ public void EnsuresNonNullArguments() } } + public class TheTransferMethod + { + [Fact] + public void EnsuresNonNullArguments() + { + var gitHubClient = Substitute.For(); + var client = new ObservableRepositoriesClient(gitHubClient); + var transfer = new RepositoryTransfer("newOwner"); + + Assert.Throws( + () => client.Transfer(null, "name", transfer)); + Assert.Throws( + () => client.Transfer("owner", null, transfer)); + Assert.Throws( + () => client.Transfer("owner", "name", null)); + } + + [Fact] + public void EnsuresNonNullArgumentsById() + { + var gitHubClient = Substitute.For(); + var client = new ObservableRepositoriesClient(gitHubClient); + var transfer = new RepositoryTransfer("newOwner"); + var repositoryId = 1; + + Assert.Throws( + () => client.Transfer(repositoryId, null)); + } + + [Fact] + public void EnsuresNonEmptyArguments() + { + var gitHubClient = Substitute.For(); + var client = new ObservableRepositoriesClient(gitHubClient); + var transfer = new RepositoryTransfer("newOwner"); + + Assert.Throws( + () => client.Transfer("", "name", transfer)); + Assert.Throws( + () => client.Transfer("owner", "", transfer)); + } + + [Fact] + public void CallsIntoClient() + { + var gitHubClient = Substitute.For(); + var client = new ObservableRepositoriesClient(gitHubClient); + var transfer = new RepositoryTransfer("newOwner"); + + client.Transfer("owner", "name", transfer); + gitHubClient.Repository.Received().Transfer("owner", "name", transfer); + } + + [Fact] + public void CallsIntoClientById() + { + var gitHubClient = Substitute.For(); + var client = new ObservableRepositoriesClient(gitHubClient); + var transfer = new RepositoryTransfer("newOwner"); + + client.Transfer(1, transfer); + gitHubClient.Repository.Received().Transfer(1, transfer); + } + } + public class TheDeleteMethod { [Fact] diff --git a/Octokit/Clients/IRepositoriesClient.cs b/Octokit/Clients/IRepositoriesClient.cs index f77f0bfb19..617b93c2fb 100644 --- a/Octokit/Clients/IRepositoriesClient.cs +++ b/Octokit/Clients/IRepositoriesClient.cs @@ -100,6 +100,29 @@ public interface IRepositoriesClient /// Thrown when a general API error occurs. Task Delete(long repositoryId); + /// + /// Transfers the ownership of the specified repository. + /// + /// + /// See the API documentation for more information. + /// + /// The current owner of the repository + /// The name of the repository + /// Repository transfer information + /// A + Task Transfer(string owner, string name, RepositoryTransfer repositoryTransfer); + + /// + /// Transfers the ownership of the specified repository. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository + /// Repository transfer information + /// A + Task Transfer(long repositoryId, RepositoryTransfer repositoryTransfer); + /// /// Gets the specified repository. /// diff --git a/Octokit/Clients/RepositoriesClient.cs b/Octokit/Clients/RepositoriesClient.cs index 68565559e1..558f4828cf 100644 --- a/Octokit/Clients/RepositoriesClient.cs +++ b/Octokit/Clients/RepositoriesClient.cs @@ -163,6 +163,41 @@ public Task Delete(long repositoryId) return ApiConnection.Delete(ApiUrls.Repository(repositoryId)); } + /// + /// Transfers the ownership of the specified repository. + /// + /// + /// See the API documentation for more information. + /// + /// The current owner of the repository + /// The name of the repository + /// Repository transfer information + /// A + public Task Transfer(string owner, string name, RepositoryTransfer repositoryTransfer) + { + Ensure.ArgumentNotNullOrEmptyString(owner, nameof(owner)); + Ensure.ArgumentNotNullOrEmptyString(name, nameof(name)); + Ensure.ArgumentNotNull(repositoryTransfer, nameof(repositoryTransfer)); + + return ApiConnection.Post(ApiUrls.RepositoryTransfer(owner, name), repositoryTransfer, AcceptHeaders.RepositoryTransferPreview); + } + + /// + /// Transfers the ownership of the specified repository. + /// + /// + /// See the API documentation for more information. + /// + /// The id of the repository + /// Repository transfer information + /// A + public Task Transfer(long repositoryId, RepositoryTransfer repositoryTransfer) + { + Ensure.ArgumentNotNull(repositoryTransfer, nameof(repositoryTransfer)); + + return ApiConnection.Post(ApiUrls.RepositoryTransfer(repositoryId), repositoryTransfer, AcceptHeaders.RepositoryTransferPreview); + } + /// /// Updates the specified repository with the values given in /// diff --git a/Octokit/Helpers/AcceptHeaders.cs b/Octokit/Helpers/AcceptHeaders.cs index c096074e44..d9bbfc424b 100644 --- a/Octokit/Helpers/AcceptHeaders.cs +++ b/Octokit/Helpers/AcceptHeaders.cs @@ -61,6 +61,8 @@ public static class AcceptHeaders public const string LabelsApiPreview = "application/vnd.github.symmetra-preview+json"; + public const string RepositoryTransferPreview = "application/vnd.github.nightshade-preview+json"; + /// /// Combines multiple preview headers. GitHub API supports Accept header with multiple /// values separated by comma. diff --git a/Octokit/Helpers/ApiUrls.cs b/Octokit/Helpers/ApiUrls.cs index cd978d596b..6cda2e72bc 100644 --- a/Octokit/Helpers/ApiUrls.cs +++ b/Octokit/Helpers/ApiUrls.cs @@ -1834,6 +1834,27 @@ public static Uri RepositoryTags(string owner, string name) return "repos/{0}/{1}/tags".FormatUri(owner, name); } + /// + /// Returns the for a repository transfer. + /// + /// The current owner of the repository + /// The name of the repository + /// + public static Uri RepositoryTransfer(string owner, string name) + { + return "repos/{0}/{1}/transfer".FormatUri(owner, name); + } + + /// + /// Returns the for a repository transfer. + /// + /// The id of the repository + /// + public static Uri RepositoryTransfer(long repositoryId) + { + return "repositories/{0}/transfer".FormatUri(repositoryId); + } + /// /// Returns the for repository commits. /// diff --git a/Octokit/Helpers/Ensure.cs b/Octokit/Helpers/Ensure.cs index e089f152fc..59a7a23b09 100644 --- a/Octokit/Helpers/Ensure.cs +++ b/Octokit/Helpers/Ensure.cs @@ -1,4 +1,6 @@ using System; +using System.Linq; +using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; namespace Octokit @@ -47,6 +49,19 @@ public static void GreaterThanZero([ValidatedNotNull]TimeSpan value, string name throw new ArgumentException("Timespan must be greater than zero", name); } + + /// + /// Checks an enumerable argument to ensure it isn't null or empty. + /// + /// The argument value to check + /// The name of the argument + public static void ArgumentNotNullOrEmptyEnumerable([ValidatedNotNull]IEnumerable value, string name) + { + ArgumentNotNull(value, name); + if (Enumerable.Any(value)) return; + + throw new ArgumentException("List cannot be empty", name); + } } [AttributeUsage(AttributeTargets.Parameter)] diff --git a/Octokit/Models/Request/RepositoryTransfer.cs b/Octokit/Models/Request/RepositoryTransfer.cs new file mode 100644 index 0000000000..ce57bf6817 --- /dev/null +++ b/Octokit/Models/Request/RepositoryTransfer.cs @@ -0,0 +1,58 @@ +using System; +using System.Text; +using System.Diagnostics; +using System.Globalization; +using System.Collections.Generic; + +namespace Octokit +{ + /// + /// Describes the transfer of a repository to a new owner. + /// + [DebuggerDisplay("{DebuggerDisplay,nq}")] + public class RepositoryTransfer + { + /// + /// Creates a new repository transfer description with no team Ids. + /// + /// The new owner of the repository after the transfer. + public RepositoryTransfer(string newOwner) + { + Ensure.ArgumentNotNullOrEmptyString(newOwner, nameof(newOwner)); + + NewOwner = newOwner; + } + + /// + /// Creates a new repository transfer description. + /// + /// The new owner of the repository after the transfer. + /// A list of team Ids to add to the repository after the transfer (only applies to transferring to an Organization). + public RepositoryTransfer(string newOwner, IReadOnlyList teamIds) + : this(newOwner) + { + Ensure.ArgumentNotNullOrEmptyEnumerable(teamIds, nameof(teamIds)); + + TeamIds = teamIds; + } + + /// + /// The new owner of the repository after the transfer. + /// + public string NewOwner { get; set; } + + /// + /// A list of team Ids to add to the repository after the transfer (only applies to transferring to an Organization). + /// + public IReadOnlyList TeamIds { get; set; } + + internal string DebuggerDisplay + { + get + { + string teamIdsStr = string.Join(", ", TeamIds ?? new int[0]); + return string.Format(CultureInfo.InvariantCulture, "NewOwner: {0}, TeamIds: [{1}]", NewOwner, teamIdsStr); + } + } + } +} \ No newline at end of file