8000 Add Repository.Stashes.Add · MicrosoftWebMatrix/libgit2sharp@0ab83f0 · GitHub
[go: up one dir, main page]

Skip to content

Commit 0ab83f0

Browse files
Saamannulltoken
authored andcommitted
Add Repository.Stashes.Add
1 parent 9dfa8c1 commit 0ab83f0

9 files changed

+356
-0
lines changed

LibGit2Sharp.Tests/LibGit2Sharp.Tests.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
</ItemGroup>
5959
<ItemGroup>
6060
<Compile Include="CheckoutFixture.cs" />
61+
<Compile Include="StashFixture.cs" />
6162
<Compile Include="CloneFixture.cs" />
6263
<Compile Include="ConflictFixture.cs" />
6364
<Compile Include="IgnoreFixture.cs" />

LibGit2Sharp.Tests/StashFixture.cs

Lines changed: 181 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,181 @@
1+
using System;
2+
using System.IO;
3+
using LibGit2Sharp.Tests.TestHelpers;
4+
using Xunit;
5+
6+
namespace LibGit2Sharp.Tests
7+
{
8+
public class StashFixture : BaseFixture
9+
{
10+
[Fact]
11+
public void CannotAddStashAgainstBareRepository()
12+
{
13+
TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo();
14+
using (var repo = new Repository(path.RepositoryPath))
15+
{
16+
var stasher = DummySignature;
17+
18+
Assert.Throws<BareRepositoryException>(() => repo.Stashes.Add(stasher, "My very first stash"));
19+
}
20+
}
21+
22+
[Fact]
23+
public void CanAddStash()
24+
{
25+
TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath);
26+
using (var repo = new Repository(path.RepositoryPath))
27+
{
28+
var stasher = DummySignature;
29+
30+
Assert.True(repo.Index.RetrieveStatus().IsDirty);
31+
32+
Stash stash = repo.Stashes.Add(stasher, "My very first stash", StashOptions.IncludeUntracked);
33+
34+
// Check that untracked files are deleted from working directory
35+
Assert.False(File.Exists(Path.Combine(repo.Info.WorkingDirectory, "new_untracked_file.txt")));
36+
37+
Assert.NotNull(stash);
38+
Assert.Equal("stash@{0}", stash.CanonicalName);
39+
Assert.Contains("My very first stash", stash.Message);
40+
41+
var stashRef = repo.Refs["refs/stash"];
42+
Assert.Equal(stash.Target.Sha, stashRef.TargetIdentifier);
43+
44+
Assert.False(repo.Index.RetrieveStatus().IsDirty);
45+
46+
// Create extra file
47+
string newFileFullPath = Path.Combine(repo.Info.WorkingDirectory, "stash_candidate.txt");
48+
File.WriteAllText(newFileFullPath, "Oh, I'm going to be stashed!\n");
49+
50+
Stash secondStash = repo.Stashes.Add(stasher, "My second stash", StashOptions.IncludeUntracked);
51+
52+
Assert.NotNull(stash);
53+
Assert.Equal("stash@{0}", stash.CanonicalName);
54+
Assert.Contains("My second stash", secondStash.Message);
55+
56+
// Stash history has been shifted
57+
Assert.Equal(repo.Lookup<Commit>("stash@{0}").Sha, secondStash.Target.Sha);
58+
}
59+
}
60+
61+
[Fact]
62+
public void AddingAStashWithNoMessageGeneratesADefaultOne()
63+
{
64+
TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath);
65+
using (var repo = new Repository(path.RepositoryPath))
66+
{
67+
var stasher = DummySignature;
68+
69+
Stash stash = repo.Stashes.Add(stasher);
70+
71+
Assert.NotNull(stash);
72+
Assert.Equal("stash@{0}", stash.CanonicalName);
73+
Assert.NotEmpty(stash.Target.Message);
74+
75+
var stashRef = repo.Refs["refs/stash"];
76+
Assert.Equal(stash.Target.Sha, stashRef.TargetIdentifier);
77+
}
78+
}
79+
80+
[Fact]
81+
public void AddStashWithBadParamsShouldThrows()
82+
{
83+
TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath);
84+
using (var repo = new Repository(path.RepositoryPath))
85+
{
86+
Assert.Throws<ArgumentNullException>(() => repo.Stashes.Add(null));
87+
}
88+
}
89+
90+
[Fact]
91+
public void StashingAgainstCleanWorkDirShouldReturnANullStash()
92+
{
93+
TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath);
94+
using (var repo = new Repository(path.RepositoryPath))
95+
{
96+
var stasher = DummySignature;
97+
98+
Stash stash = repo.Stashes.Add(stasher, "My very first stash", StashOptions.IncludeUntracked);
99+
100+
Assert.NotNull(stash);
101+
102+
//Stash against clean working directory
103+
Assert.Null(repo.Stashes.Add(stasher));
104+
}
105+
}
106+
107+
[Fact]
108+
public void CanStashWithoutOptions()
109+
{
110+
TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath);
111+
using (var repo = new Repository(path.RepositoryPath))
112+
{
113+
var stasher = DummySignature;
114+
115+
var untrackedFilePath = Path.Combine(repo.Info.WorkingDirectory, "new_untracked_file.txt");
116+
File.WriteAllText(untrackedFilePath, "I'm untracked\n");
117+
118+
string stagedfilePath = Path.Combine(repo.Info.WorkingDirectory, "staged_file_path.txt");
119+
File.WriteAllText(stagedfilePath, "I'm staged\n");
120+
repo.Index.Stage(stagedfilePath);
121+
122+
Stash stash = repo.Stashes.Add(stasher, "Stash with default options");
123+
124+
Assert.NotNull(stash);
125+
126+
//It should not keep staged files
127+
Assert.Equal(FileStatus.Nonexistent, repo.Index.RetrieveStatus("staged_file_path.txt"));
128+
129+
//It should leave untracked files untracked
130+
Assert.Equal(FileStatus.Untracked, repo.Index.RetrieveStatus("new_untracked_file.txt"));
131+
}
132+
}
133+
134+
[Fact]
135+
public void CanStashAndKeepIndex()
136+
{
137+
TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath);
138+
using (var repo = new Repository(path.RepositoryPath))
139+
{
140+
var stasher = DummySignature;
141+
142+
string stagedfilePath = Path.Combine(repo.Info.WorkingDirectory, "staged_file_path.txt");
143+
File.WriteAllText(stagedfilePath, "I'm staged\n");
144+
repo.Index.Stage(stagedfilePath);
145+
146+
Stash stash = repo.Stashes.Add(stasher, "This stash wil keep index", StashOptions.KeepIndex);
147+
148+
Assert.NotNull(stash);
149+
Assert.Equal(FileStatus.Added, repo.Index.RetrieveStatus("staged_file_path.txt"));
150+
}
151+
}
152+
153+
[Fact]
154+
public void CanStashIgnoredFiles()
155+
{
156+
TemporaryCloneOfTestRepo path = BuildTemporaryCloneOfTestRepo(StandardTestRepoWorkingDirPath);
157+
158+
using (var repo = new Repository(path.RepositoryPath))
159+
{
160+
string gitIgnoreFilePath = Path.Combine(repo.Info.WorkingDirectory, ".gitignore");
161+
File.WriteAllText(gitIgnoreFilePath, "ignored_file.txt");
162+
repo.Index.Stage(gitIgnoreFilePath);
163+
repo.Commit("Modify gitignore", Constants.Signature, Constants.Signature);
164+
165+
string ignoredFilePath = Path.Combine(repo.Info.WorkingDirectory, "ignored_file.txt");
166+
File.WriteAllText(ignoredFilePath, "I'm ignored\n");
167+
168+
Assert.True(repo.Ignore.IsPathIgnored("ignored_file.txt"));
169+
170+
var stasher = DummySignature;
171+
repo.Stashes.Add(stasher, "This stash includes ignore files", StashOptions.IncludeIgnored);
172+
173+
//TODO : below assertion doesn't pass. Bug?
174+
//Assert.False(File.Exists(ignoredFilePath));
175+
176+
var blob = repo.Lookup<Blob>("stash^3:ignored_file.txt");
177+
Assert.NotNull(blob);
178+
}
179+
}
180+
}
181+
}

