-
Notifications
You must be signed in to change notification settings - Fork 901
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
Closed
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 | ||
{ | ||
[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); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
} | ||
} | ||
} | ||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -1747,6 +1747,45 @@ public static SignatureSafeHandle git_signature_new(string name, string email, D | |
|
||
#endregion | ||
|
||
#region git_stash_ | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) | ||
|
@@ -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>( | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | ||
} | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
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.There was a problem hiding this comment.
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
Stash
instead?There was a problem hiding this comment.
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.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@Saaman I don't think you need to catch anything. Comparing the result against
(int)GitErrorCode.NotFound
just before the call toEnsure
should do the trick.There was a problem hiding this comment.
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.