diff --git a/.gitattributes b/.gitattributes index 5a2e01507..73eb6afac 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,6 @@ *.sln crlf *.csproj crlf -*.cs crlf +*.cs crlf diff=csharp *.md crlf diff --git a/CHANGELOG.md b/CHANGELOG.md index 6bd09f597..61944c636 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,27 @@ - Source code: - NuGet package: +## v0.6 + +### Additions + + - Add Configuration.Get() overload that takes key in parts + - Add tracking branch details (#75) + - Allow creation of commit using signature from configuration files + - Add Index.Remove() (#78) + - Add a string indexer to the Commit and Tree types in order to ease retrieval of TreeEntries + +### Changes + + - Provide default value for non existent configuration setting (#67) + - Change the tree structure into which libgit2 binaries are located (#70) + - Update libgit2 binaries to libgit2/libgit2@28c1451 + +### Fixes + + - Prevent enumeration of branches from throwing when the repository contains remote branches (#69) + - Fix Index.Stage(), Index.Unstage() (#78) + ## v0.5 ### Additions diff --git a/CI-build.msbuild b/CI-build.msbuild index aa9d7b432..10eb91c9b 100644 --- a/CI-build.msbuild +++ b/CI-build.msbuild @@ -39,5 +39,12 @@ + + + + + + \ No newline at end of file diff --git a/Lib/NativeBinaries/amd64/dummy.txt b/Lib/NativeBinaries/amd64/dummy.txt new file mode 100644 index 000000000..8fa586a84 --- /dev/null +++ b/Lib/NativeBinaries/amd64/dummy.txt @@ -0,0 +1 @@ +Here will be placed the x64 binaries of libgit2 \ No newline at end of file diff --git a/Lib/NativeBinaries/x86/git2.dll b/Lib/NativeBinaries/x86/git2.dll new file mode 100644 index 000000000..2f07707e2 Binary files /dev/null and b/Lib/NativeBinaries/x86/git2.dll differ diff --git a/Lib/NativeBinaries/x86/git2.pdb b/Lib/NativeBinaries/x86/git2.pdb new file mode 100644 index 000000000..108bfd92a Binary files /dev/null and b/Lib/NativeBinaries/x86/git2.pdb differ diff --git a/Lib/git2.dll b/Lib/git2.dll deleted file mode 100644 index 79943a7ea..000000000 Binary files a/Lib/git2.dll and /dev/null differ diff --git a/Lib/git2.pdb b/Lib/git2.pdb deleted file mode 100644 index 9a43b5e71..000000000 Binary files a/Lib/git2.pdb and /dev/null differ diff --git a/LibGit2Sharp.Tests/BranchFixture.cs b/LibGit2Sharp.Tests/BranchFixture.cs index 03a8db41a..395f934b4 100644 --- a/LibGit2Sharp.Tests/BranchFixture.cs +++ b/LibGit2Sharp.Tests/BranchFixture.cs @@ -10,22 +10,6 @@ public class BranchFixture : BaseFixture { private readonly string[] expectedBranches = new[] { "br2", "master", "packed", "packed-test", "test", }; - [Test] - public void CanCheckoutAnExistingBranch() - { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) - { - Branch master = repo.Branches["master"]; - master.IsCurrentRepositoryHead.ShouldBeTrue(); - - Branch test = repo.Branches.Checkout("test"); - - test.IsCurrentRepositoryHead.ShouldBeTrue(); - master.IsCurrentRepositoryHead.ShouldBeFalse(); - } - } - [Test] public void CanCreateBranch() { @@ -77,7 +61,6 @@ public void CanCreateBranchFromImplicitHead() } [Test] - [Ignore("Not implemented yet.")] public void CanCreateBranchFromExplicitHead() { TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); @@ -90,6 +73,89 @@ public void CanCreateBranchFromExplicitHead() } } + [Test] + public void CreatingABranchFromATagPeelsToTheCommit() + { + TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); + using (var repo = new Repository(path.RepositoryPath)) + { + const string name = "i-peel-tag"; + Branch newBranch = repo.CreateBranch(name, "refs/tags/test"); + newBranch.ShouldNotBeNull(); + newBranch.Tip.Sha.ShouldEqual("e90810b8df3e80c413d903f631643c716887138d"); + } + } + + [Test] + public void CreatingABranchTriggersTheCreationOfADirectReference() + { + TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); + using (var repo = new Repository(path.RepositoryPath)) + { + Branch newBranch = repo.CreateBranch("clone-of-master"); + newBranch.IsCurrentRepositoryHead.ShouldBeFalse(); + + ObjectId commitId = repo.Head.Tip.Id; + newBranch.Tip.Id.ShouldEqual(commitId); + + Reference reference = repo.Refs[newBranch.CanonicalName]; + reference.ShouldNotBeNull(); + Assert.IsInstanceOf(typeof(DirectReference), reference); + } + } + + [Test] + public void CreatingABranchFromANonCommitObjectThrows() + { + using (var repo = new Repository(Constants.BareTestRepoPath)) + { + const string name = "sorry-dude-i-do-not-do-blobs-nor-trees"; + Assert.Throws(() => repo.CreateBranch(name, "refs/tags/point_to_blob")); + Assert.Throws(() => repo.CreateBranch(name, "53fc32d")); + Assert.Throws(() => repo.CreateBranch(name, "0266163")); + } + } + + [Test] + public void CreatingBranchWithUnknownNamedTargetThrows() + { + using (var repo = new Repository(Constants.BareTestRepoPath)) + { + Assert.Throws(() => repo.Branches.Create("my_new_branch", "my_old_branch")); + } + } + + [Test] + public void CreatingBranchWithUnknownShaTargetThrows() + { + using (var repo = new Repository(Constants.BareTestRepoPath)) + { + Assert.Throws(() => repo.Branches.Create("my_new_branch", Constants.UnknownSha)); + Assert.Throws(() => repo.Branches.Create("my_new_branch", Constants.UnknownSha.Substring(0, 7))); + } + } + + [Test] + public void CreatingABranchPointingAtANonCanonicalReferenceThrows() + { + using (var repo = new Repository(Constants.BareTestRepoPath)) + { + Assert.Throws(() => repo.Branches.Create("nocanonicaltarget", "br2")); + } + } + + [Test] + public void CreatingBranchWithBadParamsThrows() + { + using (var repo = new Repository(Constants.BareTestRepoPath)) + { + Assert.Throws(() => repo.Branches.Create(null, repo.Head.CanonicalName)); + Assert.Throws(() => repo.Branches.Create(string.Empty, repo.Head.CanonicalName)); + Assert.Throws(() => repo.Branches.Create("bad_branch", null)); + Assert.Throws(() => repo.Branches.Create("bad_branch", string.Empty)); + } + } + [Test] public void CanListAllBranches() { @@ -101,6 +167,25 @@ public void CanListAllBranches() } } + [Test] + public void CanListAllBranchesIncludingRemoteRefs() + { + TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(Constants.StandardTestRepoPath); + using (var repo = new Repository(path.RepositoryPath)) + { + var expectedBranchesIncludingRemoteRefs = new[] + { + new { Name = "master", Sha = "592d3c869dbc4127fc57c189cb94f2794fa84e7e", IsRemote = false }, + new { Name = "origin/HEAD", Sha = "580c2111be43802dab11328176d94c391f1deae9", IsRemote = true }, + new { Name = "origin/br2", Sha = "a4a7dce85cf63874e984719f4fdd239f5145052f", IsRemote = true }, + new { Name = "origin/master", Sha = "580c2111be43802dab11328176d94c391f1deae9", IsRemote = true }, + new { Name = "origin/packed-test", Sha = "4a202b346bb0fb0db7eff3cffeb3c70babbd2045", IsRemote = true }, + new { Name = "origin/test", Sha = "e90810b8df3e80c413d903f631643c716887138d", IsRemote = true }, + }; + CollectionAssert.AreEqual(expectedBranchesIncludingRemoteRefs, repo.Branches.Select(b => new { b.Name, b.Tip.Sha, b.IsRemote }).ToArray()); + } + } + [Test] public void CanLookupABranchByItsCanonicalName() { @@ -135,88 +220,125 @@ public void CanLookupLocalBranch() } [Test] - public void CanWalkCommitsFromAnotherBranch() + public void LookingOutABranchByNameWithBadParamsThrows() { using (var repo = new Repository(Constants.BareTestRepoPath)) { - Branch master = repo.Branches["test"]; - master.Commits.Count().ShouldEqual(2); + Branch branch; + Assert.Throws(() => branch = repo.Branches[null]); + Assert.Throws(() => branch = repo.Branches[""]); } } [Test] - public void CanWalkCommitsFromBranch() + public void TrackingInformationIsEmptyForNonTrackingBranch() { using (var repo = new Repository(Constants.BareTestRepoPath)) { - Branch master = repo.Branches["master"]; - master.Commits.Count().ShouldEqual(7); + Branch branch = repo.Branches["test"]; + branch.IsTracking.ShouldBeFalse(); + branch.TrackedBranch.ShouldBeNull(); + branch.AheadBy.ShouldEqual(0); + branch.BehindBy.ShouldEqual(0); } } [Test] - public void CheckoutBranchWithBadParamsThrows() + public void CanGetTrackingInformationForTrackingBranch() { - using (var repo = new Repository(Constants.BareTestRepoPath)) + using (var repo = new Repository(Constants.StandardTestRepoPath)) { - Assert.Throws(() => repo.Branches.Checkout(string.Empty)); - Assert.Throws(() => repo.Branches.Checkout(null)); + Branch master = repo.Branches["master"]; + master.IsTracking.ShouldBeTrue(); + master.TrackedBranch.ShouldEqual(repo.Branches["refs/remotes/origin/master"]); + master.AheadBy.ShouldEqual(1); + master.BehindBy.ShouldEqual(2); } } [Test] - public void CreatingBranchWithEmptyNameThrows() + public void CanWalkCommitsFromAnotherBranch() { using (var repo = new Repository(Constants.BareTestRepoPath)) { - Assert.Throws(() => repo.Branches.Create(string.Empty, repo.Head.CanonicalName)); + Branch master = repo.Branches["test"]; + master.Commits.Count().ShouldEqual(2); } } [Test] - [Ignore("Not implemented yet.")] - public void CreatingBranchWithUnknownNamedTargetThrows() + public void CanWalkCommitsFromBranch() { using (var repo = new Repository(Constants.BareTestRepoPath)) { - Assert.Throws(() => repo.Branches.Create("my_new_branch", "my_old_branch")); + Branch master = repo.Branches["master"]; + master.Commits.Count().ShouldEqual(7); } } - [Test] - public void CreatingBranchWithUnknownShaTargetThrows() + [TestCase("test")] + [TestCase("refs/heads/test")] + public void CanCheckoutAnExistingBranch(string name) { - using (var repo = new Repository(Constants.BareTestRepoPath)) + TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); + using (var repo = new Repository(path.RepositoryPath)) { - Assert.Throws(() => repo.Branches.Create("my_new_branch", Constants.UnknownSha)); - Assert.Throws(() => repo.Branches.Create("my_new_branch", Constants.UnknownSha.Substring(0, 7))); + Branch master = repo.Branches["master"]; + master.IsCurrentRepositoryHead.ShouldBeTrue(); + + Branch test = repo.Branches.Checkout(name); + repo.Info.IsHeadDetached.ShouldBeFalse(); + + test.IsRemote.ShouldBeFalse(); + test.IsCurrentRepositoryHead.ShouldBeTrue(); + test.ShouldEqual(repo.Head); + + master.IsCurrentRepositoryHead.ShouldBeFalse(); } } - [Test] - public void CreatingBranchWithEmptyTargetThrows() + [TestCase("6dcf9bf")] + [TestCase("refs/tags/lw")] + public void CanCheckoutAnArbitraryCommit(string commitPointer) { - using (var repo = new Repository(Constants.BareTestRepoPath)) + TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); + using (var repo = new Repository(path.RepositoryPath)) { - Assert.Throws(() => repo.Branches.Create("bad_branch", string.Empty)); + Branch master = repo.Branches["master"]; + master.IsCurrentRepositoryHead.ShouldBeTrue(); + + Branch detachedHead = repo.Branches.Checkout(commitPointer); + + repo.Info.IsHeadDetached.ShouldBeTrue(); + + detachedHead.IsRemote.ShouldBeFalse(); + detachedHead.CanonicalName.ShouldEqual(detachedHead.Name); + detachedHead.CanonicalName.ShouldEqual("(no branch)"); + detachedHead.Tip.Sha.ShouldEqual(repo.Lookup(commitPointer).Sha); + + detachedHead.IsCurrentRepositoryHead.ShouldBeTrue(); + detachedHead.ShouldEqual(repo.Head); + + master.IsCurrentRepositoryHead.ShouldBeFalse(); } } [Test] - public void CreatingBranchWithNullNameThrows() + public void CheckingOutANonExistingBranchThrows() { using (var repo = new Repository(Constants.BareTestRepoPath)) { - Assert.Throws(() => repo.Branches.Create(null, repo.Head.CanonicalName)); + Assert.Throws(() => repo.Branches.Checkout("i-do-not-exist")); } } [Test] - public void CreatingBranchWithNullTargetThrows() + public void CheckingOutABranchWithBadParamsThrows() { using (var repo = new Repository(Constants.BareTestRepoPath)) { - Assert.Throws(() => repo.Branches.Create("bad_branch", null)); + Assert.Throws(() => repo.Branches.Checkout(string.Empty)); + Assert.Throws(() => repo.Branches.Checkout(null)); } } @@ -330,23 +452,5 @@ public void CanMoveABranchWhileOverwritingAnExistingOne() newTest.Tip.ShouldEqual(br2.Tip); } } - - [Test] - public void CreatingABranchTriggersTheCreationOfADirectReference() - { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); - using (var repo = new Repository(path.RepositoryPath)) - { - Branch newBranch = repo.CreateBranch("clone-of-master"); - newBranch.IsCurrentRepositoryHead.ShouldBeFalse(); - - ObjectId commitId = repo.Head.Tip.Id; - newBranch.Tip.Id.ShouldEqual(commitId); - - Reference reference = repo.Refs[newBranch.CanonicalName]; - reference.ShouldNotBeNull(); - Assert.IsInstanceOf(typeof(DirectReference), reference); - } - } } } diff --git a/LibGit2Sharp.Tests/CommitFixture.cs b/LibGit2Sharp.Tests/CommitFixture.cs index 8af664bcf..6607c4c64 100644 --- a/LibGit2Sharp.Tests/CommitFixture.cs +++ b/LibGit2Sharp.Tests/CommitFixture.cs @@ -196,6 +196,37 @@ public void CanEnumerateCommitsWithTopoSorting() } } + [Test] + public void CanEnumerateFromHead() + { + AssertEnumerationOfCommits( + repo => new Filter { Since = repo.Head }, + new[] + { + "4c062a6", "be3563a", "c47800c", "9fd738e", + "4a202b3", "5b5b025", "8496071", + }); + } + + [Test] + public void CanEnumerateFromDetachedHead() + { + TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); + using (var repoClone = new Repository(path.RepositoryPath)) + { + string headSha = repoClone.Head.Tip.Sha; + repoClone.Branches.Checkout(headSha); + + AssertEnumerationOfCommitsInRepo(repoClone, + repo => new Filter { Since = repo.Head }, + new[] + { + "4c062a6", "be3563a", "c47800c", "9fd738e", + "4a202b3", "5b5b025", "8496071", + }); + } + } + [Test] public void CanEnumerateUsingTwoHeadsAsBoundaries() { @@ -206,7 +237,7 @@ public void CanEnumerateUsingTwoHeadsAsBoundaries() } [Test] - public void CanEnumerateUsingOneHeadAsBoundaries() + public void CanEnumerateUsingImplicitHeadAsSinceBoundary() { AssertEnumerationOfCommits( repo => new Filter { Until = "refs/heads/br2" }, @@ -293,12 +324,17 @@ private static void AssertEnumerationOfCommits(Func filterBu { using (var repo = new Repository(Constants.BareTestRepoPath)) { - ICommitCollection commits = repo.Commits.QueryBy(filterBuilder(repo)); + AssertEnumerationOfCommitsInRepo(repo, filterBuilder, abbrevIds); + } + } - IEnumerable commitShas = commits.Select(c => c.Id.ToString(7)).ToArray(); + private static void AssertEnumerationOfCommitsInRepo(Repository repo, Func filterBuilder, IEnumerable abbrevIds) + { + ICommitCollection commits = repo.Commits.QueryBy(filterBuilder(repo)); - CollectionAssert.AreEqual(abbrevIds, commitShas); - } + IEnumerable commitShas = commits.Select(c => c.Id.ToString(7)).ToArray(); + + CollectionAssert.AreEqual(abbrevIds, commitShas); } [Test] @@ -354,6 +390,79 @@ public void CanReadCommitWithMultipleParents() } } + [Test] + public void CanDirectlyAccessABlobOfTheCommit() + { + using (var repo = new Repository(Constants.BareTestRepoPath)) + { + var commit = repo.Lookup("4c062a6"); + + var blob = commit["1/branch_file.txt"].Target as Blob; + blob.ShouldNotBeNull(); + + blob.ContentAsUtf8().ShouldEqual("hi\n"); + } + } + + [Test] + public void CanDirectlyAccessATreeOfTheCommit() + { + using (var repo = new Repository(Constants.BareTestRepoPath)) + { + var commit = repo.Lookup("4c062a6"); + + var tree1 = commit["1"].Target as Tree; + tree1.ShouldNotBeNull(); + } + } + + [Test] + public void DirectlyAccessingAnUnknownTreeEntryOfTheCommitReturnsNull() + { + using (var repo = new Repository(Constants.BareTestRepoPath)) + { + var commit = repo.Lookup("4c062a6"); + + commit["I-am-not-here"].ShouldBeNull(); + } + } + + [Test] + public void CanCommitWithSignatureFromConfig() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + string dir = Repository.Init(scd.DirectoryPath); + Path.IsPathRooted(dir).ShouldBeTrue(); + Directory.Exists(dir).ShouldBeTrue(); + + using (var repo = new Repository(dir)) + { + InconclusiveIf(() => !repo.Config.HasGlobalConfig, "No Git global configuration available"); + + const string relativeFilepath = "new.txt"; + string filePath = Path.Combine(repo.Info.WorkingDirectory, relativeFilepath); + + File.WriteAllText(filePath, "null"); + repo.Index.Stage(relativeFilepath); + File.AppendAllText(filePath, "token\n"); + repo.Index.Stage(relativeFilepath); + + repo.Head[relativeFilepath].ShouldBeNull(); + + Commit commit = repo.Commit("Initial egotistic commit"); + + AssertBlobContent(repo.Head[relativeFilepath], "nulltoken\n"); + AssertBlobContent(commit[relativeFilepath], "nulltoken\n"); + + var name = repo.Config.Get("user.name", null); + var email = repo.Config.Get("user.email", null); + Assert.AreEqual(commit.Author.Name, name); + Assert.AreEqual(commit.Author.Email, email); + Assert.AreEqual(commit.Committer.Name, name); + Assert.AreEqual(commit.Committer.Email, email); + } + } + [Test] public void CanCommitALittleBit() { @@ -364,41 +473,61 @@ public void CanCommitALittleBit() using (var repo = new Repository(dir)) { - string filePath = Path.Combine(repo.Info.WorkingDirectory, "new.txt"); + const string relativeFilepath = "new.txt"; + string filePath = Path.Combine(repo.Info.WorkingDirectory, relativeFilepath); File.WriteAllText(filePath, "null"); - repo.Index.Stage("new.txt"); + repo.Index.Stage(relativeFilepath); File.AppendAllText(filePath, "token\n"); - repo.Index.Stage("new.txt"); + repo.Index.Stage(relativeFilepath); + + repo.Head[relativeFilepath].ShouldBeNull(); var author = new Signature("Author N. Ame", "him@there.com", DateTimeOffset.Now.AddSeconds(-10)); - Commit commit = repo.Commit(author, author, "Initial egotistic commit"); + Commit commit = repo.Commit("Initial egotistic commit", author, author); + + AssertBlobContent(repo.Head[relativeFilepath], "nulltoken\n"); + AssertBlobContent(commit[relativeFilepath], "nulltoken\n"); commit.Parents.Count().ShouldEqual(0); repo.Info.IsEmpty.ShouldBeFalse(); File.WriteAllText(filePath, "nulltoken commits!\n"); - repo.Index.Stage("new.txt"); + repo.Index.Stage(relativeFilepath); var author2 = new Signature(author.Name, author.Email, author.When.AddSeconds(5)); - Commit commit2 = repo.Commit(author2, author2, "Are you trying to fork me?"); + Commit commit2 = repo.Commit("Are you trying to fork me?", author2, author2); + + AssertBlobContent(repo.Head[relativeFilepath], "nulltoken commits!\n"); + AssertBlobContent(commit2[relativeFilepath], "nulltoken commits!\n"); commit2.Parents.Count().ShouldEqual(1); commit2.Parents.First().Id.ShouldEqual(commit.Id); - repo.CreateBranch("davidfowl-rules", commit.Id.Sha); //TODO: This cries for a shortcut method :-/ - repo.Branches.Checkout("davidfowl-rules"); //TODO: This cries for a shortcut method :-/ + Branch firstCommitBranch = repo.CreateBranch("davidfowl-rules", commit.Id.Sha); //TODO: This cries for a shortcut method :-/ + repo.Branches.Checkout(firstCommitBranch.Name); //TODO: This cries for a shortcut method :-/ File.WriteAllText(filePath, "davidfowl commits!\n"); var author3 = new Signature("David Fowler", "david.fowler@microsoft.com", author.When.AddSeconds(2)); - repo.Index.Stage("new.txt"); + repo.Index.Stage(relativeFilepath); + + Commit commit3 = repo.Commit("I'm going to branch you backwards in time!", author3, author3); - Commit commit3 = repo.Commit(author3, author3, "I'm going to branch you backwards in time!"); + AssertBlobContent(repo.Head[relativeFilepath], "davidfowl commits!\n"); + AssertBlobContent(commit3[relativeFilepath], "davidfowl commits!\n"); commit3.Parents.Count().ShouldEqual(1); commit3.Parents.First().Id.ShouldEqual(commit.Id); + + AssertBlobContent(firstCommitBranch[relativeFilepath], "nulltoken\n"); } } + + private static void AssertBlobContent(TreeEntry entry, string expectedContent) + { + entry.Type.ShouldEqual(GitObjectType.Blob); + ((Blob)(entry.Target)).ContentAsUtf8().ShouldEqual(expectedContent); + } } } diff --git a/LibGit2Sharp.Tests/ConfigurationFixture.cs b/LibGit2Sharp.Tests/ConfigurationFixture.cs index 0d18c3428..5793e86c0 100644 --- a/LibGit2Sharp.Tests/ConfigurationFixture.cs +++ b/LibGit2Sharp.Tests/ConfigurationFixture.cs @@ -9,20 +9,60 @@ namespace LibGit2Sharp.Tests [TestFixture] public class ConfigurationFixture : BaseFixture { - private static void AssertValueInConfigFile(string repoPath, string regex) + private static void AssertValueInLocalConfigFile(string repoPath, string regex) { var configFilePath = Path.Combine(repoPath, "config"); + AssertValueInConfigFile(configFilePath, regex); + } + + private static void AssertValueInConfigFile(string configFilePath, string regex) + { var text = File.ReadAllText(configFilePath); - var r = new Regex(regex).Match(text); + var r = new Regex(regex, RegexOptions.Multiline).Match(text); Assert.IsTrue(r.Success, text); } + private static void AssertValueInGlobalConfigFile(string regex) + { + var configFilePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "../.gitconfig"); + AssertValueInConfigFile(configFilePath, regex); + } + + [Test] + public void CanDeleteConfiguration() + { + var path = BuildTemporaryCloneOfTestRepo(Constants.StandardTestRepoPath); + using (var repo = new Repository(path.RepositoryPath)) + { + repo.Config.Get("unittests.boolsetting", false).ShouldBeFalse(); + + repo.Config.Set("unittests.boolsetting", true); + repo.Config.Get("unittests.boolsetting", false).ShouldBeTrue(); + + repo.Config.Delete("unittests.boolsetting"); + + repo.Config.Get("unittests.boolsetting", false).ShouldBeFalse(); + } + } + + [Test] + public void CanGetGlobalStringValue() + { + using (var repo = new Repository(Constants.StandardTestRepoPath)) + { + InconclusiveIf(() => !repo.Config.HasGlobalConfig, "No Git global configuration available"); + + repo.Config.Get("user.name", null).ShouldNotBeNull(); + } + } + [Test] public void CanReadBooleanValue() { using (var repo = new Repository(Constants.StandardTestRepoPath)) { - Assert.IsFalse(repo.Config.Get("core.bare")); + Assert.IsTrue(repo.Config.Get("core.ignorecase", false)); + Assert.IsTrue(repo.Config.Get("core", "ignorecase", false)); } } @@ -31,7 +71,8 @@ public void CanReadIntValue() { using (var repo = new Repository(Constants.StandardTestRepoPath)) { - Assert.AreEqual(2, repo.Config.Get("unittests.intsetting")); + Assert.AreEqual(2, repo.Config.Get("unittests.intsetting", 42)); + Assert.AreEqual(2, repo.Config.Get("unittests", "intsetting", 42)); } } @@ -40,7 +81,8 @@ public void CanReadLongValue() { using (var repo = new Repository(Constants.StandardTestRepoPath)) { - Assert.AreEqual(15234, repo.Config.Get("unittests.longsetting")); + Assert.AreEqual(15234, repo.Config.Get("unittests.longsetting", 42)); + Assert.AreEqual(15234, repo.Config.Get("unittests", "longsetting", 42)); } } @@ -49,7 +91,8 @@ public void CanReadStringValue() { using (var repo = new Repository(Constants.StandardTestRepoPath)) { - Assert.AreEqual("+refs/heads/*:refs/remotes/origin/*", repo.Config.Get("remotes.origin.fetch")); + Assert.AreEqual("+refs/heads/*:refs/remotes/origin/*", repo.Config.Get("remote.origin.fetch", null)); + Assert.AreEqual("+refs/heads/*:refs/remotes/origin/*", repo.Config.Get("remote", "origin", "fetch", null)); } } @@ -61,24 +104,30 @@ public void CanSetBooleanValue() { repo.Config.Set("unittests.boolsetting", true); - AssertValueInConfigFile(path.RepositoryPath, "boolsetting = true$"); + AssertValueInLocalConfigFile(path.RepositoryPath, "boolsetting = true$"); } } [Test] - public void CanDeleteConfiguration() + public void CanSetGlobalStringValue() { - var path = BuildTemporaryCloneOfTestRepo(Constants.StandardTestRepoPath); - using (var repo = new Repository(path.RepositoryPath)) + using (var repo = new Repository(Constants.StandardTestRepoPath)) { - repo.Config.Set("unittests.boolsetting", true); + InconclusiveIf(() => !repo.Config.HasGlobalConfig, "No Git global configuration available"); - repo.Config.Delete("unittests.boolsetting"); - } // config file is guaranteed to be saved when config object is freed + var existing = repo.Config.Get("user.name", null); + existing.ShouldNotBeNull(); - using (var repo = new Repository(path.RepositoryPath)) - { - Assert.Throws(() => repo.Config.Get("unittests.boolsetting")); + try + { + repo.Config.Set("user.name", "Unit Test", ConfigurationLevel.Global); + + AssertValueInGlobalConfigFile("name = Unit Test$"); + } + finally + { + repo.Config.Set("user.name", existing, ConfigurationLevel.Global); + } } } @@ -90,7 +139,7 @@ public void CanSetIntValue() { repo.Config.Set("unittests.intsetting", 3); - AssertValueInConfigFile(path.RepositoryPath, "intsetting = 3$"); + AssertValueInLocalConfigFile(path.RepositoryPath, "intsetting = 3$"); } } @@ -102,7 +151,7 @@ public void CanSetLongValue() { repo.Config.Set("unittests.longsetting", (long)451); - AssertValueInConfigFile(path.RepositoryPath, "longsetting = 451"); + AssertValueInLocalConfigFile(path.RepositoryPath, "longsetting = 451"); } } @@ -114,7 +163,7 @@ public void CanSetStringValue() { repo.Config.Set("unittests.stringsetting", "val"); - AssertValueInConfigFile(path.RepositoryPath, "stringsetting = val$"); + AssertValueInLocalConfigFile(path.RepositoryPath, "stringsetting = val$"); } } @@ -123,20 +172,24 @@ public void ReadingUnsupportedTypeThrows() { using (var repo = new Repository(Constants.StandardTestRepoPath)) { - Assert.Throws(() => repo.Config.Get("unittests.setting")); - Assert.Throws(() => repo.Config.Get("unittests.setting")); + Assert.Throws(() => repo.Config.Get("unittests.setting", 42)); + Assert.Throws(() => repo.Config.Get("unittests.setting", null)); } } [Test] - public void ReadingValueThatDoesntExistThrows() + public void ReadingValueThatDoesntExistReturnsDefault() { using (var repo = new Repository(Constants.StandardTestRepoPath)) { - Assert.Throws(() => repo.Config.Get("unittests.ghostsetting")); - Assert.Throws(() => repo.Config.Get("unittests.ghostsetting")); - Assert.Throws(() => repo.Config.Get("unittests.ghostsetting")); - Assert.Throws(() => repo.Config.Get("unittests.ghostsetting")); + repo.Config.Get("unittests.ghostsetting", null).ShouldBeNull(); + repo.Config.Get("unittests.ghostsetting", 0).ShouldEqual(0); + repo.Config.Get("unittests.ghostsetting", 0L).ShouldEqual(0L); + repo.Config.Get("unittests.ghostsetting", false).ShouldBeFalse(); + repo.Config.Get("unittests.ghostsetting", "42").ShouldEqual("42"); + repo.Config.Get("unittests.ghostsetting", 42).ShouldEqual(42); + repo.Config.Get("unittests.ghostsetting", 42L).ShouldEqual(42L); + repo.Config.Get("unittests.ghostsetting", true).ShouldBeTrue(); } } @@ -146,7 +199,7 @@ public void SettingUnsupportedTypeThrows() using (var repo = new Repository(Constants.StandardTestRepoPath)) { Assert.Throws(() => repo.Config.Set("unittests.setting", (short)123)); - Assert.Throws(() => repo.Config.Set("unittests.setting", new Configuration(repo))); + Assert.Throws(() => repo.Config.Set("unittests.setting", repo.Config)); } } } diff --git a/LibGit2Sharp.Tests/IndexFixture.cs b/LibGit2Sharp.Tests/IndexFixture.cs index 8d2e92254..879e388ae 100644 --- a/LibGit2Sharp.Tests/IndexFixture.cs +++ b/LibGit2Sharp.Tests/IndexFixture.cs @@ -55,7 +55,7 @@ public void CanFetchAnIndexEntryByItsName() } [Test] - public void FetchingAnUnknwonIndexEntryReturnsNull() + public void FetchingAnUnknownIndexEntryReturnsNull() { using (var repo = new Repository(Constants.StandardTestRepoPath)) { @@ -65,54 +65,119 @@ public void FetchingAnUnknwonIndexEntryReturnsNull() } [Test] - public void CanStageANewFile() + public void ReadIndexWithBadParamsFails() + { + using (var repo = new Repository(Constants.StandardTestRepoPath)) + { + Assert.Throws(() => { IndexEntry entry = repo.Index[null]; }); + Assert.Throws(() => { IndexEntry entry = repo.Index[string.Empty]; }); + } + } + + [TestCase("1/branch_file.txt", FileStatus.Unaltered, true, FileStatus.Unaltered, true, 0)] + [TestCase("deleted_unstaged_file.txt", FileStatus.Missing, true, FileStatus.Removed, false, -1)] + [TestCase("modified_unstaged_file.txt", FileStatus.Modified, true, FileStatus.Staged, true, 0)] + [TestCase("new_untracked_file.txt", FileStatus.Untracked, false, FileStatus.Added, true, 1)] + [TestCase("modified_staged_file.txt", FileStatus.Staged, true, FileStatus.Staged, true, 0)] + [TestCase("new_tracked_file.txt", FileStatus.Added, true, FileStatus.Added, true, 0)] + public void CanStage(string relativePath, FileStatus currentStatus, bool doesCurrentlyExistInTheIndex, FileStatus expectedStatusOnceStaged, bool doesExistInTheIndexOnceStaged, int expectedIndexCountVariation) { TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(Constants.StandardTestRepoWorkingDirPath); using (var repo = new Repository(path.RepositoryPath)) { int count = repo.Index.Count; - const string filename = "unit_test.txt"; - repo.Index[filename].ShouldBeNull(); - File.WriteAllText(Path.Combine(repo.Info.WorkingDirectory, filename), "some contents"); + (repo.Index[relativePath] != null).ShouldEqual(doesCurrentlyExistInTheIndex); + repo.Index.RetrieveStatus(relativePath).ShouldEqual(currentStatus); - repo.Index.Stage(filename); + repo.Index.Stage(relativePath); - repo.Index.Count.ShouldEqual(count + 1); - repo.Index[filename].ShouldNotBeNull(); + repo.Index.Count.ShouldEqual(count + expectedIndexCountVariation); + (repo.Index[relativePath] != null).ShouldEqual(doesExistInTheIndexOnceStaged); + repo.Index.RetrieveStatus(relativePath).ShouldEqual(expectedStatusOnceStaged); } } [Test] - public void StagingANewVersionOfAFileThenUnstagingRevertsTheBlobToTheVersionOfHead() + public void CanStageTheUpdationOfAStagedFile() { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - string dir = Repository.Init(scd.DirectoryPath); + TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(Constants.StandardTestRepoWorkingDirPath); + using (var repo = new Repository(path.RepositoryPath)) + { + int count = repo.Index.Count; + const string filename = "new_tracked_file.txt"; + IndexEntry blob = repo.Index[filename]; - using (var repo = new Repository(dir)) + repo.Index.RetrieveStatus(filename).ShouldEqual(FileStatus.Added); + + File.WriteAllText(Path.Combine(repo.Info.WorkingDirectory, filename), "brand new content"); + repo.Index.RetrieveStatus(filename).ShouldEqual(FileStatus.Added | FileStatus.Modified); + + repo.Index.Stage(filename); + IndexEntry newBlob = repo.Index[filename]; + + repo.Index.Count.ShouldEqual(count); + blob.Id.ShouldNotEqual(newBlob.Id); + repo.Index.RetrieveStatus(filename).ShouldEqual(FileStatus.Added); + } + } + + [TestCase("1/I-do-not-exist.txt", FileStatus.Nonexistent)] + [TestCase("deleted_staged_file.txt", FileStatus.Removed)] + public void StagingAnUnknownFileThrows(string relativePath, FileStatus status) + { + using (var repo = new Repository(Constants.StandardTestRepoPath)) { - repo.Index.Count.ShouldEqual(0); + repo.Index[relativePath].ShouldBeNull(); + repo.Index.RetrieveStatus(relativePath).ShouldEqual(status); - const string fileName = "myFile.txt"; + Assert.Throws(() => repo.Index.Stage(relativePath)); + } + } - string fullpath = Path.Combine(repo.Info.WorkingDirectory, fileName); + [Test] + public void CanStageTheRemovalOfAStagedFile() + { + TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(Constants.StandardTestRepoWorkingDirPath); + using (var repo = new Repository(path.RepositoryPath)) + { + int count = repo.Index.Count; + const string filename = "new_tracked_file.txt"; + repo.Index[filename].ShouldNotBeNull(); - const string initialContent = "Hello?"; - File.AppendAllText(fullpath, initialContent); + repo.Index.RetrieveStatus(filename).ShouldEqual(FileStatus.Added); - repo.Index.Stage(fileName); + File.Delete(Path.Combine(repo.Info.WorkingDirectory, filename)); + repo.Index.RetrieveStatus(filename).ShouldEqual(FileStatus.Added | FileStatus.Missing); + + repo.Index.Stage(filename); + repo.Index[filename].ShouldBeNull(); + + repo.Index.Count.ShouldEqual(count - 1); + repo.Index.RetrieveStatus(filename).ShouldEqual(FileStatus.Nonexistent); + } + } + + [Test] + public void StagingANewVersionOfAFileThenUnstagingItRevertsTheBlobToTheVersionOfHead() + { + var path = BuildTemporaryCloneOfTestRepo(Constants.StandardTestRepoWorkingDirPath); + using (var repo = new Repository(path.RepositoryPath)) + { + int count = repo.Index.Count; + + const string fileName = "1/branch_file.txt"; ObjectId blobId = repo.Index[fileName].Id; - repo.Commit(Constants.Signature, Constants.Signature, "Initial commit"); - repo.Index.Count.ShouldEqual(1); + string fullpath = Path.Combine(repo.Info.WorkingDirectory, fileName); File.AppendAllText(fullpath, "Is there there anybody out there?"); repo.Index.Stage(fileName); - repo.Index.Count.ShouldEqual(1); + repo.Index.Count.ShouldEqual(count); repo.Index[fileName].Id.ShouldNotEqual((blobId)); repo.Index.Unstage(fileName); - repo.Index.Count.ShouldEqual(1); + repo.Index.Count.ShouldEqual(count); repo.Index[fileName].Id.ShouldEqual((blobId)); } } @@ -182,6 +247,71 @@ public void StagingANewFileWithAFullPathWhichEscapesOutOfTheWorkingDirThrows() } } + [Test] + public void StageFileWithBadParamsThrows() + { + using (var repo = new Repository(Constants.StandardTestRepoPath)) + { + Assert.Throws(() => repo.Index.Stage(string.Empty)); + Assert.Throws(() => repo.Index.Stage(null)); + } + } + + [TestCase("1/branch_file.txt", FileStatus.Unaltered, true, FileStatus.Unaltered, true, 0)] + [TestCase("deleted_unstaged_file.txt", FileStatus.Missing, true, FileStatus.Missing, true, 0)] + [TestCase("modified_unstaged_file.txt", FileStatus.Modified, true, FileStatus.Modified, true, 0)] + [TestCase("new_untracked_file.txt", FileStatus.Untracked, false, FileStatus.Untracked, false, 0)] + [TestCase("modified_staged_file.txt", FileStatus.Staged, true, FileStatus.Modified, true, 0)] + [TestCase("new_tracked_file.txt", FileStatus.Added, true, FileStatus.Untracked, false, -1)] + public void CanUnStage(string relativePath, FileStatus currentStatus, bool doesCurrentlyExistInTheIndex, FileStatus expectedStatusOnceStaged, bool doesExistInTheIndexOnceStaged, int expectedIndexCountVariation) + { + TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(Constants.StandardTestRepoWorkingDirPath); + using (var repo = new Repository(path.RepositoryPath)) + { + int count = repo.Index.Count; + (repo.Index[relativePath] != null).ShouldEqual(doesCurrentlyExistInTheIndex); + repo.Index.RetrieveStatus(relativePath).ShouldEqual(currentStatus); + + repo.Index.Unstage(relativePath); + + repo.Index.Count.ShouldEqual(count + expectedIndexCountVariation); + (repo.Index[relativePath] != null).ShouldEqual(doesExistInTheIndexOnceStaged); + repo.Index.RetrieveStatus(relativePath).ShouldEqual(expectedStatusOnceStaged); + } + } + + [Test] + public void CanUnstageTheRemovalOfAFile() + { + TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(Constants.StandardTestRepoWorkingDirPath); + using (var repo = new Repository(path.RepositoryPath)) + { + int count = repo.Index.Count; + + const string filename = "deleted_staged_file.txt"; + + string fullPath = Path.Combine(repo.Info.WorkingDirectory, filename); + File.Exists(fullPath).ShouldBeFalse(); + + repo.Index.RetrieveStatus(filename).ShouldEqual(FileStatus.Removed); + + repo.Index.Unstage(filename); + repo.Index.Count.ShouldEqual(count + 1); + + repo.Index.RetrieveStatus(filename).ShouldEqual(FileStatus.Missing); + } + } + + [Test] + public void UnstagingFileWithBadParamsThrows() + { + using (var repo = new Repository(Constants.StandardTestRepoPath)) + { + Assert.Throws(() => repo.Index.Stage(string.Empty)); + Assert.Throws(() => repo.Index.Stage(null)); + } + } + [Test] public void CanRenameAFile() { @@ -211,7 +341,7 @@ public void CanRenameAFile() repo.Index.Count.ShouldEqual(1); Signature who = Constants.Signature; - repo.Commit(who, who, "Initial commit"); + repo.Commit("Initial commit", who, who); repo.Index.RetrieveStatus(oldName).ShouldEqual(FileStatus.Unaltered); @@ -225,7 +355,7 @@ public void CanRenameAFile() repo.Index[newName].Id.Sha.ShouldEqual((expectedHash)); who = who.TimeShift(TimeSpan.FromMinutes(5)); - Commit commit = repo.Commit(who, who, "Fix file name"); + Commit commit = repo.Commit("Fix file name", who, who); repo.Index.RetrieveStatus(oldName).ShouldEqual(FileStatus.Nonexistent); repo.Index.RetrieveStatus(newName).ShouldEqual(FileStatus.Unaltered); @@ -235,28 +365,33 @@ public void CanRenameAFile() } [Test] - public void CanUnstageANewFile() + public void CanRemoveAFile() { TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(Constants.StandardTestRepoWorkingDirPath); using (var repo = new Repository(path.RepositoryPath)) { int count = repo.Index.Count; - const string filename = "new_untracked_file.txt"; - string fullPath = Path.Combine(repo.Info.WorkingDirectory, filename); - File.Exists(fullPath).ShouldBeTrue(); - - repo.Index.RetrieveStatus(filename).ShouldEqual(FileStatus.Untracked); + const string filename = "1/branch_file.txt"; + string fullpath = Path.Combine(repo.Info.WorkingDirectory, filename); - repo.Index.Stage(filename); - repo.Index.Count.ShouldEqual(count + 1); + File.Exists(fullpath).ShouldBeTrue(); + repo.Index.RetrieveStatus(filename).ShouldEqual(FileStatus.Unaltered); - repo.Index.RetrieveStatus(filename).ShouldEqual(FileStatus.Added); + repo.Index.Remove(filename); - repo.Index.Unstage(filename); - repo.Index.Count.ShouldEqual(count); + repo.Index.Count.ShouldEqual(count - 1); + File.Exists(fullpath).ShouldBeFalse(); + repo.Index.RetrieveStatus(filename).ShouldEqual(FileStatus.Removed); + } + } - repo.Index.RetrieveStatus(filename).ShouldEqual(FileStatus.Untracked); + [Test] + public void RemovingANonStagedFileThrows() + { + using (var repo = new Repository(Constants.StandardTestRepoPath)) + { + Assert.Throws(() => repo.Index.Remove("shadowcopy_of_an_unseen_ghost.txt")); } } @@ -294,7 +429,8 @@ public void CanRetrieveTheStatusOfTheWholeWorkingDirectory() status.Staged.Single().ShouldEqual(file); status.Removed.Single().ShouldEqual("deleted_staged_file.txt"); - File.AppendAllText(Path.Combine(repo.Info.WorkingDirectory, file), "Tclem's favorite commit message: boom"); + File.AppendAllText(Path.Combine(repo.Info.WorkingDirectory, file), + "Tclem's favorite commit message: boom"); indexEntry.State.ShouldEqual(FileStatus.Staged | FileStatus.Modified); @@ -305,7 +441,7 @@ public void CanRetrieveTheStatusOfTheWholeWorkingDirectory() status2.IsDirty.ShouldBeTrue(); status2.Untracked.Single().ShouldEqual("new_untracked_file.txt"); - CollectionAssert.AreEqual(new[] { file, "modified_unstaged_file.txt" }, status2.Modified); + CollectionAssert.AreEqual(new[] {file, "modified_unstaged_file.txt"}, status2.Modified); status2.Missing.Single().ShouldEqual("deleted_unstaged_file.txt"); status2.Added.Single().ShouldEqual("new_tracked_file.txt"); status2.Staged.Single().ShouldEqual(file); @@ -334,44 +470,5 @@ public void CanRetrieveTheStatusOfANewRepository() status.Removed.Count().ShouldEqual(0); } } - - [Test] - public void UnstagingANonStagedFileThrows() - { - using (var repo = new Repository(Constants.StandardTestRepoPath)) - { - Assert.Throws(() => repo.Index.Unstage("shadowcopy_of_an_unseen_ghost.txt")); - } - } - - [Test] - public void ReadIndexWithBadParamsFails() - { - using (var repo = new Repository(Constants.StandardTestRepoPath)) - { - Assert.Throws(() => { IndexEntry entry = repo.Index[null]; }); - Assert.Throws(() => { IndexEntry entry = repo.Index[string.Empty]; }); - } - } - - [Test] - public void StageFileWithBadParamsThrows() - { - using (var repo = new Repository(Constants.StandardTestRepoPath)) - { - Assert.Throws(() => repo.Index.Stage(string.Empty)); - Assert.Throws(() => repo.Index.Stage(null)); - } - } - - [Test] - public void UnstagingFileWithBadParamsThrows() - { - using (var repo = new Repository(Constants.StandardTestRepoPath)) - { - Assert.Throws(() => repo.Index.Stage(string.Empty)); - Assert.Throws(() => repo.Index.Stage(null)); - } - } } -} +} \ No newline at end of file diff --git a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj index 2617626a6..df538a911 100644 --- a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj +++ b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj @@ -76,11 +76,12 @@ - \ No newline at end of file diff --git a/LibGit2Sharp.Tests/ReferenceFixture.cs b/LibGit2Sharp.Tests/ReferenceFixture.cs index 2db5f8b45..ecdaee12e 100644 --- a/LibGit2Sharp.Tests/ReferenceFixture.cs +++ b/LibGit2Sharp.Tests/ReferenceFixture.cs @@ -339,6 +339,25 @@ public void CanUpdateTargetOnSymbolicReference() } } + [Test] + public void CanUpdateHeadWithEitherAnOidOrACanonicalHeadReference() + { + TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); + using (var repo = new Repository(path.RepositoryPath)) + { + Branch test = repo.Branches["test"]; + + Reference direct = repo.Refs.UpdateTarget("HEAD", test.Tip.Sha); + (direct is DirectReference).ShouldBeTrue(); + direct.ShouldEqual(repo.Refs["HEAD"]); + + Reference symref = repo.Refs.UpdateTarget("HEAD", test.CanonicalName); + (symref is SymbolicReference).ShouldBeTrue(); + symref.ShouldEqual(repo.Refs["HEAD"]); + } + + } + [Test] public void UpdatingADirectRefWithSymbolFails() { diff --git a/LibGit2Sharp.Tests/RepositoryFixture.cs b/LibGit2Sharp.Tests/RepositoryFixture.cs index 3903002f1..555c756bd 100644 --- a/LibGit2Sharp.Tests/RepositoryFixture.cs +++ b/LibGit2Sharp.Tests/RepositoryFixture.cs @@ -93,6 +93,8 @@ private static void AssertInitializedRepository(Repository repo) repo.Commits.QueryBy(new Filter { Since = repo.Head }).Count().ShouldEqual(0); repo.Commits.QueryBy(new Filter { Since = "HEAD" }).Count().ShouldEqual(0); repo.Commits.QueryBy(new Filter { Since = "refs/heads/master" }).Count().ShouldEqual(0); + + repo.Head["subdir/I-do-not-exist"].ShouldBeNull(); repo.Branches.Count().ShouldEqual(0); repo.Refs.Count().ShouldEqual(0); @@ -249,7 +251,7 @@ public void CanLookupWhithShortIdentifers() repo.Index.Stage(filePath); Signature author = Constants.Signature; - Commit commit = repo.Commit(author, author, "Initial commit"); + Commit commit = repo.Commit("Initial commit", author, author); commit.Sha.ShouldEqual(expectedSha); diff --git a/LibGit2Sharp.Tests/Resources/testrepo.git/config b/LibGit2Sharp.Tests/Resources/testrepo.git/config new file mode 100644 index 000000000..e69de29bb diff --git a/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/objects/04/c9b35f51fbff2338d5cdc959b23a93a14c5063 b/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/objects/04/c9b35f51fbff2338d5cdc959b23a93a14c5063 new file mode 100644 index 000000000..a8660a9ea Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/objects/04/c9b35f51fbff2338d5cdc959b23a93a14c5063 differ diff --git a/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/objects/33/a9574ff4dca3fbf68c6785859b80895c6b77b1 b/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/objects/33/a9574ff4dca3fbf68c6785859b80895c6b77b1 new file mode 100644 index 000000000..95151430c Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/objects/33/a9574ff4dca3fbf68c6785859b80895c6b77b1 differ diff --git a/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/objects/58/0c2111be43802dab11328176d94c391f1deae9 b/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/objects/58/0c2111be43802dab11328176d94c391f1deae9 new file mode 100644 index 000000000..81671a754 --- /dev/null +++ b/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/objects/58/0c2111be43802dab11328176d94c391f1deae9 @@ -0,0 +1,2 @@ +x�ŽAj1 E³ö)t�{d[6„ÐEvÙõ’Gî Çep¹}B{ƒ®>ôØïÛþB0™î˜I¾ \ No newline at end of file diff --git a/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/objects/74/9a42f6ef33405e5ac16687963aebab8b78abd1 b/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/objects/74/9a42f6ef33405e5ac16687963aebab8b78abd1 new file mode 100644 index 000000000..dbebba8e4 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/objects/74/9a42f6ef33405e5ac16687963aebab8b78abd1 differ diff --git a/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/objects/b3/e375c923d50c589b11b6da4a769bdd7f6502e3 b/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/objects/b3/e375c923d50c589b11b6da4a769bdd7f6502e3 new file mode 100644 index 000000000..511e72c6a Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/objects/b3/e375c923d50c589b11b6da4a769bdd7f6502e3 differ diff --git a/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/objects/b5/9b86c5f4874aea5255bf14d67a5ce13c80265f b/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/objects/b5/9b86c5f4874aea5255bf14d67a5ce13c80265f new file mode 100644 index 000000000..eed20b8e3 Binary files /dev/null and b/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/objects/b5/9b86c5f4874aea5255bf14d67a5ce13c80265f differ diff --git a/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/packed-refs b/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/packed-refs index 1ab6ad52e..f1d41cc22 100644 --- a/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/packed-refs +++ b/LibGit2Sharp.Tests/Resources/testrepo_wd/dot_git/packed-refs @@ -6,5 +6,5 @@ e90810b8df3e80c413d903f631643c716887138d refs/tags/lw ^e90810b8df3e80c413d903f631643c716887138d e90810b8df3e80c413d903f631643c716887138d refs/remotes/origin/test 4a202b346bb0fb0db7eff3cffeb3c70babbd2045 refs/remotes/origin/packed-test -4c062a6361ae6959e06292c1fa5e2822d9c96345 refs/remotes/origin/master +580c2111be43802dab11328176d94c391f1deae9 refs/remotes/origin/master a4a7dce85cf63874e984719f4fdd239f5145052f refs/remotes/origin/br2 diff --git a/LibGit2Sharp.Tests/TagFixture.cs b/LibGit2Sharp.Tests/TagFixture.cs index dc2ffba9c..0a410aa1d 100644 --- a/LibGit2Sharp.Tests/TagFixture.cs +++ b/LibGit2Sharp.Tests/TagFixture.cs @@ -204,6 +204,15 @@ public void CreatingATagForAnUnknowReferenceThrows() } } + [Test] + public void CreatingATagForANonCanonicalReferenceThrows() + { + using (var repo = new Repository(Constants.BareTestRepoPath)) + { + Assert.Throws(() => repo.ApplyTag("noncanonicaltarget", "br2")); + } + } + [Test] [Description("Ported from cgit (https://github.com/git/git/blob/1c08bf50cfcf924094eca56c2486a90e2bf1e6e2/t/t7004-tag.sh#L42)")] public void CreatingATagForAnUnknowObjectIdThrows() @@ -505,6 +514,17 @@ public void DeletingAnUnknownTagShouldFail() } } + [Test] + public void GetTagByNameWithBadParamsThrows() + { + using (var repo = new Repository(Constants.BareTestRepoPath)) + { + Tag tag; + Assert.Throws(() => tag = repo.Tags[null]); + Assert.Throws(() => tag = repo.Tags[""]); + } + } + [Test] public void CanListTags() { diff --git a/LibGit2Sharp.Tests/TestHelpers/AssertExtensions.cs b/LibGit2Sharp.Tests/TestHelpers/AssertExtensions.cs index b2762b343..c8fe140d7 100644 --- a/LibGit2Sharp.Tests/TestHelpers/AssertExtensions.cs +++ b/LibGit2Sharp.Tests/TestHelpers/AssertExtensions.cs @@ -5,8 +5,6 @@ namespace LibGit2Sharp.Tests.TestHelpers { public static class AssertExtensions { - #region public - public static void ShouldBeAboutEqualTo(this DateTimeOffset expected, DateTimeOffset current) { Assert.AreEqual(expected.Date, current.Date); @@ -50,7 +48,5 @@ public static void ShouldNotEqual(this object compareFrom, object compareTo) { Assert.AreNotEqual(compareTo, compareFrom); } - - #endregion } } diff --git a/LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs b/LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs index b8e912c74..2062d0f20 100644 --- a/LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs +++ b/LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.IO; using NUnit.Framework; @@ -73,5 +74,15 @@ public void Cleanup() DirectoryHelper.DeleteDirectory(directory); } } + + protected void InconclusiveIf(Func predicate, string message) + { + if (!predicate()) + { + return; + } + + Assert.Inconclusive(); + } } } diff --git a/LibGit2Sharp/Blob.cs b/LibGit2Sharp/Blob.cs index 646ae109c..ec6971eb3 100644 --- a/LibGit2Sharp/Blob.cs +++ b/LibGit2Sharp/Blob.cs @@ -6,6 +6,9 @@ namespace LibGit2Sharp { + /// + /// Stores the binary content of a tracked file. + /// public class Blob : GitObject { private readonly Repository repo; @@ -16,6 +19,9 @@ internal Blob(Repository repo, ObjectId id) this.repo = repo; } + /// + /// Gets the size in bytes of the contents of a blob + /// public int Size { get; set; } public byte[] Content @@ -56,7 +62,7 @@ public string ContentAsUnicode() return Encoding.Unicode.GetString(Content); } - public static Blob BuildFromPtr(IntPtr obj, ObjectId id, Repository repo) + internal static Blob BuildFromPtr(IntPtr obj, ObjectId id, Repository repo) { var blob = new Blob(repo, id) { diff --git a/LibGit2Sharp/Branch.cs b/LibGit2Sharp/Branch.cs index f64dee5f4..ee7726e6c 100644 --- a/LibGit2Sharp/Branch.cs +++ b/LibGit2Sharp/Branch.cs @@ -1,5 +1,6 @@ using System; using System.Globalization; +using System.Linq; using LibGit2Sharp.Core; namespace LibGit2Sharp @@ -7,22 +8,21 @@ namespace LibGit2Sharp /// /// A branch is a special kind of reference /// - public class Branch : IEquatable + public class Branch : NamedReference, IEquatable { private static readonly LambdaEqualityHelper equalityHelper = new LambdaEqualityHelper(new Func[] { x => x.CanonicalName, x => x.Tip }); - private readonly Repository repo; - private readonly Lazy tipBuilder; + private readonly Lazy trackedBranch; /// /// Initializes a new instance of the class. /// - /// The id of the commit which is pointed at by this Branch /// The repo. + /// The reference. /// The full name of the reference - internal Branch(string canonicalName, ObjectId targetId, Repository repo) - : this(canonicalName, new Lazy(() => repo.Lookup(targetId)), repo) + internal Branch(Repository repo, Reference reference, string canonicalName) + : this(repo, reference, _ => canonicalName) { } @@ -33,34 +33,34 @@ internal Branch(string canonicalName, ObjectId targetId, Repository repo) /// /// /// The repo. - /// The full name of the reference - internal Branch(string canonicalName, Repository repo) - : this(canonicalName, new Lazy(() => null), repo) + /// The reference. + internal Branch(Repository repo, Reference reference) + : this(repo, reference, r => r.TargetIdentifier) { } - private Branch(string canonicalName, Lazy tipBuilder, Repository repo) + private Branch(Repository repo, Reference reference, Func canonicalNameSelector) + : base(repo, reference, canonicalNameSelector) { - Ensure.ArgumentNotNullOrEmptyString(canonicalName, "canonicalName"); - Ensure.ArgumentNotNull(tipBuilder, "tipObjectId"); - Ensure.ArgumentNotNull(repo, "repo"); - - this.repo = repo; - CanonicalName = canonicalName; - this.tipBuilder = tipBuilder; + trackedBranch = new Lazy(ResolveTrackedBranch); } /// - /// Gets the full name of this branch. - /// - public string CanonicalName { get; private set; } - - /// - /// Gets the name of this branch. + /// Gets the pointed at by the in the . /// - public string Name + /// The relative path to the from the working directory. + /// null if nothing has been found, the otherwise. + public TreeEntry this[string relativePath] { - get { return ShortenName(CanonicalName); } + get + { + if (Tip == null) + { + return null; + } + + return Tip[relativePath]; + } } /// @@ -69,18 +69,38 @@ public string Name /// /// true if this instance is remote; otherwise, false. /// - public bool IsRemote + public virtual bool IsRemote { get { return IsRemoteBranch(CanonicalName); } } + public Branch TrackedBranch + { + get { return trackedBranch.Value; } + } + + public bool IsTracking + { + get { return TrackedBranch != null; } + } + + public int AheadBy + { + get { return IsTracking ? repo.Commits.QueryBy(new Filter { Since = Tip, Until = TrackedBranch }).Count() : 0; } + } + + public int BehindBy + { + get { return IsTracking ? repo.Commits.QueryBy(new Filter { Since = TrackedBranch, Until = Tip }).Count() : 0; } + } + /// /// Gets a value indicating whether this instance is current branch (HEAD) in the repository. /// /// /// true if this instance is current branch; otherwise, false. /// - public bool IsCurrentRepositoryHead + public virtual bool IsCurrentRepositoryHead { get { return repo.Refs[CanonicalName].ResolveToDirectReference() == repo.Refs["HEAD"].ResolveToDirectReference(); } } @@ -90,7 +110,7 @@ public bool IsCurrentRepositoryHead /// public Commit Tip { - get { return tipBuilder.Value; } + get { return TargetObject; } } /// @@ -134,12 +154,36 @@ public override int GetHashCode() return equalityHelper.GetHashCode(this); } + private Branch ResolveTrackedBranch() + { + var trackedRemote = repo.Config.Get("branch", Name, "remote", null); + if (trackedRemote == null) + { + return null; + } + + var trackedRefName = repo.Config.Get("branch", Name, "merge", null); + if (trackedRefName == null) + { + return null; + } + + var remoteRefName = ResolveTrackedReference(trackedRemote, trackedRefName); + return repo.Branches[remoteRefName]; + } + + private static string ResolveTrackedReference(string trackedRemote, string trackedRefName) + { + //TODO: To be replaced by native libgit2 git_branch_tracked_reference() when available. + return trackedRefName.Replace("refs/heads/", string.Concat("refs/remotes/", trackedRemote, "/")); + } + private static bool IsRemoteBranch(string canonicalName) { return canonicalName.StartsWith("refs/remotes/", StringComparison.Ordinal); } - private static string ShortenName(string branchName) + protected override string Shorten(string branchName) { if (branchName.StartsWith("refs/heads/", StringComparison.Ordinal)) { @@ -177,9 +221,9 @@ private static string ShortenName(string branchName) } /// - /// Returns the , a representation of the current . + /// Returns the , a representation of the current . /// - /// The that represents the current . + /// The that represents the current . public override string ToString() { return CanonicalName; diff --git a/LibGit2Sharp/BranchCollection.cs b/LibGit2Sharp/BranchCollection.cs index 54e1cd589..84fd71811 100644 --- a/LibGit2Sharp/BranchCollection.cs +++ b/LibGit2Sharp/BranchCollection.cs @@ -28,7 +28,13 @@ internal BranchCollection(Repository repo) /// public Branch this[string name] { - get { return repo.Refs.Resolve(NormalizeToCanonicalName(name)); } + get + { + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + string canonicalName = NormalizeToCanonicalName(name); + var reference = repo.Refs.Resolve(canonicalName); + return reference == null ? null : new Branch(repo, reference, canonicalName); + } } #region IEnumerable Members @@ -59,37 +65,59 @@ IEnumerator IEnumerable.GetEnumerator() /// /// Checkout the branch with the specified by name. /// - /// The name of the branch to checkout. + /// The sha of the commit, a canonical reference name or the name of the branch to checkout. /// - public Branch Checkout(string name) + public Branch Checkout(string shaOrReferenceName) { - // TODO: Allow checkout of an arbitrary commit, thus putting HEAD in detached state. // TODO: This does not yet checkout (write) the working directory - Ensure.ArgumentNotNullOrEmptyString(name, "name"); - repo.Refs.UpdateTarget("HEAD", this[name].CanonicalName); + Branch branch = this[shaOrReferenceName]; - return this[name]; + if (branch != null) + { + repo.Refs.UpdateTarget("HEAD", branch.CanonicalName); + return branch; + } + + ObjectId commitId = RetrieveTargetCommitId(shaOrReferenceName); + repo.Refs.UpdateTarget("HEAD", commitId.Sha); + return repo.Head; } /// /// Create a new local branch with the specified name /// /// The name of the branch. - /// The target sha or branch name. + /// The target which can be sha or a canonical reference name. /// - public Branch Create(string name, string target) + public Branch Create(string name, string shaOrReferenceName) { - ObjectId id; + ObjectId commitId = RetrieveTargetCommitId(shaOrReferenceName); - if (!ObjectId.TryParse(target, out id)) + repo.Refs.Create(NormalizeToCanonicalName(name), commitId.Sha); + return this[name]; + } + + private ObjectId RetrieveTargetCommitId(string target) + { + GitObject targetObject = repo.Refs.RetrieveTargetObject(target); + + ObjectId commitId = targetObject.PeelToCommitId(); + EnsureTargetExists(commitId, target); + + return commitId; + } + + private static void EnsureTargetExists(object target, string identifier) + { + if (target != null) { - DirectReference reference = repo.Refs[NormalizeToCanonicalName(target)].ResolveToDirectReference(); - target = reference.TargetIdentifier; + return; } - repo.Refs.Create(NormalizeToCanonicalName(name), target); - return this[name]; + throw new LibGit2Exception(String.Format(CultureInfo.InvariantCulture, + "No commit object identified by '{0}' can be found in the repository.", + identifier)); } /// @@ -107,7 +135,8 @@ public void Delete(string name) throw new LibGit2Exception(string.Format("Branch '{0}' can not be deleted as it is the current HEAD.", canonicalName)); } - repo.Refs.Delete(canonicalName); //TODO: To be replaced by native libgit2 git_branch_delete() when available. + //TODO: To be replaced by native libgit2 git_branch_delete() when available. + repo.Refs.Delete(canonicalName); } /// @@ -137,6 +166,11 @@ private static string NormalizeToCanonicalName(string name) { Ensure.ArgumentNotNullOrEmptyString(name, "name"); + if (name == "HEAD") + { + return name; + } + if (LooksLikeABranchName(name)) { return name; diff --git a/LibGit2Sharp/Commit.cs b/LibGit2Sharp/Commit.cs index 48ad86fe1..b35aa366c 100644 --- a/LibGit2Sharp/Commit.cs +++ b/LibGit2Sharp/Commit.cs @@ -19,11 +19,21 @@ internal Commit(ObjectId id, ObjectId treeId, Repository repo) : base(id) { tree = new Lazy(() => repo.Lookup(treeId)); - parents = new Lazy>(() => RetrieveParentsOfCommit(id.Oid)); + parents = new Lazy>(() => RetrieveParentsOfCommit(id)); shortMessage = new Lazy(ExtractShortMessage); this.repo = repo; } + /// + /// Gets the pointed at by the in the . + /// + /// The relative path to the from the working directory. + /// null if nothing has been found, the otherwise. + public TreeEntry this[string relativePath] + { + get { return Tree[relativePath]; } + } + /// /// Gets the commit message. /// @@ -34,14 +44,14 @@ internal Commit(ObjectId id, ObjectId treeId, Repository repo) /// public string MessageShort { - get { return shortMessage.Value; } + get { return shortMessage.Value; } } private string ExtractShortMessage() { if (Message == null) { - return string.Empty; //TODO: Add some test coverage + return string.Empty; //TODO: Add some test coverage } return Message.Split('\n')[0]; @@ -78,32 +88,19 @@ public IEnumerable Parents get { return parents.Value; } } - private IEnumerable RetrieveParentsOfCommit(GitOid oid) //TODO: Convert to a ParentEnumerator + private IEnumerable RetrieveParentsOfCommit(ObjectId oid) { - IntPtr obj; - int res = NativeMethods.git_object_lookup(out obj, repo.Handle, ref oid, GitObjectType.Commit); - Ensure.Success(res); - - var parentsOfCommits = new List(); - - try + using (var obj = new ObjectSafeWrapper(oid, repo)) { - uint parentsCount = NativeMethods.git_commit_parentcount(obj); + uint parentsCount = NativeMethods.git_commit_parentcount(obj.ObjectPtr); for (uint i = 0; i < parentsCount; i++) { IntPtr parentCommit; - res = NativeMethods.git_commit_parent(out parentCommit, obj, i); - Ensure.Success(res); - parentsOfCommits.Add((Commit)CreateFromPtr(parentCommit, ObjectIdOf(parentCommit), repo)); + Ensure.Success(NativeMethods.git_commit_parent(out parentCommit, obj.ObjectPtr, i)); + yield return (Commit)CreateFromPtr(parentCommit, ObjectIdOf(parentCommit), repo); } } - finally - { - NativeMethods.git_object_close(obj); - } - - return parentsOfCommits; } internal static Commit BuildFromPtr(IntPtr obj, ObjectId id, Repository repo) diff --git a/LibGit2Sharp/CommitCollection.cs b/LibGit2Sharp/CommitCollection.cs index 02a6e995d..93db5a7ab 100644 --- a/LibGit2Sharp/CommitCollection.cs +++ b/LibGit2Sharp/CommitCollection.cs @@ -104,7 +104,7 @@ private static IList ToList(object obj) { typeof(string), typeof(ObjectId), typeof(Commit), typeof(TagAnnotation), - typeof(Tag), typeof(Branch), + typeof(Tag), typeof(Branch), typeof(DetachedHead), typeof(Reference), typeof(DirectReference), typeof(SymbolicReference) }; @@ -126,12 +126,16 @@ private static bool PointsAtTheHead(string shaOrRefName) /// /// Stores the content of the as a new into the repository. /// + /// The description of why a change was made to the repository. /// The of who made the change. /// The of who added the change to the repository. - /// The description of why a change was made to the repository. /// The generated . - public Commit Create(Signature author, Signature committer, string message) + public Commit Create(string message, Signature author, Signature committer) { + Ensure.ArgumentNotNull(message, "message"); + Ensure.ArgumentNotNull(author, "author"); + Ensure.ArgumentNotNull(committer, "committer"); + GitOid treeOid; int res = NativeMethods.git_tree_create_fromindex(out treeOid, repo.Index.Handle); string encoding = null; @@ -235,12 +239,7 @@ public void Dispose() private void Dispose(bool disposing) { - if (handle == null || handle.IsInvalid) - { - return; - } - - handle.Dispose(); + handle.SafeDispose(); } private delegate int HidePushSignature(RevWalkerSafeHandle handle, ref GitOid oid); diff --git a/LibGit2Sharp/Configuration.cs b/LibGit2Sharp/Configuration.cs index 696a5ce56..1ab194ea4 100644 --- a/LibGit2Sharp/Configuration.cs +++ b/LibGit2Sharp/Configuration.cs @@ -1,29 +1,69 @@ -using System; +using System; +using System.Collections.Generic; +using System.Globalization; using LibGit2Sharp.Core; namespace LibGit2Sharp { /// - /// Provides access to the '.git\config' configuration for a repository. + /// Provides access to configuration variables for a repository. /// public class Configuration : IDisposable { - private readonly ConfigurationSafeHandle handle; + private readonly string globalConfigPath; + private readonly string systemConfigPath; - public Configuration(Repository repository) + private readonly Repository repository; + + private ConfigurationSafeHandle systemHandle; + private ConfigurationSafeHandle globalHandle; + private ConfigurationSafeHandle localHandle; + + internal Configuration(Repository repository) + { + this.repository = repository; + + globalConfigPath = ConvertPath(NativeMethods.git_config_find_global); + systemConfigPath = ConvertPath(NativeMethods.git_config_find_system); + + Init(); + } + + public bool HasGlobalConfig { - Ensure.Success(NativeMethods.git_repository_config(out handle, repository.Handle, null, null)); + get { return globalConfigPath != null; } } - internal ConfigurationSafeHandle Handle + public bool HasSystemConfig { - get { return handle; } + get { return systemConfigPath != null; } + } + + private static string ConvertPath(Func pathRetriever) + { + var buffer = new byte[NativeMethods.GIT_PATH_MAX]; + + int result = pathRetriever(buffer); + + //TODO: Make libgit2 return different codes to clearly identify a not found file (GIT_ENOTFOUND ) from any other error (!= GIT_SUCCESS) + if (result != (int)GitErrorCode.GIT_SUCCESS) + { + return null; + } + + return Utf8Marshaler.Utf8FromBuffer(buffer); + } + + internal ConfigurationSafeHandle LocalHandle + { + get { return localHandle; } } #region IDisposable Members /// /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// Saves any open configuration files. /// public void Dispose() { @@ -39,7 +79,8 @@ public void Dispose() /// The key to delete. public void Delete(string key) { - Ensure.Success(NativeMethods.git_config_delete(handle, key)); + Ensure.Success(NativeMethods.git_config_delete(localHandle, key)); + Save(); } /// @@ -47,10 +88,63 @@ public void Delete(string key) /// protected virtual void Dispose(bool disposing) { - if (handle != null && !handle.IsInvalid) + localHandle.SafeDispose(); + globalHandle.SafeDispose(); + systemHandle.SafeDispose(); + } + + private static T ProcessReadResult(int res, T value, T defaultValue, Func postProcessor = null) + { + if (res == (int)GitErrorCode.GIT_ENOTFOUND) + { + return defaultValue; + } + + Ensure.Success(res); + + if (postProcessor == null) { - handle.Dispose(); + return value; } + + return postProcessor(value); + } + + private readonly IDictionary> configurationTypedRetriever = ConfigurationTypedRetriever(); + + private static Dictionary> ConfigurationTypedRetriever() + { + var dic = new Dictionary>(); + + dic.Add(typeof(int), (key, dv, handle) => + { + int value; + int res = NativeMethods.git_config_get_int32(handle, key, out value); + return ProcessReadResult(res, value, dv); + }); + + dic.Add(typeof(long), (key, dv, handle) => + { + long value; + int res = NativeMethods.git_config_get_int64(handle, key, out value); + return ProcessReadResult(res, value, dv); + }); + + dic.Add(typeof(bool), (key, dv, handle) => + { + bool value; + int res = NativeMethods.git_config_get_bool(handle, key, out value); + return ProcessReadResult(res, value, dv); + }); + + dic.Add(typeof(string), (key, dv, handle) => + { + IntPtr value; + int res = NativeMethods.git_config_get_string(handle, key, out value); + return ProcessReadResult(res, value, dv, s => ((IntPtr)s).MarshallAsString()); + }); + + return dic; } /// @@ -58,68 +152,157 @@ protected virtual void Dispose(bool disposing) /// /// For example in order to get the value for this in a .git\config file: /// + /// /// [core] /// bare = true + /// /// /// You would call: /// - /// bool isBare = repo.Config.Get<bool>("core.bare"); + /// + /// bool isBare = repo.Config.Get<bool>("core.bare", false); + /// /// /// - /// - /// - /// - public T Get(string key) + /// The configuration value type + /// The key + /// The default value + /// The configuration value, or defaultValue if not set + public T Get(string key, T defaultValue) { - if (typeof(T) == typeof(string)) - { - return (T)(object)GetString(key); - } + Ensure.ArgumentNotNullOrEmptyString(key, "key"); - if (typeof(T) == typeof(bool)) + if (!configurationTypedRetriever.ContainsKey(typeof(T))) { - return (T)(object)GetBool(key); + throw new ArgumentException(string.Format("Generic Argument of type '{0}' is not supported.", typeof(T).FullName)); } - if (typeof(T) == typeof(int)) - { - return (T)(object)GetInt(key); - } + return (T)configurationTypedRetriever[typeof(T)](key, defaultValue, LocalHandle); + } - if (typeof(T) == typeof(long)) - { - return (T)(object)GetLong(key); - } + /// + /// Get a configuration value for a key. Keys are in the form 'section.name'. + /// + /// For example in order to get the value for this in a .git\config file: + /// + /// + /// [core] + /// bare = true + /// + /// + /// You would call: + /// + /// + /// bool isBare = repo.Config.Get<bool>("core", "bare", false); + /// + /// + /// + /// The configuration value type + /// The first key part + /// The second key part + /// The default value + /// The configuration value, or defaultValue if not set + public T Get(string firstKeyPart, string secondKeyPart, T defaultValue) + { + Ensure.ArgumentNotNull(firstKeyPart, "firstKeyPart"); + Ensure.ArgumentNotNull(secondKeyPart, "secondKeyPart"); - throw new ArgumentException(string.Format("Generic Argument of type '{0}' is not supported.", typeof(T).FullName)); + return Get(new[] { firstKeyPart, secondKeyPart }, defaultValue); } - private bool GetBool(string key) + /// + /// Get a configuration value for the given key parts. + /// + /// For example in order to get the value for this in a .git\config file: + /// + /// + /// [difftool "kdiff3"] + /// path = c:/Program Files/KDiff3/kdiff3.exe + /// + /// + /// You would call: + /// + /// + /// string where = repo.Config.Get<string>("difftool", "kdiff3", "path", null); + /// + /// + /// + /// The configuration value type + /// The first key part + /// The second key part + /// The third key part + /// The default value + /// The configuration value, or defaultValue if not set + public T Get(string firstKeyPart, string secondKeyPart, string thirdKeyPart, T defaultValue) { - bool value; - Ensure.Success(NativeMethods.git_config_get_bool(handle, key, out value)); - return value; + Ensure.ArgumentNotNull(firstKeyPart, "firstKeyPart"); + Ensure.ArgumentNotNull(secondKeyPart, "secondKeyPart"); + Ensure.ArgumentNotNull(thirdKeyPart, "secondKeyPart"); + + return Get(new [] { firstKeyPart, secondKeyPart, thirdKeyPart }, defaultValue); } - private int GetInt(string key) + /// + /// Get a configuration value for the given key parts. + /// + /// For example in order to get the value for this in a .git\config file: + /// + /// + /// [core] + /// bare = true + /// + /// + /// You would call: + /// + /// + /// bool isBare = repo.Config.Get<bool>(new []{ "core", "bare" }, false); + /// + /// + /// + /// The configuration value type + /// The key parts + /// The default value + /// The configuration value, or defaultValue if not set + public T Get(string[] keyParts, T defaultValue) + { + Ensure.ArgumentNotNull(keyParts, "keyParts"); + + return Get(string.Join(".", keyParts), defaultValue); + } + + private void Init() { - int value; - Ensure.Success(NativeMethods.git_config_get_int(handle, key, out value)); - return value; + Ensure.Success(NativeMethods.git_repository_config(out localHandle, repository.Handle, globalConfigPath, systemConfigPath)); + + if (globalConfigPath != null) + { + Ensure.Success(NativeMethods.git_config_open_ondisk(out globalHandle, globalConfigPath)); + } + + if (systemConfigPath != null) + { + Ensure.Success(NativeMethods.git_config_open_ondisk(out systemHandle, systemConfigPath)); + } } - private long GetLong(string key) + private void Save() { - long value; - Ensure.Success(NativeMethods.git_config_get_long(handle, key, out value)); - return value; + Dispose(true); + Init(); } - private string GetString(string key) + private readonly IDictionary> configurationTypedUpdater = ConfigurationTypedUpdater(); + + private static Dictionary> ConfigurationTypedUpdater() { - IntPtr value; - Ensure.Success(NativeMethods.git_config_get_string(handle, key, out value)); - return value.MarshallAsString(); + var dic = new Dictionary>(); + + dic.Add(typeof(int), (key, val, handle) => Ensure.Success(NativeMethods.git_config_set_int32(handle, key, (int)val))); + dic.Add(typeof(long), (key, val, handle) => Ensure.Success(NativeMethods.git_config_set_int64(handle, key, (long)val))); + dic.Add(typeof(bool), (key, val, handle) => Ensure.Success(NativeMethods.git_config_set_bool(handle, key, (bool)val))); + dic.Add(typeof(string), (key, val, handle) => Ensure.Success(NativeMethods.git_config_set_string(handle, key, (string)val))); + + return dic; } /// @@ -135,36 +318,51 @@ private string GetString(string key) /// repo.Config.Set("test.boolsetting", true); /// /// - /// - /// - /// - public void Set(string key, T value) + /// + /// + /// + /// + public void Set(string key, T value, ConfigurationLevel level = ConfigurationLevel.Local) { - if (typeof(T) == typeof(string)) + Ensure.ArgumentNotNullOrEmptyString(key, "key"); + + if (level == ConfigurationLevel.Global && !HasGlobalConfig) { - Ensure.Success(NativeMethods.git_config_set_string(handle, key, (string)(object)value)); - return; + throw new LibGit2Exception("No global configuration file has been found."); } - if (typeof(T) == typeof(bool)) + if (level == ConfigurationLevel.System && !HasSystemConfig) { - Ensure.Success(NativeMethods.git_config_set_bool(handle, key, (bool)(object)value)); - return; + throw new LibGit2Exception("No system configuration file has been found."); } - if (typeof(T) == typeof(int)) + ConfigurationSafeHandle h; + + switch (level) { - Ensure.Success(NativeMethods.git_config_set_int(handle, key, (int)(object)value)); - return; + case ConfigurationLevel.Local: + h = localHandle; + break; + + case ConfigurationLevel.Global: + h = globalHandle; + break; + + case ConfigurationLevel.System: + h = systemHandle; + break; + + default: + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Configuration level has an unexpected value ('{0}').", Enum.GetName(typeof(ConfigurationLevel), level)), "level"); } - if (typeof(T) == typeof(long)) + if (!configurationTypedUpdater.ContainsKey(typeof(T))) { - Ensure.Success(NativeMethods.git_config_set_long(handle, key, (long)(object)value)); - return; + throw new ArgumentException(string.Format("Generic Argument of type '{0}' is not supported.", typeof(T).FullName)); } - throw new ArgumentException(string.Format("Generic Argument of type {0} is not supported.", typeof(T))); + configurationTypedUpdater[typeof(T)](key, value, h); + Save(); } } } diff --git a/LibGit2Sharp/ConfigurationLevel.cs b/LibGit2Sharp/ConfigurationLevel.cs new file mode 100644 index 000000000..987345adf --- /dev/null +++ b/LibGit2Sharp/ConfigurationLevel.cs @@ -0,0 +1,23 @@ +namespace LibGit2Sharp +{ + /// + /// Specifies the level of configuration to use. + /// + public enum ConfigurationLevel + { + /// + /// The local .git/config of the current repository. + /// + Local, + + /// + /// The global ~/.gitconfig of the current user. + /// + Global, + + /// + /// The system wide .gitconfig. + /// + System + } +} diff --git a/LibGit2Sharp/Core/EnumExtensions.cs b/LibGit2Sharp/Core/EnumExtensions.cs new file mode 100644 index 000000000..9baa42f7e --- /dev/null +++ b/LibGit2Sharp/Core/EnumExtensions.cs @@ -0,0 +1,12 @@ +using System; + +namespace LibGit2Sharp.Core +{ + internal static class EnumExtensions + { + public static bool Has(this Enum enumInstance, T entry) + { + return ((int)(object)enumInstance & (int)(object)entry) == (int)(object)(entry); + } + } +} diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index bdce80faf..dc4a10b7b 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -1,12 +1,29 @@ using System; +using System.IO; +using System.Reflection; using System.Runtime.InteropServices; namespace LibGit2Sharp.Core { internal static class NativeMethods { + public const int GIT_PATH_MAX = 4096; private const string libgit2 = "git2"; + static NativeMethods() + { + string originalAssemblypath = new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath; + + //TODO: When amd64 version of libgit2.dll is available, value this depending of the size of an IntPtr + const string currentArchSubPath = "NativeBinaries/x86"; + + string path = Path.Combine(Path.GetDirectoryName(originalAssemblypath), currentArchSubPath); + + const string pathEnvVariable = "PATH"; + Environment.SetEnvironmentVariable(pathEnvVariable, + String.Format("{0}{1}{2}", path, Path.PathSeparator, Environment.GetEnvironmentVariable(pathEnvVariable))); + } + [DllImport(libgit2)] public static extern IntPtr git_blob_rawcontent(IntPtr blob); @@ -53,6 +70,12 @@ public static extern int git_commit_create( [DllImport(libgit2)] public static extern int git_config_delete(ConfigurationSafeHandle cfg, string name); + [DllImport(libgit2)] + public static extern int git_config_find_global(byte[] global_config_path); + + [DllImport(libgit2)] + public static extern int git_config_find_system(byte[] system_config_path); + [DllImport(libgit2)] public static extern void git_config_free(IntPtr cfg); @@ -63,13 +86,13 @@ public static extern int git_config_get_bool( out bool value); [DllImport(libgit2)] - public static extern int git_config_get_int( + public static extern int git_config_get_int32( ConfigurationSafeHandle cfg, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string name, out int value); [DllImport(libgit2)] - public static extern int git_config_get_long( + public static extern int git_config_get_int64( ConfigurationSafeHandle cfg, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string name, out long value); @@ -95,13 +118,13 @@ public static extern int git_config_set_bool( [MarshalAs(UnmanagedType.Bool)] bool value); [DllImport(libgit2)] - public static extern int git_config_set_int( + public static extern int git_config_set_int32( ConfigurationSafeHandle cfg, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string name, int value); [DllImport(libgit2)] - public static extern int git_config_set_long( + public static extern int git_config_set_int64( ConfigurationSafeHandle cfg, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string name, long value); @@ -392,5 +415,8 @@ public static extern IntPtr git_tree_entry_byname( [DllImport(libgit2)] public static extern uint git_tree_entrycount(IntPtr tree); + + [DllImport(libgit2)] + public static extern int git_tree_frompath(out IntPtr tree, IntPtr root, string treeentry_path); } } diff --git a/LibGit2Sharp/Core/SafeHandleExtensions.cs b/LibGit2Sharp/Core/SafeHandleExtensions.cs new file mode 100644 index 000000000..954320d2a --- /dev/null +++ b/LibGit2Sharp/Core/SafeHandleExtensions.cs @@ -0,0 +1,15 @@ +namespace LibGit2Sharp.Core +{ + internal static class SafeHandleExtensions + { + public static void SafeDispose(this SafeHandleBase handle) + { + if (handle == null || handle.IsInvalid) + { + return; + } + + handle.Dispose(); + } + } +} diff --git a/LibGit2Sharp/Core/Utf8Marshaler.cs b/LibGit2Sharp/Core/Utf8Marshaler.cs index 5f454f79c..66a295761 100644 --- a/LibGit2Sharp/Core/Utf8Marshaler.cs +++ b/LibGit2Sharp/Core/Utf8Marshaler.cs @@ -75,5 +75,24 @@ public static ICustomMarshaler GetInstance(string cookie) return staticInstance; } + + public static string Utf8FromBuffer(byte[] buffer) + { + int nullTerminator; + for (nullTerminator = 0; nullTerminator < buffer.Length; nullTerminator++) + { + if (buffer[nullTerminator] == 0) + { + break; + } + } + + if (nullTerminator == 0) + { + return null; + } + + return Encoding.UTF8.GetString(buffer, 0, nullTerminator); + } } } diff --git a/LibGit2Sharp/DetachedHead.cs b/LibGit2Sharp/DetachedHead.cs new file mode 100644 index 000000000..9d83b0e9d --- /dev/null +++ b/LibGit2Sharp/DetachedHead.cs @@ -0,0 +1,29 @@ +namespace LibGit2Sharp +{ + internal class DetachedHead : Branch + { + internal DetachedHead(Repository repo, Reference reference) + : base(repo, reference, "(no branch)") + { + } + + /// + /// Gets a value indicating whether this instance is current branch (HEAD) in the repository. + /// + /// + /// true if this instance is current branch; otherwise, false. + /// + public override bool IsCurrentRepositoryHead + { + get + { + return repo.Head == this; + } + } + + protected override string Shorten(string branchName) + { + return branchName; + } + } +} \ No newline at end of file diff --git a/LibGit2Sharp/FileStatus.cs b/LibGit2Sharp/FileStatus.cs index b8dd951fd..a5f88be89 100644 --- a/LibGit2Sharp/FileStatus.cs +++ b/LibGit2Sharp/FileStatus.cs @@ -5,7 +5,14 @@ namespace LibGit2Sharp [Flags] public enum FileStatus { + /// + /// The file doesn't exist. + /// Nonexistent = -1, /* GIT_STATUS_NOTFOUND */ + + /// + /// The file hasn't been modified. + /// Unaltered = 0, /* GIT_STATUS_CURRENT */ /// diff --git a/LibGit2Sharp/GitObject.cs b/LibGit2Sharp/GitObject.cs index 1e4c66dcc..c8cb1fa41 100644 --- a/LibGit2Sharp/GitObject.cs +++ b/LibGit2Sharp/GitObject.cs @@ -76,6 +76,26 @@ internal static ObjectId ObjectIdOf(IntPtr obj) return new ObjectId((GitOid)Marshal.PtrToStructure(ptr, typeof(GitOid))); } + internal ObjectId PeelToCommitId() + { + if (this is Blob || this is Tree) + { + return null; + } + + if (this is Commit) + { + return Id; + } + + if (this is TagAnnotation) + { + return ((TagAnnotation)this).Target.PeelToCommitId(); + } + + throw new InvalidOperationException(string.Format("Unsupported type of GitObject ('{0}').", GetType().FullName)); + } + /// /// Determines whether the specified is equal to the current . /// diff --git a/LibGit2Sharp/IQueryableCommitCollection.cs b/LibGit2Sharp/IQueryableCommitCollection.cs index ba4113226..c2fa1220e 100644 --- a/LibGit2Sharp/IQueryableCommitCollection.cs +++ b/LibGit2Sharp/IQueryableCommitCollection.cs @@ -12,10 +12,10 @@ public interface IQueryableCommitCollection : ICommitCollection //TODO: Find a n /// /// Stores the content of the content as a new into the repository. /// + /// The description of why a change was made to the repository. /// The of who made the change. /// The of who added the change to the repository. - /// The description of why a change was made to the repository. /// The generated . - Commit Create(Signature author, Signature committer, string message); + Commit Create(string message, Signature author, Signature committer); } } diff --git a/LibGit2Sharp/Index.cs b/LibGit2Sharp/Index.cs index 539480ed9..93ddd9cfd 100644 --- a/LibGit2Sharp/Index.cs +++ b/LibGit2Sharp/Index.cs @@ -7,6 +7,10 @@ namespace LibGit2Sharp { + /// + /// The Index is a staging area between the Working directory and the Repository. + /// It's used to prepare and aggregate the changes that will be part of the next commit. + /// public class Index : IEnumerable, IDisposable { private readonly IndexSafeHandle handle; @@ -24,11 +28,17 @@ internal IndexSafeHandle Handle get { return handle; } } + /// + /// Gets the number of in the index. + /// public int Count { get { return (int)NativeMethods.git_index_entrycount(handle); } } + /// + /// Gets the with the specified relative path. + /// public IndexEntry this[string path] { get @@ -73,10 +83,7 @@ public void Dispose() /// protected virtual void Dispose(bool disposing) { - if (handle != null && !handle.IsInvalid) - { - handle.Dispose(); - } + handle.SafeDispose(); } #endregion @@ -106,28 +113,69 @@ IEnumerator IEnumerable.GetEnumerator() #endregion + /// + /// Promotes to the staging area the latest modifications of a file in the working directory (addition, updation or removal). + /// + /// The relative path within the working directory to the file. public void Stage(string path) { Ensure.ArgumentNotNullOrEmptyString(path, "path"); - AddToIndex(path); + string relativePath = BuildRelativePathFrom(repo, path); + + FileStatus fileStatus = RetrieveStatus(relativePath); + + if (fileStatus.Has(FileStatus.Nonexistent)) + { + throw new LibGit2Exception("Can not stage '{0}'. The file does not exist."); + } + + if (fileStatus.Has(FileStatus.Missing)) + { + RemoveFromIndex(relativePath); + } + else + { + AddToIndex(relativePath); + } UpdatePhysicalIndex(); } + /// + /// Removes from the staging area all the modifications of a file since the latest commit (addition, updation or removal). + /// + /// The relative path within the working directory to the file. public void Unstage(string path) { Ensure.ArgumentNotNullOrEmptyString(path, "path"); string relativePath = BuildRelativePathFrom(repo, path); - RemoveFromIndex(relativePath); + FileStatus fileStatus = RetrieveStatus(relativePath); + + bool doesExistInindex = + !(fileStatus.Has(FileStatus.Nonexistent) || fileStatus.Has(FileStatus.Removed) || + fileStatus.Has(FileStatus.Untracked)); - RestorePotentialPreviousVersionOf(relativePath); + if (doesExistInindex) + { + RemoveFromIndex(relativePath); + } + + bool doesExistInWorkingDirectory = + !(fileStatus.Has(FileStatus.Removed) || fileStatus.Has(FileStatus.Nonexistent) || + fileStatus.Has(FileStatus.Missing)); + RestorePotentialPreviousVersionOfHeadIntoIndex(relativePath, doesExistInWorkingDirectory); UpdatePhysicalIndex(); } + /// + /// Moves and/or renames a file in the working directory and promotes the change to the staging area. + /// + /// The relative path within the working directory to the file which has to be moved/renamed. + /// The target relative path within the working directory of the file. public void Move(string sourcePath, string destinationPath) { Ensure.ArgumentNotNullOrEmptyString(sourcePath, "sourcepath"); @@ -151,31 +199,72 @@ public void Move(string sourcePath, string destinationPath) UpdatePhysicalIndex(); } - private void AddToIndex(string path) + /// + /// Removes a file from the working directory and promotes the removal to the staging area. + /// + /// The relative path within the working directory to the file. + public void Remove(string path) { - int res = NativeMethods.git_index_add(handle, BuildRelativePathFrom(repo, path)); + Ensure.ArgumentNotNullOrEmptyString(path, "path"); + + string relativePath = BuildRelativePathFrom(repo, path); + + string wd = repo.Info.WorkingDirectory; + if (Directory.Exists(Path.Combine(wd, relativePath))) + { + throw new NotImplementedException(); + } + + RemoveFromIndex(relativePath); + + File.Delete(Path.Combine(wd, relativePath)); + + UpdatePhysicalIndex(); + } + + private void AddToIndex(string relativePath) + { + int res = NativeMethods.git_index_add(handle, relativePath); Ensure.Success(res); } - private void RemoveFromIndex(string path) + private void RemoveFromIndex(string relativePath) { - int res = NativeMethods.git_index_find(handle, BuildRelativePathFrom(repo, path)); + int res = NativeMethods.git_index_find(handle, relativePath); Ensure.Success(res, true); res = NativeMethods.git_index_remove(handle, res); Ensure.Success(res); } - private void RestorePotentialPreviousVersionOf(string relativePath) + private void RestorePotentialPreviousVersionOfHeadIntoIndex(string relativePath, + bool doesExistInWorkingDirectory) { - TreeEntry currentHeadBlob = repo.Head.Tip.Tree[relativePath]; - if ((currentHeadBlob == null) || currentHeadBlob.Type != GitObjectType.Blob) + // TODO: Warning! Hack. Should be moved down to libgit2 (git reset HEAD filename) + TreeEntry entry = repo.Head[relativePath]; + if (entry == null || entry.Type != GitObjectType.Blob) { return; } - File.WriteAllBytes(Path.Combine(repo.Info.WorkingDirectory, relativePath), ((Blob)currentHeadBlob.Target).Content); + string filename = Path.Combine(repo.Info.WorkingDirectory, relativePath); + + string randomFileName = null; + if (doesExistInWorkingDirectory) + { + randomFileName = Path.GetRandomFileName(); + File.Move(filename, Path.Combine(repo.Info.WorkingDirectory, randomFileName)); + } + + File.WriteAllBytes(filename, ((Blob)(entry.Target)).Content); AddToIndex(relativePath); + + File.Delete(filename); + + if (doesExistInWorkingDirectory) + { + File.Move(Path.Combine(repo.Info.WorkingDirectory, randomFileName), filename); + } } private void UpdatePhysicalIndex() @@ -184,8 +273,9 @@ private void UpdatePhysicalIndex() Ensure.Success(res); } - private static string BuildRelativePathFrom(Repository repo, string path) //TODO: To be removed when libgit2 natively implements this + private static string BuildRelativePathFrom(Repository repo, string path) { + //TODO: To be removed when libgit2 natively implements this if (!Path.IsPathRooted(path)) { return path; @@ -195,12 +285,19 @@ private static string BuildRelativePathFrom(Repository repo, string path) //TODO if (!normalizedPath.StartsWith(repo.Info.WorkingDirectory, StringComparison.Ordinal)) { - throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, "Unable to process file '{0}'. This file is not located under the working directory of the repository ('{1}').", normalizedPath, repo.Info.WorkingDirectory)); + throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, + "Unable to process file '{0}'. This file is not located under the working directory of the repository ('{1}').", + normalizedPath, repo.Info.WorkingDirectory)); } return normalizedPath.Substring(repo.Info.WorkingDirectory.Length); } + /// + /// Retrieves the state of a file in the working directory, comparing it against the staging area and the latest commmit. + /// + /// The relative path within the working directory to the file. + /// A representing the state of the parameter. public FileStatus RetrieveStatus(string filePath) { Ensure.ArgumentNotNullOrEmptyString(filePath, "filePath"); @@ -220,9 +317,13 @@ public FileStatus RetrieveStatus(string filePath) return status; } + /// + /// Retrieves the state of all files in the working directory, comparing them against the staging area and the latest commmit. + /// + /// A holding the state of all the files. public RepositoryStatus RetrieveStatus() { - return new RepositoryStatus(repo.Handle); + return new RepositoryStatus(repo); } } } diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index 80541b535..8b29323bf 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -51,6 +51,9 @@ + + + @@ -66,6 +69,7 @@ + Code @@ -91,6 +95,7 @@ + @@ -116,22 +121,17 @@ - - - git2.dll - PreserveNewest - - - \ No newline at end of file diff --git a/LibGit2Sharp/NamedReference.cs b/LibGit2Sharp/NamedReference.cs new file mode 100644 index 000000000..32a097b31 --- /dev/null +++ b/LibGit2Sharp/NamedReference.cs @@ -0,0 +1,72 @@ +using System; +using LibGit2Sharp.Core; + +namespace LibGit2Sharp +{ + public abstract class NamedReference where TObject : GitObject + { + protected readonly Repository repo; + private readonly Lazy objectBuilder; + + protected internal NamedReference(Repository repo, Reference reference, Func canonicalNameSelector) + { + Ensure.ArgumentNotNull(repo, "repo"); + Ensure.ArgumentNotNull(canonicalNameSelector, "canonicalNameSelector"); + + this.repo = repo; + CanonicalName = canonicalNameSelector(reference); + objectBuilder = new Lazy(() => RetrieveTargetObject(reference)); + } + + /// + /// Gets the full name of this reference. + /// + public string CanonicalName { get; protected set; } + + /// + /// Gets the name of this reference. + /// + public string Name + { + get { return Shorten(CanonicalName); } + } + + /// + /// Returns the , a representation of the current reference. + /// + /// The that represents the current reference. + public override string ToString() + { + return CanonicalName; + } + + /// + /// Gets the this points to. + /// + protected TObject TargetObject + { + get { return objectBuilder.Value; } + } + + protected abstract string Shorten(string tagName); + + private TObject RetrieveTargetObject(Reference reference) + { + Ensure.ArgumentNotNull(reference, "reference"); + + var directReference = reference.ResolveToDirectReference(); + if (directReference == null) + { + return null; + } + + var target = directReference.Target; + if (target == null) + { + return null; + } + + return repo.Lookup(target.Id); + } + } +} diff --git a/LibGit2Sharp/ObjectId.cs b/LibGit2Sharp/ObjectId.cs index 028d203d6..640b1fc90 100644 --- a/LibGit2Sharp/ObjectId.cs +++ b/LibGit2Sharp/ObjectId.cs @@ -13,7 +13,14 @@ public class ObjectId : IEquatable private const int rawSize = 20; private readonly string sha; + /// + /// Size of the string-based representation of a SHA-1. + /// protected const int HexSize = rawSize*2; + + /// + /// Mininum size of the string-based representation of an abbreviated SHA-1. + /// protected const int MinHexSize = 4; private const string hexDigits = "0123456789abcdef"; diff --git a/LibGit2Sharp/Properties/AssemblyInfo.cs b/LibGit2Sharp/Properties/AssemblyInfo.cs index 56202f45a..447e80cdd 100644 --- a/LibGit2Sharp/Properties/AssemblyInfo.cs +++ b/LibGit2Sharp/Properties/AssemblyInfo.cs @@ -42,5 +42,5 @@ // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("0.5.0")] -[assembly: AssemblyFileVersion("0.5.0")] +[assembly: AssemblyVersion("0.6.0")] +[assembly: AssemblyFileVersion("0.6.0")] diff --git a/LibGit2Sharp/Reference.cs b/LibGit2Sharp/Reference.cs index 627303b13..308c8ec88 100644 --- a/LibGit2Sharp/Reference.cs +++ b/LibGit2Sharp/Reference.cs @@ -19,7 +19,7 @@ public abstract class Reference : IEquatable public string CanonicalName { get; protected set; } //TODO: Cries for refactoring... really! - internal static T BuildFromPtr(IntPtr ptr, Repository repo) where T : class + internal static T BuildFromPtr(IntPtr ptr, Repository repo) where T : Reference { if (ptr == IntPtr.Zero) { @@ -30,13 +30,12 @@ internal static T BuildFromPtr(IntPtr ptr, Repository repo) where T : class GitReferenceType type = NativeMethods.git_reference_type(ptr); Reference reference; - string targetIdentifier; switch (type) { case GitReferenceType.Symbolic: IntPtr resolveRef; - targetIdentifier = NativeMethods.git_reference_target(ptr).MarshallAsString(); + var targetIdentifier = NativeMethods.git_reference_target(ptr).MarshallAsString(); int res = NativeMethods.git_reference_resolve(out resolveRef, ptr); if (res == (int)GitErrorCode.GIT_ENOTFOUND) @@ -47,45 +46,24 @@ internal static T BuildFromPtr(IntPtr ptr, Repository repo) where T : class Ensure.Success(res); - var targetRef = BuildFromPtr(resolveRef, repo); + var targetRef = BuildFromPtr(resolveRef, repo); reference = new SymbolicReference { CanonicalName = name, Target = targetRef, TargetIdentifier = targetIdentifier }; break; case GitReferenceType.Oid: IntPtr oidPtr = NativeMethods.git_reference_oid(ptr); var oid = (GitOid)Marshal.PtrToStructure(oidPtr, typeof(GitOid)); - var targetId = new ObjectId(oid); - targetIdentifier = targetId.Sha; + var targetOid = new ObjectId(oid); - var targetBuilder = new Lazy(() => repo.Lookup(targetId)); - reference = new DirectReference(targetBuilder) { CanonicalName = name, TargetIdentifier = targetIdentifier }; + var targetBuilder = new Lazy(() => repo.Lookup(targetOid)); + reference = new DirectReference(targetBuilder) { CanonicalName = name, TargetIdentifier = targetOid.Sha }; break; default: throw new LibGit2Exception(String.Format(CultureInfo.InvariantCulture, "Unable to build a new reference from a type '{0}'.", Enum.GetName(typeof(GitReferenceType), type))); } - if (typeof(Reference).IsAssignableFrom(typeof(T))) - { - return reference as T; - } - - var targetOid = new ObjectId(targetIdentifier); - - if (Equals(typeof(T), typeof(Tag))) - { - return new Tag(reference.CanonicalName, targetOid, repo) as T; - } - - if (Equals(typeof(T), typeof(Branch))) - { - return new Branch(reference.CanonicalName, targetOid, repo) as T; - } - - throw new LibGit2Exception( - string.Format(CultureInfo.InvariantCulture, "Unable to build a new instance of '{0}' from a reference of type '{1}'.", - typeof(T), - Enum.GetName(typeof(GitReferenceType), type))); + return reference as T; } /// diff --git a/LibGit2Sharp/ReferenceCollection.cs b/LibGit2Sharp/ReferenceCollection.cs index 1158dd5bf..494fad0f6 100644 --- a/LibGit2Sharp/ReferenceCollection.cs +++ b/LibGit2Sharp/ReferenceCollection.cs @@ -154,7 +154,7 @@ public Reference Move(string currentName, string newName, bool allowOverwrite = return Reference.BuildFromPtr(referencePtr, repo); } - internal T Resolve(string name) where T : class + internal T Resolve(string name) where T : Reference { Ensure.ArgumentNotNullOrEmptyString(name, "name"); @@ -173,11 +173,17 @@ public Reference UpdateTarget(string name, string target) Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNullOrEmptyString(target, "target"); + if (name == "HEAD") + { + return Create("HEAD", target, true); + } + IntPtr reference = RetrieveReferencePtr(name); int res; ObjectId id; bool isObjectIdentifier = ObjectId.TryParse(target, out id); + GitReferenceType type = NativeMethods.git_reference_type(reference); switch (type) { @@ -193,7 +199,7 @@ public Reference UpdateTarget(string name, string target) case GitReferenceType.Symbolic: if (isObjectIdentifier) { - throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, "The reference specified by {0} is an Symbolic reference, you must provide a symbol as the target.", name), "target"); + throw new ArgumentException(String.Format(CultureInfo.InvariantCulture, "The reference specified by {0} is a Symbolic reference, you must provide a reference canonical name as the target.", name), "target"); } res = NativeMethods.git_reference_set_target(reference, target); break; @@ -207,12 +213,12 @@ public Reference UpdateTarget(string name, string target) return Reference.BuildFromPtr(reference, repo); } - private IntPtr RetrieveReferencePtr(string referenceName, bool shouldThrow = true) + private IntPtr RetrieveReferencePtr(string referenceName, bool shouldThrowIfNotFound = true) { IntPtr reference; int res = NativeMethods.git_reference_lookup(out reference, repo.Handle, referenceName); - if (!shouldThrow && res == (int)GitErrorCode.GIT_ENOTFOUND) + if (!shouldThrowIfNotFound && res == (int)GitErrorCode.GIT_ENOTFOUND) { return IntPtr.Zero; } @@ -221,5 +227,17 @@ private IntPtr RetrieveReferencePtr(string referenceName, bool shouldThrow = tru return reference; } + + internal GitObject RetrieveTargetObject(string target) + { + GitObject objectToTag = repo.Lookup(target); + + if (objectToTag == null) + { + throw new LibGit2Exception(String.Format(CultureInfo.InvariantCulture, "No object identified by '{0}' can be found in the repository.", target)); + } + + return objectToTag; + } } } diff --git a/LibGit2Sharp/Remote.cs b/LibGit2Sharp/Remote.cs index 17f7654be..b4add398b 100644 --- a/LibGit2Sharp/Remote.cs +++ b/LibGit2Sharp/Remote.cs @@ -1,8 +1,18 @@ namespace LibGit2Sharp { + /// + /// A remote repository whose branches are tracked. + /// public class Remote { + /// + /// Gets the alias of this remote repository. + /// public string Name { get; internal set; } + + /// + /// Gets the urls to use to communicate with this remote repository. + /// public string Url { get; internal set; } } } diff --git a/LibGit2Sharp/RemoteCollection.cs b/LibGit2Sharp/RemoteCollection.cs index b69d163f3..66f78e987 100644 --- a/LibGit2Sharp/RemoteCollection.cs +++ b/LibGit2Sharp/RemoteCollection.cs @@ -20,9 +20,9 @@ private Remote RemoteForName(string name) { var remote = new Remote(); RemoteSafeHandle handle; - - int res = NativeMethods.git_remote_get(out handle, repository.Config.Handle, name); - + + int res = NativeMethods.git_remote_get(out handle, repository.Config.LocalHandle, name); + if (res == (int)GitErrorCode.GIT_ENOTFOUND) { return null; diff --git a/LibGit2Sharp/Repository.cs b/LibGit2Sharp/Repository.cs index 27bbc9766..335d21b44 100644 --- a/LibGit2Sharp/Repository.cs +++ b/LibGit2Sharp/Repository.cs @@ -1,6 +1,5 @@ using System; using System.IO; -using System.Text; using LibGit2Sharp.Core; namespace LibGit2Sharp @@ -62,14 +61,14 @@ public Branch Head { get { - Reference headRef = Refs["HEAD"]; + Reference reference = Refs["HEAD"]; - if (Info.IsEmpty) + if (reference is SymbolicReference) { - return new Branch(headRef.TargetIdentifier, this); + return new Branch(this, reference); } - return Refs.Resolve(headRef.ResolveToDirectReference().CanonicalName); + return new DetachedHead(this, reference); } } @@ -154,10 +153,7 @@ public void Dispose() /// protected virtual void Dispose(bool disposing) { - if (handle != null && !handle.IsInvalid) - { - handle.Dispose(); - } + handle.SafeDispose(); if (index != null) { @@ -200,7 +196,7 @@ public static string Init(string path, bool isBare = false) Ensure.Success(res); string normalizedPath = NativeMethods.git_repository_path(repo, GitRepositoryPathId.GIT_REPO_PATH).MarshallAsString(); - repo.Dispose(); + repo.SafeDispose(); string nativePath = PosixPathHelper.ToNative(normalizedPath); @@ -292,7 +288,7 @@ private static bool IsReferencePeelable(Reference reference) /// The path to the git repository. public static string Discover(string startingPath) { - var buffer = new byte[4096]; + var buffer = new byte[NativeMethods.GIT_PATH_MAX]; int result = NativeMethods.git_repository_discover(buffer, buffer.Length, PosixPathHelper.ToPosix(startingPath), false, null); @@ -303,21 +299,7 @@ public static string Discover(string startingPath) Ensure.Success(result); - int nullTerminator; - for (nullTerminator = 0; nullTerminator < buffer.Length; nullTerminator++) - { - if (buffer[nullTerminator] == 0) - { - break; - } - } - - if (nullTerminator == 0) - { - return null; - } - - return PosixPathHelper.ToNative(Encoding.UTF8.GetString(buffer, 0, nullTerminator)); + return PosixPathHelper.ToNative(Utf8Marshaler.Utf8FromBuffer(buffer)); } } } diff --git a/LibGit2Sharp/RepositoryExtensions.cs b/LibGit2Sharp/RepositoryExtensions.cs index d8134b143..20740a205 100644 --- a/LibGit2Sharp/RepositoryExtensions.cs +++ b/LibGit2Sharp/RepositoryExtensions.cs @@ -1,4 +1,6 @@ -namespace LibGit2Sharp +using System; + +namespace LibGit2Sharp { /// /// Provides helper overloads to a . @@ -97,16 +99,58 @@ public static Branch CreateBranch(this Repository repository, string branchName, } /// - /// Stores the content of the as a new into the repository. + /// Stores the content of the as a new into the repository. + /// Both the Author and Committer will be guessed from the Git configuration. An exception will be raised if no configuration is reachable. + /// + /// The being worked with. + /// The description of why a change was made to the repository. + /// The generated . + public static Commit Commit(this Repository repository, string message) + { + Signature author = BuildSignatureFromGlobalConfiguration(repository, DateTimeOffset.Now); + + return repository.Commit(message, author); + } + + /// + /// Stores the content of the as a new into the repository. + /// The Committer will be guessed from the Git configuration. An exception will be raised if no configuration is reachable. + /// + /// The being worked with. + /// The of who made the change. + /// The description of why a change was made to the repository. + /// The generated . + public static Commit Commit(this Repository repository, string message, Signature author) + { + Signature committer = BuildSignatureFromGlobalConfiguration(repository, DateTimeOffset.Now); + + return repository.Commit(message, author, committer); + } + + /// + /// Stores the content of the as a new into the repository. /// /// The being worked with. /// The of who made the change. /// The of who added the change to the repository. /// The description of why a change was made to the repository. - /// The generated . - public static Commit Commit(this Repository repository, Signature author, Signature committer, string message) + /// The generated . + public static Commit Commit(this Repository repository, string message, Signature author, Signature committer) { - return repository.Commits.Create(author, committer, message); + return repository.Commits.Create(message, author, committer); + } + + private static Signature BuildSignatureFromGlobalConfiguration(Repository repository, DateTimeOffset now) + { + var name = repository.Config.Get("user.name", string.Empty); + var email = repository.Config.Get("user.email", string.Empty); + + if ((name == null) || (email == null)) + { + throw new LibGit2Exception("Can not find Name and Email settings of the current user in Git configuration."); + } + + return new Signature(name, email, now); } } } diff --git a/LibGit2Sharp/RepositoryStatus.cs b/LibGit2Sharp/RepositoryStatus.cs index ba3ae7927..ec2e196e9 100644 --- a/LibGit2Sharp/RepositoryStatus.cs +++ b/LibGit2Sharp/RepositoryStatus.cs @@ -20,8 +20,6 @@ public class RepositoryStatus : IEnumerable private readonly List untracked = new List(); private readonly bool isDirty; - private readonly NativeMethods.status_callback callback; - private readonly IDictionary> dispatcher = Build(); private static IDictionary> Build() @@ -37,19 +35,12 @@ private static IDictionary> Build() }; } - internal RepositoryStatus(RepositorySafeHandle handle) + internal RepositoryStatus(Repository repo) { - callback = new NativeMethods.status_callback(StateChanged); - Process(handle); + Ensure.Success(NativeMethods.git_status_foreach(repo.Handle, StateChanged, IntPtr.Zero)); isDirty = statusEntries.Count != 0; } - private void Process(RepositorySafeHandle handle) - { - int res = NativeMethods.git_status_foreach(handle, callback, IntPtr.Zero); - Ensure.Success(res); - } - private int StateChanged(string filePath, uint state, IntPtr payload) { var gitStatus = (FileStatus)state; @@ -57,7 +48,7 @@ private int StateChanged(string filePath, uint state, IntPtr payload) foreach (KeyValuePair> kvp in dispatcher) { - if ((gitStatus & kvp.Key) != kvp.Key) + if (!gitStatus.Has(kvp.Key)) { continue; } diff --git a/LibGit2Sharp/StatusEntry.cs b/LibGit2Sharp/StatusEntry.cs index b1695ea32..1c32630a9 100644 --- a/LibGit2Sharp/StatusEntry.cs +++ b/LibGit2Sharp/StatusEntry.cs @@ -3,6 +3,9 @@ namespace LibGit2Sharp { + /// + /// Holds the calculated status of a particular file at a particular instant. + /// public class StatusEntry : IEquatable { private readonly string filePath; @@ -17,11 +20,17 @@ internal StatusEntry(string filePath, FileStatus state) this.state = state; } + /// + /// Gets the of the file. + /// public FileStatus State { get { return state; } } + /// + /// Gets the relative filepath to the working directory of the file. + /// public string FilePath { get { return filePath; } diff --git a/LibGit2Sharp/Tag.cs b/LibGit2Sharp/Tag.cs index 376c297a5..c2fdad642 100644 --- a/LibGit2Sharp/Tag.cs +++ b/LibGit2Sharp/Tag.cs @@ -6,21 +6,14 @@ namespace LibGit2Sharp /// /// A Tag /// - public class Tag : IEquatable + public class Tag : NamedReference, IEquatable { private static readonly LambdaEqualityHelper equalityHelper = new LambdaEqualityHelper(new Func[] { x => x.CanonicalName, x => x.Target }); - private readonly Lazy targetBuilder; - - internal Tag(string canonicalName, ObjectId targetId, Repository repo) + internal Tag(Repository repo, Reference reference, string canonicalName) + : base(repo, reference, _ => canonicalName) { - Ensure.ArgumentNotNullOrEmptyString(canonicalName, "canonicalName"); - Ensure.ArgumentNotNull(targetId, "targetId"); - Ensure.ArgumentNotNull(repo, "repo"); - - CanonicalName = canonicalName; - targetBuilder = new Lazy(() => repo.Lookup(targetId)); } /// @@ -29,20 +22,7 @@ internal Tag(string canonicalName, ObjectId targetId, Repository repo) /// public TagAnnotation Annotation { - get { return targetBuilder.Value as TagAnnotation; } - } - - /// - /// Gets the full name of this branch. - /// - public string CanonicalName { get; private set; } - - /// - /// Gets the name of this tag. - /// - public string Name - { - get { return Shorten(CanonicalName); } + get { return TargetObject as TagAnnotation; } } /// @@ -52,7 +32,7 @@ public GitObject Target { get { - GitObject target = targetBuilder.Value; + GitObject target = TargetObject; if ((!(target is TagAnnotation))) { @@ -71,22 +51,13 @@ public bool IsAnnotated get { return Annotation != null; } } - private static string Shorten(string tagName) + protected override string Shorten(string tagName) { Ensure.ArgumentConformsTo(tagName, s => s.StartsWith("refs/tags/", StringComparison.Ordinal), "tagName"); return tagName.Substring("refs/tags/".Length); } - /// - /// Returns the , a representation of the current . - /// - /// The that represents the current . - public override string ToString() - { - return CanonicalName; - } - /// /// Determines whether the specified is equal to the current . /// diff --git a/LibGit2Sharp/TagCollection.cs b/LibGit2Sharp/TagCollection.cs index 1ba74f2d9..8174ccdba 100644 --- a/LibGit2Sharp/TagCollection.cs +++ b/LibGit2Sharp/TagCollection.cs @@ -29,7 +29,13 @@ internal TagCollection(Repository repo) /// public Tag this[string name] { - get { return repo.Refs.Resolve(NormalizeToCanonicalName(name)); } + get + { + Ensure.ArgumentNotNullOrEmptyString(name, "name"); + var canonicalName = NormalizeToCanonicalName(name); + var reference = repo.Refs.Resolve(canonicalName); + return reference == null ? null : new Tag(repo, reference, canonicalName); + } } #region IEnumerable Members @@ -73,7 +79,7 @@ public Tag Create(string name, string target, Signature tagger, string message, Ensure.ArgumentNotNull(tagger, "tagger"); Ensure.ArgumentNotNull(message, "message"); - GitObject objectToTag = RetrieveObjectToTag(target); + GitObject objectToTag = repo.Refs.RetrieveTargetObject(target); int res; using (var objectPtr = new ObjectSafeWrapper(objectToTag.Id, repo)) @@ -99,7 +105,7 @@ public Tag Create(string name, string target, bool allowOverwrite = false) Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNullOrEmptyString(target, "target"); - GitObject objectToTag = RetrieveObjectToTag(target); + GitObject objectToTag = repo.Refs.RetrieveTargetObject(target); int res; using (var objectPtr = new ObjectSafeWrapper(objectToTag.Id, repo)) @@ -116,7 +122,7 @@ public Tag Create(string name, string target, bool allowOverwrite = false) /// /// Deletes the tag with the specified name. /// - /// The short or canonical name of the tag or the to delete. + /// The short or canonical name of the tag to delete. public void Delete(string name) { Ensure.ArgumentNotNullOrEmptyString(name, "name"); @@ -125,18 +131,6 @@ public void Delete(string name) Ensure.Success(res); } - private GitObject RetrieveObjectToTag(string target) - { - GitObject objectToTag = repo.Lookup(target); - - if (objectToTag == null) - { - throw new LibGit2Exception(String.Format(CultureInfo.InvariantCulture, "No object identified by '{0}' can be found in the repository.", target)); - } - - return objectToTag; - } - private static string NormalizeToCanonicalName(string name) { Ensure.ArgumentNotNullOrEmptyString(name, "name"); diff --git a/LibGit2Sharp/Tree.cs b/LibGit2Sharp/Tree.cs index d327f3777..f4811e81a 100644 --- a/LibGit2Sharp/Tree.cs +++ b/LibGit2Sharp/Tree.cs @@ -15,26 +15,52 @@ internal Tree(ObjectId id) { } + /// + /// Gets the number of immediately under this . + /// public int Count { get; private set; } - public TreeEntry this[string name] + /// + /// Gets the pointed at by the in this instance. + /// + /// The relative path to the from this instance. + /// null if nothing has been found, the otherwise. + public TreeEntry this[string relativePath] { - get + get { return RetrieveFromPath(PosixPathHelper.ToPosix(relativePath)); } + } + + private TreeEntry RetrieveFromPath(string relativePath) + { + Ensure.ArgumentNotNullOrEmptyString(relativePath, "relativePath"); + + using (var obj = new ObjectSafeWrapper(Id, repo)) { - using (var obj = new ObjectSafeWrapper(Id, repo)) + IntPtr objectPtr; + + int res = NativeMethods.git_tree_frompath(out objectPtr, obj.ObjectPtr, relativePath); + + if (res == (int)GitErrorCode.GIT_ENOTFOUND) { - IntPtr e = NativeMethods.git_tree_entry_byname(obj.ObjectPtr, name); + return null; + } - if (e == IntPtr.Zero) - { - return null; - } + Ensure.Success(res); - return new TreeEntry(e, Id, repo); + IntPtr e = NativeMethods.git_tree_entry_byname(objectPtr, relativePath.Split('/').Last()); + + if (e == IntPtr.Zero) + { + return null; } + + return new TreeEntry(e, Id, repo); } } + /// + /// Gets the s immediately under this . + /// public IEnumerable Trees { get @@ -46,6 +72,9 @@ public IEnumerable Trees } } + /// + /// Gets the s immediately under this . + /// public IEnumerable Files { get diff --git a/README.md b/README.md index 85e761826..234a5b80d 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,14 @@ It provides a very opiniated API which should be very easy to use and discover. - Fork and clone locally - Configure your repo to convert line endings on commit so they are always LF in the repo: + - On Windows: +``` +$ git config --global core.autocrlf true +``` + - On Linux: +``` +$ git config --global core.autocrlf input +``` - Create a topic specific branch. Add some nice feature. Do not forget the tests ;-) - Send a Pull Request to spread the fun! diff --git a/libgit2 b/libgit2 index 35e9407c4..28c1451a7 160000 --- a/libgit2 +++ b/libgit2 @@ -1 +1 @@ -Subproject commit 35e9407c4698080b29f2bcb3d379454bb0ae62bb +Subproject commit 28c1451a7c863cb89d598c03d53c4e73497e4c83 diff --git a/nuget.package/LibGit2Sharp.nuspec b/nuget.package/LibGit2Sharp.nuspec index 29fac7f25..b32710a37 100644 --- a/nuget.package/LibGit2Sharp.nuspec +++ b/nuget.package/LibGit2Sharp.nuspec @@ -12,8 +12,7 @@ libgit2 git wrapper bindings API dvcs vcs - - + diff --git a/nuget.package/Tools/GetLibGit2SharpPostBuildCmd.ps1 b/nuget.package/Tools/GetLibGit2SharpPostBuildCmd.ps1 index 35cf68efa..6d4a41fa2 100644 --- a/nuget.package/Tools/GetLibGit2SharpPostBuildCmd.ps1 +++ b/nuget.package/Tools/GetLibGit2SharpPostBuildCmd.ps1 @@ -3,6 +3,11 @@ $path = $installPath.Replace($solutionDir, "`$(SolutionDir)") $NativeAssembliesDir = Join-Path $path "NativeBinaries" $x86 = $(Join-Path $NativeAssembliesDir "x86\*.*") +$x64 = $(Join-Path $NativeAssembliesDir "amd64\*.*") $LibGit2SharpPostBuildCmd = " -xcopy /s /y `"$x86`" `"`$(TargetDir)`"" \ No newline at end of file +if not exist `"`$(TargetDir)NativeBinaries`" md `"`$(TargetDir)NativeBinaries`" +if not exist `"`$(TargetDir)NativeBinaries\x86`" md `"`$(TargetDir)NativeBinaries\x86`" +xcopy /s /y `"$x86`" `"`$(TargetDir)NativeBinaries\x86`" +if not exist `"`$(TargetDir)NativeBinaries\amd64`" md `"`$(TargetDir)NativeBinaries\amd64`" +xcopy /s /y `"$x64`" `"`$(TargetDir)NativeBinaries\amd64`"" \ No newline at end of file