Skip to content

Commit 9df9cda

Browse files
committed
Introduce StashCollection.Apply and Pop
1 parent 2c98992 commit 9df9cda

10 files changed

+415
-0
lines changed

LibGit2Sharp.Tests/StashFixture.cs

+150
Original file line numberDiff line numberDiff line change
@@ -204,6 +204,156 @@ public void CanStashIgnoredFiles()
204204
}
205205
}
206206

207+
[Fact]
208+
public void CanStashAndApplyWithOptions()
209+
{
210+
string path = SandboxStandardTestRepo();
211+
using (var repo = new Repository(path))
212+
{
213+
var stasher = Constants.Signature;
214+
215+
const string filename = "staged_file_path.txt";
216+
Touch(repo.Info.WorkingDirectory, filename, "I'm staged\n");
217+
repo.Stage(filename);
218+
219+
repo.Stashes.Add(stasher, "This stash with default options");
220+
Assert.Equal(StashApplyStatus.Applied, repo.Stashes.Apply(0));
221+
222+
Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus(filename));
223+
Assert.Equal(1, repo.Stashes.Count());
224+
225+
repo.Stage(filename);
226+
227+
repo.Stashes.Add(stasher, "This stash with default options");
228+
Assert.Equal(StashApplyStatus.Applied, repo.Stashes.Apply(
229+
0,
230+
new StashApplyOptions
231+
{
232+
ApplyModifiers = StashApplyModifiers.ReinstateIndex,
233+
}));
234+
235+
Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(filename));
236+
Assert.Equal(2, repo.Stashes.Count());
237+
}
238+
}
239+
240+
[Fact]
241+
public void CanStashAndPop()
242+
{
243+
string path = SandboxStandardTestRepo();
244+
using (var repo = new Repository(path))
245+
{
246+
var stasher = Constants.Signature;
247+
248+
Assert.Equal(0, repo.Stashes.Count());
249+
250+
const string filename = "staged_file_path.txt";
251+
const string contents = "I'm staged";
252+
Touch(repo.Info.WorkingDirectory, filename, contents);
253+
repo.Stage(filename);
254+
255+
repo.Stashes.Add(stasher, "This stash with default options");
256+
Assert.Equal(1, repo.Stashes.Count());
257+
258+
Assert.Equal(StashApplyStatus.Applied, repo.Stashes.Pop(0));
259+
Assert.Equal(0, repo.Stashes.Count());
260+
261+
Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus(filename));
262+
Assert.Equal(contents, File.ReadAllText(Path.Combine(repo.Info.WorkingDirectory, filename)));
263+
}
264+
}
265+
266+
[Fact]
267+
public void StashReportsConflictsWhenReinstated()
268+
{
269+
string path = SandboxStandardTestRepo();
270+
using (var repo = new Repository(path))
271+
{
272+
var stasher = Constants.Signature;
273+
274+
const string filename = "staged_file_path.txt";
275+
const string originalContents = "I'm pre-stash.";
276+
const string filename2 = "unstaged_file_path.txt";
277+
const string newContents = "I'm post-stash.";
278+
279+
Touch(repo.Info.WorkingDirectory, filename, originalContents);
280+
repo.Stage(filename);
281+
Touch(repo.Info.WorkingDirectory, filename2, originalContents);
282+
283+
repo.Stashes.Add(stasher, "This stash with default options");
284+
285+
Touch(repo.Info.WorkingDirectory, filename, newContents);
286+
repo.Stage(filename);
287+
Touch(repo.Info.WorkingDirectory, filename2, newContents);
288+
289+
Assert.Equal(StashApplyStatus.Conflicts, repo.Stashes.Pop(0, new StashApplyOptions
290+
{
291+
ApplyModifiers = StashApplyModifiers.ReinstateIndex,
292+
}));
293+
Assert.Equal(1, repo.Stashes.Count());
294+
Assert.Equal(newContents, File.ReadAllText(Path.Combine(repo.Info.WorkingDirectory, filename)));
295+
Assert.Equal(newContents, File.ReadAllText(Path.Combine(repo.Info.WorkingDirectory, filename2)));
296+
}
297+
}
298+
299+
[Fact]
300+
public void StashCallsTheCallback()
301+
{
302+
string path = SandboxStandardTestRepo();
303+
using (var repo = new Repository(path))
304+
{
305+
var stasher = Constants.Signature;
306+
bool called;
307+
308+
const string filename = "staged_file_path.txt";
309+
const string filename2 = "unstaged_file_path.txt";
310+
const string originalContents = "I'm pre-stash.";
311+
312+
Touch(repo.Info.WorkingDirectory, filename, originalContents);
313+
repo.Stage(filename);
314+
Touch(repo.Info.WorkingDirectory, filename2, originalContents);
315+
316+
repo.Stashes.Add(stasher, "This stash with default options");
317+
318+
called = false;
319+
repo.Stashes.Apply(0, new StashApplyOptions
320+
{
321+
ProgressHandler = (progress) => { called = true; return true; }
322+
});
323+
324+
Assert.Equal(true, called);
325+
326+
repo.Reset(ResetMode.Hard);
327+
328+
called = false;
329+
repo.Stashes.Pop(0, new StashApplyOptions
330+
{
331+
ProgressHandler = (progress) => { called = true; return true; }
332+
});
333+
334+
Assert.Equal(true, called);
335+
}
336+
}
337+
338+
[Fact]
339+
public void StashApplyReportsNotFound()
340+
{
341+
string path = SandboxStandardTestRepo();
342+
using (var repo = new Repository(path))
343+
{
344+
var stasher = Constants.Signature;
345+
346+
const string filename = "unstaged_file_path.txt";
347+
Touch(repo.Info.WorkingDirectory, filename, "I'm unstaged\n");
348+
349+
repo.Stashes.Add(stasher, "This stash with default options", StashModifiers.IncludeUntracked);
350+
Touch(repo.Info.WorkingDirectory, filename, "I'm another unstaged\n");
351+
352+
Assert.Equal(StashApplyStatus.NotFound, repo.Stashes.Pop(1));
353+
Assert.Throws<ArgumentException>(() => repo.Stashes.Pop(-1));
354+
}
355+
}
356+
207357
[Theory]
208358
[InlineData(-1)]
209359
[InlineData(-42)]
+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System;
2+
using System.Runtime.InteropServices;
3+
4+
namespace LibGit2Sharp.Core
5+
{
6+
internal delegate int stash_apply_progress_cb(StashApplyProgress progress, IntPtr payload);
7+
8+
[StructLayout(LayoutKind.Sequential)]
9+
internal class GitStashApplyOpts
10+
{
11+
public uint Version = 1;
12+
13+
public StashApplyModifiers Flags;
14+
public GitCheckoutOpts CheckoutOptions;
15+
16+
public stash_apply_progress_cb ApplyProgressCallback;
17+
public IntPtr ProgressPayload;
18+
}
19+
}
20+

