Skip to content

Commit a7d32d6

Browse files
committed
stash: avoid recomputing tree when committing worktree
When creating a new stash, we need to create there separate commits storing differences stored in the index, untracked changes as well as differences in the working directory. The first two will only be done conditionally if the equivalent options "git stash --keep-index --include-untracked" are being passed to `git_stash_save`, but even when only creating a stash of worktree changes we're much slower than git.git. Using our new stash example: $ time git stash Saved working directory and index state WIP on (no branch): 2f7d9d47575e Linux 5.1.7 real 0m0.528s user 0m0.309s sys 0m0.381s $ time lg2 stash real 0m27.165s user 0m13.645s sys 0m6.403s As can be seen, libgit2 is more than 50x slower than git.git! When creating the stash commit that includes all worktree changes, we create a completely new index to prepare for the new commit and populate it with the entries contained in the index' tree. Here comes the catch: by populating the index with a tree's contents, we do not have any stat caches in the index. This means that we have to re-validate every single file from the worktree and see whether it has changed. The issue can be fixed by populating the new index with the repo's existing index instead of with the tree. This retains all stat cache information, and thus we really only need to check files that have changed stat information. This is semantically equivalent to what we previously did: previously, we used the tree of the commit computed from the index. Now we're just using the index directly. And, in fact, the cache is doing wonders: time lg2 stash real 0m1.836s user 0m1.166s sys 0m0.663s We're now performing 15x faster than before and are only 3x slower than git.git now.
1 parent 88731e3 commit a7d32d6

File tree

1 file changed

+9
-14
lines changed

1 file changed

+9
-14
lines changed

src/stash.c

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -398,28 +398,23 @@ static int commit_worktree(
398398
git_commit *b_commit,
399399
git_commit *u_commit)
400400
{
401-
int error = 0;
402-
git_tree *w_tree = NULL, *i_tree = NULL;
403-
git_index *i_index = NULL;
404-
const git_commit *parents[] = { NULL, NULL, NULL };
405-
int ignorecase;
401+
const git_commit *parents[] = { NULL, NULL, NULL };
402+
git_index *i_index = NULL, *r_index = NULL;
403+
git_tree *w_tree = NULL;
404+
int error = 0, ignorecase;
406405

407406
parents[0] = b_commit;
408407
parents[1] = i_commit;
409408
parents[2] = u_commit;
410409

411-
if ((error = git_commit_tree(&i_tree, i_commit)) < 0)
412-
goto cleanup;
413-
414-
if ((error = git_index_new(&i_index)) < 0 ||
415-
(error = git_repository__configmap_lookup(&ignorecase, repo, GIT_CONFIGMAP_IGNORECASE)) < 0)
410+
if ((error = git_repository_index(&r_index, repo) < 0) ||
411+
(error = git_index_new(&i_index)) < 0 ||
412+
(error = git_index__fill(i_index, &r_index->entries) < 0) ||
413+
(error = git_repository__configmap_lookup(&ignorecase, repo, GIT_CONFIGMAP_IGNORECASE)) < 0)
416414
goto cleanup;
417415

418416
git_index__set_ignore_case(i_index, ignorecase);
419417

420-
if ((error = git_index_read_tree(i_index, i_tree)) < 0)
421-
goto cleanup;
422-
423418
if ((error = build_workdir_tree(&w_tree, repo, i_index, b_commit)) < 0)
424419
goto cleanup;
425420

@@ -436,9 +431,9 @@ static int commit_worktree(
436431
parents);
437432

438433
cleanup:
439-
git_tree_free(i_tree);
440434
git_tree_free(w_tree);
441435
git_index_free(i_index);
436+
git_index_free(r_index);
442437
return error;
443438
}
444439

0 commit comments

Comments
 (0)