diff --git a/Octokit.Reactive/Clients/ObservableRepositoriesClient.cs b/Octokit.Reactive/Clients/ObservableRepositoriesClient.cs
index 6084e1c7..1511259c 100644
--- a/Octokit.Reactive/Clients/ObservableRepositoriesClient.cs
+++ b/Octokit.Reactive/Clients/ObservableRepositoriesClient.cs
@@ -15,6 +15,20 @@ namespace Octokit.Reactive.Clients
_client = client;
}
+ ///
+ /// Creates a new repository for the current user.
+ ///
+ /// A instance describing the new repository to create
+ /// An instance for the created repository
+ public IObservable Create(NewRepository newRepository)
+ {
+ Ensure.ArgumentNotNull(newRepository, "newRepository");
+ if (string.IsNullOrEmpty(newRepository.Name))
+ throw new ArgumentException("The new repository's name must not be null.");
+
+ return _client.Create(newRepository).ToObservable();
+ }
+
public IObservable Get(string owner, string name)
{
Ensure.ArgumentNotNullOrEmptyString(owner, "owner");
diff --git a/Octokit.Reactive/IObservableRepositoriesClient.cs b/Octokit.Reactive/IObservableRepositoriesClient.cs
index bc8a1a32..2dc6bb33 100644
--- a/Octokit.Reactive/IObservableRepositoriesClient.cs
+++ b/Octokit.Reactive/IObservableRepositoriesClient.cs
@@ -6,6 +6,13 @@ namespace Octokit.Reactive
{
public interface IObservableRepositoriesClient
{
+ ///
+ /// Creates a new repository for the current user.
+ ///
+ /// A instance describing the new repository to create
+ /// An instance for the created repository
+ IObservable Create(NewRepository newRepository);
+
///
/// Retrieves the for the specified owner and name.
///
diff --git a/Octokit.Tests.Integration/AutomationSettings.cs b/Octokit.Tests.Integration/AutomationSettings.cs
index fca5ac97..ea7ae1df 100644
--- a/Octokit.Tests.Integration/AutomationSettings.cs
+++ b/Octokit.Tests.Integration/AutomationSettings.cs
@@ -57,5 +57,15 @@ namespace Octokit.Tests.Integration
/// Username of a GitHub test account (DO NOT USE A "REAL" ACCOUNT).
///
public string GitHubUsername { get; private set; }
+
+ ///
+ /// Makes a name with an appended timestamp so that it's safe for testing (i.e., won't collide with existing names).
+ ///
+ /// The name to use as a base, to which a timestamp will be appended
+ /// The name with a timestamp appended
+ public static string MakeNameWithTimestamp(string name)
+ {
+ return string.Concat(name, "-", DateTime.UtcNow.ToString("yyyyMMddhhmmssfff"));
+ }
}
}
diff --git a/Octokit.Tests.Integration/RepositoriesClientTests.cs b/Octokit.Tests.Integration/RepositoriesClientTests.cs
index d53d27c2..67f5ece5 100644
--- a/Octokit.Tests.Integration/RepositoriesClientTests.cs
+++ b/Octokit.Tests.Integration/RepositoriesClientTests.cs
@@ -1,4 +1,5 @@
using System;
+using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Xunit;
@@ -6,6 +7,199 @@ namespace Octokit.Tests.Integration
{
public class RepositoriesClientTests
{
+ public class TheCreateMethod
+ {
+ [IntegrationTest]
+ public async Task CreatesANewPublicRepository()
+ {
+ var github = new GitHubClient("Test Runner User Agent")
+ {
+ Credentials = AutomationSettings.Current.GitHubCredentials
+ };
+ var repoName = AutomationSettings.MakeNameWithTimestamp("public-repo");
+
+ var createdRepository = await github.Repository.Create(new NewRepository { Name = repoName });
+
+ var cloneUrl = string.Format("https://github.com/{0}/{1}.git", github.Credentials.Login, repoName);
+ Assert.Equal(repoName, createdRepository.Name);
+ Assert.False(createdRepository.Private);
+ Assert.Equal(cloneUrl, createdRepository.CloneUrl);
+ var repository = await github.Repository.Get(github.Credentials.Login, repoName);
+ Assert.Equal(repoName, repository.Name);
+ Assert.Null(repository.Description);
+ Assert.False(repository.Private);
+ Assert.True(repository.HasDownloads);
+ Assert.True(repository.HasIssues);
+ Assert.True(repository.HasWiki);
+ Assert.Null(repository.Homepage);
+ }
+
+ [IntegrationTest]
+ public async Task CreatesANewPrivateRepository()
+ {
+ var github = new GitHubClient("Test Runner User Agent")
+ {
+ Credentials = AutomationSettings.Current.GitHubCredentials
+ };
+ var repoName = AutomationSettings.MakeNameWithTimestamp("private-repo");
+
+ var createdRepository = await github.Repository.Create(new NewRepository
+ {
+ Name = repoName,
+ Private = true
+ });
+
+ Assert.True(createdRepository.Private);
+ var repository = await github.Repository.Get(github.Credentials.Login, repoName);
+ Assert.True(repository.Private);
+ }
+
+ [IntegrationTest]
+ public async Task CreatesARepositoryWithoutDownloads()
+ {
+ var github = new GitHubClient("Test Runner User Agent")
+ {
+ Credentials = AutomationSettings.Current.GitHubCredentials
+ };
+ var repoName = AutomationSettings.MakeNameWithTimestamp("repo-without-downloads");
+
+ var createdRepository = await github.Repository.Create(new NewRepository
+ {
+ Name = repoName,
+ HasDownloads = false
+ });
+
+ Assert.False(createdRepository.HasDownloads);
+ var repository = await github.Repository.Get(github.Credentials.Login, repoName);
+ Assert.False(repository.HasDownloads);
+ }
+
+ [IntegrationTest]
+ public async Task CreatesARepositoryWithoutIssues()
+ {
+ var github = new GitHubClient("Test Runner User Agent")
+ {
+ Credentials = AutomationSettings.Current.GitHubCredentials
+ };
+ var repoName = AutomationSettings.MakeNameWithTimestamp("repo-without-issues");
+
+ var createdRepository = await github.Repository.Create(new NewRepository
+ {
+ Name = repoName,
+ HasIssues = false
+ });
+
+ Assert.False(createdRepository.HasIssues);
+ var repository = await github.Repository.Get(github.Credentials.Login, repoName);
+ Assert.False(repository.HasIssues);
+ }
+
+ [IntegrationTest]
+ public async Task CreatesARepositoryWithoutAWiki()
+ {
+ var github = new GitHubClient("Test Runner User Agent")
+ {
+ Credentials = AutomationSettings.Current.GitHubCredentials
+ };
+ var repoName = AutomationSettings.MakeNameWithTimestamp("repo-without-wiki");
+
+ var createdRepository = await github.Repository.Create(new NewRepository
+ {
+ Name = repoName,
+ HasWiki = false
+ });
+
+ Assert.False(createdRepository.HasWiki);
+ var repository = await github.Repository.Get(github.Credentials.Login, repoName);
+ Assert.False(repository.HasWiki);
+ }
+
+ [IntegrationTest]
+ public async Task CreatesARepositoryWithADescription()
+ {
+ var github = new GitHubClient("Test Runner User Agent")
+ {
+ Credentials = AutomationSettings.Current.GitHubCredentials
+ };
+ var repoName = AutomationSettings.MakeNameWithTimestamp("repo-with-description");
+
+ var createdRepository = await github.Repository.Create(new NewRepository
+ {
+ Name = repoName,
+ Description = "theDescription"
+ });
+
+ Assert.Equal("theDescription", createdRepository.Description);
+ var repository = await github.Repository.Get(github.Credentials.Login, repoName);
+ Assert.Equal("theDescription", repository.Description);
+ }
+
+ [IntegrationTest]
+ public async Task CreatesARepositoryWithAHomepage()
+ {
+ var github = new GitHubClient("Test Runner User Agent")
+ {
+ Credentials = AutomationSettings.Current.GitHubCredentials
+ };
+ var repoName = AutomationSettings.MakeNameWithTimestamp("repo-with-homepage");
+
+ var createdRepository = await github.Repository.Create(new NewRepository
+ {
+ Name = repoName,
+ Homepage = "http://aUrl.to/nowhere"
+ });
+
+ Assert.Equal("http://aUrl.to/nowhere", createdRepository.Homepage);
+ var repository = await github.Repository.Get(github.Credentials.Login, repoName);
+ Assert.Equal("http://aUrl.to/nowhere", repository.Homepage);
+ }
+
+ [IntegrationTest]
+ public async Task CreatesARepositoryWithAutoInit()
+ {
+ var github = new GitHubClient("Test Runner User Agent")
+ {
+ Credentials = AutomationSettings.Current.GitHubCredentials
+ };
+ var repoName = AutomationSettings.MakeNameWithTimestamp("repo-with-autoinit");
+
+ var createdRepository = await github.Repository.Create(new NewRepository
+ {
+ Name = repoName,
+ AutoInit = true
+ });
+
+ // TODO: Once the contents API has been added, check the actual files in the created repo
+ Assert.Equal(repoName, createdRepository.Name);
+ var repository = await github.Repository.Get(github.Credentials.Login, repoName);
+ Assert.Equal(repoName, repository.Name);
+ }
+
+ [IntegrationTest]
+ public async Task CreatesARepositoryWithAGitignoreTemplate()
+ {
+ var github = new GitHubClient("Test Runner User Agent")
+ {
+ Credentials = AutomationSettings.Current.GitHubCredentials
+ };
+ var repoName = AutomationSettings.MakeNameWithTimestamp("repo-with-gitignore");
+
+ var createdRepository = await github.Repository.Create(new NewRepository
+ {
+ Name = repoName,
+ AutoInit = true,
+ GitignoreTemplate = "visualstudio"
+ });
+
+ // TODO: Once the contents API has been added, check the actual files in the created repo
+ Assert.Equal(repoName, createdRepository.Name);
+ var repository = await github.Repository.Get(github.Credentials.Login, repoName);
+ Assert.Equal(repoName, repository.Name);
+ }
+
+ // TODO: Add a test for the team_id param once an overload that takes an oranization is added
+ }
+
public class TheGetAsyncMethod
{
[IntegrationTest]
diff --git a/Octokit.Tests/Clients/RepositoriesClientTests.cs b/Octokit.Tests/Clients/RepositoriesClientTests.cs
index 08a0ec5b..2c9a698d 100644
--- a/Octokit.Tests/Clients/RepositoriesClientTests.cs
+++ b/Octokit.Tests/Clients/RepositoriesClientTests.cs
@@ -24,6 +24,41 @@ namespace Octokit.Tests.Clients
}
}
+ public class TheCreateMethod
+ {
+ [Fact]
+ public async Task EnsuresNonNullArguments()
+ {
+ var repositoriesClient = new RepositoriesClient(Substitute.For>());
+
+ await AssertEx.Throws(async () => await repositoriesClient.Create(null));
+ await AssertEx.Throws(async () => await repositoriesClient.Create(new NewRepository { Name = null }));
+ }
+
+ [Fact]
+ public void UsesTheUserReposUrl()
+ {
+ var client = Substitute.For>();
+ var repositoriesClient = new RepositoriesClient(client);
+
+ repositoriesClient.Create(new NewRepository { Name = "aName" });
+
+ client.Received().Create(Arg.Is(u => u.ToString() == "user/repos"), Arg.Any());
+ }
+
+ [Fact]
+ public void TheNewRepositoryDescription()
+ {
+ var client = Substitute.For>();
+ var repositoriesClient = new RepositoriesClient(client);
+ var newRepository = new NewRepository { Name = "aName" };
+
+ repositoriesClient.Create(newRepository);
+
+ client.Received().Create(Arg.Any(), newRepository);
+ }
+ }
+
public class TheGetMethod
{
[Fact]
diff --git a/Octokit.Tests/SimpleJsonSerializerTests.cs b/Octokit.Tests/SimpleJsonSerializerTests.cs
index 0c69c8ba..cf523cfa 100644
--- a/Octokit.Tests/SimpleJsonSerializerTests.cs
+++ b/Octokit.Tests/SimpleJsonSerializerTests.cs
@@ -1,4 +1,5 @@
-using Octokit.Http;
+using System;
+using Octokit.Http;
using Xunit;
namespace Octokit.Tests
@@ -16,6 +17,52 @@ namespace Octokit.Tests
Assert.Equal("{\"id\":42,\"first_name\":\"Phil\",\"is_something\":true,\"private\":true}", json);
}
+
+ [Fact]
+ public void OmitsPropertiesWithNullValue()
+ {
+ var item = new
+ {
+ Object = (object)null,
+ NullableInt = (int?)null,
+ NullableBool = (bool?)null
+ };
+
+ var json = new SimpleJsonSerializer().Serialize(item);
+
+ Assert.Equal("{}", json);
+ }
+
+ [Fact]
+ public void DoesNotOmitsNullablePropertiesWithAValue()
+ {
+ var item = new
+ {
+ Object = new { Id = 42 },
+ NullableInt = (int?)1066,
+ NullableBool = (bool?)true
+ };
+
+ var json = new SimpleJsonSerializer().Serialize(item);
+
+ Assert.Equal("{\"object\":{\"id\":42},\"nullable_int\":1066,\"nullable_bool\":true}", json);
+ }
+
+ [Fact]
+ public void HandlesMixingNullAndNotNullData()
+ {
+ var item = new
+ {
+ Int = 42,
+ Bool = true,
+ NullableInt = (int?)null,
+ NullableBool = (bool?)null
+ };
+
+ var json = new SimpleJsonSerializer().Serialize(item);
+
+ Assert.Equal("{\"int\":42,\"bool\":true}", json);
+ }
}
public class TheDeserializeMethod
diff --git a/Octokit/Clients/RepositoriesClient.cs b/Octokit/Clients/RepositoriesClient.cs
index 7cf0025c..4480c465 100644
--- a/Octokit/Clients/RepositoriesClient.cs
+++ b/Octokit/Clients/RepositoriesClient.cs
@@ -11,6 +11,21 @@ namespace Octokit.Clients
{
}
+ ///
+ /// Creates a new repository for the current user.
+ ///
+ /// A instance describing the new repository to create
+ /// A instance for the created repository
+ public async Task Create(NewRepository newRepository)
+ {
+ Ensure.ArgumentNotNull(newRepository, "newRepository");
+ if (string.IsNullOrEmpty(newRepository.Name))
+ throw new ArgumentException("The new repository's name must not be null.");
+
+ var endpoint = new Uri("user/repos", UriKind.Relative);
+ return await Client.Create(endpoint, newRepository);
+ }
+
public async Task Get(string owner, string name)
{
Ensure.ArgumentNotNullOrEmptyString(owner, "owner");
diff --git a/Octokit/GitHubModels.cs b/Octokit/GitHubModels.cs
index 9562494b..230e2d5f 100644
--- a/Octokit/GitHubModels.cs
+++ b/Octokit/GitHubModels.cs
@@ -530,4 +530,61 @@ namespace Octokit
public string Resource { get; set; }
}
+
+ ///
+ /// Describes a new repository to create via the method.
+ ///
+ public class NewRepository
+ {
+ ///
+ /// Optional. Gets or sets whether to create an initial commit with empty README. The default is false.
+ ///
+ public bool? AutoInit { get; set; }
+
+ ///
+ /// Required. Gets or sets the new repository's description
+ ///
+ public string Description { get; set; }
+
+ /// s
+ /// Optional. Gets or sets whether to the enable downloads for the new repository. The default is true.
+ ///
+ public bool? HasDownloads { get; set; }
+
+ /// s
+ /// Optional. Gets or sets whether to the enable issues for the new repository. The default is true.
+ ///
+ public bool? HasIssues { get; set; }
+
+ /// s
+ /// Optional. Gets or sets whether to the enable the wiki for the new repository. The default is true.
+ ///
+ public bool? HasWiki { get; set; }
+
+ ///
+ /// Optional. Gets or sets the new repository's optional website.
+ ///
+ public string Homepage { get; set; }
+
+ ///
+ /// Optional. Gets or sets the desired language's or platform's .gitignore template to apply. Use the name of the template without the extension; "Haskell", for example. Ignored if is null or false.
+ ///
+ [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Gitignore", Justification = "It needs to be this way for proper serialization.")]
+ public string GitignoreTemplate { get; set; }
+
+ ///
+ /// Required. Gets or sets the new repository's name.
+ ///
+ public string Name { get; set; }
+
+ ///
+ /// Optional. Gets or sets whether the new repository is private; the default is false.
+ ///
+ public bool? Private { get; set; }
+
+ ///
+ /// Optional. Gets or sets the ID of the team to grant access to this repository. This is only valid when creating a repository for an organization.
+ ///
+ public int? TeamId { get; set; }
+ }
}
diff --git a/Octokit/Http/SimpleJsonSerializer.cs b/Octokit/Http/SimpleJsonSerializer.cs
index 8c846cf4..8876b95b 100644
--- a/Octokit/Http/SimpleJsonSerializer.cs
+++ b/Octokit/Http/SimpleJsonSerializer.cs
@@ -1,4 +1,10 @@
-namespace Octokit.Http
+using System;
+using System.Collections.Generic;
+using System.Diagnostics.CodeAnalysis;
+using System.Reflection;
+using Octokit.Reflection;
+
+namespace Octokit.Http
{
public class SimpleJsonSerializer : IJsonSerializer
{
@@ -20,6 +26,32 @@
{
return clrPropertyName.ToRubyCase();
}
+
+ // This is overridden so that null values are omitted from serialized objects.
+ [SuppressMessage("Microsoft.Design", "CA1007:UseGenericsWhereAppropriate", Justification = "Need to support .NET 2")]
+ protected override bool TrySerializeUnknownTypes(object input, out object output)
+ {
+ if (input == null) throw new ArgumentNullException("input");
+ output = null;
+ Type type = input.GetType();
+ if (type.FullName == null)
+ return false;
+ IDictionary obj = new JsonObject();
+ IDictionary getters = GetCache[type];
+ foreach (KeyValuePair getter in getters)
+ {
+ if (getter.Value != null)
+ {
+ var value = getter.Value(input);
+ if (value == null)
+ continue;
+
+ obj.Add(MapClrMemberNameToJsonFieldName(getter.Key), value);
+ }
+ }
+ output = obj;
+ return true;
+ }
}
}
}
diff --git a/Octokit/IRepositoriesClient.cs b/Octokit/IRepositoriesClient.cs
index 47c7a44d..e6028c21 100644
--- a/Octokit/IRepositoriesClient.cs
+++ b/Octokit/IRepositoriesClient.cs
@@ -7,6 +7,13 @@ namespace Octokit
{
public interface IRepositoriesClient
{
+ ///
+ /// Creates a new repository for the current user.
+ ///
+ /// A instance describing the new repository to create
+ /// A instance for the created repository
+ Task Create(NewRepository newRepository);
+
///
/// Retrieves the for the specified owner and name.
///
diff --git a/Octokit/Octokit.csproj b/Octokit/Octokit.csproj
index d4227ada..258ac06c 100644
--- a/Octokit/Octokit.csproj
+++ b/Octokit/Octokit.csproj
@@ -72,6 +72,7 @@
..\packages\Microsoft.Bcl.1.1.3\lib\net40\System.Runtime.dll
+
..\packages\Microsoft.Bcl.1.1.3\lib\net40\System.Threading.Tasks.dll