am --skip/--abort: merge HEAD/ORIG_HEAD tree into index

After running "git am --abort", and then running "git reset --hard",
files that were not modified would still be re-checked out.

This is because clean_index() in builtin/am.c mistakenly called the
read_tree() function, which overwrites all entries in the index,
including the stat info.

"git am --skip" did not seem to have this issue because am_skip() called
am_run(), which called refresh_cache() to update the stat info. However,
there's still a performance penalty as the lack of stat info meant that
refresh_cache() would have to scan all files for changes.

Fix this by using unpack_trees() instead to merge the tree into the
index, so that the stat info from the index is kept.

Reported-by: Linus Torvalds <torvalds@linux-foundation.org>
Helped-by: Junio C Hamano <gitster@pobox.com>
Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
Signed-off-by: Paul Tan <pyokagan@gmail.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>
This commit is contained in:
Paul Tan 2015-08-19 16:22:22 +08:00 committed by Junio C Hamano
parent e97a5e765d
commit 3ecc7040ef
2 changed files with 60 additions and 13 deletions

View file

@ -1939,16 +1939,49 @@ static int fast_forward_to(struct tree *head, struct tree *remote, int reset)
return 0;
}
/**
* Merges a tree into the index. The index's stat info will take precedence
* over the merged tree's. Returns 0 on success, -1 on failure.
*/
static int merge_tree(struct tree *tree)
{
struct lock_file *lock_file;
struct unpack_trees_options opts;
struct tree_desc t[1];
if (parse_tree(tree))
return -1;
lock_file = xcalloc(1, sizeof(struct lock_file));
hold_locked_index(lock_file, 1);
memset(&opts, 0, sizeof(opts));
opts.head_idx = 1;
opts.src_index = &the_index;
opts.dst_index = &the_index;
opts.merge = 1;
opts.fn = oneway_merge;
init_tree_desc(&t[0], tree->buffer, tree->size);
if (unpack_trees(1, t, &opts)) {
rollback_lock_file(lock_file);
return -1;
}
if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
die(_("unable to write new index file"));
return 0;
}
/**
* Clean the index without touching entries that are not modified between
* `head` and `remote`.
*/
static int clean_index(const unsigned char *head, const unsigned char *remote)
{
struct lock_file *lock_file;
struct tree *head_tree, *remote_tree, *index_tree;
unsigned char index[GIT_SHA1_RAWSZ];
struct pathspec pathspec;
head_tree = parse_tree_indirect(head);
if (!head_tree)
@ -1973,18 +2006,8 @@ static int clean_index(const unsigned char *head, const unsigned char *remote)
if (fast_forward_to(index_tree, remote_tree, 0))
return -1;
memset(&pathspec, 0, sizeof(pathspec));
lock_file = xcalloc(1, sizeof(struct lock_file));
hold_locked_index(lock_file, 1);
if (read_tree(remote_tree, 0, &pathspec)) {
rollback_lock_file(lock_file);
if (merge_tree(remote_tree))
return -1;
}
if (write_locked_index(&the_index, lock_file, COMMIT_LOCK))
die(_("unable to write new index file"));
remove_branch_state();

View file

@ -168,4 +168,28 @@ test_expect_success 'am --abort on unborn branch will keep local commits intact'
test_cmp expect actual
'
test_expect_success 'am --skip leaves index stat info alone' '
git checkout -f --orphan skip-stat-info &&
git reset &&
test_commit skip-should-be-untouched &&
test-chmtime =0 skip-should-be-untouched.t &&
git update-index --refresh &&
git diff-files --exit-code --quiet &&
test_must_fail git am 0001-*.patch &&
git am --skip &&
git diff-files --exit-code --quiet
'
test_expect_success 'am --abort leaves index stat info alone' '
git checkout -f --orphan abort-stat-info &&
git reset &&
test_commit abort-should-be-untouched &&
test-chmtime =0 abort-should-be-untouched.t &&
git update-index --refresh &&
git diff-files --exit-code --quiet &&
test_must_fail git am 0001-*.patch &&
git am --abort &&
git diff-files --exit-code --quiet
'
test_done