Skip to content

Introduce StashCollection.Apply and Pop #1068

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

Merged
merged 1 commit into from
Jun 10, 2015
Merged
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
150 changes: 150 additions & 0 deletions LibGit2Sharp.Tests/StashFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,156 @@ public void CanStashIgnoredFiles()
}
}

[Fact]
public void CanStashAndApplyWithOptions()
{
string path = SandboxStandardTestRepo();
using (var repo = new Repository(path))
{
var stasher = Constants.Signature;

const string filename = "staged_file_path.txt";
Touch(repo.Info.WorkingDirectory, filename, "I'm staged\n");
repo.Stage(filename);

repo.Stashes.Add(stasher, "This stash with default options");
Assert.Equal(StashApplyStatus.Applied, repo.Stashes.Apply(0));

Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus(filename));
Assert.Equal(1, repo.Stashes.Count());

repo.Stage(filename);

repo.Stashes.Add(stasher, "This stash with default options");
Assert.Equal(StashApplyStatus.Applied, repo.Stashes.Apply(
0,
new StashApplyOptions
{
ApplyModifiers = StashApplyModifiers.ReinstateIndex,
}));

Assert.Equal(FileStatus.NewInIndex, repo.RetrieveStatus(filename));
Assert.Equal(2, repo.Stashes.Count());
}
}

[Fact]
public void CanStashAndPop()
{
string path = SandboxStandardTestRepo();
using (var repo = new Repository(path))
{
var stasher = Constants.Signature;

Copy link
Member

Choose a reason for hiding this comment

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

Assert.Equal(0, repo.Stashes.Count());

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixt.

Assert.Equal(0, repo.Stashes.Count());

const string filename = "staged_file_path.txt";
const string contents = "I'm staged";
Touch(repo.Info.WorkingDirectory, filename, contents);
repo.Stage(filename);

repo.Stashes.Add(stasher, "This stash with default options");
Copy link
Member

Choose a reason for hiding this comment

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

Assert.Equal(1, repo.Stashes.Count());

Assert.Equal(1, repo.Stashes.Count());

Assert.Equal(StashApplyStatus.Applied, repo.Stashes.Pop(0));
Assert.Equal(0, repo.Stashes.Count());

Assert.Equal(FileStatus.NewInWorkdir, repo.RetrieveStatus(filename));
Assert.Equal(contents, File.ReadAllText(Path.Combine(repo.Info.WorkingDirectory, filename)));
}
}

[Fact]
public void StashReportsConflictsWhenReinstated()
{
string path = SandboxStandardTestRepo();
using (var repo = new Repository(path))
{
var stasher = Constants.Signature;

const string filename = "staged_file_path.txt";
const string originalContents = "I'm pre-stash.";
const string filename2 = "unstaged_file_path.txt";
const string newContents = "I'm post-stash.";

Touch(repo.Info.WorkingDirectory, filename, originalContents);
repo.Stage(filename);
Touch(repo.Info.WorkingDirectory, filename2, originalContents);

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

Touch(repo.Info.WorkingDirectory, filename, newContents);
repo.Stage(filename);
Touch(repo.Info.WorkingDirectory, filename2, newContents);

Assert.Equal(StashApplyStatus.Conflicts, repo.Stashes.Pop(0, new StashApplyOptions
{
ApplyModifiers = StashApplyModifiers.ReinstateIndex,
}));
Assert.Equal(1, repo.Stashes.Count());
Assert.Equal(newContents, File.ReadAllText(Path.Combine(repo.Info.WorkingDirectory, filename)));
Assert.Equal(newContents, File.ReadAllText(Path.Combine(repo.Info.WorkingDirectory, filename2)));
}
}

[Fact]
public void StashCallsTheCallback()
{
string path = SandboxStandardTestRepo();
using (var repo = new Repository(path))
{
var stasher = Constants.Signature;
bool called;

const string filename = "staged_file_path.txt";
const string filename2 = "unstaged_file_path.txt";
const string originalContents = "I'm pre-stash.";

Touch(repo.Info.WorkingDirectory, filename, originalContents);
repo.Stage(filename);
Touch(repo.Info.WorkingDirectory, filename2, originalContents);

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

called = false;
repo.Stashes.Apply(0, new StashApplyOptions
{
ProgressHandler = (progress) => { called = true; return true; }
});

Assert.Equal(true, called);

repo.Reset(ResetMode.Hard);

called = false;
repo.Stashes.Pop(0, new StashApplyOptions
{
ProgressHandler = (progress) => { called = true; return true; }
});

Assert.Equal(true, called);
}
}

