From 4357bf36afa18638f4f4d65fca8e08c8e5cd62e6 Mon Sep 17 00:00:00 2001 From: nulltoken Date: Fri, 14 Oct 2011 15:33:42 +0200 Subject: [PATCH] Fix Index.Stage(), Index.Unstage() and enforce test coverage Should fix issue #78. --- LibGit2Sharp.Tests/IndexFixture.cs | 122 ++++++++++++++++------------- LibGit2Sharp/Index.cs | 71 ++++++++++++++--- LibGit2Sharp/Tree.cs | 10 +-- 3 files changed, 132 insertions(+), 71 deletions(-) diff --git a/LibGit2Sharp.Tests/IndexFixture.cs b/LibGit2Sharp.Tests/IndexFixture.cs index a4b4f87a6..879e388ae 100644 --- a/LibGit2Sharp.Tests/IndexFixture.cs +++ b/LibGit2Sharp.Tests/IndexFixture.cs @@ -74,30 +74,31 @@ public void ReadIndexWithBadParamsFails() } } - [Test] - public void CanStageTheCreationOfANewFile() + [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(); - repo.Index.RetrieveStatus(filename).ShouldEqual(FileStatus.Nonexistent); + (repo.Index[relativePath] != null).ShouldEqual(doesCurrentlyExistInTheIndex); + repo.Index.RetrieveStatus(relativePath).ShouldEqual(currentStatus); - File.WriteAllText(Path.Combine(repo.Info.WorkingDirectory, filename), "some contents"); - repo.Index.RetrieveStatus(filename).ShouldEqual(FileStatus.Untracked); - - repo.Index.Stage(filename); + repo.Index.Stage(relativePath); - repo.Index.Count.ShouldEqual(count + 1); - repo.Index[filename].ShouldNotBeNull(); - repo.Index.RetrieveStatus(filename).ShouldEqual(FileStatus.Added); + repo.Index.Count.ShouldEqual(count + expectedIndexCountVariation); + (repo.Index[relativePath] != null).ShouldEqual(doesExistInTheIndexOnceStaged); + repo.Index.RetrieveStatus(relativePath).ShouldEqual(expectedStatusOnceStaged); } } [Test] - public void CanStageTheUpdationOfANewFile() + public void CanStageTheUpdationOfAStagedFile() { TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(Constants.StandardTestRepoWorkingDirPath); using (var repo = new Repository(path.RepositoryPath)) @@ -120,39 +121,63 @@ public void CanStageTheUpdationOfANewFile() } } - [Test] - public void StagingANewVersionOfAFileThenUnstagingRevertsTheBlobToTheVersionOfHead() + [TestCase("1/I-do-not-exist.txt", FileStatus.Nonexistent)] + [TestCase("deleted_staged_file.txt", FileStatus.Removed)] + public void StagingAnUnknownFileThrows(string relativePath, FileStatus status) { - SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); - string dir = Repository.Init(scd.DirectoryPath); + using (var repo = new Repository(Constants.StandardTestRepoPath)) + { + repo.Index[relativePath].ShouldBeNull(); + repo.Index.RetrieveStatus(relativePath).ShouldEqual(status); - using (var repo = new Repository(dir)) + Assert.Throws(() => repo.Index.Stage(relativePath)); + } + } + + [Test] + public void CanStageTheRemovalOfAStagedFile() + { + TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(Constants.StandardTestRepoWorkingDirPath); + using (var repo = new Repository(path.RepositoryPath)) { - repo.Index.Count.ShouldEqual(0); + int count = repo.Index.Count; + const string filename = "new_tracked_file.txt"; + repo.Index[filename].ShouldNotBeNull(); - const string fileName = "subdir/myFile.txt"; + repo.Index.RetrieveStatus(filename).ShouldEqual(FileStatus.Added); - string fullpath = Path.Combine(repo.Info.WorkingDirectory, fileName); + File.Delete(Path.Combine(repo.Info.WorkingDirectory, filename)); + repo.Index.RetrieveStatus(filename).ShouldEqual(FileStatus.Added | FileStatus.Missing); - const string initialContent = "Hello?"; + repo.Index.Stage(filename); + repo.Index[filename].ShouldBeNull(); - Directory.CreateDirectory(Path.GetDirectoryName(fullpath)); - File.AppendAllText(fullpath, initialContent); + repo.Index.Count.ShouldEqual(count - 1); + repo.Index.RetrieveStatus(filename).ShouldEqual(FileStatus.Nonexistent); + } + } - repo.Index.Stage(fileName); + [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("Initial commit", Constants.Signature, Constants.Signature); - 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)); } } @@ -232,34 +257,30 @@ public void StageFileWithBadParamsThrows() } } - [Test] - public void CanUnstageANewFile() + [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); - const string filename = "new_untracked_file.txt"; - string fullPath = Path.Combine(repo.Info.WorkingDirectory, filename); - File.Exists(fullPath).ShouldBeTrue(); + repo.Index.Unstage(relativePath); - repo.Index.RetrieveStatus(filename).ShouldEqual(FileStatus.Untracked); - - repo.Index.Stage(filename); - repo.Index.Count.ShouldEqual(count + 1); - - repo.Index.RetrieveStatus(filename).ShouldEqual(FileStatus.Added); - - repo.Index.Unstage(filename); - repo.Index.Count.ShouldEqual(count); - - repo.Index.RetrieveStatus(filename).ShouldEqual(FileStatus.Untracked); + repo.Index.Count.ShouldEqual(count + expectedIndexCountVariation); + (repo.Index[relativePath] != null).ShouldEqual(doesExistInTheIndexOnceStaged); + repo.Index.RetrieveStatus(relativePath).ShouldEqual(expectedStatusOnceStaged); } } [Test] - [Ignore("Not implemented yet.")] public void CanUnstageTheRemovalOfAFile() { TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(Constants.StandardTestRepoWorkingDirPath); @@ -281,15 +302,6 @@ public void CanUnstageTheRemovalOfAFile() } } - [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 UnstagingFileWithBadParamsThrows() { diff --git a/LibGit2Sharp/Index.cs b/LibGit2Sharp/Index.cs index f8ba62710..81937562c 100644 --- a/LibGit2Sharp/Index.cs +++ b/LibGit2Sharp/Index.cs @@ -107,7 +107,23 @@ 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(); } @@ -118,9 +134,21 @@ public void Unstage(string path) string relativePath = BuildRelativePathFrom(repo, path); - RemoveFromIndex(relativePath); + FileStatus fileStatus = RetrieveStatus(relativePath); - RestorePotentialPreviousVersionOf(relativePath); + bool doesExistInindex = + !(fileStatus.Has(FileStatus.Nonexistent) || fileStatus.Has(FileStatus.Removed) || + fileStatus.Has(FileStatus.Untracked)); + + if (doesExistInindex) + { + RemoveFromIndex(relativePath); + } + + bool doesExistInWorkingDirectory = + !(fileStatus.Has(FileStatus.Removed) || fileStatus.Has(FileStatus.Nonexistent) || + fileStatus.Has(FileStatus.Missing)); + RestorePotentialPreviousVersionOfHeadIntoIndex(relativePath, doesExistInWorkingDirectory); UpdatePhysicalIndex(); } @@ -167,31 +195,49 @@ public void Remove(string path) UpdatePhysicalIndex(); } - private void AddToIndex(string path) + private void AddToIndex(string relativePath) { - int res = NativeMethods.git_index_add(handle, BuildRelativePathFrom(repo, path)); + 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) { + // 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)(entry.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() @@ -200,8 +246,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; @@ -211,7 +258,9 @@ private void UpdatePhysicalIndex() 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); diff --git a/LibGit2Sharp/Tree.cs b/LibGit2Sharp/Tree.cs index e618d8198..bd51639c9 100644 --- a/LibGit2Sharp/Tree.cs +++ b/LibGit2Sharp/Tree.cs @@ -16,7 +16,7 @@ internal Tree(ObjectId id) } /// - /// Gets the number of immediately under this . + /// Gets the number of immediately under this . /// public int Count { get; private set; } @@ -51,11 +51,11 @@ private TreeEntry RetrieveFromPath(string relativePath) return tree.RetrieveFromName(pathSegments[pathSegments.Length - 1]); } - private TreeEntry RetrieveFromName(string name) + private TreeEntry RetrieveFromName(string entryName) { using (var obj = new ObjectSafeWrapper(Id, repo)) { - IntPtr e = NativeMethods.git_tree_entry_byname(obj.ObjectPtr, name); + IntPtr e = NativeMethods.git_tree_entry_byname(obj.ObjectPtr, entryName); if (e == IntPtr.Zero) { @@ -67,7 +67,7 @@ private TreeEntry RetrieveFromName(string name) } /// - /// Gets the s immediately under this . + /// Gets the s immediately under this . /// public IEnumerable Trees { @@ -81,7 +81,7 @@ public IEnumerable Trees } /// - /// Gets the s immediately under this . + /// Gets the s immediately under this . /// public IEnumerable Files {