Skip to content

Commit

Permalink
Fix Index.Stage(), Index.Unstage() and enforce test coverage
Browse files Browse the repository at this point in the history
Should fix issue #78.
  • Loading branch information
nulltoken committed Oct 21, 2011
1 parent dcc034f commit 4357bf3
Show file tree
Hide file tree
Showing 3 changed files with 132 additions and 71 deletions.
122 changes: 67 additions & 55 deletions LibGit2Sharp.Tests/IndexFixture.cs
Expand Up @@ -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))
Expand All @@ -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<LibGit2Exception>(() => 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));
}
}
Expand Down Expand Up @@ -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);
Expand All @@ -281,15 +302,6 @@ public void CanUnstageTheRemovalOfAFile()
}
}

[Test]
public void UnstagingANonStagedFileThrows()
{
using (var repo = new Repository(Constants.StandardTestRepoPath))
{
Assert.Throws<LibGit2Exception>(() => repo.Index.Unstage("shadowcopy_of_an_unseen_ghost.txt"));
}
}

[Test]
public void UnstagingFileWithBadParamsThrows()
{
Expand Down
71 changes: 60 additions & 11 deletions LibGit2Sharp/Index.cs
Expand Up @@ -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();
}
Expand All @@ -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();
}
Expand Down Expand Up @@ -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()
Expand All @@ -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;
Expand All @@ -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);
Expand Down
10 changes: 5 additions & 5 deletions LibGit2Sharp/Tree.cs
Expand Up @@ -16,7 +16,7 @@ internal Tree(ObjectId id)
}

/// <summary>
/// Gets the number of <see cref="TreeEntry"/> immediately under this <see cref="Tree"/>.
/// Gets the number of <see cref = "TreeEntry" /> immediately under this <see cref = "Tree" />.
/// </summary>
public int Count { get; private set; }

Expand Down Expand Up @@ -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)
{
Expand All @@ -67,7 +67,7 @@ private TreeEntry RetrieveFromName(string name)
}

/// <summary>
/// Gets the <see cref="Tree"/>s immediately under this <see cref="Tree"/>.
/// Gets the <see cref = "Tree" />s immediately under this <see cref = "Tree" />.
/// </summary>
public IEnumerable<Tree> Trees
{
Expand All @@ -81,7 +81,7 @@ public IEnumerable<Tree> Trees
}

/// <summary>
/// Gets the <see cref="Blob"/>s immediately under this <see cref="Tree"/>.
/// Gets the <see cref = "Blob" />s immediately under this <see cref = "Tree" />.
/// </summary>
public IEnumerable<Blob> Files
{
Expand Down

0 comments on commit 4357bf3

Please sign in to comment.