using System; using System.Collections.Generic; using System.IO; using System.Linq; using LibGit2Sharp.Tests.TestHelpers; using Xunit; using Xunit.Extensions; namespace LibGit2Sharp.Tests { public class IndexFixture : BaseFixture { private static readonly string subBranchFile = Path.Combine("1", "branch_file.txt"); private readonly string[] expectedEntries = new[] { "1.txt", subBranchFile, "README", "branch_file.txt", //"deleted_staged_file.txt", "deleted_unstaged_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new.txt", "new_tracked_file.txt" }; [Fact] public void CanCountEntriesInIndex() { var path = SandboxStandardTestRepoGitDir(); using (var repo = new Repository(path)) { Assert.Equal(expectedEntries.Count(), repo.Index.Count); } } [Fact] public void CanEnumerateIndex() { var path = SandboxStandardTestRepoGitDir(); using (var repo = new Repository(path)) { Assert.Equal(expectedEntries, repo.Index.Select(e => e.Path).OrderBy(p => p, StringComparer.Ordinal).ToArray()); } } [Fact] public void CanFetchAnIndexEntryByItsName() { var path = SandboxStandardTestRepoGitDir(); using (var repo = new Repository(path)) { IndexEntry entry = repo.Index["README"]; Assert.Equal("README", entry.Path); // Expressed in Posix format... IndexEntry entryWithPath = repo.Index["1/branch_file.txt"]; Assert.Equal(subBranchFile, entryWithPath.Path); //...or in native format IndexEntry entryWithPath2 = repo.Index[subBranchFile]; Assert.Equal(entryWithPath, entryWithPath2); } } [Fact] public void FetchingAnUnknownIndexEntryReturnsNull() { var path = SandboxStandardTestRepoGitDir(); using (var repo = new Repository(path)) { IndexEntry entry = repo.Index["I-do-not-exist.txt"]; Assert.Null(entry); } } [Fact] public void ReadIndexWithBadParamsFails() { var path = SandboxStandardTestRepoGitDir(); using (var repo = new Repository(path)) { Assert.Throws(() => { IndexEntry entry = repo.Index[null]; }); Assert.Throws(() => { IndexEntry entry = repo.Index[string.Empty]; }); } } [Fact] public void CanRenameAFile() { string repoPath = InitNewRepository(); using (var repo = new Repository(repoPath)) { Assert.Equal(0, repo.Index.Count); const string oldName = "polite.txt"; Assert.Equal(FileStatus.Nonexistent, repo.RetrieveStatus(oldName)); Touch(repo.Info.WorkingDirectory, oldName, "hello test file\n"); Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus(oldName)); repo.Stage(oldName); Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(oldName)); // Generated through // $ echo "hello test file" | git hash-object --stdin const string expectedHash = "88df547706c30fa19f02f43cb2396e8129acfd9b"; Assert.Equal(expectedHash, repo.Index[oldName].Id.Sha); Assert.Equal(1, repo.Index.Count); Signature who = Constants.Signature; repo.Commit("Initial commit", who, who); Assert.Equal(FileStatus.Unaltered, repo.RetrieveStatus(oldName)); const string newName = "being.frakking.polite.txt"; repo.Move(oldName, newName); Assert.Equal(FileStatus.DeletedFromIndex, repo.RetrieveStatus(oldName)); Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(newName)); Assert.Equal(1, repo.Index.Count); Assert.Equal(expectedHash, repo.Index[newName].Id.Sha); who = who.TimeShift(TimeSpan.FromMinutes(5)); Commit commit = repo.Commit("Fix file name", who, who); Assert.Equal(FileStatus.Nonexistent, repo.RetrieveStatus(oldName)); Assert.Equal(FileStatus.Unaltered, repo.RetrieveStatus(newName)); Assert.Equal(expectedHash, commit.Tree[newName].Target.Id.Sha); } } [Theory] [InlineData("README", FileStatus.Unaltered, "deleted_unstaged_file.txt", FileStatus.DeletedFromWorkdir, FileStatus.DeletedFromIndex, FileStatus.ModifiedInIndex)] [InlineData("new_tracked_file.txt", FileStatus.NewInIndex, "deleted_unstaged_file.txt", FileStatus.DeletedFromWorkdir, FileStatus.Nonexistent, FileStatus.ModifiedInIndex)] [InlineData("modified_staged_file.txt", FileStatus.ModifiedInIndex, "deleted_unstaged_file.txt", FileStatus.DeletedFromWorkdir, FileStatus.DeletedFromIndex, FileStatus.ModifiedInIndex)] [InlineData("modified_unstaged_file.txt", FileStatus.ModifiedInWorkdir, "deleted_unstaged_file.txt", FileStatus.DeletedFromWorkdir, FileStatus.DeletedFromIndex, FileStatus.ModifiedInIndex)] public void CanMoveAnExistingFileOverANonExistingFile(string sourcePath, FileStatus sourceStatus, string destPath, FileStatus destStatus, FileStatus sourcePostStatus, FileStatus destPostStatus) { string path = SandboxStandardTestRepo(); using (var repo = new Repository(path)) { Assert.Equal(sourceStatus, repo.RetrieveStatus(sourcePath)); Assert.Equal(destStatus, repo.RetrieveStatus(destPath)); repo.Move(sourcePath, destPath); Assert.Equal(sourcePostStatus, repo.RetrieveStatus(sourcePath)); Assert.Equal(destPostStatus, repo.RetrieveStatus(destPath)); } } [Theory] [InlineData("README", FileStatus.Unaltered, new[] { "README", "new_tracked_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new_untracked_file.txt" })] [InlineData("new_tracked_file.txt", FileStatus.NewInIndex, new[] { "README", "new_tracked_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new_untracked_file.txt" })] [InlineData("modified_staged_file.txt", FileStatus.ModifiedInIndex, new[] { "README", "new_tracked_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new_untracked_file.txt" })] [InlineData("modified_unstaged_file.txt", FileStatus.ModifiedInWorkdir, new[] { "README", "new_tracked_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new_untracked_file.txt" })] public void MovingOverAnExistingFileThrows(string sourcePath, FileStatus sourceStatus, IEnumerable destPaths) { InvalidMoveUseCases(sourcePath, sourceStatus, destPaths); } [Theory] [InlineData("new_untracked_file.txt", FileStatus.NewInWorkdir, new[] { "README", "new_tracked_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new_untracked_file.txt", "deleted_unstaged_file.txt", "deleted_staged_file.txt", "i_dont_exist.txt" })] public void MovingAFileWichIsNotUnderSourceControlThrows(string sourcePath, FileStatus sourceStatus, IEnumerable destPaths) { InvalidMoveUseCases(sourcePath, sourceStatus, destPaths); } [Theory] [InlineData("deleted_unstaged_file.txt", FileStatus.DeletedFromWorkdir, new[] { "README", "new_tracked_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new_untracked_file.txt", "deleted_unstaged_file.txt", "deleted_staged_file.txt", "i_dont_exist.txt" })] [InlineData("deleted_staged_file.txt", FileStatus.DeletedFromIndex, new[] { "README", "new_tracked_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new_untracked_file.txt", "deleted_unstaged_file.txt", "deleted_staged_file.txt", "i_dont_exist.txt" })] [InlineData("i_dont_exist.txt", FileStatus.Nonexistent, new[] { "README", "new_tracked_file.txt", "modified_staged_file.txt", "modified_unstaged_file.txt", "new_untracked_file.txt", "deleted_unstaged_file.txt", "deleted_staged_file.txt", "i_dont_exist.txt" })] public void MovingAFileNotInTheWorkingDirectoryThrows(string sourcePath, FileStatus sourceStatus, IEnumerable destPaths) { InvalidMoveUseCases(sourcePath, sourceStatus, destPaths); } private void InvalidMoveUseCases(string sourcePath, FileStatus sourceStatus, IEnumerable destPaths) { var repoPath = SandboxStandardTestRepoGitDir(); using (var repo = new Repository(repoPath)) { Assert.Equal(sourceStatus, repo.RetrieveStatus(sourcePath)); foreach (var destPath in destPaths) { string path = destPath; Assert.Throws(() => repo.Move(sourcePath, path)); } } } [Fact] public void PathsOfIndexEntriesAreExpressedInNativeFormat() { // Build relative path string relFilePath = Path.Combine("directory", "Testfile.txt"); string repoPath = InitNewRepository(); using (var repo = new Repository(repoPath)) { Touch(repo.Info.WorkingDirectory, relFilePath, "Anybody out there?"); // Stage the file repo.Stage(relFilePath); // Get the index Index index = repo.Index; // Get the index entry IndexEntry ie = index[relFilePath]; // Make sure the IndexEntry has been found Assert.NotNull(ie); // Make sure that the (native) relFilePath and ie.Path are equal Assert.Equal(relFilePath, ie.Path); } } [Fact] public void CanReadIndexEntryAttributes() { var path = SandboxStandardTestRepoGitDir(); using (var repo = new Repository(path)) { Assert.Equal(Mode.NonExecutableFile, repo.Index["README"].Mode); Assert.Equal(Mode.ExecutableFile, repo.Index["1/branch_file.txt"].Mode); } } [Fact] public void StagingAFileWhenTheIndexIsLockedThrowsALockedFileException() { string repoPath = InitNewRepository(); using (var repo = new Repository(repoPath)) { Touch(repo.Info.Path, "index.lock"); Touch(repo.Info.WorkingDirectory, "newfile", "my my, this is gonna crash\n"); Assert.Throws(() => repo.Stage("newfile")); } } [Fact] public void CanCopeWithExternalChangesToTheIndex() { SelfCleaningDirectory scd = BuildSelfCleaningDirectory(); Touch(scd.DirectoryPath, "a.txt", "a\n"); Touch(scd.DirectoryPath, "b.txt", "b\n"); string path = Repository.Init(scd.DirectoryPath); using (var repoWrite = new Repository(path)) using (var repoRead = new Repository(path)) { var writeStatus = repoWrite.RetrieveStatus(); Assert.True(writeStatus.IsDirty); Assert.Equal(0, repoWrite.Index.Count); var readStatus = repoRead.RetrieveStatus(); Assert.True(readStatus.IsDirty); Assert.Equal(0, repoRead.Index.Count); repoWrite.Stage("*"); repoWrite.Commit("message", Constants.Signature, Constants.Signature); writeStatus = repoWrite.RetrieveStatus(); Assert.False(writeStatus.IsDirty); Assert.Equal(2, repoWrite.Index.Count); readStatus = repoRead.RetrieveStatus(); Assert.False(readStatus.IsDirty); Assert.Equal(2, repoRead.Index.Count); } } [Fact] public void CanResetFullyMergedIndexFromTree() { string path = SandboxStandardTestRepo(); const string testFile = "new_tracked_file.txt"; // It is sufficient to check just one of the stage area changes, such as the added file, // to verify that the index has indeed been read from the tree. using (var repo = new Repository(path)) { const string headIndexTreeSha = "e5d221fc5da11a3169bf503d76497c81be3207b6"; Assert.True(repo.Index.IsFullyMerged); Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(testFile)); var headIndexTree = repo.Lookup(headIndexTreeSha); Assert.NotNull(headIndexTree); repo.Index.Replace(headIndexTree); Assert.True(repo.Index.IsFullyMerged); Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus(testFile)); } // Check that the index was persisted to disk. using (var repo = new Repository(path)) { Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus(testFile)); } } [Fact] public void CanResetIndexWithUnmergedEntriesFromTree() { string path = SandboxMergedTestRepo(); const string testFile = "one.txt"; // It is sufficient to check just one of the stage area changes, such as the modified file, // to verify that the index has indeed been read from the tree. using (var repo = new Repository(path)) { const string headIndexTreeSha = "1cb365141a52dfbb24933515820eb3045fbca12b"; Assert.False(repo.Index.IsFullyMerged); Assert.Equal(FileStatus.ModifiedInIndex, repo.RetrieveStatus(testFile)); var headIndexTree = repo.Lookup(headIndexTreeSha); Assert.NotNull(headIndexTree); repo.Index.Replace(headIndexTree); Assert.True(repo.Index.IsFullyMerged); Assert.Equal(FileStatus.ModifiedInWorkdir, repo.RetrieveStatus(testFile)); } // Check that the index was persisted to disk. using (var repo = new Repository(path)) { Assert.Equal(FileStatus.ModifiedInWorkdir, repo.RetrieveStatus(testFile)); } } [Fact] public void CanClearTheIndex() { string path = SandboxStandardTestRepo(); const string testFile = "1.txt"; // It is sufficient to check just one of the stage area changes, such as the modified file, // to verify that the index has indeed been read from the tree. using (var repo = new Repository(path)) { Assert.Equal(FileStatus.Unaltered, repo.RetrieveStatus(testFile)); Assert.NotEqual(0, repo.Index.Count); repo.Index.Clear(); Assert.Equal(0, repo.Index.Count); Assert.Equal(FileStatus.DeletedFromIndex | FileStatus.NewInWorkdir, repo.RetrieveStatus(testFile)); } // Check that the index was persisted to disk. using (var repo = new Repository(path)) { Assert.Equal(FileStatus.DeletedFromIndex | FileStatus.NewInWorkdir, repo.RetrieveStatus(testFile)); } } [Theory] [InlineData("new_tracked_file.txt", FileStatus.NewInIndex, FileStatus.NewInWorkdir)] [InlineData("modified_staged_file.txt", FileStatus.ModifiedInIndex, FileStatus.DeletedFromIndex | FileStatus.NewInWorkdir)] [InlineData("i_dont_exist.txt", FileStatus.Nonexistent, FileStatus.Nonexistent)] public void CanRemoveAnEntryFromTheIndex(string pathInTheIndex, FileStatus expectedBeforeStatus, FileStatus expectedAfterStatus) { var path = SandboxStandardTestRepoGitDir(); using (var repo = new Repository(path)) { var before = repo.RetrieveStatus(pathInTheIndex); Assert.Equal(expectedBeforeStatus, before); repo.Index.Remove(pathInTheIndex); var after = repo.RetrieveStatus(pathInTheIndex); Assert.Equal(expectedAfterStatus, after); } } [Theory] [InlineData("new_untracked_file.txt", FileStatus.NewInWorkdir, FileStatus.NewInIndex)] [InlineData("modified_unstaged_file.txt", FileStatus.ModifiedInWorkdir, FileStatus.ModifiedInIndex)] public void CanAddAnEntryToTheIndexFromAFileInTheWorkdir(string pathInTheWorkdir, FileStatus expectedBeforeStatus, FileStatus expectedAfterStatus) { var path = SandboxStandardTestRepoGitDir(); using (var repo = new Repository(path)) { var before = repo.RetrieveStatus(pathInTheWorkdir); Assert.Equal(expectedBeforeStatus, before); repo.Index.Add(pathInTheWorkdir); var after = repo.RetrieveStatus(pathInTheWorkdir); Assert.Equal(expectedAfterStatus, after); } } [Fact] public void CanAddAnEntryToTheIndexFromABlob() { var path = SandboxStandardTestRepoGitDir(); using (var repo = new Repository(path)) { const string targetIndexEntryPath = "1.txt"; var before = repo.RetrieveStatus(targetIndexEntryPath); Assert.Equal(FileStatus.Unaltered, before); var blob = repo.Lookup("a8233120f6ad708f843d861ce2b7228ec4e3dec6"); repo.Index.Add(blob, targetIndexEntryPath, Mode.NonExecutableFile); var after = repo.RetrieveStatus(targetIndexEntryPath); Assert.Equal(FileStatus.ModifiedInIndex | FileStatus.ModifiedInWorkdir, after); } } [Fact] public void AddingAnEntryToTheIndexFromAUnknwonFileInTheWorkdirThrows() { var path = SandboxStandardTestRepoGitDir(); using (var repo = new Repository(path)) { const string filePath = "i_dont_exist.txt"; var before = repo.RetrieveStatus(filePath); Assert.Equal(FileStatus.Nonexistent, before); Assert.Throws(() => repo.Index.Add(filePath)); } } [Fact] public void CanMimicGitAddAll() { var path = SandboxStandardTestRepoGitDir(); using (var repo = new Repository(path)) { var before = repo.RetrieveStatus(); Assert.True(before.Any(se => se.State == FileStatus.NewInWorkdir)); Assert.True(before.Any(se => se.State == FileStatus.ModifiedInWorkdir)); Assert.True(before.Any(se => se.State == FileStatus.DeletedFromWorkdir)); AddSomeCornerCases(repo); repo.Stage("*"); var after = repo.RetrieveStatus(); Assert.False(after.Any(se => se.State == FileStatus.NewInWorkdir)); Assert.False(after.Any(se => se.State == FileStatus.ModifiedInWorkdir)); Assert.False(after.Any(se => se.State == FileStatus.DeletedFromWorkdir)); } } [Fact] public void RetrievingAssumedUnchangedMarkedIndexEntries() { var path = SandboxAssumeUnchangedTestRepo(); using (var repo = new Repository(path)) { var regularFile = repo.Index["hello.txt"]; Assert.False(regularFile.AssumeUnchanged); var assumeUnchangedFile = repo.Index["world.txt"]; Assert.True(assumeUnchangedFile.AssumeUnchanged); } } private static void AddSomeCornerCases(Repository repo) { // Turn 1.txt into a directory in the Index repo.Index.Remove("1.txt"); var blob = repo.Lookup("a8233120f6ad708f843d861ce2b7228ec4e3dec6"); repo.Index.Add(blob, "1.txt/Sneaky", Mode.NonExecutableFile); // Turn README into a symlink Blob linkContent = OdbHelper.CreateBlob(repo, "1.txt/sneaky"); repo.Index.Add(linkContent, "README", Mode.SymbolicLink); } } }