Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Add Stash first implementation #352

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 2 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
1 change: 1 addition & 0 deletions LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
</ItemGroup>
<ItemGroup>
<Compile Include="CheckoutFixture.cs" />
<Compile Include="StashFixture.cs" />
<Compile Include="CloneFixture.cs" />
<Compile Include="ConflictFixture.cs" />
<Compile Include="IgnoreFixture.cs" />
Expand Down
191 changes: 191 additions & 0 deletions LibGit2Sharp.Tests/StashFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
using System;
using System.IO;
using System.Linq;
using LibGit2Sharp.Tests.TestHelpers;
using Xunit;

namespace LibGit2Sharp.Tests
{
public class StashFixture : BaseFixture
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please another test asserting that stashing against a clean working directory doesn't do anything and returns a null stash.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wrote the test, it currently throws an exception : Category = Stash (NotFound). Cannot stash changes - There is nothing to stash.
You prefer to catch this and return null as Stashinstead?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍 for returning null instead of throwing.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You prefer to catch this and return null as Stashinstead?

@Saaman I don't think you need to catch anything. Comparing the result against (int)GitErrorCode.NotFound just before the call to Ensure should do the trick.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok I'll do that. I will also add a test case to cover it.

{
[Fact]
public void CannotAddStashAgainstBareRepository()
{
TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo();
using (var repo = new Repository(path.RepositoryPath))
{
var stasher = DummySignature;

Assert.Throws<BareRepositoryException>(() => repo.Stashes.Add(stasher, "My very first stash"));
}
}

[Fact]
public void CanAddStash()
{
TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath);
using (var repo = new Repository(path.RepositoryPath))
{
var stasher = DummySignature;

Assert.True(repo.Index.RetrieveStatus().IsDirty);

Stash stash = repo.Stashes.Add(stasher, "My very first stash", StashOptions.IncludeUntracked);

// Check that untracked files are deleted from working directory
Assert.False(File.Exists(Path.Combine(repo.Info.WorkingDirectory, "new_untracked_file.txt")));

Assert.NotNull(stash);
Assert.Equal("stash@{0}", stash.CanonicalName);
Assert.Contains("My very first stash", stash.Message);

var stashRef = repo.Refs["refs/stash"];
Assert.Equal(stash.Target.Sha, stashRef.TargetIdentifier);

Assert.False(repo.Index.RetrieveStatus().IsDirty);

// Create extra file
string newFileFullPath = Path.Combine(repo.Info.WorkingDirectory, "stash_candidate.txt");
File.WriteAllText(newFileFullPath, "Oh, I'm going to be stashed!\n");

Stash secondStash = repo.Stashes.Add(stasher, "My second stash", StashOptions.IncludeUntracked);

Assert.NotNull(stash);
Assert.Equal("stash@{0}", stash.CanonicalName);
Assert.Contains("My second stash", secondStash.Message);

Assert.Equal(2, repo.Stashes.Count());
Assert.Equal("stash@{0}", repo.Stashes.First().CanonicalName);
Assert.Equal("stash@{1}", repo.Stashes.Last().CanonicalName);

Assert.Equal(2, repo.Stashes.Count());
Assert.Equal("stash@{0}", repo.Stashes.First().CanonicalName);
Assert.Equal("stash@{1}", repo.Stashes.Last().CanonicalName);

// Stash history has been shifted
Assert.Equal(repo.Lookup<Commit>("stash@{0}").Sha, secondStash.Target.Sha);
Assert.Equal(repo.Lookup<Commit>("stash@{1}").Sha, stash.Target.Sha);
}
}

[Fact]
public void AddingAStashWithNoMessageGeneratesADefaultOne()
{
TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath);
using (var repo = new Repository(path.RepositoryPath))
{
var stasher = DummySignature;

Stash stash = repo.Stashes.Add(stasher);

Assert.NotNull(stash);
Assert.Equal("stash@{0}", stash.CanonicalName);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could please assert, that the Message from the Tip is not empty nor null?

Assert.NotEmpty(stash.Target.Message);

var stashRef = repo.Refs["refs/stash"];
Assert.Equal(stash.Target.Sha, stashRef.TargetIdentifier);
}
}

[Fact]
public void AddStashWithBadParamsShouldThrows()
{
TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath);
using (var repo = new Repository(path.RepositoryPath))
{
Assert.Throws<ArgumentNullException>(() => repo.Stashes.Add(null));
}
}

