Skip to content

Checkout removes ignored directories - WIP #254

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file modified Lib/NativeBinaries/amd64/git2.dll
Binary file not shown.
Binary file modified Lib/NativeBinaries/amd64/git2.pdb
Binary file not shown.
Binary file modified Lib/NativeBinaries/x86/git2.dll
Binary file not shown.
Binary file modified Lib/NativeBinaries/x86/git2.pdb
Binary file not shown.
68 changes: 60 additions & 8 deletions LibGit2Sharp.Tests/CheckoutFixture.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Linq;
using LibGit2Sharp.Tests.TestHelpers;
using Xunit;
using Xunit.Extensions;
Expand All @@ -23,8 +24,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];
Expand Down Expand Up @@ -55,8 +57,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);
Expand Down Expand Up @@ -84,8 +87,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);
Expand Down Expand Up @@ -197,8 +201,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
Expand Down Expand Up @@ -321,6 +326,39 @@ public void CheckingOutThroughRepositoryCallsCheckoutProgress()
}
}

[Fact]
public void CheckoutLeavesUntrackedDirectory()
{
SelfCleaningDirectory scd = BuildSelfCleaningDirectory();

using (var repo = Repository.Init(scd.DirectoryPath))
{
PopulateBasicRepository(repo);

// Generate a .gitignore file
string gitIgnoreFilePath = Path.Combine(repo.Info.WorkingDirectory, ".gitignore");
File.WriteAllText(gitIgnoreFilePath, ".bin");
repo.Index.Stage(gitIgnoreFilePath);
repo.Commit("Add git ignore file", Constants.Signature, Constants.Signature);

// Create a bin directory
string ignoredDirectoryPath = Path.Combine(repo.Info.WorkingDirectory, "bin");
Directory.CreateDirectory(ignoredDirectoryPath);

// Create file in ignored bin directory
string ignoredFilePath = Path.Combine(repo.Info.WorkingDirectory, Path.Combine("bin", "some_ignored_file.txt"));
File.WriteAllText(ignoredFilePath, "hello from this ignored file.");

// Verify that there is an untracked entry
Assert.Equal(1, repo.Index.RetrieveStatus().Untracked.Count());

repo.Checkout(otherBranchName, CheckoutOptions.Force, null);

Assert.True(Directory.Exists(ignoredDirectoryPath));
Assert.True(File.Exists(ignoredFilePath));
}
}

/// <summary>
/// Helper method to populate a simple repository with
/// a single file and two branches.
Expand All @@ -335,5 +373,19 @@ private void PopulateBasicRepository(Repository repo)

repo.CreateBranch(otherBranchName);
}

