diff --git a/LibGit2Sharp.Tests/CheckoutFixture.cs b/LibGit2Sharp.Tests/CheckoutFixture.cs index a09802d1f..17d4a0528 100644 --- a/LibGit2Sharp.Tests/CheckoutFixture.cs +++ b/LibGit2Sharp.Tests/CheckoutFixture.cs @@ -23,8 +23,9 @@ public void CanCheckoutAnExistingBranch(string branchName) Branch master = repo.Branches["master"]; Assert.True(master.IsCurrentRepositoryHead); - // Hard reset to ensure that working directory, index, and HEAD match - repo.Reset(ResetOptions.Hard); + // Set the working directory to the current head + ResetAndCleanWorkingDirectory(repo); + Assert.False(repo.Index.RetrieveStatus().IsDirty); Branch branch = repo.Branches[branchName]; @@ -55,8 +56,9 @@ public void CanCheckoutAnExistingBranchByName(string branchName) Branch master = repo.Branches["master"]; Assert.True(master.IsCurrentRepositoryHead); - // Hard reset to ensure that working directory, index, and HEAD match - repo.Reset(ResetOptions.Hard); + // Set the working directory to the current head + ResetAndCleanWorkingDirectory(repo); + Assert.False(repo.Index.RetrieveStatus().IsDirty); Branch test = repo.Checkout(branchName); @@ -84,8 +86,9 @@ public void CanCheckoutAnArbitraryCommit(string commitPointer) Branch master = repo.Branches["master"]; Assert.True(master.IsCurrentRepositoryHead); - // Hard reset to ensure that working directory, index, and HEAD match - repo.Reset(ResetOptions.Hard); + // Set the working directory to the current head + ResetAndCleanWorkingDirectory(repo); + Assert.False(repo.Index.RetrieveStatus().IsDirty); Branch detachedHead = repo.Checkout(commitPointer); @@ -197,8 +200,9 @@ public void CanForcefullyCheckoutWithStagedChanges() Branch master = repo.Branches["master"]; Assert.True(master.IsCurrentRepositoryHead); - // Hard reset to ensure that working directory, index, and HEAD match - repo.Reset(ResetOptions.Hard); + // Set the working directory to the current head + ResetAndCleanWorkingDirectory(repo); + Assert.False(repo.Index.RetrieveStatus().IsDirty); // Add local change @@ -335,5 +339,19 @@ private void PopulateBasicRepository(Repository repo) repo.CreateBranch(otherBranchName); } + + /// + /// Reset and clean current working directory. This will ensure that the current + /// working directory matches the current Head commit. + /// + /// Repository whose current working directory should be operated on. + private void ResetAndCleanWorkingDirectory(Repository repo) + { + // Reset the index and the working tree. + repo.Reset(ResetOptions.Hard); + + // Remove untracked files. + repo.Index.CleanWorkingDirectory(); + } } } diff --git a/LibGit2Sharp.Tests/IndexFixture.cs b/LibGit2Sharp.Tests/IndexFixture.cs index c286da293..50d0d01ba 100644 --- a/LibGit2Sharp.Tests/IndexFixture.cs +++ b/LibGit2Sharp.Tests/IndexFixture.cs @@ -82,6 +82,24 @@ public void ReadIndexWithBadParamsFails() } } + [Fact] + public void CanCleanWorkingDirectory() + { + TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath); + using (var repo = new Repository(path.RepositoryPath)) + { + // Verify that there are the expected number of entries and untracked files + Assert.Equal(6, repo.Index.RetrieveStatus().Count()); + Assert.Equal(1, repo.Index.RetrieveStatus().Untracked.Count()); + + repo.Index.CleanWorkingDirectory(); + + // Verify that there are the expected number of entries and 0 untracked files + Assert.Equal(5, repo.Index.RetrieveStatus().Count()); + Assert.Equal(0, repo.Index.RetrieveStatus().Untracked.Count()); + } + } + [Theory] [InlineData("1/branch_file.txt", FileStatus.Unaltered, true, FileStatus.Unaltered, true, 0)] [InlineData("README", FileStatus.Unaltered, true, FileStatus.Unaltered, true, 0)] diff --git a/LibGit2Sharp/Core/NativeMethods.cs b/LibGit2Sharp/Core/NativeMethods.cs index 0d3b2f207..e246920d8 100644 --- a/LibGit2Sharp/Core/NativeMethods.cs +++ b/LibGit2Sharp/Core/NativeMethods.cs @@ -157,6 +157,11 @@ internal static extern int git_checkout_head( RepositorySafeHandle repo, GitCheckoutOpts opts); + [DllImport(libgit2)] + internal static extern int git_checkout_index( + RepositorySafeHandle repo, + GitCheckoutOpts opts); + [DllImport(libgit2)] internal static extern int git_clone( out RepositorySafeHandle repo, diff --git a/LibGit2Sharp/Core/Proxy.cs b/LibGit2Sharp/Core/Proxy.cs index 179f0bc22..9a7a94897 100644 --- a/LibGit2Sharp/Core/Proxy.cs +++ b/LibGit2Sharp/Core/Proxy.cs @@ -188,6 +188,15 @@ public static void git_checkout_head(RepositorySafeHandle repo, GitCheckoutOpts } } + public static void git_checkout_index(RepositorySafeHandle repo, GitCheckoutOpts opts) + { + using (ThreadAffinity()) + { + int res = NativeMethods.git_checkout_index(repo, opts); + Ensure.Success(res); + } + } + #endregion #region git_clone_ diff --git a/LibGit2Sharp/Index.cs b/LibGit2Sharp/Index.cs index 8a9f8d4dd..5e3e68cef 100644 --- a/LibGit2Sharp/Index.cs +++ b/LibGit2Sharp/Index.cs @@ -338,6 +338,19 @@ public virtual void Remove(IEnumerable paths) UpdatePhysicalIndex(); } + /// + /// Clean the working tree by removing files that are not under version control. + /// + public virtual void CleanWorkingDirectory() + { + GitCheckoutOpts options = new GitCheckoutOpts + { + checkout_strategy = CheckoutStrategy.GIT_CHECKOUT_REMOVE_UNTRACKED, + }; + + Proxy.git_checkout_index(this.repo.Handle, options); + } + private IEnumerable> PrepareBatch(IEnumerable paths) { Ensure.ArgumentNotNull(paths, "paths");