[Fact]
public void StashApplyReportsNotFound()
{
string path = SandboxStandardTestRepo();
using (var repo = new Repository(path))
{
var stasher = Constants.Signature;

const string filename = "unstaged_file_path.txt";
Touch(repo.Info.WorkingDirectory, filename, "I'm unstaged\n");

repo.Stashes.Add(stasher, "This stash with default options", StashModifiers.IncludeUntracked);
Touch(repo.Info.WorkingDirectory, filename, "I'm another unstaged\n");

Assert.Equal(StashApplyStatus.NotFound, repo.Stashes.Pop(1));
Assert.Throws<ArgumentException>(() => repo.Stashes.Pop(-1));
}
}

[Theory]
[InlineData(-1)]
[InlineData(-42)]
Expand Down
19 changes: 19 additions & 0 deletions LibGit2Sharp/Core/GitStashApplyOpts.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
using System;
using System.Runtime.InteropServices;

namespace LibGit2Sharp.Core
{
internal delegate int stash_apply_progress_cb(StashApplyProgress progress, IntPtr payload);

[StructLayout(LayoutKind.Sequential)]
internal class GitStashApplyOpts
{
public uint Version = 1;

public StashApplyModifiers Flags;
public GitCheckoutOpts CheckoutOptions;

public stash_apply_progress_cb ApplyProgressCallback;
public IntPtr ProgressPayload;
}
}
12 changes: 12 additions & 0 deletions LibGit2Sharp/Core/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1366,6 +1366,18 @@ internal static extern int git_stash_foreach(
[DllImport(libgit2)]
internal static extern int git_stash_drop(RepositorySafeHandle repo, UIntPtr index);

[DllImport(libgit2)]
internal static extern int git_stash_apply(
RepositorySafeHandle repo,
UIntPtr index,
GitStashApplyOpts opts);

[DllImport(libgit2)]
internal static extern int git_stash_pop(
RepositorySafeHandle repo,
UIntPtr index,
GitStashApplyOpts opts);

[DllImport(libgit2)]
internal static extern int git_status_file(
out FileStatus statusflags,
Expand Down
32 changes: 32 additions & 0 deletions LibGit2Sharp/Core/Proxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2452,6 +2452,38 @@ public static void git_stash_drop(RepositorySafeHandle repo, int index)
Ensure.BooleanResult(res);
}

private static StashApplyStatus get_stash_status(int res)
{
if (res == (int)GitErrorCode.Conflict)
{
return StashApplyStatus.Conflicts;
}

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

Ensure.ZeroResult(res);
return StashApplyStatus.Applied;
}

public static StashApplyStatus git_stash_apply(
RepositorySafeHandle repo,
int index,
GitStashApplyOpts opts)
{
return get_stash_status(NativeMethods.git_stash_apply(repo, (UIntPtr)index, opts));
}

public static StashApplyStatus git_stash_pop(
RepositorySafeHandle repo,
int index,
GitStashApplyOpts opts)
{
return get_stash_status(NativeMethods.git_stash_pop(repo, (UIntPtr)index, opts));
}

#endregion

#region git_status_
Expand Down
7 changes: 7 additions & 0 deletions LibGit2Sharp/Handlers.cs
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,13 @@ namespace LibGit2Sharp.Handlers
/// <param name="problematicRefspec">The refspec which didn't match the default.</param>
public delegate void RemoteRenameFailureHandler(string problematicRefspec);

/// <summary>
/// Delegate definition for stash application callback.
/// </summary>
/// <param name="progress">The current step of the stash application.</param>
/// <returns>True to continue checkout operation; false to cancel checkout operation.</returns>
public delegate bool StashApplyProgressHandler(StashApplyProgress progress);

/// <summary>
/// The stages of pack building.
/// </summary>
Expand Down
4 changes: 4 additions & 0 deletions LibGit2Sharp/LibGit2Sharp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -357,6 +357,10 @@
<Compile Include="Core\RawContentStream.cs" />
<Compile Include="Core\Handles\OdbStreamSafeHandle.cs" />
<Compile Include="SupportedCredentialTypes.cs" />
<Compile Include="StashApplyProgress.cs" />
<Compile Include="StashApplyOptions.cs" />
<Compile Include="StashApplyStatus.cs" />
<Compile Include="Core\GitStashApplyOpts.cs" />
</ItemGroup>
<ItemGroup>
<CodeAnalysisDictionary Include="CustomDictionary.xml" />
Expand Down
48 changes: 48 additions & 0 deletions LibGit2Sharp/StashApplyOptions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
using System;
using LibGit2Sharp.Core;
using LibGit2Sharp.Handlers;

namespace LibGit2Sharp
{
/// <summary>
/// The options to be used for stash application.
/// </summary>
public sealed class StashApplyOptions
{
/// <summary>
/// <see cref="StashApplyModifiers"/> for controlling checkout index reinstating./>
/// </summary>
/// <value>The flags.</value>
public StashApplyModifiers ApplyModifiers { get; set; }

/// <summary>
/// <see cref="CheckoutOptions"/> controlling checkout behavior.
/// </summary>
/// <value>The checkout options.</value>
public CheckoutOptions CheckoutOptions { get; set; }

/// <summary>
/// <see cref="StashApplyProgressHandler"/> for controlling stash application progress./>
/// </summary>
/// <value>The progress handler.</value>
public StashApplyProgressHandler ProgressHandler { get; set; }
}

/// <summary>
/// The flags which control whether the index should be reinstated.
/// </summary>
[Flags]
public enum StashApplyModifiers
{
/// <summary>
/// Default. Will apply the stash and result in an index with conflicts
/// if any arise.
/// </summary>
Default = 0,
Copy link
Member

Choose a reason for hiding this comment

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

How about making it explicit? eg. ReinstateWorkingDirectory


/// <summary>
/// In case any conflicts arise, this will not apply the stash.
/// </summary>
ReinstateIndex = (1 << 0),
Copy link
Member

Choose a reason for hiding this comment

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

And ReinstateWorkingDirectoryAndIndex

Copy link
Member Author

Choose a reason for hiding this comment

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

The thing is, it's weird git naming. ReinstateIndex gives a merge conflict, default doesn't pop.

Copy link
Member

Choose a reason for hiding this comment

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

The thing is, it's weird git naming.

Ok.

ReinstateIndex gives a merge conflict

Could you please update the xml doc to make that explicit to the reader.

default doesn't pop.

Huh? I'm sorry. You kinda lost me here, mate.

pop-fart

Copy link
Member Author

Choose a reason for hiding this comment

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

I was really bad at thinking when I responded. I'm bringing this libgit2 comment into place.

What I meant is that ReinstateIndex will not create an index when conflicts arise (doesn't pop/apply), but when the default is used, it'll generate an index with conflicts.

Copy link
Member

Choose a reason for hiding this comment

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

Duh. Stash is hard. But your explanation makes it much clearer. Thanks!

I really think this deserves to be described in the xml doc.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah so, naming should stay as is, so we match cli git and libgit2 flags.

I'll update the docs to reflect the stuff.

}
}
50 changes: 50 additions & 0 deletions LibGit2Sharp/StashApplyProgress.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
using System;

namespace LibGit2Sharp
{
/// <summary>
/// The current progress of the stash application.
/// </summary>
public enum StashApplyProgress
{
/// <summary>
/// Not passed by the callback. Used as dummy value.
/// </summary>
None = 0,
Copy link
Member

Choose a reason for hiding this comment

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

Initiated?

Copy link
Member Author

Choose a reason for hiding this comment

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

Actually, I'm not even sure that its called the first time. Judging from https://github.com/libgit2/libgit2/pull/3018/files#diff-f54e4a7883cb063f36ebd8f4206ce55fR290, I may have misnamed it.


/// <summary>
/// Loading the stashed data from the object database.
/// </summary>
LoadingStash,

/// <summary>
/// The stored index is being analyzed.
/// </summary>
AnalyzeIndex,

/// <summary>
/// The modified files are being analyzed.
/// </summary>
AnalyzeModified,

/// <summary>
/// The untracked and ignored files are being analyzed.
/// </summary>
AnalyzeUntracked,

/// <summary>
/// The untracked files are being written to disk.
/// </summary>
CheckoutUntracked,

/// <summary>
/// The modified files are being written to disk.
/// </summary>
CheckoutModified,

/// <summary>
/// The stash was applied successfully.
/// </summary>
Done,
}
}
25 changes: 25 additions & 0 deletions LibGit2Sharp/StashApplyStatus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
using System;

namespace LibGit2Sharp
{
/// <summary>
/// The result of a stash application operation.
/// </summary>
public enum StashApplyStatus
{
/// <summary>
/// The stash application was successful.
/// </summary>
Applied,

/// <summary>
/// The stash application ended up with conflicts.
/// </summary>
Conflicts,

/// <summary>
/// The stash index given was not found.
/// </summary>
NotFound,
}
}
Loading