/// <summary>
/// Reset and clean current working directory. This will ensure that the current
/// working directory matches the current Head commit.
/// </summary>
/// <param name="repo">Repository whose current working directory should be operated on.</param>
private void ResetAndCleanWorkingDirectory(Repository repo)
{
// Reset the index and the working tree.
repo.Reset(ResetOptions.Hard);

// Remove untracked files.
repo.Index.CleanWorkingDirectory();
}
}
}
6 changes: 3 additions & 3 deletions LibGit2Sharp.Tests/CommitFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public void CanCorrectlyCountCommitsWhenSwitchingToAnotherBranch()
{
repo.Reset(ResetOptions.Hard);

repo.Checkout("test");
repo.Checkout("test", CheckoutOptions.Force, null);
Assert.Equal(2, repo.Commits.Count());
Assert.Equal("e90810b8df3e80c413d903f631643c716887138d", repo.Commits.First().Id.Sha);

Expand Down Expand Up @@ -229,8 +229,8 @@ public void CanEnumerateFromDetachedHead()
{
repoClone.Reset(ResetOptions.Hard);

string headSha = repoClone.Head.Tip.Sha;
repoClone.Checkout(headSha);
Branch head = repoClone.Head;
repoClone.Checkout(head, CheckoutOptions.Force, null);

AssertEnumerationOfCommitsInRepo(repoClone,
repo => new Filter { Since = repo.Head },
Expand Down
1 change: 1 addition & 0 deletions LibGit2Sharp.Tests/ConfigurationFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ public void CanUnsetAnEntryFromTheGlobalConfiguration()
.AppendFormat("Man-I-am-totally-global = 42{0}", Environment.NewLine);

File.WriteAllText(globalLocation, sb.ToString());
File.WriteAllText(systemLocation, string.Empty);

var options = new RepositoryOptions
{
Expand Down
18 changes: 18 additions & 0 deletions LibGit2Sharp.Tests/IndexFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down
1 change: 1 addition & 0 deletions LibGit2Sharp.Tests/RepositoryOptionsFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,7 @@ public void CanProvideDifferentConfigurationFilesToARepository()
.AppendFormat("email = {0}{1}", email, Environment.NewLine);

File.WriteAllText(globalLocation, sb.ToString());
File.WriteAllText(systemLocation, string.Empty);

var options = new RepositoryOptions {
GlobalConfigurationLocation = globalLocation,
Expand Down
4 changes: 2 additions & 2 deletions LibGit2Sharp.Tests/ResetHeadFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ public void HardResetUpdatesTheContentOfTheWorkingDirectory()
names.Sort(StringComparer.Ordinal);

File.Delete(Path.Combine(repo.Info.WorkingDirectory, "README"));
File.WriteAllText(Path.Combine(repo.Info.WorkingDirectory, "WillBeRemoved.txt"), "content\n");
File.WriteAllText(Path.Combine(repo.Info.WorkingDirectory, "WillNotBeRemoved.txt"), "content\n");

Assert.True(names.Count > 4);

Expand All @@ -183,7 +183,7 @@ public void HardResetUpdatesTheContentOfTheWorkingDirectory()
names = new DirectoryInfo(repo.Info.WorkingDirectory).GetFileSystemInfos().Select(fsi => fsi.Name).ToList();
names.Sort(StringComparer.Ordinal);

Assert.Equal(new[] { ".git", "README", "branch_file.txt", "new.txt" }, names);
Assert.Equal(new[] { ".git", "README", "WillNotBeRemoved.txt", "branch_file.txt", "new.txt", "new_untracked_file.txt" }, names);
}
}
}
Expand Down
63 changes: 53 additions & 10 deletions LibGit2Sharp/Core/GitCheckoutOpts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@

namespace LibGit2Sharp.Core
{
internal delegate int skipped_notify_cb(
IntPtr skipped_file,
internal delegate int conflict_cb(
IntPtr conflicting_path,
ref GitOid blob_oid,
int file_mode,
IntPtr payload);
uint index_mode,
uint wd_mode,
IntPtr payload);

internal delegate void progress_cb(
IntPtr strPtr,
Expand All @@ -23,8 +24,8 @@ internal class GitCheckoutOpts
public int DirMode;
public int FileMode;
public int FileOpenFlags;
public skipped_notify_cb skippedNotifyCb;
public IntPtr NotifyPayload;
public conflict_cb conflictCb;
public IntPtr ConflictPayload;
public progress_cb ProgressCb;
public IntPtr ProgressPayload;
public UnSafeNativeMethods.git_strarray paths;
Expand All @@ -33,9 +34,51 @@ internal class GitCheckoutOpts
[Flags]
internal enum CheckoutStrategy
{
GIT_CHECKOUT_DEFAULT = (1 << 0),
GIT_CHECKOUT_OVERWRITE_MODIFIED = (1 << 1),
GIT_CHECKOUT_CREATE_MISSING = (1 << 2),
GIT_CHECKOUT_REMOVE_UNTRACKED = (1 << 3),
GIT_CHECKOUT_DEFAULT = 0, /** default is a dry run, no actual updates */

/** Allow update of entries where working dir matches HEAD. */
GIT_CHECKOUT_UPDATE_UNMODIFIED = (1 << 0),

/** Allow update of entries where working dir does not have file. */
GIT_CHECKOUT_UPDATE_MISSING = (1 << 1),

/** Allow safe updates that cannot overwrite uncommited data */
GIT_CHECKOUT_SAFE =
(GIT_CHECKOUT_UPDATE_UNMODIFIED | GIT_CHECKOUT_UPDATE_MISSING),

/** Allow update of entries in working dir that are modified from HEAD. */
GIT_CHECKOUT_UPDATE_MODIFIED = (1 << 2),

/** Update existing untracked files that are now present in the index. */
GIT_CHECKOUT_UPDATE_UNTRACKED = (1 << 3),

/** Allow all updates to force working directory to look like index */
GIT_CHECKOUT_FORCE =
(GIT_CHECKOUT_SAFE | GIT_CHECKOUT_UPDATE_MODIFIED | GIT_CHECKOUT_UPDATE_UNTRACKED),

/** Allow checkout to make updates even if conflicts are found */
GIT_CHECKOUT_ALLOW_CONFLICTS = (1 << 4),

/** Remove untracked files not in index (that are not ignored) */
GIT_CHECKOUT_REMOVE_UNTRACKED = (1 << 5),

/** Only update existing files, don't create new ones */
GIT_CHECKOUT_UPDATE_ONLY = (1 << 6),

/**
* THE FOLLOWING OPTIONS ARE NOT YET IMPLEMENTED
*/

/** Allow checkout to skip unmerged files (NOT IMPLEMENTED) */
GIT_CHECKOUT_SKIP_UNMERGED = (1 << 10),
/** For unmerged files, checkout stage 2 from index (NOT IMPLEMENTED) */
GIT_CHECKOUT_USE_OURS = (1 << 11),
/** For unmerged files, checkout stage 3 from index (NOT IMPLEMENTED) */
GIT_CHECKOUT_USE_THEIRS = (1 << 12),

/** Recursively checkout submodules with same options (NOT IMPLEMENTED) */
GIT_CHECKOUT_UPDATE_SUBMODULES = (1 << 16),
/** Recursively checkout submodules if HEAD moved in super repo (NOT IMPLEMENTED) */
GIT_CHECKOUT_UPDATE_SUBMODULES_IF_CHANGED = (1 << 17),
}
}
44 changes: 44 additions & 0 deletions LibGit2Sharp/Core/GitDiff.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,60 @@ namespace LibGit2Sharp.Core
[Flags]
internal enum GitDiffOptionFlags
{
/** Normal diff, the default */
GIT_DIFF_NORMAL = 0,
/** Reverse the sides of the diff */
GIT_DIFF_REVERSE = (1 << 0),
/** Treat all files as text, disabling binary attributes & detection */
GIT_DIFF_FORCE_TEXT = (1 << 1),
/** Ignore all whitespace */
GIT_DIFF_IGNORE_WHITESPACE = (1 << 2),
/** Ignore changes in amount of whitespace */
GIT_DIFF_IGNORE_WHITESPACE_CHANGE = (1 << 3),
/** Ignore whitespace at end of line */
GIT_DIFF_IGNORE_WHITESPACE_EOL = (1 << 4),
/** Exclude submodules from the diff completely */
GIT_DIFF_IGNORE_SUBMODULES = (1 << 5),
/** Use the "patience diff" algorithm (currently unimplemented) */
GIT_DIFF_PATIENCE = (1 << 6),
/** Include ignored files in the diff list */
GIT_DIFF_INCLUDE_IGNORED = (1 << 7),
/** Include untracked files in the diff list */
GIT_DIFF_INCLUDE_UNTRACKED = (1 << 8),
/** Include unmodified files in the diff list */
GIT_DIFF_INCLUDE_UNMODIFIED = (1 << 9),
/** Even with the GIT_DIFF_INCLUDE_UNTRACKED flag, when an untracked
* directory is found, only a single entry for the directory is added
* to the diff list; with this flag, all files under the directory will
* be included, too.
*/
GIT_DIFF_RECURSE_UNTRACKED_DIRS = (1 << 10),
/** If the pathspec is set in the diff options, this flags means to
* apply it as an exact match instead of as an fnmatch pattern.
*/
GIT_DIFF_DISABLE_PATHSPEC_MATCH = (1 << 11),
/** Use case insensitive filename comparisons */
GIT_DIFF_DELTAS_ARE_ICASE = (1 << 12),
/** When generating patch text, include the content of untracked files */
GIT_DIFF_INCLUDE_UNTRACKED_CONTENT = (1 << 13),
/** Disable updating of the `binary` flag in delta records. This is
* useful when iterating over a diff if you don't need hunk and data
* callbacks and want to avoid having to load file completely.
*/
GIT_DIFF_SKIP_BINARY_CHECK = (1 << 14),
/** Normally, a type change between files will be converted into a
* DELETED record for the old and an ADDED record for the new; this
* options enabled the generation of TYPECHANGE delta records.
*/
GIT_DIFF_INCLUDE_TYPECHANGE = (1 << 15),
/** Even with GIT_DIFF_INCLUDE_TYPECHANGE, blob->tree changes still
* generally show as a DELETED blob. This flag tries to correctly
* label blob->tree transitions as TYPECHANGE records with new_file's
* mode set to tree. Note: the tree SHA will not be available.
*/
GIT_DIFF_INCLUDE_TYPECHANGE_TREES = (1 << 16),
/** Ignore file mode changes */
GIT_DIFF_IGNORE_FILEMODE = (1 << 17),
}

[StructLayout(LayoutKind.Sequential)]
Expand Down
5 changes: 5 additions & 0 deletions LibGit2Sharp/Core/GitErrorCategory.cs
Original file line number Diff line number Diff line change
Expand Up @@ -18,5 +18,10 @@ internal enum GitErrorCategory
Tag,
Tree,
Indexer,
Ssl,
Submodule,
Thread,
Stash,
Checkout,
}
}
10 changes: 10 additions & 0 deletions LibGit2Sharp/Core/GitErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,16 @@ internal enum GitErrorCode
/// </summary>
BareRepo = -8,

/// <summary>
/// Operation cannot be performed against an orphaned HEAD.
/// </summary>
OrphanedHead = -9,

/// <summary>
/// Operation cannot be performed against a not fully merged index.
/// </summary>
UnmergedEntries = -8,

/// <summary>
/// Skip and passthrough the given ODB backend.
/// </summary>
Expand Down
1 change: 1 addition & 0 deletions LibGit2Sharp/Core/GitOdbBackend.cs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ static GitOdbBackend()
public readstream_callback ReadStream;
public exists_callback Exists;
public foreach_callback Foreach;
public IntPtr Writepack;
public free_callback Free;

/* The libgit2 structure definition ends here. Subsequent fields are for libgit2sharp bookkeeping. */
Expand Down
Loading