LibGit2Sharp/Core/NativeMethods.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -904,6 +904,14 @@ internal static extern int git_signature_new(
904904
long time,
905905
int offset);
906906

907+
[DllImport(libgit2)]
908+
internal static extern int git_stash_save(
909+
out GitOid id,
910+
RepositorySafeHandle repo,
911+
SignatureSafeHandle stasher,
912+
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalTypeRef = typeof(Utf8Marshaler))] string message,
913+
StashOptions flags);
914+
907915
[DllImport(libgit2)]
908916
internal static extern int git_status_file(
909917
out FileStatus statusflags,

LibGit2Sharp/Core/Proxy.cs

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1747,6 +1747,33 @@ public static SignatureSafeHandle git_signature_new(string name, string email, D
17471747

17481748
#endregion
17491749

1750+
#region git_stash_
1751+
1752+
public static ObjectId git_stash_save(
1753+
RepositorySafeHandle repo,
1754+
Signature stasher,
1755+
string prettifiedMessage,
1756+
StashOptions options)
1757+
{
1758+
using (ThreadAffinity())
1759+
using (SignatureSafeHandle stasherHandle = stasher.BuildHandle())
1760+
{
1761+
GitOid stashOid;
1762+
1763+
int res = NativeMethods.git_stash_save(out stashOid, repo, stasherHandle, prettifiedMessage, options);
1764+
1765+
if (res == (int)GitErrorCode.NotFound)
1766+
{
1767+
return null;
1768+
}
1769+
1770+
Ensure.Int32Result(res);
1771+
1772+
return new ObjectId(stashOid);
1773+
}
1774+
}
1775+
#endregion
1776+
17501777
#region git_status_
17511778

17521779
public static FileStatus git_status_file(RepositorySafeHandle repo, FilePath path)

LibGit2Sharp/LibGit2Sharp.csproj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,10 @@
7171
<Compile Include="Core\Handles\GitFetchSpecHandle.cs" />
7272
<Compile Include="Network.cs" />
7373
<Compile Include="Core\GitRemoteHead.cs" />
74+
<Compile Include="Stash.cs" />
75+
<Compile Include="StashOptions.cs" />
7476
<Compile Include="OrphanedHeadException.cs" />
77+
<Compile Include="StashCollection.cs" />
7578
<Compile Include="UnmergedIndexEntriesException.cs" />
7679
<Compile Include="Commit.cs" />
7780
<Compile Include="CommitLog.cs" />

LibGit2Sharp/Repository.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ public class Repository : IRepository
2828
private readonly ConflictCollection conflicts;
2929
private readonly ReferenceCollection refs;
3030
private readonly TagCollection tags;
31+
private readonly StashCollection stashes;
3132
private readonly Lazy<RepositoryInformation> info;
3233
private readonly Diff diff;
3334
private readonly NoteCollection notes;
@@ -103,6 +104,7 @@ public Repository(string path, RepositoryOptions options = null)
103104
refs = new ReferenceCollection(this);
104105
branches = new BranchCollection(this);
105106
tags = new TagCollection(this);
107+
stashes = new StashCollection(this);
106108
info = new Lazy<RepositoryInformation>(() => new RepositoryInformation(this, isBare));
107109
config =
108110
new Lazy<Configuration>(
@@ -286,6 +288,14 @@ public TagCollection Tags
286288
get { return tags; }
287289
}
288290

291+
///<summary>
292+
/// Lookup and enumerate stashes in the repository.
293+
///</summary>
294+
public StashCollection Stashes
295+
{
296+
get { return stashes; }
297+
}
298+
289299
/// <summary>
290300
/// Provides high level information about this repository.
291301
/// </summary>

LibGit2Sharp/Stash.cs

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
namespace LibGit2Sharp
2+
{
3+
///<summary>
4+
/// A Stash
5+
/// <para>A stash is a snapshot of the dirty state of the working directory (i.e. the modified tracked files and staged changes)</para>
6+
///</summary>
7+
public class Stash : ReferenceWrapper<Commit>
8+
{
9+
/// <summary>
10+
/// Needed for mocking purposes.
11+
/// </summary>
12+
protected Stash()
13+
{ }
14+
15+
internal Stash(Repository repo, ObjectId targetId)
16+
: base(repo, new DirectReference("stash@{0}", repo, targetId), r => r.CanonicalName)
17+
{
18+
}
19+
20+
/// <summary>
21+
/// Gets the <see cref = "Commit" /> that this stash points to.
22+
/// </summary>
23+
public virtual Commit Target
24+
{
25+
get { return TargetObject; }
26+
}
27+
28+
/// <summary>
29+
/// Gets the message associated to this <see cref="Stash"/>.
30+
/// </summary>
31+
public virtual string Message
32+
{
33+
get { return Target.Message; }
34+
}
35+
36+
protected override string Shorten()
37+
{
38+
return CanonicalName;
39+
}
40+
}
41+
}

LibGit2Sharp/StashCollection.cs

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
using LibGit2Sharp.Core;
2+
3+
namespace LibGit2Sharp
4+
{
5+
/// <summary>
6+
/// The collection of <see cref = "Stash" />es in a <see cref = "Repository" />
7+
/// </summary>
8+
public class StashCollection
9+
{
10+
internal readonly Repository repo;
11+
12+
/// <summary>
13+
/// Needed for mocking purposes.
14+
/// </summary>
15+
protected StashCollection()
16+
{ }
17+
18+
/// <summary>
19+
/// Initializes a new instance of the <see cref = "StashCollection" /> class.
20+
/// </summary>
21+
/// <param name = "repo">The repo.</param>
22+
internal StashCollection(Repository repo)
23+
{
24+
this.repo = repo;
25+
}
26+
27+
/// <summary>
28+
/// Creates a stash with the specified message.
29+
/// </summary>
30+
/// <param name="stasher">The <see cref="Signature"/> of the user who stashes </param>
31+
/// <param name = "message">The message of the stash.</param>
32+
/// <param name = "options">A combination of <see cref="StashOptions"/> flags</param>
33+
/// <returns>the newly created <see cref="Stash"/></returns>
34+
public virtual Stash Add(Signature stasher, string message = null, StashOptions options = StashOptions.Default)
35+
{
36+
Ensure.ArgumentNotNull(stasher, "stasher");
37+
38+
string prettifiedMessage = Proxy.git_message_prettify(string.IsNullOrEmpty(message) ? string.Empty : message);
39+
40+
ObjectId oid = Proxy.git_stash_save(repo.Handle, stasher, prettifiedMessage, options);
41+
42+
// in case there is nothing to stash
43+
if (oid == null)
44+
{
45+
return null;
46+
}
47+
48+
return new Stash(repo, oid);
49+
}
50+
}
51+
}

0 commit comments

Comments
 (0)
0