Commit c2f274c6 by Carlos Martín Nieto

Merge pull request #3250 from ethomson/stash

Stash workdir correctly when added in the index, modified in the workdir
parents aacfd03d 90177111
...@@ -123,6 +123,25 @@ extern int git_diff_find_similar__calc_similarity( ...@@ -123,6 +123,25 @@ extern int git_diff_find_similar__calc_similarity(
extern int git_diff__commit( extern int git_diff__commit(
git_diff **diff, git_repository *repo, const git_commit *commit, const git_diff_options *opts); git_diff **diff, git_repository *repo, const git_commit *commit, const git_diff_options *opts);
/* Merge two `git_diff`s according to the callback given by `cb`. */
typedef git_diff_delta *(*git_diff__merge_cb)(
const git_diff_delta *left,
const git_diff_delta *right,
git_pool *pool);
extern int git_diff__merge(
git_diff *onto, const git_diff *from, git_diff__merge_cb cb);
extern git_diff_delta *git_diff__merge_like_cgit(
const git_diff_delta *a,
const git_diff_delta *b,
git_pool *pool);
/* Duplicate a `git_diff_delta` out of the `git_pool` */
extern git_diff_delta *git_diff__delta_dup(
const git_diff_delta *d, git_pool *pool);
/* /*
* Sometimes a git_diff_file will have a zero size; this attempts to * Sometimes a git_diff_file will have a zero size; this attempts to
* fill in the size without loading the blob if possible. If that is * fill in the size without loading the blob if possible. If that is
......
...@@ -15,7 +15,7 @@ ...@@ -15,7 +15,7 @@
#include "fileops.h" #include "fileops.h"
#include "config.h" #include "config.h"
static git_diff_delta *diff_delta__dup( git_diff_delta *git_diff__delta_dup(
const git_diff_delta *d, git_pool *pool) const git_diff_delta *d, git_pool *pool)
{ {
git_diff_delta *delta = git__malloc(sizeof(git_diff_delta)); git_diff_delta *delta = git__malloc(sizeof(git_diff_delta));
...@@ -46,7 +46,7 @@ fail: ...@@ -46,7 +46,7 @@ fail:
return NULL; return NULL;
} }
static git_diff_delta *diff_delta__merge_like_cgit( git_diff_delta *git_diff__merge_like_cgit(
const git_diff_delta *a, const git_diff_delta *a,
const git_diff_delta *b, const git_diff_delta *b,
git_pool *pool) git_pool *pool)
...@@ -67,23 +67,24 @@ static git_diff_delta *diff_delta__merge_like_cgit( ...@@ -67,23 +67,24 @@ static git_diff_delta *diff_delta__merge_like_cgit(
/* If one of the diffs is a conflict, just dup it */ /* If one of the diffs is a conflict, just dup it */
if (b->status == GIT_DELTA_CONFLICTED) if (b->status == GIT_DELTA_CONFLICTED)
return diff_delta__dup(b, pool); return git_diff__delta_dup(b, pool);
if (a->status == GIT_DELTA_CONFLICTED) if (a->status == GIT_DELTA_CONFLICTED)
return diff_delta__dup(a, pool); return git_diff__delta_dup(a, pool);
/* if f2 == f3 or f2 is deleted, then just dup the 'a' diff */ /* if f2 == f3 or f2 is deleted, then just dup the 'a' diff */
if (b->status == GIT_DELTA_UNMODIFIED || a->status == GIT_DELTA_DELETED) if (b->status == GIT_DELTA_UNMODIFIED || a->status == GIT_DELTA_DELETED)
return diff_delta__dup(a, pool); return git_diff__delta_dup(a, pool);
/* otherwise, base this diff on the 'b' diff */ /* otherwise, base this diff on the 'b' diff */
if ((dup = diff_delta__dup(b, pool)) == NULL) if ((dup = git_diff__delta_dup(b, pool)) == NULL)
return NULL; return NULL;
/* If 'a' status is uninteresting, then we're done */ /* If 'a' status is uninteresting, then we're done */
if (a->status == GIT_DELTA_UNMODIFIED) if (a->status == GIT_DELTA_UNMODIFIED ||
a->status == GIT_DELTA_UNTRACKED ||
a->status == GIT_DELTA_UNREADABLE)
return dup; return dup;
assert(a->status != GIT_DELTA_UNMODIFIED);
assert(b->status != GIT_DELTA_UNMODIFIED); assert(b->status != GIT_DELTA_UNMODIFIED);
/* A cgit exception is that the diff of a file that is only in the /* A cgit exception is that the diff of a file that is only in the
...@@ -108,48 +109,8 @@ static git_diff_delta *diff_delta__merge_like_cgit( ...@@ -108,48 +109,8 @@ static git_diff_delta *diff_delta__merge_like_cgit(
return dup; return dup;
} }
static git_diff_delta *diff_delta__merge_like_cgit_reversed( int git_diff__merge(
const git_diff_delta *a, git_diff *onto, const git_diff *from, git_diff__merge_cb cb)
const git_diff_delta *b,
git_pool *pool)
{
git_diff_delta *dup;
/* reversed version of above logic */
if (a->status == GIT_DELTA_CONFLICTED)
return diff_delta__dup(a, pool);
if (b->status == GIT_DELTA_CONFLICTED)
return diff_delta__dup(b, pool);
if (a->status == GIT_DELTA_UNMODIFIED)
return diff_delta__dup(b, pool);
if ((dup = diff_delta__dup(a, pool)) == NULL)
return NULL;
if (b->status == GIT_DELTA_UNMODIFIED || b->status == GIT_DELTA_UNTRACKED || b->status == GIT_DELTA_UNREADABLE)
return dup;
if (dup->status == GIT_DELTA_DELETED) {
if (b->status == GIT_DELTA_ADDED) {
dup->status = GIT_DELTA_UNMODIFIED;
dup->nfiles = 2;
}
} else {
dup->status = b->status;
dup->nfiles = b->nfiles;
}
git_oid_cpy(&dup->old_file.id, &b->old_file.id);
dup->old_file.mode = b->old_file.mode;
dup->old_file.size = b->old_file.size;
dup->old_file.flags = b->old_file.flags;
return dup;
}
int git_diff_merge(git_diff *onto, const git_diff *from)
{ {
int error = 0; int error = 0;
git_pool onto_pool; git_pool onto_pool;
...@@ -185,15 +146,16 @@ int git_diff_merge(git_diff *onto, const git_diff *from) ...@@ -185,15 +146,16 @@ int git_diff_merge(git_diff *onto, const git_diff *from)
STRCMP_CASESELECT(ignore_case, o->old_file.path, f->old_file.path); STRCMP_CASESELECT(ignore_case, o->old_file.path, f->old_file.path);
if (cmp < 0) { if (cmp < 0) {
delta = diff_delta__dup(o, &onto_pool); delta = git_diff__delta_dup(o, &onto_pool);
i++; i++;
} else if (cmp > 0) { } else if (cmp > 0) {
delta = diff_delta__dup(f, &onto_pool); delta = git_diff__delta_dup(f, &onto_pool);
j++; j++;
} else { } else {
delta = reversed ? const git_diff_delta *left = reversed ? f : o;
diff_delta__merge_like_cgit_reversed(o, f, &onto_pool) : const git_diff_delta *right = reversed ? o : f;
diff_delta__merge_like_cgit(o, f, &onto_pool);
delta = cb(left, right, &onto_pool);
i++; i++;
j++; j++;
} }
...@@ -201,7 +163,7 @@ int git_diff_merge(git_diff *onto, const git_diff *from) ...@@ -201,7 +163,7 @@ int git_diff_merge(git_diff *onto, const git_diff *from)
/* the ignore rules for the target may not match the source /* the ignore rules for the target may not match the source
* or the result of a merged delta could be skippable... * or the result of a merged delta could be skippable...
*/ */
if (git_diff_delta__should_skip(&onto->opts, delta)) { if (delta && git_diff_delta__should_skip(&onto->opts, delta)) {
git__free(delta); git__free(delta);
continue; continue;
} }
...@@ -232,6 +194,11 @@ int git_diff_merge(git_diff *onto, const git_diff *from) ...@@ -232,6 +194,11 @@ int git_diff_merge(git_diff *onto, const git_diff *from)
return error; return error;
} }
int git_diff_merge(git_diff *onto, const git_diff *from)
{
return git_diff__merge(onto, from, git_diff__merge_like_cgit);
}
int git_diff_find_similar__hashsig_for_file( int git_diff_find_similar__hashsig_for_file(
void **out, const git_diff_file *f, const char *path, void *p) void **out, const git_diff_file *f, const char *path, void *p)
{ {
...@@ -380,7 +347,7 @@ static int insert_delete_side_of_split( ...@@ -380,7 +347,7 @@ static int insert_delete_side_of_split(
git_diff *diff, git_vector *onto, const git_diff_delta *delta) git_diff *diff, git_vector *onto, const git_diff_delta *delta)
{ {
/* make new record for DELETED side of split */ /* make new record for DELETED side of split */
git_diff_delta *deleted = diff_delta__dup(delta, &diff->pool); git_diff_delta *deleted = git_diff__delta_dup(delta, &diff->pool);
GITERR_CHECK_ALLOC(deleted); GITERR_CHECK_ALLOC(deleted);
deleted->status = GIT_DELTA_DELETED; deleted->status = GIT_DELTA_DELETED;
......
...@@ -22,6 +22,7 @@ ...@@ -22,6 +22,7 @@
#include "signature.h" #include "signature.h"
#include "iterator.h" #include "iterator.h"
#include "merge.h" #include "merge.h"
#include "diff.h"
static int create_error(int error, const char *msg) static int create_error(int error, const char *msg)
{ {
...@@ -292,6 +293,25 @@ cleanup: ...@@ -292,6 +293,25 @@ cleanup:
return error; return error;
} }
static git_diff_delta *stash_delta_merge(
const git_diff_delta *a,
const git_diff_delta *b,
git_pool *pool)
{
/* Special case for stash: if a file is deleted in the index, but exists
* in the working tree, we need to stash the workdir copy for the workdir.
*/
if (a->status == GIT_DELTA_DELETED && b->status == GIT_DELTA_UNTRACKED) {
git_diff_delta *dup = git_diff__delta_dup(b, pool);
if (dup)
dup->status = GIT_DELTA_MODIFIED;
return dup;
}
return git_diff__merge_like_cgit(a, b, pool);
}
static int build_workdir_tree( static int build_workdir_tree(
git_tree **tree_out, git_tree **tree_out,
git_index *index, git_index *index,
...@@ -299,17 +319,19 @@ static int build_workdir_tree( ...@@ -299,17 +319,19 @@ static int build_workdir_tree(
{ {
git_repository *repo = git_index_owner(index); git_repository *repo = git_index_owner(index);
git_tree *b_tree = NULL; git_tree *b_tree = NULL;
git_diff *diff = NULL; git_diff *diff = NULL, *idx_to_wd = NULL;
git_diff_options opts = GIT_DIFF_OPTIONS_INIT; git_diff_options opts = GIT_DIFF_OPTIONS_INIT;
struct stash_update_rules data = {0}; struct stash_update_rules data = {0};
int error; int error;
opts.flags = GIT_DIFF_IGNORE_SUBMODULES; opts.flags = GIT_DIFF_IGNORE_SUBMODULES | GIT_DIFF_INCLUDE_UNTRACKED;
if ((error = git_commit_tree(&b_tree, b_commit)) < 0) if ((error = git_commit_tree(&b_tree, b_commit)) < 0)
goto cleanup; goto cleanup;
if ((error = git_diff_tree_to_workdir(&diff, repo, b_tree, &opts)) < 0) if ((error = git_diff_tree_to_index(&diff, repo, b_tree, index, &opts)) < 0 ||
(error = git_diff_index_to_workdir(&idx_to_wd, repo, index, &opts)) < 0 ||
(error = git_diff__merge(diff, idx_to_wd, stash_delta_merge)) < 0)
goto cleanup; goto cleanup;
data.include_changed = true; data.include_changed = true;
...@@ -320,6 +342,7 @@ static int build_workdir_tree( ...@@ -320,6 +342,7 @@ static int build_workdir_tree(
error = build_tree_from_index(tree_out, index); error = build_tree_from_index(tree_out, index);
cleanup: cleanup:
git_diff_free(idx_to_wd);
git_diff_free(diff); git_diff_free(diff);
git_tree_free(b_tree); git_tree_free(b_tree);
......
...@@ -69,16 +69,16 @@ void test_stash_foreach__enumerating_a_empty_repository_doesnt_fail(void) ...@@ -69,16 +69,16 @@ void test_stash_foreach__enumerating_a_empty_repository_doesnt_fail(void)
void test_stash_foreach__can_enumerate_a_repository(void) void test_stash_foreach__can_enumerate_a_repository(void)
{ {
char *oids_default[] = { char *oids_default[] = {
"1d91c842a7cdfc25872b3a763e5c31add8816c25", NULL }; "493568b7a2681187aaac8a58d3f1eab1527cba84", NULL };
char *oids_untracked[] = { char *oids_untracked[] = {
"7f89a8b15c878809c5c54d1ff8f8c9674154017b", "7f89a8b15c878809c5c54d1ff8f8c9674154017b",
"1d91c842a7cdfc25872b3a763e5c31add8816c25", NULL }; "493568b7a2681187aaac8a58d3f1eab1527cba84", NULL };
char *oids_ignored[] = { char *oids_ignored[] = {
"c95599a8fef20a7e57582c6727b1a0d02e0a5828", "c95599a8fef20a7e57582c6727b1a0d02e0a5828",
"7f89a8b15c878809c5c54d1ff8f8c9674154017b", "7f89a8b15c878809c5c54d1ff8f8c9674154017b",
"1d91c842a7cdfc25872b3a763e5c31add8816c25", NULL }; "493568b7a2681187aaac8a58d3f1eab1527cba84", NULL };
cl_git_pass(git_repository_init(&repo, REPO_NAME, 0)); cl_git_pass(git_repository_init(&repo, REPO_NAME, 0));
...@@ -96,9 +96,7 @@ void test_stash_foreach__can_enumerate_a_repository(void) ...@@ -96,9 +96,7 @@ void test_stash_foreach__can_enumerate_a_repository(void)
cl_git_pass(git_stash_foreach(repo, callback_cb, &data)); cl_git_pass(git_stash_foreach(repo, callback_cb, &data));
cl_assert_equal_i(1, data.invokes); cl_assert_equal_i(1, data.invokes);
data.oids = oids_untracked; /* ensure stash_foreach operates with INCLUDE_UNTRACKED */
data.invokes = 0;
cl_git_pass(git_stash_save( cl_git_pass(git_stash_save(
&stash_tip_oid, &stash_tip_oid,
repo, repo,
...@@ -106,12 +104,13 @@ void test_stash_foreach__can_enumerate_a_repository(void) ...@@ -106,12 +104,13 @@ void test_stash_foreach__can_enumerate_a_repository(void)
NULL, NULL,
GIT_STASH_INCLUDE_UNTRACKED)); GIT_STASH_INCLUDE_UNTRACKED));
data.oids = oids_untracked;
data.invokes = 0;
cl_git_pass(git_stash_foreach(repo, callback_cb, &data)); cl_git_pass(git_stash_foreach(repo, callback_cb, &data));
cl_assert_equal_i(2, data.invokes); cl_assert_equal_i(2, data.invokes);
data.oids = oids_ignored; /* ensure stash_foreach operates with INCLUDE_IGNORED */
data.invokes = 0;
cl_git_pass(git_stash_save( cl_git_pass(git_stash_save(
&stash_tip_oid, &stash_tip_oid,
repo, repo,
...@@ -119,6 +118,9 @@ void test_stash_foreach__can_enumerate_a_repository(void) ...@@ -119,6 +118,9 @@ void test_stash_foreach__can_enumerate_a_repository(void)
NULL, NULL,
GIT_STASH_INCLUDE_IGNORED)); GIT_STASH_INCLUDE_IGNORED));
data.oids = oids_ignored;
data.invokes = 0;
cl_git_pass(git_stash_foreach(repo, callback_cb, &data)); cl_git_pass(git_stash_foreach(repo, callback_cb, &data));
cl_assert_equal_i(3, data.invokes); cl_assert_equal_i(3, data.invokes);
} }
...@@ -100,12 +100,18 @@ $ git status --short ...@@ -100,12 +100,18 @@ $ git status --short
assert_blob_oid("refs/stash:how", "e6d64adb2c7f3eb8feb493b556cc8070dca379a3"); /* not so small and */ assert_blob_oid("refs/stash:how", "e6d64adb2c7f3eb8feb493b556cc8070dca379a3"); /* not so small and */
assert_blob_oid("refs/stash:who", "a0400d4954659306a976567af43125a0b1aa8595"); /* funky world */ assert_blob_oid("refs/stash:who", "a0400d4954659306a976567af43125a0b1aa8595"); /* funky world */
assert_blob_oid("refs/stash:when", NULL); assert_blob_oid("refs/stash:when", NULL);
assert_blob_oid("refs/stash:why", "88c2533e21f098b89c91a431d8075cbdbe422a51"); /* would anybody use stash? */
assert_blob_oid("refs/stash:where", "e3d6434ec12eb76af8dfa843a64ba6ab91014a0b"); /* .... */
assert_blob_oid("refs/stash:.gitignore", "ac4d88de61733173d9959e4b77c69b9f17a00980");
assert_blob_oid("refs/stash:just.ignore", NULL); assert_blob_oid("refs/stash:just.ignore", NULL);
assert_blob_oid("refs/stash^2:what", "dd7e1c6f0fefe118f0b63d9f10908c460aa317a6"); /* goodbye */ assert_blob_oid("refs/stash^2:what", "dd7e1c6f0fefe118f0b63d9f10908c460aa317a6"); /* goodbye */
assert_blob_oid("refs/stash^2:how", "e6d64adb2c7f3eb8feb493b556cc8070dca379a3"); /* not so small and */ assert_blob_oid("refs/stash^2:how", "e6d64adb2c7f3eb8feb493b556cc8070dca379a3"); /* not so small and */
assert_blob_oid("refs/stash^2:who", "cc628ccd10742baea8241c5924df992b5c019f71"); /* world */ assert_blob_oid("refs/stash^2:who", "cc628ccd10742baea8241c5924df992b5c019f71"); /* world */
assert_blob_oid("refs/stash^2:when", NULL); assert_blob_oid("refs/stash^2:when", NULL);
assert_blob_oid("refs/stash^2:why", "88c2533e21f098b89c91a431d8075cbdbe422a51"); /* would anybody use stash? */
assert_blob_oid("refs/stash^2:where", "e08f7fbb9a42a0c5367cf8b349f1f08c3d56bd72"); /* ???? */
assert_blob_oid("refs/stash^2:.gitignore", "ac4d88de61733173d9959e4b77c69b9f17a00980");
assert_blob_oid("refs/stash^2:just.ignore", NULL); assert_blob_oid("refs/stash^2:just.ignore", NULL);
assert_blob_oid("refs/stash^3", NULL); assert_blob_oid("refs/stash^3", NULL);
...@@ -243,11 +249,13 @@ void test_stash_save__cannot_stash_when_there_are_no_local_change(void) ...@@ -243,11 +249,13 @@ void test_stash_save__cannot_stash_when_there_are_no_local_change(void)
cl_git_pass(git_repository_index(&index, repo)); cl_git_pass(git_repository_index(&index, repo));
/* /*
* 'what' and 'who' are being committed. * 'what', 'where' and 'who' are being committed.
* 'when' remain untracked. * 'when' remains untracked.
*/ */
cl_git_pass(git_index_add_bypath(index, "what")); cl_git_pass(git_index_add_bypath(index, "what"));
cl_git_pass(git_index_add_bypath(index, "where"));
cl_git_pass(git_index_add_bypath(index, "who")); cl_git_pass(git_index_add_bypath(index, "who"));
cl_repo_commit_from_index(NULL, repo, signature, 0, "Initial commit"); cl_repo_commit_from_index(NULL, repo, signature, 0, "Initial commit");
git_index_free(index); git_index_free(index);
......
...@@ -26,12 +26,17 @@ void setup_stash(git_repository *repo, git_signature *signature) ...@@ -26,12 +26,17 @@ void setup_stash(git_repository *repo, git_signature *signature)
cl_git_rewritefile("stash/what", "goodbye\n"); /* dd7e1c6f0fefe118f0b63d9f10908c460aa317a6 */ cl_git_rewritefile("stash/what", "goodbye\n"); /* dd7e1c6f0fefe118f0b63d9f10908c460aa317a6 */
cl_git_rewritefile("stash/how", "not so small and\n"); /* e6d64adb2c7f3eb8feb493b556cc8070dca379a3 */ cl_git_rewritefile("stash/how", "not so small and\n"); /* e6d64adb2c7f3eb8feb493b556cc8070dca379a3 */
cl_git_rewritefile("stash/who", "funky world\n"); /* a0400d4954659306a976567af43125a0b1aa8595 */ cl_git_rewritefile("stash/who", "funky world\n"); /* a0400d4954659306a976567af43125a0b1aa8595 */
cl_git_mkfile("stash/why", "would anybody use stash?\n"); /* 88c2533e21f098b89c91a431d8075cbde422a51 */
cl_git_mkfile("stash/where", "????\n"); /* e08f7fbb9a42a0c5367cf8b349f1f08c3d56bd72 */
cl_git_pass(git_index_add_bypath(index, "what")); cl_git_pass(git_index_add_bypath(index, "what"));
cl_git_pass(git_index_add_bypath(index, "how")); cl_git_pass(git_index_add_bypath(index, "how"));
cl_git_pass(git_index_add_bypath(index, "why"));
cl_git_pass(git_index_add_bypath(index, "where"));
cl_git_pass(git_index_write(index)); cl_git_pass(git_index_write(index));
cl_git_rewritefile("stash/what", "see you later\n"); /* bc99dc98b3eba0e9157e94769cd4d49cb49de449 */ cl_git_rewritefile("stash/what", "see you later\n"); /* bc99dc98b3eba0e9157e94769cd4d49cb49de449 */
cl_git_mkfile("stash/where", "....\n"); /* e3d6434ec12eb76af8dfa843a64ba6ab91014a0b */
git_index_free(index); git_index_free(index);
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment