Skip to content

Implement Stash API. #871

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 6 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
Binary file removed Lib/NativeBinaries/amd64/git2-9bbc8f3.dll
Binary file not shown.
Binary file added Lib/NativeBinaries/amd64/git2-b2d0243.dll
Binary file not shown.
Binary file not shown.
Binary file removed Lib/NativeBinaries/x86/git2-9bbc8f3.dll
Binary file not shown.
Binary file added Lib/NativeBinaries/x86/git2-b2d0243.dll
Binary file not shown.
Binary file not shown.
100 changes: 100 additions & 0 deletions LibGit2Sharp.Tests/StashFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -176,6 +176,106 @@ public void CanStashAndKeepIndex()
}
}

[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.Untracked, 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, StashApplyModifiers.ReinstateIndex));

Assert.Equal(FileStatus.Added, 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;

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

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

Assert.Equal(FileStatus.Untracked, repo.RetrieveStatus(filename));
Assert.Equal(0, repo.Stashes.Count());
Assert.Equal(contentsNew, 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, StashApplyModifiers.ReinstateIndex));
Assert.Equal(1, repo.Stashes.Count());
Assert.Equal(originalContents, File.ReadAllText(Path.Combine(repo.Info.WorkingDirectory, filename)));
Assert.Equal(originalContents, File.ReadAllText(Path.Combine(repo.Info.WorkingDirectory, filename2)));

}
}

[Fact]
public void StashReportsExistingInWorkDir()
{
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.UntrackedExist, repo.Stashes.Pop(0));
}
}

[Fact]
public void CanStashIgnoredFiles()
{
Expand Down
1 change: 1 addition & 0 deletions LibGit2Sharp/Core/GitCheckoutOpts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ internal struct GitCheckoutOpts
public GitStrArray paths;

public IntPtr baseline;
public IntPtr baseline_index;
public IntPtr target_directory;

public IntPtr ancestor_label;
Expand Down
54 changes: 54 additions & 0 deletions LibGit2Sharp/Core/GitMergeOpts.cs
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,11 @@ internal struct GitMergeOpts
/// Flags for automerging content.
/// </summary>
public MergeFileFavor MergeFileFavorFlags;

/// <summary>
/// Flags to use for file merging.
/// </summary>
public GitMergeFileFlags FileFlags;
}

/// <summary>
Expand Down Expand Up @@ -105,4 +110,53 @@ internal enum GitMergeTreeFlags
/// </summary>
GIT_MERGE_TREE_FIND_RENAMES = (1 << 0),
}

[Flags]
internal enum GitMergeFileFlags
{
/// <summary>
/// No options.
/// </summary>
GIT_MERGE_FILE_DEFAULT = 0,

/// <summary>
/// Creates standard conflicted merge files.
/// </summary>
GIT_MERGE_FILE_STYLE_MERGE = (1 << 0),

/// <summary>
/// Creates diff3 style files.
/// </summary>
GIT_MERGE_FILE_STYLE_DIFF3 = (1 << 1),

/// <summary>
/// Condenses non-alphanumeric regions for simplified diff files.
/// </summary>
GIT_MERGE_FILE_SIMPLIFY_ALNUM = (1 << 2),

/// <summary>
/// Ignores all whitespace.
/// </summary>
GIT_MERGE_FILE_IGNORE_WHITESPACE = (1 << 3),

/// <summary>
/// Ignores changes in amount of whitespace.
/// </summary>
GIT_MERGE_FILE_IGNORE_WHITESPACE_CHANGE = (1 << 4),

/// <summary>
/// Ignores whitespace at the end of the line.
/// </summary>
GIT_MERGE_FILE_IGNORE_WHITESPACE_EOL = (1 << 5),

/// <summary>
/// Uses the 'patience' diff algorithm.
/// </summary>
GIT_MERGE_FILE_DIFF_PATIENCE = (1 << 6),

/// <summary>
/// Take extra time to find the minimal diff.
/// </summary>
GIT_MERGE_FILE_DIFF_MINIMAL = (1 << 7),
}
}
4 changes: 3 additions & 1 deletion LibGit2Sharp/Core/GitSmartSubtransportRegistration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,11 @@ internal class GitSmartSubtransportRegistration
{
public IntPtr SubtransportCallback;
public uint Rpc;
public IntPtr Param;

public delegate int create_callback(
out IntPtr subtransport,
IntPtr transport);
IntPtr transport,
IntPtr param);
}
}
2 changes: 1 addition & 1 deletion LibGit2Sharp/Core/NativeDllName.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ namespace LibGit2Sharp.Core
{
internal static class NativeDllName
{
public const string Name = "git2-9bbc8f3";
public const string Name = "git2-b2d0243";
}
}
16 changes: 15 additions & 1 deletion LibGit2Sharp/Core/NativeMethods.cs
Original file line number Diff line number Diff line change
Expand Up @@ -726,7 +726,7 @@ internal static extern int git_note_remove(

[DllImport(libgit2)]
internal static extern int git_note_default_ref(
[MarshalAs(UnmanagedType.CustomMarshaler, MarshalCookie = UniqueId.UniqueIdentifier, MarshalTypeRef = typeof(LaxUtf8NoCleanupMarshaler))] out string notes_ref,
GitBuf buf,
RepositorySafeHandle repo);

internal delegate int git_note_foreach_cb(
Expand Down Expand Up @@ -1325,6 +1325,20 @@ 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,
ref GitCheckoutOpts opts,
StashApplyModifiers flags);

[DllImport(libgit2)]
internal static extern int git_stash_pop(
RepositorySafeHandle repo,
UIntPtr index,
ref GitCheckoutOpts opts,
StashApplyModifiers flags);

[DllImport(libgit2)]
internal static extern int git_status_file(
out FileStatus statusflags,
Expand Down
48 changes: 44 additions & 4 deletions LibGit2Sharp/Core/Proxy.cs
Original file line number Diff line number Diff line change
Expand Up @@ -1298,12 +1298,12 @@ public static ObjectId git_note_create(
public static string git_note_default_ref(RepositorySafeHandle repo)
{
using (ThreadAffinity())
using (var buf = new GitBuf())
{
string notes_ref;
int res = NativeMethods.git_note_default_ref(out notes_ref, repo);
int res = NativeMethods.git_note_default_ref(buf, repo);
Ensure.ZeroResult(res);

return notes_ref;
return LaxUtf8Marshaler.FromNative(buf.ptr);
}
}

Expand Down Expand Up @@ -2719,7 +2719,47 @@ public static void git_stash_drop(RepositorySafeHandle repo, int index)
using (ThreadAffinity())
{
int res = NativeMethods.git_stash_drop(repo, (UIntPtr) index);
Ensure.BooleanResult(res);
Ensure.ZeroResult(res);
}
}

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

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

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

public static StashApplyStatus git_stash_apply(
RepositorySafeHandle repo,
int index,
ref GitCheckoutOpts opts,
StashApplyModifiers flags)
{
using (ThreadAffinity())
{
return get_stash_status(NativeMethods.git_stash_apply(repo, (UIntPtr)index, ref opts, flags));
}
}

public static StashApplyStatus git_stash_pop(
RepositorySafeHandle repo,
int index,
ref GitCheckoutOpts opts,
StashApplyModifiers flags)
{
using (ThreadAffinity())
{
return get_stash_status(NativeMethods.git_stash_pop(repo, (UIntPtr)index, ref opts, flags));
}
}

Expand Down
1 change: 1 addition & 0 deletions LibGit2Sharp/LibGit2Sharp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@
<Compile Include="RevertResult.cs" />
<Compile Include="RevertOptions.cs" />
<Compile Include="StageOptions.cs" />
<Compile Include="StashApplyStatus.cs" />
<Compile Include="StatusOptions.cs" />
<Compile Include="SimilarityOptions.cs" />
<Compile Include="Log.cs" />
Expand Down
3 changes: 2 additions & 1 deletion LibGit2Sharp/SmartSubtransportRegistration.cs
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,8 @@ private static class EntryPoints

private static int Subtransport(
out IntPtr subtransport,
IntPtr transport)
IntPtr transport,
IntPtr payload)
{
subtransport = IntPtr.Zero;

Expand Down
30 changes: 30 additions & 0 deletions LibGit2Sharp/StashApplyStatus.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace LibGit2Sharp
{
/// <summary>
/// The status of what happened as a result of a stash application.
/// </summary>
public enum StashApplyStatus
{
/// <summary>
/// The changes were successfully stashed.
/// </summary>
Applied,

/// <summary>
/// The stash application resulted in conflicts.
/// </summary>
Conflicts,

/// <summary>
/// The stash application was not applied due to existing
/// untracked files that would be overwritten by the stash
/// contents.
/// </summary>
UntrackedExist,
}
}
30 changes: 30 additions & 0 deletions LibGit2Sharp/StashCollection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,36 @@ public virtual void Remove(int index)
Proxy.git_stash_drop(repo.Handle, index);
}

/// <summary>
/// Applies a single stashed state from the stash list
/// </summary>
/// <param name="index">the index of the stash to remove (0 being the most recent one).</param>
/// <param name="options">the options to use for checking out the stash.</param>
/// <param name="flags">the flags to use for applying the changes.</param>
public virtual StashApplyStatus Apply(int index, StashApplyModifiers flags = StashApplyModifiers.Default, CheckoutOptions options = null)
Copy link
Member Author

Choose a reason for hiding this comment

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

Probably this could be abstracted in a StashApplyOptions.

class StashApplyOptions
{
    StashApplyModifiers flags;
    CheckoutOptions checkoutOptions;
}

/cc @nulltoken

{
using (GitCheckoutOptsWrapper checkoutOptionsWrapper = new GitCheckoutOptsWrapper(options ?? new CheckoutOptions()))
{
var opts = checkoutOptionsWrapper.Options;
return Proxy.git_stash_apply(repo.Handle, index, ref opts, flags);
}
}

/// <summary>
/// Pops a single stashed state from the stash list
/// </summary>
/// <param name="index">the index of the stash to remove (0 being the most recent one).</param>
/// <param name="options">the options to use for checking out the stash.</param>
/// <param name="flags">the flags to use for applying the changes.</param>
public virtual StashApplyStatus Pop(int index, StashApplyModifiers flags = StashApplyModifiers.Default, CheckoutOptions options = null)
{
using (GitCheckoutOptsWrapper checkoutOptionsWrapper = new GitCheckoutOptsWrapper(options ?? new CheckoutOptions()))
{
var opts = checkoutOptionsWrapper.Options;
return Proxy.git_stash_pop(repo.Handle, index, ref opts, flags);
}
}

private string DebuggerDisplay
{
get
Expand Down
Loading