[Fact]
public void StashingAgainstCleanWorkDirShouldReturnANullStash()
{
TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath);
using (var repo = new Repository(path.RepositoryPath))
{
var stasher = DummySignature;

Stash stash = repo.Stashes.Add(stasher, "My very first stash", StashOptions.IncludeUntracked);

Assert.NotNull(stash);

//Stash against clean working directory
Assert.Null(repo.Stashes.Add(stasher));
}
}

[Fact]
public void CanStashWithoutOptions()
{
TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath);
using (var repo = new Repository(path.RepositoryPath))
{
var stasher = DummySignature;

var untrackedFilePath = Path.Combine(repo.Info.WorkingDirectory, "new_untracked_file.txt");
File.WriteAllText(untrackedFilePath, "I'm untracked\n");

string stagedfilePath = Path.Combine(repo.Info.WorkingDirectory, "staged_file_path.txt");
File.WriteAllText(stagedfilePath, "I'm staged\n");
repo.Index.Stage(stagedfilePath);

Stash stash = repo.Stashes.Add(stasher, "Stash with default options");

Assert.NotNull(stash);

//It should not keep staged files
Assert.Equal(FileStatus.Nonexistent, repo.Index.RetrieveStatus("staged_file_path.txt"));

//It should leave untracked files untracked
Assert.Equal(FileStatus.Untracked, repo.Index.RetrieveStatus("new_untracked_file.txt"));
}
}

[Fact]
public void CanStashAndKeepIndex()
{
TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath);
using (var repo = new Repository(path.RepositoryPath))
{
var stasher = DummySignature;

string stagedfilePath = Path.Combine(repo.Info.WorkingDirectory, "staged_file_path.txt");
File.WriteAllText(stagedfilePath, "I'm staged\n");
repo.Index.Stage(stagedfilePath);

Stash stash = repo.Stashes.Add(stasher, "This stash wil keep index", StashOptions.KeepIndex);

Assert.NotNull(stash);
Assert.Equal(FileStatus.Added, repo.Index.RetrieveStatus("staged_file_path.txt"));
}
}

[Fact]
public void CanStashIgnoredFiles()
{
TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath);

using (var repo = new Repository(path.RepositoryPath))
{
string gitIgnoreFilePath = Path.Combine(repo.Info.WorkingDirectory, ".gitignore");
File.WriteAllText(gitIgnoreFilePath, "ignored_file.txt");
repo.Index.Stage(gitIgnoreFilePath);
repo.Commit("Modify gitignore", Constants.Signature, Constants.Signature);

string ignoredFilePath = Path.Combine(repo.Info.WorkingDirectory, "ignored_file.txt");
File.WriteAllText(ignoredFilePath, "I'm ignored\n");

Assert.True(repo.Ignore.IsPathIgnored("ignored_file.txt"));

var stasher = DummySignature;
repo.Stashes.Add(stasher, "This stash includes ignore files", StashOptions.IncludeIgnored);

//TODO : below assertion doesn't pass. Bug?
//Assert.False(File.Exists(ignoredFilePath));

var blob = repo.Lookup<Blob>("stash^3:ignored_file.txt");
Assert.NotNull(blob);
}
}
}
}
20 changes: 20 additions & 0 deletions LibGit2Sharp/Core/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -904,6 +904,26 @@ internal static extern int git_signature_new(
long time,
int offset);

[DllImport(libgit2)]
internal static extern int git_stash_save(
out GitOid id,
RepositorySafeHandle repo,
SignatureSafeHandle stasher,
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string message,
StashOptions flags);

internal delegate int git_stash_cb(
UIntPtr index,
IntPtr message,
ref GitOid stash_id,
IntPtr payload);

[DllImport(libgit2)]
internal static extern int git_stash_foreach(
RepositorySafeHandle repo,
git_stash_cb callback,
IntPtr payload);

