diff --git a/.gitignore b/.gitignore index a9a004981..c23615b5f 100644 --- a/.gitignore +++ b/.gitignore @@ -32,4 +32,5 @@ _ReSharper*/ [bB]uild/ *.pidb *.userprefs -*.swp \ No newline at end of file +*.swp +*.DotSettings \ No newline at end of file diff --git a/.gitmodules b/.gitmodules index a4f58a7a6..30eb68a28 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,3 @@ [submodule "libgit2"] path = libgit2 - url = http://github.com/libgit2/libgit2.git + url = https://github.com/libgit2/libgit2.git diff --git a/BACKLOG.md b/BACKLOG.md index 1141dddcc..3e1cd4d38 100644 --- a/BACKLOG.md +++ b/BACKLOG.md @@ -9,7 +9,6 @@ - Launch Code Analysis (Issues related to interop and marshaling will be worked on once we're able to succesffully exchange non ascii encoded data with libgit2) - https://bugzilla.novell.com/show_bug.cgi?id=566247 prevents MonoDevelop users from benefiting from optional parameters while still target at 3.5 - https://bugzilla.novell.com/show_bug.cgi?id=324680 generates false-positive warnings regarding xml documentation when LibGit2Sharp is built with xbuild - - The freeing of a newly created signature pointer doesn't "feel" to be done at the right place. - Favor overloads over optional parameters (http://msdn.microsoft.com/en-us/library/ms182135.aspx) - Ensure that types that are not supposed to be built by the Consumer do not expose a constructor. - Escape as early as possible from a method. Fight against the arrowhead effect (cf. http://elegantcode.com/2009/08/14/observations-on-the-if-statement/) diff --git a/CHANGELOG.md b/CHANGELOG.md index 033072337..8bc937fbe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,27 @@ - CI server: - @libgit2sharp: +## v0.8 + +### Additions + + - Add Repository.Reset() and support of Soft and Mixed modes + - Make Repository.Commit() able to amend the current tip of the Head + - Make the constructor of Repository able to open a repository from a working directory path + - Make Repository.Index.RetriveStatus honor the .gitgnore files + +### Changes + + - Remove Repository.HasObject() + - Change Repository.Init() to make it return an instance of the Repository type, instead of a string containing the path of the repository + - Update libgit2 binaries to libgit2/libgit2@6d39c0d + +### Fixes + + - Reinit a repository doesn't throw anymore (#54) + - Embedded libgit2 binaries are now compiled with THREADSAFE=ON flag (#64) + - Prevent Repository.Head.IsCurrentRepositoryHead from throwing when the Repository is empty (#105) + ## v0.7 ### Additions diff --git a/Lib/NativeBinaries/x86/git2.dll b/Lib/NativeBinaries/x86/git2.dll index 8de18ab4d..026177e2f 100644 Binary files a/Lib/NativeBinaries/x86/git2.dll and b/Lib/NativeBinaries/x86/git2.dll differ diff --git a/Lib/NativeBinaries/x86/git2.pdb b/Lib/NativeBinaries/x86/git2.pdb index 1ff189bf6..fea220418 100644 Binary files a/Lib/NativeBinaries/x86/git2.pdb and b/Lib/NativeBinaries/x86/git2.pdb differ diff --git a/LibGit2Sharp.Tests/BranchFixture.cs b/LibGit2Sharp.Tests/BranchFixture.cs index 81e3b62bb..92d2999fd 100644 --- a/LibGit2Sharp.Tests/BranchFixture.cs +++ b/LibGit2Sharp.Tests/BranchFixture.cs @@ -167,11 +167,21 @@ public void CanListAllBranches() } } + [Test] + public void CanListAllBranchesWhenGivenWorkingDir() + { + using (var repo = new Repository(StandardTestRepoWorkingDirPath)) + { + var expectedWdBranches = new[] { "master", "origin/HEAD", "origin/br2", "origin/master", "origin/packed-test", "origin/test" }; + + CollectionAssert.AreEqual(expectedWdBranches, repo.Branches.Select(b => b.Name).ToArray()); + } + } + [Test] public void CanListAllBranchesIncludingRemoteRefs() { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoPath); - using (var repo = new Repository(path.RepositoryPath)) + using (var repo = new Repository(StandardTestRepoPath)) { var expectedBranchesIncludingRemoteRefs = new[] { @@ -316,10 +326,11 @@ public void CanCheckoutAnArbitraryCommit(string commitPointer) detachedHead.CanonicalName.ShouldEqual("(no branch)"); detachedHead.Tip.Sha.ShouldEqual(repo.Lookup(commitPointer).Sha); - detachedHead.IsCurrentRepositoryHead.ShouldBeTrue(); detachedHead.ShouldEqual(repo.Head); master.IsCurrentRepositoryHead.ShouldBeFalse(); + detachedHead.IsCurrentRepositoryHead.ShouldBeTrue(); + repo.Head.IsCurrentRepositoryHead.ShouldBeTrue(); } } diff --git a/LibGit2Sharp.Tests/CommitFixture.cs b/LibGit2Sharp.Tests/CommitFixture.cs index f5c016949..ddbc7082e 100644 --- a/LibGit2Sharp.Tests/CommitFixture.cs +++ b/LibGit2Sharp.Tests/CommitFixture.cs @@ -130,14 +130,16 @@ public void QueryingTheCommitHistoryWithBadParamsThrows() [Test] public void CanEnumerateCommitsWithReverseTimeSorting() { - expectedShas.Reverse(); + var reversedShas = new List(expectedShas); + reversedShas.Reverse(); + int count = 0; using (var repo = new Repository(BareTestRepoPath)) { foreach (Commit commit in repo.Commits.QueryBy(new Filter { Since = "a4a7dce85cf63874e984719f4fdd239f5145052f", SortBy = GitSortOptions.Time | GitSortOptions.Reverse })) { commit.ShouldNotBeNull(); - commit.Sha.StartsWith(expectedShas[count]); + commit.Sha.StartsWith(reversedShas[count]).ShouldBeTrue(); count++; } } @@ -162,6 +164,15 @@ public void CanEnumerateCommitsWithReverseTopoSorting() } } + [Test] + public void CanGetParentsCount() + { + using (var repo = new Repository(BareTestRepoPath)) + { + repo.Commits.First().ParentsCount.ShouldEqual(1); + } + } + [Test] public void CanEnumerateCommitsWithTimeSorting() { @@ -171,7 +182,7 @@ public void CanEnumerateCommitsWithTimeSorting() foreach (Commit commit in repo.Commits.QueryBy(new Filter { Since = "a4a7dce85cf63874e984719f4fdd239f5145052f", SortBy = GitSortOptions.Time })) { commit.ShouldNotBeNull(); - commit.Sha.StartsWith(expectedShas[count]); + commit.Sha.StartsWith(expectedShas[count]).ShouldBeTrue(); count++; } } @@ -376,7 +387,7 @@ public void CanReadCommitData() commit.Tree.Sha.ShouldEqual("181037049a54a1eb5fab404658a3a250b44335d7"); - commit.Parents.Count().ShouldEqual(0); + commit.ParentsCount.ShouldEqual(0); } } @@ -387,6 +398,7 @@ public void CanReadCommitWithMultipleParents() { var commit = repo.Lookup("a4a7dce85cf63874e984719f4fdd239f5145052f"); commit.Parents.Count().ShouldEqual(2); + commit.ParentsCount.ShouldEqual(2); } } @@ -431,12 +443,13 @@ public void DirectlyAccessingAnUnknownTreeEntryOfTheCommitReturnsNull() 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)) + using (var repo = Repository.Init(scd.DirectoryPath)) { + string dir = repo.Info.Path; + Path.IsPathRooted(dir).ShouldBeTrue(); + Directory.Exists(dir).ShouldBeTrue(); + InconclusiveIf(() => !repo.Config.HasGlobalConfig, "No Git global configuration available"); const string relativeFilepath = "new.txt"; @@ -467,12 +480,13 @@ public void CanCommitWithSignatureFromConfig() public void CanCommitALittleBit() { SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - string dir = Repository.Init(scd.DirectoryPath); - Path.IsPathRooted(dir).ShouldBeTrue(); - Directory.Exists(dir).ShouldBeTrue(); - using (var repo = new Repository(dir)) + using (var repo = Repository.Init(scd.DirectoryPath)) { + string dir = repo.Info.Path; + Path.IsPathRooted(dir).ShouldBeTrue(); + Directory.Exists(dir).ShouldBeTrue(); + const string relativeFilepath = "new.txt"; string filePath = Path.Combine(repo.Info.WorkingDirectory, relativeFilepath); @@ -483,13 +497,13 @@ public void CanCommitALittleBit() repo.Head[relativeFilepath].ShouldBeNull(); - var author = new Signature("Author N. Ame", "him@there.com", DateTimeOffset.Now.AddSeconds(-10)); + var author = DummySignature; 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); + commit.ParentsCount.ShouldEqual(0); repo.Info.IsEmpty.ShouldBeFalse(); File.WriteAllText(filePath, "nulltoken commits!\n"); @@ -501,7 +515,7 @@ public void CanCommitALittleBit() AssertBlobContent(repo.Head[relativeFilepath], "nulltoken commits!\n"); AssertBlobContent(commit2[relativeFilepath], "nulltoken commits!\n"); - commit2.Parents.Count().ShouldEqual(1); + commit2.ParentsCount.ShouldEqual(1); commit2.Parents.First().Id.ShouldEqual(commit.Id); Branch firstCommitBranch = repo.CreateBranch("davidfowl-rules", commit.Id.Sha); //TODO: This cries for a shortcut method :-/ @@ -517,7 +531,7 @@ public void CanCommitALittleBit() AssertBlobContent(repo.Head[relativeFilepath], "davidfowl commits!\n"); AssertBlobContent(commit3[relativeFilepath], "davidfowl commits!\n"); - commit3.Parents.Count().ShouldEqual(1); + commit3.ParentsCount.ShouldEqual(1); commit3.Parents.First().Id.ShouldEqual(commit.Id); AssertBlobContent(firstCommitBranch[relativeFilepath], "nulltoken\n"); @@ -530,13 +544,9 @@ private static void AssertBlobContent(TreeEntry entry, string expectedContent) ((Blob)(entry.Target)).ContentAsUtf8().ShouldEqual(expectedContent); } - [Test] - public void CanGeneratePredictableObjectShas() + private static void CommitToANewRepository(string path) { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - string dir = Repository.Init(scd.DirectoryPath); - - using (var repo = new Repository(dir)) + using (Repository repo = Repository.Init(path)) { const string relativeFilepath = "test.txt"; string filePath = Path.Combine(repo.Info.WorkingDirectory, relativeFilepath); @@ -545,10 +555,21 @@ public void CanGeneratePredictableObjectShas() repo.Index.Stage(relativeFilepath); var author = new Signature("nulltoken", "emeric.fermas@gmail.com", DateTimeOffset.Parse("Wed, Dec 14 2011 08:29:03 +0100")); - Commit commit = repo.Commit("Initial commit\n", author, author); + repo.Commit("Initial commit\n", author, author); + } + } - commit.Sha.ShouldEqual("1fe3126578fc4eca68c193e4a3a0a14a0704624d"); + [Test] + public void CanGeneratePredictableObjectShas() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + CommitToANewRepository(scd.DirectoryPath); + + using (var repo = new Repository(scd.DirectoryPath)) + { + Commit commit = repo.Commits.Single(); + commit.Sha.ShouldEqual("1fe3126578fc4eca68c193e4a3a0a14a0704624d"); Tree tree = commit.Tree; tree.Sha.ShouldEqual("2b297e643c551e76cfa1f93810c50811382f9117"); @@ -556,5 +577,78 @@ public void CanGeneratePredictableObjectShas() blob.Sha.ShouldEqual("9daeafb9864cf43055ae93beb0afd6c7d144bfa4"); } } + + [Test] + public void CanAmendARootCommit() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + + CommitToANewRepository(scd.DirectoryPath); + + using (var repo = new Repository(scd.DirectoryPath)) + { + repo.Head.Commits.Count().ShouldEqual(1); + + Commit originalCommit = repo.Head.Tip; + originalCommit.ParentsCount.ShouldEqual(0); + + CreateAndStageANewFile(repo); + + Commit amendedCommit = repo.Commit("I'm rewriting the history!", DummySignature, DummySignature, true); + + repo.Head.Commits.Count().ShouldEqual(1); + + AssertCommitHasBeenAmended(repo, amendedCommit, originalCommit); + } + } + + [Test] + public void CanAmendACommitWithMoreThanOneParent() + { + TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoPath); + using (var repo = new Repository(path.RepositoryPath)) + { + var mergedCommit = repo.Lookup("be3563a"); + mergedCommit.ShouldNotBeNull(); + mergedCommit.ParentsCount.ShouldEqual(2); + + repo.Reset(ResetOptions.Soft, mergedCommit.Sha); + + CreateAndStageANewFile(repo); + + Commit amendedCommit = repo.Commit("I'm rewriting the history!", DummySignature, DummySignature, true); + + AssertCommitHasBeenAmended(repo, amendedCommit, mergedCommit); + } + } + + private static void CreateAndStageANewFile(Repository repo) + { + string relativeFilepath = string.Format("new-file-{0}.txt", Guid.NewGuid()); + string filePath = Path.Combine(repo.Info.WorkingDirectory, relativeFilepath); + + File.WriteAllText(filePath, "brand new content\n"); + repo.Index.Stage(relativeFilepath); + } + + private void AssertCommitHasBeenAmended(Repository repo, Commit amendedCommit, Commit originalCommit) + { + Commit headCommit = repo.Head.Tip; + headCommit.ShouldEqual(amendedCommit); + + amendedCommit.Sha.ShouldNotEqual(originalCommit.Sha); + CollectionAssert.AreEqual(originalCommit.Parents, amendedCommit.Parents); + } + + [Test] + public void CanNotAmendAnEmptyRepository() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + + using (Repository repo = Repository.Init(scd.DirectoryPath)) + { + Assert.Throws(() => repo.Commit("I can not amend anything !:(", DummySignature, DummySignature, true)); + } + } } } diff --git a/LibGit2Sharp.Tests/IndexFixture.cs b/LibGit2Sharp.Tests/IndexFixture.cs index 32bf1b204..196df26e8 100644 --- a/LibGit2Sharp.Tests/IndexFixture.cs +++ b/LibGit2Sharp.Tests/IndexFixture.cs @@ -352,9 +352,8 @@ public void UnstagingFileWithBadParamsThrows() public void CanRenameAFile() { SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - string dir = Repository.Init(scd.DirectoryPath); - using (var repo = new Repository(dir)) + using (var repo = Repository.Init(scd.DirectoryPath)) { repo.Index.Count.ShouldEqual(0); @@ -489,133 +488,11 @@ public void RemovingAInvalidFileThrows(string filepath) } } - [Test] - public void CanRetrieveTheStatusOfAFile() - { - using (var repo = new Repository(StandardTestRepoPath)) - { - FileStatus status = repo.Index.RetrieveStatus("new_tracked_file.txt"); - status.ShouldEqual(FileStatus.Added); - } - } - - [Test] - public void RetrievingTheStatusOfADirectoryThrows() - { - using (var repo = new Repository(StandardTestRepoPath)) - { - Assert.Throws(() => { FileStatus status = repo.Index.RetrieveStatus("1"); }); - } - } - - [Test] - public void CanRetrieveTheStatusOfTheWholeWorkingDirectory() - { - TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); - using (var repo = new Repository(path.RepositoryPath)) - { - const string file = "modified_staged_file.txt"; - - RepositoryStatus status = repo.Index.RetrieveStatus(); - - IndexEntry indexEntry = repo.Index[file]; - indexEntry.State.ShouldEqual(FileStatus.Staged); - - status.ShouldNotBeNull(); - status.Count().ShouldEqual(6); - status.IsDirty.ShouldBeTrue(); - - status.Untracked.Single().ShouldEqual("new_untracked_file.txt"); - status.Modified.Single().ShouldEqual("modified_unstaged_file.txt"); - status.Missing.Single().ShouldEqual("deleted_unstaged_file.txt"); - status.Added.Single().ShouldEqual("new_tracked_file.txt"); - 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"); - - indexEntry.State.ShouldEqual(FileStatus.Staged | FileStatus.Modified); - - RepositoryStatus status2 = repo.Index.RetrieveStatus(); - - status2.ShouldNotBeNull(); - status2.Count().ShouldEqual(6); - status2.IsDirty.ShouldBeTrue(); - - status2.Untracked.Single().ShouldEqual("new_untracked_file.txt"); - 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); - status2.Removed.Single().ShouldEqual("deleted_staged_file.txt"); - } - } - - [Test] - public void CanRetrieveTheStatusOfANewRepository() - { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - string dir = Repository.Init(scd.DirectoryPath); - - using (var repo = new Repository(dir)) - { - RepositoryStatus status = repo.Index.RetrieveStatus(); - status.ShouldNotBeNull(); - status.Count().ShouldEqual(0); - status.IsDirty.ShouldBeFalse(); - - status.Untracked.Count().ShouldEqual(0); - status.Modified.Count().ShouldEqual(0); - status.Missing.Count().ShouldEqual(0); - status.Added.Count().ShouldEqual(0); - status.Staged.Count().ShouldEqual(0); - status.Removed.Count().ShouldEqual(0); - } - } - - [Test] - public void RetrievingTheStatusOfARepositoryReturnNativeFilePaths() - { - // Initialize a new repository - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - string dir = Repository.Init(scd.DirectoryPath); - - const string directoryName = "directory"; - const string fileName = "Testfile.txt"; - - // Create a file and insert some content - string directoryPath = Path.Combine(scd.RootedDirectoryPath, directoryName); - string filePath = Path.Combine(directoryPath, fileName); - - Directory.CreateDirectory(directoryPath); - File.WriteAllText(filePath, "Anybody out there?"); - - // Open the repository - using (var repo = new Repository(dir)) - { - // Add the file to the index - repo.Index.Stage(filePath); - - // Get the repository status - RepositoryStatus repoStatus = repo.Index.RetrieveStatus(); - - repoStatus.Count().ShouldEqual(1); - var statusEntry = repoStatus.Single(); - - string expectedPath = string.Format("{0}{1}{2}", directoryName, Path.DirectorySeparatorChar, fileName); - statusEntry.FilePath.ShouldEqual(expectedPath); - - repoStatus.Added.Single().ShouldEqual(statusEntry.FilePath); - } - } - [Test] public void PathsOfIndexEntriesAreExpressedInNativeFormat() { // Initialize a new repository SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - string dir = Repository.Init(scd.DirectoryPath); const string directoryName = "directory"; const string fileName = "Testfile.txt"; @@ -627,8 +504,8 @@ public void PathsOfIndexEntriesAreExpressedInNativeFormat() Directory.CreateDirectory(directoryPath); File.WriteAllText(filePath, "Anybody out there?"); - // Open the repository - using (var repo = new Repository(dir)) + // Initialize the repository + using (var repo = Repository.Init(scd.DirectoryPath)) { // Stage the file repo.Index.Stage(filePath); diff --git a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj index e0bff6420..80da00cd1 100644 --- a/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj +++ b/LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj @@ -43,8 +43,10 @@ + + diff --git a/LibGit2Sharp.Tests/ReferenceFixture.cs b/LibGit2Sharp.Tests/ReferenceFixture.cs index 63cf6a1a8..0579f58a2 100644 --- a/LibGit2Sharp.Tests/ReferenceFixture.cs +++ b/LibGit2Sharp.Tests/ReferenceFixture.cs @@ -236,7 +236,7 @@ public void CanResolveHeadByName() head2.CanonicalName.ShouldEqual("refs/heads/master"); head2.Tip.ShouldNotBeNull(); - head2.Tip.Equals(head.ResolveToDirectReference().Target); + head2.Tip.ShouldEqual(head.ResolveToDirectReference().Target); } } diff --git a/LibGit2Sharp.Tests/RepositoryFixture.cs b/LibGit2Sharp.Tests/RepositoryFixture.cs index e5f183823..24ffbd93e 100644 --- a/LibGit2Sharp.Tests/RepositoryFixture.cs +++ b/LibGit2Sharp.Tests/RepositoryFixture.cs @@ -15,13 +15,13 @@ public class RepositoryFixture : BaseFixture public void CanCreateBareRepo() { SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - string dir = Repository.Init(scd.DirectoryPath, true); - Path.IsPathRooted(dir).ShouldBeTrue(); - Directory.Exists(dir).ShouldBeTrue(); - CheckGitConfigFile(dir); - - using (var repo = new Repository(dir)) + using (var repo = Repository.Init(scd.DirectoryPath, true)) { + string dir = repo.Info.Path; + Path.IsPathRooted(dir).ShouldBeTrue(); + Directory.Exists(dir).ShouldBeTrue(); + CheckGitConfigFile(dir); + repo.Info.WorkingDirectory.ShouldBeNull(); repo.Info.Path.ShouldEqual(scd.RootedDirectoryPath + Path.DirectorySeparatorChar); repo.Info.IsBare.ShouldBeTrue(); @@ -34,13 +34,14 @@ public void CanCreateBareRepo() public void CanCreateStandardRepo() { SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - string dir = Repository.Init(scd.DirectoryPath); - Path.IsPathRooted(dir).ShouldBeTrue(); - Directory.Exists(dir).ShouldBeTrue(); - CheckGitConfigFile(dir); - using (var repo = new Repository(dir)) + using (var repo = Repository.Init(scd.DirectoryPath)) { + string dir = repo.Info.Path; + Path.IsPathRooted(dir).ShouldBeTrue(); + Directory.Exists(dir).ShouldBeTrue(); + CheckGitConfigFile(dir); + repo.Info.WorkingDirectory.ShouldNotBeNull(); repo.Info.Path.ShouldEqual(Path.Combine(scd.RootedDirectoryPath, ".git" + Path.DirectorySeparatorChar)); repo.Info.IsBare.ShouldBeFalse(); @@ -57,7 +58,7 @@ private static void CheckGitConfigFile(string dir) File.Exists(configFilePath).ShouldBeTrue(); string contents = File.ReadAllText(configFilePath); - contents.IndexOf("repositoryformatversion = 0").ShouldNotEqual(-1); + contents.IndexOf("repositoryformatversion = 0", StringComparison.Ordinal).ShouldNotEqual(-1); } private static void AssertIsHidden(string repoPath) @@ -67,6 +68,18 @@ private static void AssertIsHidden(string repoPath) (attribs & FileAttributes.Hidden).ShouldEqual(FileAttributes.Hidden); } + [Test] + public void CanReinitARepository() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + + using (Repository repository = Repository.Init(scd.DirectoryPath)) + using (Repository repository2 = Repository.Init(scd.DirectoryPath)) + { + repository.Info.Path.ShouldEqual(repository2.Info.Path); + } + } + [Test] public void CreatingRepoWithBadParamsThrows() { @@ -86,6 +99,7 @@ private static void AssertInitializedRepository(Repository repo) headRef.ResolveToDirectReference().ShouldBeNull(); repo.Head.ShouldNotBeNull(); + repo.Head.IsCurrentRepositoryHead.ShouldBeTrue(); repo.Head.CanonicalName.ShouldEqual(headRef.TargetIdentifier); repo.Head.Tip.ShouldBeNull(); @@ -102,12 +116,33 @@ private static void AssertInitializedRepository(Repository repo) } [Test] - public void CanOpenRepoWithFullPath() + public void CanOpenBareRepositoryThroughAFullPathToTheGitDir() { string path = Path.GetFullPath(BareTestRepoPath); using (var repo = new Repository(path)) { repo.ShouldNotBeNull(); + repo.Info.WorkingDirectory.ShouldBeNull(); + } + } + + [Test] + public void CanOpenStandardRepositoryThroughAWorkingDirPath() + { + using (var repo = new Repository(StandardTestRepoWorkingDirPath)) + { + repo.ShouldNotBeNull(); + repo.Info.WorkingDirectory.ShouldNotBeNull(); + } + } + + [Test] + public void OpeningStandardRepositoryThroughTheGitDirGuessesTheWorkingDirPath() + { + using (var repo = new Repository(StandardTestRepoPath)) + { + repo.ShouldNotBeNull(); + repo.Info.WorkingDirectory.ShouldNotBeNull(); } } @@ -231,9 +266,8 @@ public void CanLookupWhithShortIdentifers() const string expectedSha = expectedAbbrevSha + "02d96c9dbf64f6e238c45ddcfa762eef0"; SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - string dir = Repository.Init(scd.DirectoryPath); - using (var repo = new Repository(dir)) + using (var repo = Repository.Init(scd.DirectoryPath)) { string filePath = Path.Combine(repo.Info.WorkingDirectory, "new.txt"); @@ -267,28 +301,6 @@ public void LookingUpWithBadParamsThrows() } } - [Test] - public void CanCheckForObjectExistence() - { - using (var repo = new Repository(BareTestRepoPath)) - { - repo.HasObject("8496071c1b46c854b31185ea97743be6a8774479").ShouldBeTrue(); - repo.HasObject("1385f264afb75a56a5bec74243be9b367ba4ca08").ShouldBeTrue(); - repo.HasObject("ce08fe4884650f067bd5703b6a59a8b3b3c99a09").ShouldBeFalse(); - repo.HasObject("8496071c1c46c854b31185ea97743be6a8774479").ShouldBeFalse(); - } - } - - [Test] - public void CheckingForObjectExistenceWithBadParamsThrows() - { - using (var repo = new Repository(BareTestRepoPath)) - { - Assert.Throws(() => repo.HasObject(string.Empty)); - Assert.Throws(() => repo.HasObject(null)); - } - } - [Test] public void CanDiscoverABareRepoGivenTheRepoPath() { diff --git a/LibGit2Sharp.Tests/ResetFixture.cs b/LibGit2Sharp.Tests/ResetFixture.cs new file mode 100644 index 000000000..cfe0791dc --- /dev/null +++ b/LibGit2Sharp.Tests/ResetFixture.cs @@ -0,0 +1,155 @@ +using System; +using System.IO; +using LibGit2Sharp.Tests.TestHelpers; +using NUnit.Framework; + +namespace LibGit2Sharp.Tests +{ + [TestFixture] + public class ResetFixture : BaseFixture + { + [TestCase(true)] + [TestCase(false)] + public void ResetANewlyInitializedRepositoryThrows(bool isBare) + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + + using (var repo = Repository.Init(scd.DirectoryPath, isBare)) + { + Assert.Throws(() => repo.Reset(ResetOptions.Soft, repo.Head.CanonicalName)); + } + } + + [Test] + public void SoftResetToTheHeadOfARepositoryDoesNotChangeTheTargetOfTheHead() + { + using (var repo = new Repository(BareTestRepoPath)) + { + Branch oldHead = repo.Head; + + repo.Reset(ResetOptions.Soft, oldHead.CanonicalName); + + repo.Head.ShouldEqual(oldHead); + } + } + + [Test] + public void SoftResetSetsTheHeadToTheDereferencedCommitOfAChainedTag() + { + TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(); + + using (var repo = new Repository(path.RepositoryPath)) + { + Tag tag = repo.Tags["test"]; + repo.Reset(ResetOptions.Soft, tag.CanonicalName); + repo.Head.Tip.Sha.ShouldEqual("e90810b8df3e80c413d903f631643c716887138d"); + } + } + + [Test] + public void ResettingWithBadParamsThrows() + { + using (var repo = new Repository(BareTestRepoPath)) + { + Assert.Throws(() => repo.Reset(ResetOptions.Soft, null)); + Assert.Throws(() => repo.Reset(ResetOptions.Soft, "")); + Assert.Throws(() => repo.Reset(ResetOptions.Soft, Constants.UnknownSha)); + } + } + + [Test] + public void SoftResetSetsTheHeadToTheSpecifiedCommit() + { + /* Make the Head point to a branch through its name */ + AssertSoftReset(b => b.Name, false, b => b.Name); + } + + [Test] + public void SoftResetSetsTheDetachedHeadToTheSpecifiedCommit() + { + /* Make the Head point to a commit through its sha (Detaches the Head) */ + AssertSoftReset(b => b.Tip.Sha, true, b => "(no branch)"); + } + + private void AssertSoftReset(Func branchIdentifierRetriever, bool shouldHeadBeDetached, Func expectedHeadNameRetriever) + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + + using (var repo = Repository.Init(scd.DirectoryPath)) + { + FeedTheRepository(repo); + + Tag tag = repo.Tags["mytag"]; + Branch branch = repo.Branches["mybranch"]; + + string branchIdentifier = branchIdentifierRetriever(branch); + repo.Branches.Checkout(branchIdentifier); + repo.Info.IsHeadDetached.ShouldEqual(shouldHeadBeDetached); + + string expectedHeadName = expectedHeadNameRetriever(branch); + repo.Head.Name.ShouldEqual(expectedHeadName); + repo.Head.Tip.Sha.ShouldEqual(branch.Tip.Sha); + + /* Reset --soft the Head to a tag through its canonical name */ + repo.Reset(ResetOptions.Soft, tag.CanonicalName); + repo.Head.Name.ShouldEqual(expectedHeadName); + repo.Head.Tip.Id.ShouldEqual(tag.Target.Id); + + repo.Index.RetrieveStatus("a.txt").ShouldEqual(FileStatus.Staged); + + /* Reset --soft the Head to a commit through its sha */ + repo.Reset(ResetOptions.Soft, branch.Tip.Sha); + repo.Head.Name.ShouldEqual(expectedHeadName); + repo.Head.Tip.Sha.ShouldEqual(branch.Tip.Sha); + + repo.Index.RetrieveStatus("a.txt").ShouldEqual(FileStatus.Unaltered); + } + } + + private static void FeedTheRepository(Repository repo) + { + string fullPath = Path.Combine(repo.Info.WorkingDirectory, "a.txt"); + File.WriteAllText(fullPath, "Hello\n"); + repo.Index.Stage(fullPath); + repo.Commit("Initial commit", Constants.Signature, Constants.Signature); + repo.ApplyTag("mytag"); + + File.AppendAllText(fullPath, "World\n"); + repo.Index.Stage(fullPath); + + Signature shiftedSignature = Constants.Signature.TimeShift(TimeSpan.FromMinutes(1)); + repo.Commit("Update file", shiftedSignature, shiftedSignature); + repo.CreateBranch("mybranch"); + + repo.Branches.Checkout("mybranch"); + + repo.Index.RetrieveStatus().IsDirty.ShouldBeFalse(); + } + + [Test] + public void MixedResetRefreshesTheIndex() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + + using (var repo = Repository.Init(scd.DirectoryPath)) + { + FeedTheRepository(repo); + + Tag tag = repo.Tags["mytag"]; + + repo.Reset(ResetOptions.Mixed, tag.CanonicalName); + + repo.Index.RetrieveStatus("a.txt").ShouldEqual(FileStatus.Modified); + } + } + + [Test] + public void MixedResetInABareRepositoryThrows() + { + using (var repo = new Repository(BareTestRepoPath)) + { + Assert.Throws(() => repo.Reset(ResetOptions.Mixed, repo.Head.Tip.Sha)); + } + } + } +} diff --git a/LibGit2Sharp.Tests/StatusFixture.cs b/LibGit2Sharp.Tests/StatusFixture.cs new file mode 100644 index 000000000..8c09974ba --- /dev/null +++ b/LibGit2Sharp.Tests/StatusFixture.cs @@ -0,0 +1,154 @@ +using System; +using System.IO; +using System.Linq; +using LibGit2Sharp.Tests.TestHelpers; +using NUnit.Framework; + +namespace LibGit2Sharp.Tests +{ + public class StatusFixture : BaseFixture + { + [Test] + public void CanRetrieveTheStatusOfAFile() + { + using (var repo = new Repository(StandardTestRepoPath)) + { + FileStatus status = repo.Index.RetrieveStatus("new_tracked_file.txt"); + status.ShouldEqual(FileStatus.Added); + } + } + + [Test] + public void RetrievingTheStatusOfADirectoryThrows() + { + using (var repo = new Repository(StandardTestRepoPath)) + { + Assert.Throws(() => { FileStatus status = repo.Index.RetrieveStatus("1"); }); + } + } + + [Test] + public void CanRetrieveTheStatusOfTheWholeWorkingDirectory() + { + TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); + using (var repo = new Repository(path.RepositoryPath)) + { + const string file = "modified_staged_file.txt"; + + RepositoryStatus status = repo.Index.RetrieveStatus(); + + IndexEntry indexEntry = repo.Index[file]; + indexEntry.State.ShouldEqual(FileStatus.Staged); + + status.ShouldNotBeNull(); + status.Count().ShouldEqual(6); + status.IsDirty.ShouldBeTrue(); + + status.Untracked.Single().ShouldEqual("new_untracked_file.txt"); + status.Modified.Single().ShouldEqual("modified_unstaged_file.txt"); + status.Missing.Single().ShouldEqual("deleted_unstaged_file.txt"); + status.Added.Single().ShouldEqual("new_tracked_file.txt"); + 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"); + + indexEntry.State.ShouldEqual(FileStatus.Staged | FileStatus.Modified); + + RepositoryStatus status2 = repo.Index.RetrieveStatus(); + + status2.ShouldNotBeNull(); + status2.Count().ShouldEqual(6); + status2.IsDirty.ShouldBeTrue(); + + status2.Untracked.Single().ShouldEqual("new_untracked_file.txt"); + 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); + status2.Removed.Single().ShouldEqual("deleted_staged_file.txt"); + } + } + + [Test] + public void CanRetrieveTheStatusOfANewRepository() + { + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + + using (Repository repo = Repository.Init(scd.DirectoryPath)) + { + RepositoryStatus status = repo.Index.RetrieveStatus(); + status.ShouldNotBeNull(); + status.Count().ShouldEqual(0); + status.IsDirty.ShouldBeFalse(); + + status.Untracked.Count().ShouldEqual(0); + status.Modified.Count().ShouldEqual(0); + status.Missing.Count().ShouldEqual(0); + status.Added.Count().ShouldEqual(0); + status.Staged.Count().ShouldEqual(0); + status.Removed.Count().ShouldEqual(0); + } + } + + [Test] + public void RetrievingTheStatusOfARepositoryReturnNativeFilePaths() + { + // Initialize a new repository + SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); + + const string directoryName = "directory"; + const string fileName = "Testfile.txt"; + + // Create a file and insert some content + string directoryPath = Path.Combine(scd.RootedDirectoryPath, directoryName); + string filePath = Path.Combine(directoryPath, fileName); + + Directory.CreateDirectory(directoryPath); + File.WriteAllText(filePath, "Anybody out there?"); + + // Open the repository + using (Repository repo = Repository.Init(scd.DirectoryPath)) + { + // Add the file to the index + repo.Index.Stage(filePath); + + // Get the repository status + RepositoryStatus repoStatus = repo.Index.RetrieveStatus(); + + repoStatus.Count().ShouldEqual(1); + StatusEntry statusEntry = repoStatus.Single(); + + string expectedPath = string.Format("{0}{1}{2}", directoryName, Path.DirectorySeparatorChar, fileName); + statusEntry.FilePath.ShouldEqual(expectedPath); + + repoStatus.Added.Single().ShouldEqual(statusEntry.FilePath); + } + } + + [Test] + public void RetrievingTheStatusOfTheRepositoryHonorsTheGitIgnoreDirectives() + { + TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); + using (var repo = new Repository(path.RepositoryPath)) + { + string relativePath = Path.Combine("1", "look-ma.txt"); + string fullFilePath = Path.Combine(repo.Info.WorkingDirectory, relativePath); + File.WriteAllText(fullFilePath, "I'm going to be ignored!"); + + RepositoryStatus status = repo.Index.RetrieveStatus(); + + CollectionAssert.AreEqual(new[]{relativePath, "new_untracked_file.txt"}, status.Untracked); + + string gitignorePath = Path.Combine(repo.Info.WorkingDirectory, ".gitignore"); + File.WriteAllText(gitignorePath, "*.txt" + Environment.NewLine); + + RepositoryStatus newStatus = repo.Index.RetrieveStatus(); + newStatus.Untracked.Single().ShouldEqual(".gitignore"); + + repo.Index.RetrieveStatus(relativePath).ShouldEqual(FileStatus.Ignored); + } + } + } +} diff --git a/LibGit2Sharp.Tests/TagFixture.cs b/LibGit2Sharp.Tests/TagFixture.cs index 5f45a8355..0eb9e640f 100644 --- a/LibGit2Sharp.Tests/TagFixture.cs +++ b/LibGit2Sharp.Tests/TagFixture.cs @@ -173,9 +173,8 @@ public void CreatingAnAnnotatedTagIsDeterministic() public void CreatingATagInAEmptyRepositoryThrows() { SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - string dir = Repository.Init(scd.DirectoryPath); - using (var repo = new Repository(dir)) + using (var repo = Repository.Init(scd.DirectoryPath)) { Assert.Throws(() => repo.ApplyTag("mynotag")); } @@ -186,9 +185,8 @@ public void CreatingATagInAEmptyRepositoryThrows() public void CreatingATagForHeadInAEmptyRepositoryThrows() { SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - string dir = Repository.Init(scd.DirectoryPath); - using (var repo = new Repository(dir)) + using (var repo = Repository.Init(scd.DirectoryPath)) { Assert.Throws(() => repo.ApplyTag("mytaghead", "HEAD")); } @@ -541,9 +539,8 @@ public void CanListTags() public void CanListAllTagsInAEmptyRepository() { SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - string dir = Repository.Init(scd.DirectoryPath); - using (var repo = new Repository(dir)) + using (var repo = Repository.Init(scd.DirectoryPath)) { repo.Info.IsEmpty.ShouldBeTrue(); repo.Tags.Count().ShouldEqual(0); diff --git a/LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs b/LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs index 52f363fd5..08b5ba54e 100644 --- a/LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs +++ b/LibGit2Sharp.Tests/TestHelpers/BaseFixture.cs @@ -20,6 +20,8 @@ static BaseFixture() public static string StandardTestRepoPath { get; private set; } public static DirectoryInfo ResourcesDirectory { get; private set; } + public static readonly Signature DummySignature = new Signature("Author N. Ame", "him@there.com", DateTimeOffset.Now); + private static void SetUpTestEnvironment() { var source = new DirectoryInfo(@"../../Resources"); diff --git a/LibGit2Sharp/Branch.cs b/LibGit2Sharp/Branch.cs index 745ef60a6..fdfd74fb3 100644 --- a/LibGit2Sharp/Branch.cs +++ b/LibGit2Sharp/Branch.cs @@ -111,11 +111,11 @@ public int BehindBy /// Gets a value indicating whether this instance is current branch (HEAD) in the repository. /// /// - /// true if this instance is current branch; otherwise, false. + /// true if this instance is the current branch; otherwise, false. /// - public virtual bool IsCurrentRepositoryHead + public bool IsCurrentRepositoryHead { - get { return repo.Refs[CanonicalName].ResolveToDirectReference() == repo.Refs["HEAD"].ResolveToDirectReference(); } + get { return repo.Head == this; } } /// diff --git a/LibGit2Sharp/BranchCollection.cs b/LibGit2Sharp/BranchCollection.cs index 84fd71811..9061017d5 100644 --- a/LibGit2Sharp/BranchCollection.cs +++ b/LibGit2Sharp/BranchCollection.cs @@ -100,24 +100,8 @@ public Branch Create(string name, string shaOrReferenceName) 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) - { - return; - } - - throw new LibGit2Exception(String.Format(CultureInfo.InvariantCulture, - "No commit object identified by '{0}' can be found in the repository.", - identifier)); + GitObject commit = repo.Lookup(target, GitObjectType.Any, LookUpOptions.ThrowWhenNoGitObjectHasBeenFound | LookUpOptions.DereferenceResultToCommit | LookUpOptions.ThrowWhenCanNotBeDereferencedToACommit); + return commit.Id; } /// diff --git a/LibGit2Sharp/Commit.cs b/LibGit2Sharp/Commit.cs index c5bc5ec6b..1aa5107ec 100644 --- a/LibGit2Sharp/Commit.cs +++ b/LibGit2Sharp/Commit.cs @@ -89,6 +89,20 @@ public IEnumerable Parents get { return parents.Value; } } + /// + /// Gets The count of parent commits. + /// + public uint ParentsCount + { + get + { + using (var obj = new ObjectSafeWrapper(Id, repo)) + { + return NativeMethods.git_commit_parentcount(obj.ObjectPtr); + } + } + } + private IEnumerable RetrieveParentsOfCommit(ObjectId oid) { using (var obj = new ObjectSafeWrapper(oid, repo)) diff --git a/LibGit2Sharp/CommitCollection.cs b/LibGit2Sharp/CommitCollection.cs index 93db5a7ab..27f622760 100644 --- a/LibGit2Sharp/CommitCollection.cs +++ b/LibGit2Sharp/CommitCollection.cs @@ -129,53 +129,55 @@ private static bool PointsAtTheHead(string shaOrRefName) /// 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. + /// True to amend the current pointed at by , false otherwise. /// The generated . - public Commit Create(string message, Signature author, Signature committer) + public Commit Create(string message, Signature author, Signature committer, bool amendPreviousCommit) { Ensure.ArgumentNotNull(message, "message"); Ensure.ArgumentNotNull(author, "author"); Ensure.ArgumentNotNull(committer, "committer"); + if (amendPreviousCommit && repo.Info.IsEmpty) + { + throw new LibGit2Exception("Can not amend anything. The Head doesn't point at any commit."); + } + GitOid treeOid; int res = NativeMethods.git_tree_create_fromindex(out treeOid, repo.Index.Handle); - string encoding = null; - Ensure.Success(res); - Reference head = repo.Refs["HEAD"]; + var parentIds = RetrieveParentIdsOfTheCommitBeingCreated(repo, amendPreviousCommit); GitOid commitOid; using (var treePtr = new ObjectSafeWrapper(new ObjectId(treeOid), repo)) - using (ObjectSafeWrapper headPtr = RetrieveHeadCommitPtr(head)) + using (var parentObjectPtrs = new DisposableEnumerable(parentIds.Select(id => new ObjectSafeWrapper(id, repo)))) + using (SignatureSafeHandle authorHandle = author.BuildHandle()) + using (SignatureSafeHandle committerHandle = committer.BuildHandle()) { - IntPtr[] parentPtrs = BuildArrayFrom(headPtr); - res = NativeMethods.git_commit_create(out commitOid, repo.Handle, head.CanonicalName, author.Handle, - committer.Handle, encoding, message, treePtr.ObjectPtr, parentPtrs.Count(), parentPtrs); + string encoding = null; //TODO: Handle the encoding of the commit to be created + + IntPtr[] parentsPtrs = parentObjectPtrs.Select(o => o.ObjectPtr ).ToArray(); + res = NativeMethods.git_commit_create(out commitOid, repo.Handle, repo.Refs["HEAD"].CanonicalName, authorHandle, + committerHandle, encoding, message, treePtr.ObjectPtr, parentObjectPtrs.Count(), parentsPtrs); + Ensure.Success(res); } - Ensure.Success(res); return repo.Lookup(new ObjectId(commitOid)); } - private static IntPtr[] BuildArrayFrom(ObjectSafeWrapper headPtr) + private static IEnumerable RetrieveParentIdsOfTheCommitBeingCreated(Repository repo, bool amendPreviousCommit) { - if (headPtr.ObjectPtr == IntPtr.Zero) + if (amendPreviousCommit) { - return new IntPtr[] { }; + return repo.Head.Tip.Parents.Select(c => c.Id); } - return new[] { headPtr.ObjectPtr }; - } - - private ObjectSafeWrapper RetrieveHeadCommitPtr(Reference head) - { - DirectReference oidRef = head.ResolveToDirectReference(); - if (oidRef == null) + if (repo.Info.IsEmpty) { - return new ObjectSafeWrapper(null, repo); + return Enumerable.Empty(); } - return new ObjectSafeWrapper(new ObjectId(oidRef.TargetIdentifier), repo); + return new[] { repo.Head.Tip.Id }; } private class CommitEnumerator : IEnumerator @@ -276,50 +278,12 @@ private void Sort(GitSortOptions options) NativeMethods.git_revwalk_sorting(handle, options); } - private GitObject RetrieveObject(string shaOrReferenceName) - { - GitObject gitObj = repo.Lookup(shaOrReferenceName); - - // TODO: Should we check the type? Git-log allows TagAnnotation oid as parameter. But what about Blobs and Trees? - EnsureGitObjectNotNull(shaOrReferenceName, gitObj); - - return gitObj; - } - - private static void EnsureGitObjectNotNull(string shaOrReferenceName, GitObject gitObj) - { - if (gitObj != null) - { - return; - } - - throw new LibGit2Exception(string.Format(CultureInfo.InvariantCulture, - "No valid git object pointed at by '{0}' exists in the repository.", - shaOrReferenceName)); - } - private ObjectId DereferenceToCommit(string identifier) { - GitObject obj = RetrieveObject(identifier); - - if (obj is Commit) - { - return obj.Id; - } - - if (obj is TagAnnotation) - { - return DereferenceToCommit(((TagAnnotation)obj).Target.Sha); - } - - if (obj is Blob || obj is Tree) - { - return null; - } + // TODO: Should we check the type? Git-log allows TagAnnotation oid as parameter. But what about Blobs and Trees? + GitObject commit = repo.Lookup(identifier, GitObjectType.Any, LookUpOptions.ThrowWhenNoGitObjectHasBeenFound | LookUpOptions.DereferenceResultToCommit); - throw new LibGit2Exception(string.Format(CultureInfo.InvariantCulture, - "The Git object pointed at by '{0}' can not be dereferenced to a commit.", - identifier)); + return commit != null ? commit.Id : null; } private IEnumerable RetrieveCommitOids(object identifier) @@ -357,7 +321,7 @@ private IEnumerable RetrieveCommitOids(object identifier) if (identifier is Branch) { var branch = (Branch)identifier; - EnsureGitObjectNotNull(branch.CanonicalName, branch.Tip); + Ensure.GitObjectIsNotNull(branch.Tip, branch.CanonicalName); yield return branch.Tip.Id; yield break; diff --git a/LibGit2Sharp/Core/DisposableEnumerable.cs b/LibGit2Sharp/Core/DisposableEnumerable.cs new file mode 100644 index 000000000..9c4d532ac --- /dev/null +++ b/LibGit2Sharp/Core/DisposableEnumerable.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace LibGit2Sharp.Core +{ + internal class DisposableEnumerable : IEnumerable, IDisposable where T : IDisposable + { + private readonly IEnumerable enumerable; + + public DisposableEnumerable(IEnumerable enumerable) + { + this.enumerable = enumerable; + } + + public IEnumerator GetEnumerator() + { + return enumerable.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + public void Dispose() + { + foreach (var entry in enumerable) + { + entry.Dispose(); + } + } + } +} diff --git a/LibGit2Sharp/Core/Ensure.cs b/LibGit2Sharp/Core/Ensure.cs index 956dea8a2..1e0cf6116 100644 --- a/LibGit2Sharp/Core/Ensure.cs +++ b/LibGit2Sharp/Core/Ensure.cs @@ -81,5 +81,17 @@ public static void ArgumentConformsTo(T argumentValue, Func checker, throw new ArgumentException(argumentName); } + + public static void GitObjectIsNotNull(GitObject gitObject, string identifier) + { + if (gitObject != null) + { + return; + } + + throw new LibGit2Exception(string.Format(CultureInfo.InvariantCulture, + "No valid git object identified by '{0}' exists in the repository.", + identifier)); + } } } diff --git a/LibGit2Sharp/Core/GitIndexEntry.cs b/LibGit2Sharp/Core/GitIndexEntry.cs index 2066cb0bb..bf0df2ca6 100644 --- a/LibGit2Sharp/Core/GitIndexEntry.cs +++ b/LibGit2Sharp/Core/GitIndexEntry.cs @@ -1,4 +1,5 @@ -using System.Runtime.InteropServices; +using System; +using System.Runtime.InteropServices; namespace LibGit2Sharp.Core { @@ -16,6 +17,6 @@ internal class GitIndexEntry public GitOid oid; public ushort Flags; public ushort ExtendedFlags; - public string Path; + public IntPtr Path; } } diff --git a/LibGit2Sharp/Core/GitObjectExtensions.cs b/LibGit2Sharp/Core/GitObjectExtensions.cs new file mode 100644 index 000000000..59e12500c --- /dev/null +++ b/LibGit2Sharp/Core/GitObjectExtensions.cs @@ -0,0 +1,35 @@ +using System.Globalization; + +namespace LibGit2Sharp.Core +{ + internal static class GitObjectExtensions + { + public static Commit DereferenceToCommit(this GitObject gitObject, string identifier, bool throwsIfCanNotBeDereferencedToACommit) + { + if (gitObject == null && !throwsIfCanNotBeDereferencedToACommit) + { + return null; + } + + if (gitObject is Commit) + { + return (Commit)gitObject; + } + + if (gitObject is TagAnnotation) + { + var target = ((TagAnnotation)gitObject).Target; + return target.DereferenceToCommit(identifier, throwsIfCanNotBeDereferencedToACommit); + } + + if (!throwsIfCanNotBeDereferencedToACommit && (gitObject is Blob || gitObject is Tree)) + { + return null; + } + + throw new LibGit2Exception(string.Format(CultureInfo.InvariantCulture, + "The Git object pointed at by '{0}' can not be dereferenced to a commit.", + identifier)); + } + } +} diff --git a/LibGit2Sharp/Core/GitSignature.cs b/LibGit2Sharp/Core/GitSignature.cs index b1ce4b66e..3261d4c57 100644 --- a/LibGit2Sharp/Core/GitSignature.cs +++ b/LibGit2Sharp/Core/GitSignature.cs @@ -1,12 +1,13 @@ -using System.Runtime.InteropServices; +using System; +using System.Runtime.InteropServices; namespace LibGit2Sharp.Core { [StructLayout(LayoutKind.Sequential)] internal class GitSignature { - public string Name; - public string Email; + public IntPtr Name; + public IntPtr Email; public GitTime When; } } diff --git a/LibGit2Sharp/Core/LookUpOptions.cs b/LibGit2Sharp/Core/LookUpOptions.cs new file mode 100644 index 000000000..7a555fdff --- /dev/null +++ b/LibGit2Sharp/Core/LookUpOptions.cs @@ -0,0 +1,13 @@ +using System; + +namespace LibGit2Sharp.Core +{ + [Flags] + internal enum LookUpOptions + { + None = 0, + ThrowWhenNoGitObjectHasBeenFound = 1, + DereferenceResultToCommit = 2, + ThrowWhenCanNotBeDereferencedToACommit = 4, + } +} diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index 62d025000..4c9b4c5f5 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -12,16 +12,34 @@ internal static class NativeMethods static NativeMethods() { - string originalAssemblypath = new Uri(Assembly.GetExecutingAssembly().CodeBase).LocalPath; + if (!IsRunningOnLinux()) + { + 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"; + //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); + 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))); + const string pathEnvVariable = "PATH"; + Environment.SetEnvironmentVariable(pathEnvVariable, + String.Format("{0}{1}{2}", path, Path.PathSeparator, Environment.GetEnvironmentVariable(pathEnvVariable))); + } + + git_threads_init(); + AppDomain.CurrentDomain.ProcessExit += ThreadsShutdown; + } + + private static void ThreadsShutdown(object sender, EventArgs e) + { + git_threads_shutdown(); + } + + private static bool IsRunningOnLinux() + { + // see http://mono-project.com/FAQ%3a_Technical#Mono_Platforms + var p = (int)Environment.OSVersion.Platform; + return (p == 4) || (p == 6) || (p == 128); } [DllImport(libgit2)] @@ -41,8 +59,8 @@ public static extern int git_commit_create( out GitOid oid, RepositorySafeHandle repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string updateRef, - GitSignature author, - GitSignature committer, + SignatureSafeHandle author, + SignatureSafeHandle committer, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string encoding, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string message, IntPtr tree, @@ -141,6 +159,11 @@ public static extern int git_index_add( [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string path, int stage = 0); + [DllImport(libgit2)] + public static extern int git_index_add2( + IndexSafeHandle index, + GitIndexEntry entry); + [DllImport(libgit2)] public static extern uint git_index_entrycount(IndexSafeHandle index); @@ -155,6 +178,9 @@ public static extern int git_index_find( [DllImport(libgit2)] public static extern IntPtr git_index_get(IndexSafeHandle index, uint n); + [DllImport(libgit2)] + public static extern int git_index_read_tree(IndexSafeHandle index, IntPtr tree); + [DllImport(libgit2)] public static extern int git_index_remove(IndexSafeHandle index, int n); @@ -179,13 +205,6 @@ public static extern int git_index_find( [DllImport(libgit2)] public static extern GitObjectType git_object_type(IntPtr obj); - [DllImport(libgit2)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool git_odb_exists(DatabaseSafeHandle db, ref GitOid id); - - [DllImport(libgit2)] - public static extern void git_odb_free(IntPtr obj); - [DllImport(libgit2)] public static extern int git_oid_cmp(ref GitOid a, ref GitOid b); @@ -266,9 +285,6 @@ public static extern int git_repository_config( out ConfigurationSafeHandle cfg, RepositorySafeHandle repo); - [DllImport(libgit2)] - public static extern int git_repository_odb(out DatabaseSafeHandle odb, RepositorySafeHandle repository); - [DllImport(libgit2)] public static extern int git_repository_discover( byte[] repository_path, // NB: This is more properly a StringBuilder, but it's UTF8 @@ -336,7 +352,7 @@ public static extern int git_repository_open( [DllImport(libgit2)] public static extern int git_signature_new( - out IntPtr signature, + out SignatureSafeHandle signature, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string name, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string email, long time, @@ -362,7 +378,7 @@ public static extern int git_tag_create( RepositorySafeHandle repo, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string name, IntPtr target, - GitSignature signature, + SignatureSafeHandle signature, [MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string message, bool force); @@ -391,6 +407,12 @@ public static extern int git_tag_delete( [DllImport(libgit2)] public static extern IntPtr git_tag_target_oid(IntPtr tag); + [DllImport(libgit2)] + public static extern void git_threads_init(); + + [DllImport(libgit2)] + public static extern void git_threads_shutdown(); + [DllImport(libgit2)] public static extern int git_tree_create_fromindex(out GitOid treeOid, IndexSafeHandle index); @@ -398,7 +420,7 @@ public static extern int git_tag_delete( public static extern int git_tree_entry_2object(out IntPtr obj, RepositorySafeHandle repo, IntPtr entry); [DllImport(libgit2)] - public static extern int git_tree_entry_attributes(IntPtr entry); + public static extern uint git_tree_entry_attributes(IntPtr entry); [DllImport(libgit2)] public static extern IntPtr git_tree_entry_byindex(IntPtr tree, uint idx); diff --git a/LibGit2Sharp/Core/ReferenceExtensions.cs b/LibGit2Sharp/Core/ReferenceExtensions.cs new file mode 100644 index 000000000..ddf009b48 --- /dev/null +++ b/LibGit2Sharp/Core/ReferenceExtensions.cs @@ -0,0 +1,22 @@ +namespace LibGit2Sharp.Core +{ + internal static class ReferenceExtensions + { + private static bool IsPeelable(this Reference reference) + { + return reference is DirectReference || (reference is SymbolicReference && ((SymbolicReference)reference).Target != null); + } + + public static ObjectId PeelToTargetObjectId(this Reference reference) + { + if (!reference.IsPeelable()) + { + return null; + } + + string sha = reference.ResolveToDirectReference().TargetIdentifier; + + return new ObjectId(sha); + } + } +} diff --git a/LibGit2Sharp/Core/DatabaseSafeHandle.cs b/LibGit2Sharp/Core/SignatureSafeHandle.cs similarity index 56% rename from LibGit2Sharp/Core/DatabaseSafeHandle.cs rename to LibGit2Sharp/Core/SignatureSafeHandle.cs index 2761be5fa..8ad7a2c08 100644 --- a/LibGit2Sharp/Core/DatabaseSafeHandle.cs +++ b/LibGit2Sharp/Core/SignatureSafeHandle.cs @@ -1,10 +1,10 @@ namespace LibGit2Sharp.Core { - internal class DatabaseSafeHandle : SafeHandleBase + internal class SignatureSafeHandle : SafeHandleBase { protected override bool ReleaseHandle() { - NativeMethods.git_odb_free(handle); + NativeMethods.git_signature_free(handle); return true; } } diff --git a/LibGit2Sharp/DetachedHead.cs b/LibGit2Sharp/DetachedHead.cs index 9d83b0e9d..2756f451c 100644 --- a/LibGit2Sharp/DetachedHead.cs +++ b/LibGit2Sharp/DetachedHead.cs @@ -7,20 +7,6 @@ internal DetachedHead(Repository repo, Reference reference) { } - /// - /// 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; diff --git a/LibGit2Sharp/DirectReference.cs b/LibGit2Sharp/DirectReference.cs index a75fe6ccd..27bb3985c 100644 --- a/LibGit2Sharp/DirectReference.cs +++ b/LibGit2Sharp/DirectReference.cs @@ -1,5 +1,4 @@ -using LibGit2Sharp.Core; -using LibGit2Sharp.Core.Compat; +using LibGit2Sharp.Core.Compat; namespace LibGit2Sharp { diff --git a/LibGit2Sharp/FileStatus.cs b/LibGit2Sharp/FileStatus.cs index a5f88be89..565576112 100644 --- a/LibGit2Sharp/FileStatus.cs +++ b/LibGit2Sharp/FileStatus.cs @@ -45,7 +45,9 @@ public enum FileStatus /// Missing = (1 << 5), /* GIT_STATUS_WT_DELETED */ - //TODO: Ignored files not handled yet - GIT_STATUS_IGNORED = (1 << 6), /* GIT_STATUS_IGNORED */ + /// + /// The file is but its name and/or path macthes an exclude pattern in a gitignore file. + /// + Ignored = (1 << 6), /* GIT_STATUS_IGNORED */ } } diff --git a/LibGit2Sharp/GitObject.cs b/LibGit2Sharp/GitObject.cs index 8137011f5..5837c8a4d 100644 --- a/LibGit2Sharp/GitObject.cs +++ b/LibGit2Sharp/GitObject.cs @@ -76,26 +76,6 @@ 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 c2fa1220e..af4c59335 100644 --- a/LibGit2Sharp/IQueryableCommitCollection.cs +++ b/LibGit2Sharp/IQueryableCommitCollection.cs @@ -15,7 +15,8 @@ public interface IQueryableCommitCollection : ICommitCollection //TODO: Find a n /// 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. + /// True to amend the current pointed at by , false otherwise. /// The generated . - Commit Create(string message, Signature author, Signature committer); + Commit Create(string message, Signature author, Signature committer, bool amendPreviousCommit); } } diff --git a/LibGit2Sharp/Index.cs b/LibGit2Sharp/Index.cs index 900fc1d85..02f378af5 100644 --- a/LibGit2Sharp/Index.cs +++ b/LibGit2Sharp/Index.cs @@ -17,6 +17,8 @@ public class Index : IEnumerable, IDisposable private readonly IndexSafeHandle handle; private readonly Repository repo; + private static readonly Utf8Marshaler utf8Marshaler = new Utf8Marshaler(); + internal Index(Repository repo) { this.repo = repo; @@ -219,10 +221,7 @@ public void Unstage(IEnumerable paths) RemoveFromIndex(kvp.Key); } - bool doesExistInWorkingDirectory = - !(kvp.Value.Has(FileStatus.Removed) || kvp.Value.Has(FileStatus.Nonexistent) || - kvp.Value.Has(FileStatus.Missing)); - RestorePotentialPreviousVersionOfHeadIntoIndex(kvp.Key, doesExistInWorkingDirectory); + RestorePotentialPreviousVersionOfHeadIntoIndex(kvp.Key); } UpdatePhysicalIndex(); @@ -435,34 +434,23 @@ private void RemoveFromIndex(string relativePath) Ensure.Success(res); } - private void RestorePotentialPreviousVersionOfHeadIntoIndex(string relativePath, - bool doesExistInWorkingDirectory) + private void RestorePotentialPreviousVersionOfHeadIntoIndex(string relativePath) { - // 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) + TreeEntry treeEntry = repo.Head[relativePath]; + if (treeEntry == null || treeEntry.Type != GitObjectType.Blob) { return; } - 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)); - } + var indexEntry = new GitIndexEntry + { + Mode = treeEntry.Attributes, + oid = treeEntry.Target.Id.Oid, + Path = utf8Marshaler.MarshalManagedToNative(relativePath), + }; - File.WriteAllBytes(filename, ((Blob)(entry.Target)).Content); - AddToIndex(relativePath); - - File.Delete(filename); - - if (doesExistInWorkingDirectory) - { - File.Move(Path.Combine(repo.Info.WorkingDirectory, randomFileName), filename); - } + NativeMethods.git_index_add2(handle, indexEntry); + utf8Marshaler.CleanUpNativeData(indexEntry.Path); } private void UpdatePhysicalIndex() @@ -523,5 +511,15 @@ public RepositoryStatus RetrieveStatus() { return new RepositoryStatus(repo); } + + internal void ReplaceContentWithTree(Tree tree) + { + using (var nativeTree = new ObjectSafeWrapper(tree.Id, repo)) + { + int res = NativeMethods.git_index_read_tree(Handle, nativeTree.ObjectPtr); + Ensure.Success(res); + UpdatePhysicalIndex(); + } + } } } diff --git a/LibGit2Sharp/IndexEntry.cs b/LibGit2Sharp/IndexEntry.cs index 9b9dbd20b..57d4a3730 100644 --- a/LibGit2Sharp/IndexEntry.cs +++ b/LibGit2Sharp/IndexEntry.cs @@ -14,6 +14,8 @@ public class IndexEntry : IEquatable private Func state; + private static readonly Utf8Marshaler marshaler = new Utf8Marshaler(); + /// /// State of the version of the pointed at by this , /// compared against the known from the and the file in the working directory. @@ -36,11 +38,13 @@ public FileStatus State internal static IndexEntry CreateFromPtr(Repository repo, IntPtr ptr) { var entry = (GitIndexEntry)Marshal.PtrToStructure(ptr, typeof(GitIndexEntry)); + var path = (string)marshaler.MarshalNativeToManaged(entry.Path); + return new IndexEntry { - Path = PosixPathHelper.ToNative(entry.Path), + Path = PosixPathHelper.ToNative(path), Id = new ObjectId(entry.oid), - state = () => repo.Index.RetrieveStatus(entry.Path) + state = () => repo.Index.RetrieveStatus(path) }; } diff --git a/LibGit2Sharp/LibGit2Sharp.csproj b/LibGit2Sharp/LibGit2Sharp.csproj index 00b193d82..fa503a125 100644 --- a/LibGit2Sharp/LibGit2Sharp.csproj +++ b/LibGit2Sharp/LibGit2Sharp.csproj @@ -53,8 +53,11 @@ - + + + + @@ -96,6 +99,7 @@ + @@ -110,6 +114,7 @@ + diff --git a/LibGit2Sharp/ObjectId.cs b/LibGit2Sharp/ObjectId.cs index 640b1fc90..eda897a12 100644 --- a/LibGit2Sharp/ObjectId.cs +++ b/LibGit2Sharp/ObjectId.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; using System.Linq; using LibGit2Sharp.Core; @@ -99,7 +100,7 @@ public static bool TryParse(string sha, out ObjectId result) { result = BuildFrom(sha, false, true); - return (result == null) ? false : true; + return result != null; } private static GitOid? BuildOidFrom(string sha, bool shouldThrowIfInvalid, bool allowShortIdentifier) @@ -283,7 +284,7 @@ private static bool LooksValid(string objectId, bool throwIfInvalid, bool allowS return false; } - throw new ArgumentNullException(objectId); + throw new ArgumentNullException("objectId"); } if (objectId.Length < MinHexSize || objectId.Length > HexSize) @@ -303,7 +304,7 @@ private static bool LooksValid(string objectId, bool throwIfInvalid, bool allowS "objectId"); } - return objectId.All(c => hexDigits.Contains(c.ToString())); + return objectId.All(c => hexDigits.Contains(c.ToString(CultureInfo.InvariantCulture))); } } } diff --git a/LibGit2Sharp/Properties/AssemblyInfo.cs b/LibGit2Sharp/Properties/AssemblyInfo.cs index 9a8f54a92..f5a693af3 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.7.0")] -[assembly: AssemblyFileVersion("0.7.0")] +[assembly: AssemblyVersion("0.8.0")] +[assembly: AssemblyFileVersion("0.8.0")] diff --git a/LibGit2Sharp/ReferenceCollection.cs b/LibGit2Sharp/ReferenceCollection.cs index 907576ded..39c8bdd44 100644 --- a/LibGit2Sharp/ReferenceCollection.cs +++ b/LibGit2Sharp/ReferenceCollection.cs @@ -227,17 +227,5 @@ private IntPtr RetrieveReferencePtr(string referenceName, bool shouldThrowIfNotF 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/RemoteCollection.cs b/LibGit2Sharp/RemoteCollection.cs index dcb582b81..bbc4e71b4 100644 --- a/LibGit2Sharp/RemoteCollection.cs +++ b/LibGit2Sharp/RemoteCollection.cs @@ -1,4 +1,5 @@ -using LibGit2Sharp.Core; +using System; +using LibGit2Sharp.Core; namespace LibGit2Sharp { @@ -16,23 +17,35 @@ public Remote this[string name] get { return RemoteForName(name); } } - private Remote RemoteForName(string name) + internal RemoteSafeHandle LoadRemote(string name, bool throwsIfNotFound) { - var remote = new Remote(); RemoteSafeHandle handle; int res = NativeMethods.git_remote_load(out handle, repository.Handle, name); - if (res == (int)GitErrorCode.GIT_ENOTFOUND) + if (res == (int)GitErrorCode.GIT_ENOTFOUND && !throwsIfNotFound) { return null; } Ensure.Success(res); + return handle; + } + + private Remote RemoteForName(string name) + { + RemoteSafeHandle handle = LoadRemote(name, false); + + if (handle == null) + { + return null; + } + + var remote = new Remote(); using (handle) { - var ptr = NativeMethods.git_remote_name(handle); + IntPtr ptr = NativeMethods.git_remote_name(handle); remote.Name = ptr.MarshallAsString(); ptr = NativeMethods.git_remote_url(handle); diff --git a/LibGit2Sharp/Repository.cs b/LibGit2Sharp/Repository.cs index a658fc01c..8d433d818 100644 --- a/LibGit2Sharp/Repository.cs +++ b/LibGit2Sharp/Repository.cs @@ -23,9 +23,12 @@ public class Repository : IDisposable /// /// Initializes a new instance of the class. - /// For a standard repository, should point to the ".git" folder. For a bare repository, should directly point to the repository folder. + /// For a standard repository, should either point to the ".git" folder or to the working directory. For a bare repository, should directly point to the repository folder. /// - /// The path to the git repository to open. + /// + /// The path to the git repository to open, can be either the path to the git directory (for non-bare repositories this + /// would be the ".git" folder inside the working directory) or the path to the working directory. + /// public Repository(string path) { Ensure.ArgumentNotNullOrEmptyString(path, "path"); @@ -55,7 +58,7 @@ internal RepositorySafeHandle Handle } /// - /// Shortcut to return the reference to HEAD + /// Shortcut to return the branch pointed to by HEAD /// /// public Branch Head @@ -164,37 +167,13 @@ protected virtual void Dispose(bool disposing) #endregion - /// - /// Tells if the specified sha exists in the repository. - /// - /// Exceptions: - /// ArgumentException - /// ArgumentNullException - /// - ///The sha. - /// - [Obsolete] - public bool HasObject(string sha) //TODO: To be removed from front facing API (maybe should we create an Repository.Advanced to hold those kind of functions)? - { - var id = new ObjectId(sha); - - DatabaseSafeHandle odb; - Ensure.Success(NativeMethods.git_repository_odb(out odb, handle)); - - using(odb) - { - GitOid oid = id.Oid; - return NativeMethods.git_odb_exists(odb, ref oid); - } - } - /// - /// Init a repo at the specified . + /// Initialize a repository at the specified . /// /// The path to the working folder when initializing a standard ".git" repository. Otherwise, when initializing a bare repository, the path to the expected location of this later. /// true to initialize a bare repository. False otherwise, to initialize a standard ".git" repository. - /// Path the git repository. - public static string Init(string path, bool isBare = false) + /// a new instance of the class. The client code is responsible for calling on this instance. + public static Repository Init(string path, bool isBare = false) { Ensure.ArgumentNotNullOrEmptyString(path, "path"); @@ -207,7 +186,7 @@ public static string Init(string path, bool isBare = false) string nativePath = PosixPathHelper.ToNative(normalizedPath); - return nativePath; + return new Repository(nativePath); } /// @@ -255,27 +234,47 @@ public GitObject Lookup(ObjectId id, GitObjectType type = GitObjectType.Any) /// The kind of being looked up /// The or null if it was not found. public GitObject Lookup(string shaOrReferenceName, GitObjectType type = GitObjectType.Any) + { + return Lookup(shaOrReferenceName, type, LookUpOptions.None); + } + + internal GitObject Lookup(string shaOrReferenceName, GitObjectType type, LookUpOptions lookUpOptions) { ObjectId id; - if (ObjectId.TryParse(shaOrReferenceName, out id)) + Reference reference = Refs[shaOrReferenceName]; + if (reference != null) { - return Lookup(id, type); + id = reference.PeelToTargetObjectId(); + } + else + { + ObjectId.TryParse(shaOrReferenceName, out id); } - Reference reference = Refs[shaOrReferenceName]; - - if (!IsReferencePeelable(reference)) + if (id == null) { + if (lookUpOptions.Has(LookUpOptions.ThrowWhenNoGitObjectHasBeenFound)) + { + Ensure.GitObjectIsNotNull(null, shaOrReferenceName); + } + return null; } - return Lookup(reference.ResolveToDirectReference().TargetIdentifier, type); - } + GitObject gitObj = Lookup(id, type); - private static bool IsReferencePeelable(Reference reference) - { - return reference != null && ((reference is DirectReference) || (reference is SymbolicReference && ((SymbolicReference)reference).Target != null)); + if (lookUpOptions.Has(LookUpOptions.ThrowWhenNoGitObjectHasBeenFound)) + { + Ensure.GitObjectIsNotNull(gitObj, shaOrReferenceName); + } + + if (!lookUpOptions.Has(LookUpOptions.DereferenceResultToCommit)) + { + return gitObj; + } + + return gitObj.DereferenceToCommit(shaOrReferenceName, lookUpOptions.Has(LookUpOptions.ThrowWhenCanNotBeDereferencedToACommit)); } /// @@ -299,5 +298,42 @@ public static string Discover(string startingPath) return PosixPathHelper.ToNative(Utf8Marshaler.Utf8FromBuffer(buffer)); } + + /// + /// Sets the current to the specified commit and optionally resets the and + /// the content of the working tree to match. + /// + /// Flavor of reset operation to perform. + /// The sha or reference canonical name of the target commit object. + public void Reset(ResetOptions resetOptions, string shaOrReferenceName) + { + Ensure.ArgumentNotNullOrEmptyString(shaOrReferenceName, "shaOrReferenceName"); + + if (resetOptions.Has(ResetOptions.Mixed) && Info.IsBare) + { + throw new LibGit2Exception("Mixed reset is not allowed in a bare repository"); + } + + GitObject commit = Lookup(shaOrReferenceName, GitObjectType.Any, LookUpOptions.ThrowWhenNoGitObjectHasBeenFound | LookUpOptions.DereferenceResultToCommit | LookUpOptions.ThrowWhenCanNotBeDereferencedToACommit); + + //TODO: Check for unmerged entries + + string refToUpdate = Info.IsHeadDetached ? "HEAD" : Head.CanonicalName; + Refs.UpdateTarget(refToUpdate, commit.Sha); + + if (resetOptions == ResetOptions.Soft) + { + return; + } + + Index.ReplaceContentWithTree(((Commit)commit).Tree); + + if (resetOptions == ResetOptions.Mixed) + { + return; + } + + throw new NotImplementedException(); + } } } diff --git a/LibGit2Sharp/RepositoryExtensions.cs b/LibGit2Sharp/RepositoryExtensions.cs index 20740a205..b061d19d6 100644 --- a/LibGit2Sharp/RepositoryExtensions.cs +++ b/LibGit2Sharp/RepositoryExtensions.cs @@ -104,12 +104,13 @@ public static Branch CreateBranch(this Repository repository, string branchName, /// /// The being worked with. /// The description of why a change was made to the repository. + /// True to amend the current pointed at by , false otherwise. /// The generated . - public static Commit Commit(this Repository repository, string message) + public static Commit Commit(this Repository repository, string message, bool amendPreviousCommit = false) { Signature author = BuildSignatureFromGlobalConfiguration(repository, DateTimeOffset.Now); - return repository.Commit(message, author); + return repository.Commit(message, author, amendPreviousCommit); } /// @@ -119,12 +120,13 @@ public static Commit Commit(this Repository repository, string message) /// The being worked with. /// The of who made the change. /// The description of why a change was made to the repository. + /// True to amend the current pointed at by , false otherwise. /// The generated . - public static Commit Commit(this Repository repository, string message, Signature author) + public static Commit Commit(this Repository repository, string message, Signature author, bool amendPreviousCommit = false) { Signature committer = BuildSignatureFromGlobalConfiguration(repository, DateTimeOffset.Now); - return repository.Commit(message, author, committer); + return repository.Commit(message, author, committer, amendPreviousCommit); } /// @@ -134,16 +136,17 @@ public static Commit Commit(this Repository repository, string message, Signatur /// 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. + /// True to amend the current pointed at by , false otherwise. /// The generated . - public static Commit Commit(this Repository repository, string message, Signature author, Signature committer) + public static Commit Commit(this Repository repository, string message, Signature author, Signature committer, bool amendPreviousCommit = false) { - return repository.Commits.Create(message, author, committer); + return repository.Commits.Create(message, author, committer, amendPreviousCommit); } 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); + var name = repository.Config.Get("user.name", null); + var email = repository.Config.Get("user.email", null); if ((name == null) || (email == null)) { diff --git a/LibGit2Sharp/ResetOptions.cs b/LibGit2Sharp/ResetOptions.cs new file mode 100644 index 000000000..428e3a26a --- /dev/null +++ b/LibGit2Sharp/ResetOptions.cs @@ -0,0 +1,19 @@ +namespace LibGit2Sharp +{ + /// + /// Specifies the kind of operation that should perform. + /// + public enum ResetOptions + { + /// + /// Moves the branch pointed to by HEAD to the specified commit object. + /// + Soft, + + /// + /// Moves the branch pointed to by HEAD to the specified commit object and resets the index + /// to the tree recorded by the commit. + /// + Mixed, + } +} diff --git a/LibGit2Sharp/Signature.cs b/LibGit2Sharp/Signature.cs index fe4867405..342c6ee4e 100644 --- a/LibGit2Sharp/Signature.cs +++ b/LibGit2Sharp/Signature.cs @@ -9,16 +9,22 @@ namespace LibGit2Sharp /// public class Signature { - private readonly GitSignature handle = new GitSignature(); - private DateTimeOffset? when; + private readonly DateTimeOffset when; + private readonly string name; + private readonly string email; - internal Signature(IntPtr signaturePtr, bool ownedByRepo = true) + private static readonly Utf8Marshaler marshaler = new Utf8Marshaler(); + + internal Signature(IntPtr signaturePtr) { + var handle = new GitSignature(); Marshal.PtrToStructure(signaturePtr, handle); - if (!ownedByRepo) - { - NativeMethods.git_signature_free(signaturePtr); - } + + // XXX: This is unbelievably hacky, but I can't get the + // Utf8Marshaller to work properly. + name = (string)marshaler.MarshalNativeToManaged(handle.Name); + email = (string)marshaler.MarshalNativeToManaged(handle.Email); + when = Epoch.ToDateTimeOffset(handle.When.Time, handle.When.Offset); } /// @@ -28,30 +34,28 @@ internal Signature(IntPtr signaturePtr, bool ownedByRepo = true) /// The email. /// The when. public Signature(string name, string email, DateTimeOffset when) - : this(CreateSignature(name, email, when), false) { + this.name = name; + this.email = email; + this.when = when; } - private static IntPtr CreateSignature(string name, string email, DateTimeOffset when) + internal SignatureSafeHandle BuildHandle() { - IntPtr signature; - int result = NativeMethods.git_signature_new(out signature, name, email, when.ToSecondsSinceEpoch(), (int)when.Offset.TotalMinutes); - Ensure.Success(result); + SignatureSafeHandle signature; + int result = NativeMethods.git_signature_new(out signature, name, email, when.ToSecondsSinceEpoch(), + (int) when.Offset.TotalMinutes); + Ensure.Success(result); return signature; } - internal GitSignature Handle - { - get { return handle; } - } - /// /// Gets the name. /// public string Name { - get { return handle.Name; } + get { return name; } } /// @@ -59,7 +63,7 @@ public string Name /// public string Email { - get { return handle.Email; } + get { return email; } } /// @@ -67,14 +71,7 @@ public string Email /// public DateTimeOffset When { - get - { - if (when == null) - { - when = Epoch.ToDateTimeOffset(handle.When.Time, handle.When.Offset); - } - return when.Value; - } + get { return when; } } } } diff --git a/LibGit2Sharp/TagCollection.cs b/LibGit2Sharp/TagCollection.cs index 8174ccdba..d3d733577 100644 --- a/LibGit2Sharp/TagCollection.cs +++ b/LibGit2Sharp/TagCollection.cs @@ -1,7 +1,6 @@ using System; using System.Collections; using System.Collections.Generic; -using System.Globalization; using System.Linq; using LibGit2Sharp.Core; @@ -79,13 +78,14 @@ public Tag Create(string name, string target, Signature tagger, string message, Ensure.ArgumentNotNull(tagger, "tagger"); Ensure.ArgumentNotNull(message, "message"); - GitObject objectToTag = repo.Refs.RetrieveTargetObject(target); + GitObject objectToTag = repo.Lookup(target, GitObjectType.Any, LookUpOptions.ThrowWhenNoGitObjectHasBeenFound); int res; using (var objectPtr = new ObjectSafeWrapper(objectToTag.Id, repo)) + using (SignatureSafeHandle taggerHandle = tagger.BuildHandle()) { GitOid oid; - res = NativeMethods.git_tag_create(out oid, repo.Handle, name, objectPtr.ObjectPtr, tagger.Handle, message, allowOverwrite); + res = NativeMethods.git_tag_create(out oid, repo.Handle, name, objectPtr.ObjectPtr, taggerHandle, message, allowOverwrite); } Ensure.Success(res); @@ -105,7 +105,7 @@ public Tag Create(string name, string target, bool allowOverwrite = false) Ensure.ArgumentNotNullOrEmptyString(name, "name"); Ensure.ArgumentNotNullOrEmptyString(target, "target"); - GitObject objectToTag = repo.Refs.RetrieveTargetObject(target); + GitObject objectToTag = repo.Lookup(target, GitObjectType.Any, LookUpOptions.ThrowWhenNoGitObjectHasBeenFound); int res; using (var objectPtr = new ObjectSafeWrapper(objectToTag.Id, repo)) diff --git a/LibGit2Sharp/TreeEntry.cs b/LibGit2Sharp/TreeEntry.cs index 3fc488c08..a51a9c8a7 100755 --- a/LibGit2Sharp/TreeEntry.cs +++ b/LibGit2Sharp/TreeEntry.cs @@ -34,7 +34,7 @@ internal TreeEntry(IntPtr obj, ObjectId parentTreeId, Repository repo) /// /// Gets the UNIX file attributes. /// - public int Attributes { get; private set; } + public uint Attributes { get; private set; } /// /// Gets the filename. diff --git a/libgit2 b/libgit2 index be00b00dd..6d39c0dd6 160000 --- a/libgit2 +++ b/libgit2 @@ -1 +1 @@ -Subproject commit be00b00dd1468f1c625ca3fadc61f2a16edfb8d5 +Subproject commit 6d39c0dd6fc138fdd994e7b31ab7f5eed85d2688