LibGit2Sharp/Core/NativeMethods.cs

+12
Original file line numberDiff line numberDiff line change
@@ -1366,6 +1366,18 @@ internal static extern int git_stash_foreach(
13661366
[DllImport(libgit2)]
13671367
internal static extern int git_stash_drop(RepositorySafeHandle repo, UIntPtr index);
13681368

1369+
[DllImport(libgit2)]
1370+
internal static extern int git_stash_apply(
1371+
RepositorySafeHandle repo,
1372+
UIntPtr index,
1373+
GitStashApplyOpts opts);
1374+
1375+
[DllImport(libgit2)]
1376+
internal static extern int git_stash_pop(
1377+
RepositorySafeHandle repo,
1378+
UIntPtr index,
1379+
GitStashApplyOpts opts);
1380+
13691381
[DllImport(libgit2)]
13701382
internal static extern int git_status_file(
13711383
out FileStatus statusflags,

LibGit2Sharp/Core/Proxy.cs

+32
Original file line numberDiff line numberDiff line change
@@ -2452,6 +2452,38 @@ public static void git_stash_drop(RepositorySafeHandle repo, int index)
24522452
Ensure.BooleanResult(res);
24532453
}
24542454

2455+
private static StashApplyStatus get_stash_status(int res)
2456+
{
2457+
if (res == (int)GitErrorCode.Conflict)
2458+
{
2459+
return StashApplyStatus.Conflicts;
2460+
}
2461+
2462+
if (res == (int)GitErrorCode.NotFound)
2463+
{
2464+
return StashApplyStatus.NotFound;
2465+
}
2466+
2467+
Ensure.ZeroResult(res);
2468+
return StashApplyStatus.Applied;
2469+
}
2470+
2471+
public static StashApplyStatus git_stash_apply(
2472+
RepositorySafeHandle repo,
2473+
int index,
2474+
GitStashApplyOpts opts)
2475+
{
2476+
return get_stash_status(NativeMethods.git_stash_apply(repo, (UIntPtr)index, opts));
2477+
}
2478+
2479+
public static StashApplyStatus git_stash_pop(
2480+
RepositorySafeHandle repo,
2481+
int index,
2482+
GitStashApplyOpts opts)
2483+
{
2484+
return get_stash_status(NativeMethods.git_stash_pop(repo, (UIntPtr)index, opts));
2485+
}
2486+
24552487
#endregion
24562488

24572489
#region git_status_

LibGit2Sharp/Handlers.cs

+7
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,13 @@ namespace LibGit2Sharp.Handlers
112112
/// <param name="problematicRefspec">The refspec which didn't match the default.</param>
113113
public delegate void RemoteRenameFailureHandler(string problematicRefspec);
114114

115+
/// <summary>
116+
/// Delegate definition for stash application callback.
117+
/// </summary>
118+
/// <param name="progress">The current step of the stash application.</param>
119+
/// <returns>True to continue checkout operation; false to cancel checkout operation.</returns>
120+
public delegate bool StashApplyProgressHandler(StashApplyProgress progress);
121+
115122
/// <summary>
116123
/// The stages of pack building.
117124
/// </summary>

LibGit2Sharp/LibGit2Sharp.csproj

+4
Original file line numberDiff line numberDiff line change
@@ -357,6 +357,10 @@
357357
<Compile Include="Core\RawContentStream.cs" />
358358
<Compile Include="Core\Handles\OdbStreamSafeHandle.cs" />
359359
<Compile Include="SupportedCredentialTypes.cs" />
360+
<Compile Include="StashApplyProgress.cs" />
361+
<Compile Include="StashApplyOptions.cs" />
362+
<Compile Include="StashApplyStatus.cs" />
363+
<Compile Include="Core\GitStashApplyOpts.cs" />
360364
</ItemGroup>
361365
<ItemGroup>
362366
<CodeAnalysisDictionary Include="CustomDictionary.xml" />

LibGit2Sharp/StashApplyOptions.cs

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using System;
2+
using LibGit2Sharp.Core;
3+
using LibGit2Sharp.Handlers;
4+
5+
namespace LibGit2Sharp
6+
{
7+
/// <summary>
8+
/// The options to be used for stash application.
9+
/// </summary>
10+
public sealed class StashApplyOptions
11+
{
12+
/// <summary>
13+
/// <see cref="StashApplyModifiers"/> for controlling checkout index reinstating./>
14+
/// </summary>
15+
/// <value>The flags.</value>
16+
public StashApplyModifiers ApplyModifiers { get; set; }
17+
18+
/// <summary>
19+
/// <see cref="CheckoutOptions"/> controlling checkout behavior.
20+
/// </summary>
21+
/// <value>The checkout options.</value>
22+
public CheckoutOptions CheckoutOptions { get; set; }
23+
24+
/// <summary>
25+
/// <see cref="StashApplyProgressHandler"/> for controlling stash application progress./>
26+
/// </summary>
27+
/// <value>The progress handler.</value>
28+
public StashApplyProgressHandler ProgressHandler { get; set; }
29+
}
30+
31+
/// <summary>
32+
/// The flags which control whether the index should be reinstated.
33+
/// </summary>
34+
[Flags]
35+
public enum StashApplyModifiers
36+
{
37+
/// <summary>
38+
/// Default. Reinstate working directory stashed changes.
39+
/// </summary>
40+
Default = 0,
41+
42+
/// <summary>
43+
/// Reinstate both index and working directory stashed changes.
44+
/// </summary>
45+
ReinstateIndex = (1 << 0),
46+
}
47+
}

LibGit2Sharp/StashApplyProgress.cs

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
using System;
2+
3+
namespace LibGit2Sharp
4+
{
5+
/// <summary>
6+
/// The current progress of the stash application.
7+
/// </summary>
8+
public enum StashApplyProgress
9+
{
10+
/// <summary>
11+
/// Not passed by the callback. Used as dummy value.
12+
/// </summary>
13+
None = 0,
14+
15+
/// <summary>
16+
/// Loading the stashed data from the object database.
17+
/// </summary>
18+
LoadingStash,
19+
20+
/// <summary>
21+
/// The stored index is being analyzed.
22+
/// </summary>
23+
AnalyzeIndex,
24+
25+
/// <summary>
26+
/// The modified files are being analyzed.
27+
/// </summary>
28+
AnalyzeModified,
29+
30+
/// <summary>
31+
/// The untracked and ignored files are being analyzed.
32+
/// </summary>
33+
AnalyzeUntracked,
34+
35+
/// <summary>
36+
/// The untracked files are being written to disk.
37+
/// </summary>
38+
CheckoutUntracked,
39+
40+
/// <summary>
41+
/// The modified files are being written to disk.
42+
/// </summary>
43+
CheckoutModified,
44+
45+
/// <summary>
46+
/// The stash was applied successfully.
47+
/// </summary>
48+
Done,
49+
}
50+
}

LibGit2Sharp/StashApplyStatus.cs

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
using System;
2+
3+
namespace LibGit2Sharp
4+
{
5+
/// <summary>
6+
/// The result of a stash application operation.
7+
/// </summary>
8+
public enum StashApplyStatus
9+
{
10+
/// <summary>
11+
/// The stash application was successful.
12+
/// </summary>
13+
Applied,
14+
15+
/// <summary>
16+
/// The stash application ended up with conflicts.
17+
/// </summary>
18+
Conflicts,
19+
20+
/// <summary>
21+
/// The stash index given was not found.
22+
/// </summary>
23+
NotFound,
24+
}
25+
}

0 commit comments

Comments
 (0)