[DllImport(libgit2)]
internal static extern int git_status_file(
out FileStatus statusflags,
Expand Down
63 changes: 63 additions & 0 deletions LibGit2Sharp/Core/Proxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1747,6 +1747,45 @@ public static SignatureSafeHandle git_signature_new(string name, string email, D

#endregion

#region git_stash_
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please add a blank line after the region definition


public static ObjectId git_stash_save(
RepositorySafeHandle repo,
Signature stasher,
string prettifiedMessage,
StashOptions options)
{
using (ThreadAffinity())
using (SignatureSafeHandle stasherHandle = stasher.BuildHandle())
{
GitOid stashOid;

int res = NativeMethods.git_stash_save(out stashOid, repo, stasherHandle, prettifiedMessage, options);

if (res == (int)GitErrorCode.NotFound)
{
return null;
}

Ensure.Int32Result(res);

return new ObjectId(stashOid);
}
}

public static ICollection<TResult> git_stash_foreach<TResult>(
RepositorySafeHandle repo,
Func<int, IntPtr, GitOid, TResult> resultSelector)
{
return git_foreach(
resultSelector,
c => NativeMethods.git_stash_foreach(
repo, (UIntPtr i, IntPtr m, ref GitOid x, IntPtr p) => c((int)i, m, x, p), IntPtr.Zero),
GitErrorCode.NotFound);
}

#endregion

#region git_status_

public static FileStatus git_status_file(RepositorySafeHandle repo, FilePath path)
Expand Down Expand Up @@ -2012,6 +2051,30 @@ private static ICollection<TResult> git_foreach<T1, T2, TResult>(
}
}

private static ICollection<TResult> git_foreach<T1, T2, T3, TResult>(
Func<T1, T2, T3, TResult> resultSelector,
Func<Func<T1, T2, T3, IntPtr, int>, int> iterator,
params GitErrorCode[] ignoredErrorCodes)
{
using (ThreadAffinity())
{
var result = new List<TResult>();
var res = iterator((w, x, y, payload) =>
{
result.Add(resultSelector(w, x, y));
return 0;
});

if (ignoredErrorCodes != null && ignoredErrorCodes.Contains((GitErrorCode)res))
{
return new TResult[0];
}

Ensure.ZeroResult(res);
return result;
}
}

public delegate TResult Func<T1, T2, T3, T4, T5, TResult>(T1 arg1, T2 arg2, T3 arg3, T4 arg4, T5 arg5);

private static ICollection<TResult> git_foreach<T1, T2, T3, T4, TResult>(
Expand Down
3 changes: 3 additions & 0 deletions LibGit2Sharp/LibGit2Sharp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,10 @@
<Compile Include="Core\Handles\GitFetchSpecHandle.cs" />
<Compile Include="Network.cs" />
<Compile Include="Core\GitRemoteHead.cs" />
<Compile Include="Stash.cs" />
<Compile Include="StashOptions.cs" />
<Compile Include="OrphanedHeadException.cs" />
<Compile Include="StashCollection.cs" />
<Compile Include="UnmergedIndexEntriesException.cs" />
<Compile Include="Commit.cs" />
<Compile Include="CommitLog.cs" />
Expand Down
10 changes: 10 additions & 0 deletions LibGit2Sharp/Repository.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public class Repository : IRepository
private readonly ConflictCollection conflicts;
private readonly ReferenceCollection refs;
private readonly TagCollection tags;
private readonly StashCollection stashes;
private readonly Lazy<RepositoryInformation> info;
private readonly Diff diff;
private readonly NoteCollection notes;
Expand Down Expand Up @@ -103,6 +104,7 @@ public Repository(string path, RepositoryOptions options = null)
refs = new ReferenceCollection(this);
branches = new BranchCollection(this);
tags = new TagCollection(this);
stashes = new StashCollection(this);
info = new Lazy<RepositoryInformation>(() => new RepositoryInformation(this, isBare));
config =
new Lazy<Configuration>(
Expand Down Expand Up @@ -286,6 +288,14 @@ public TagCollection Tags
get { return tags; }
}

///<summary>
/// Lookup and enumerate stashes in the repository.
///</summary>
public StashCollection Stashes
{
get { return stashes; }
}

/// <summary>
/// Provides high level information about this repository.
/// </summary>
Expand Down
40 changes: 40 additions & 0 deletions LibGit2Sharp/Stash.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
namespace LibGit2Sharp
{
///<summary>
/// A Stash
/// <para>A stash is a snapshot of the dirty state of the working directory (i.e. the modified tracked files and staged changes)</para>
///</summary>
public class Stash : ReferenceWrapper<Commit>
{
/// <summary>
/// Needed for mocking purposes.
/// </summary>
protected Stash()
{ }

internal Stash(Repository repo, ObjectId targetId, int index)
: base(repo, new DirectReference(string.Format("stash@{{{0}}}", index), repo, targetId), r => r.CanonicalName)
{ }

/// <summary>
/// Gets the <see cref = "Commit" /> that this stash points to.
/// </summary>
public virtual Commit Target
{
get { return TargetObject; }
}

/// <summary>
/// Gets the message associated to this <see cref="Stash"/>.
/// </summary>
public virtual string Message
{
get { return Target.Message; }
}

protected override string Shorten()
{
return CanonicalName;
}
}
}
Loading