diff --git a/builtin/blame.c b/builtin/blame.c index f9ee3f8c68..7fafeac408 100644 --- a/builtin/blame.c +++ b/builtin/blame.c @@ -939,6 +939,9 @@ parse_done: revs.diffopt.flags.follow_renames = 0; argc = parse_options_end(&ctx); + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + if (incremental || (output_option & OUTPUT_PORCELAIN)) { if (show_progress > 0) die(_("--progress can't be used with --incremental or porcelain formats")); diff --git a/builtin/diff.c b/builtin/diff.c index dd8ce688ba..fa4683377e 100644 --- a/builtin/diff.c +++ b/builtin/diff.c @@ -437,6 +437,11 @@ int cmd_diff(int argc, const char **argv, const char *prefix) prefix = setup_git_directory_gently(&nongit); + if (!nongit) { + prepare_repo_settings(the_repository); + the_repository->settings.command_requires_full_index = 0; + } + if (!no_index) { /* * Treat git diff with at least one path outside of the diff --git a/commit-graph.c b/commit-graph.c index 2706683acf..265c010122 100644 --- a/commit-graph.c +++ b/commit-graph.c @@ -632,10 +632,13 @@ static int prepare_commit_graph(struct repository *r) struct object_directory *odb; /* + * Early return if there is no git dir or if the commit graph is + * disabled. + * * This must come before the "already attempted?" check below, because * we want to disable even an already-loaded graph file. */ - if (r->commit_graph_disabled) + if (!r->gitdir || r->commit_graph_disabled) return 0; if (r->objects->commit_graph_attempted) diff --git a/git.c b/git.c index 5ff21be21f..7edafd8ecf 100644 --- a/git.c +++ b/git.c @@ -421,27 +421,30 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv) int status, help; struct stat st; const char *prefix; + int run_setup = (p->option & (RUN_SETUP | RUN_SETUP_GENTLY)); - prefix = NULL; help = argc == 2 && !strcmp(argv[1], "-h"); - if (!help) { - if (p->option & RUN_SETUP) - prefix = setup_git_directory(); - else if (p->option & RUN_SETUP_GENTLY) { - int nongit_ok; - prefix = setup_git_directory_gently(&nongit_ok); - } - precompose_argv_prefix(argc, argv, NULL); - if (use_pager == -1 && p->option & (RUN_SETUP | RUN_SETUP_GENTLY) && - !(p->option & DELAY_PAGER_CONFIG)) - use_pager = check_pager_config(p->cmd); - if (use_pager == -1 && p->option & USE_PAGER) - use_pager = 1; + if (help && (run_setup & RUN_SETUP)) + /* demote to GENTLY to allow 'git cmd -h' outside repo */ + run_setup = RUN_SETUP_GENTLY; - if ((p->option & (RUN_SETUP | RUN_SETUP_GENTLY)) && - startup_info->have_repository) /* get_git_dir() may set up repo, avoid that */ - trace_repo_setup(prefix); + if (run_setup & RUN_SETUP) { + prefix = setup_git_directory(); + } else if (run_setup & RUN_SETUP_GENTLY) { + int nongit_ok; + prefix = setup_git_directory_gently(&nongit_ok); + } else { + prefix = NULL; } + precompose_argv_prefix(argc, argv, NULL); + if (use_pager == -1 && run_setup && + !(p->option & DELAY_PAGER_CONFIG)) + use_pager = check_pager_config(p->cmd); + if (use_pager == -1 && p->option & USE_PAGER) + use_pager = 1; + if (run_setup && startup_info->have_repository) + /* get_git_dir() may set up repo, avoid that */ + trace_repo_setup(prefix); commit_pager_choice(); if (!help && get_super_prefix()) { diff --git a/repo-settings.c b/repo-settings.c index b93e91a212..00ca5571a1 100644 --- a/repo-settings.c +++ b/repo-settings.c @@ -17,6 +17,9 @@ void prepare_repo_settings(struct repository *r) char *strval; int manyfiles; + if (!r->gitdir) + BUG("Cannot add settings for uninitialized repository"); + if (r->settings.initialized++) return; diff --git a/t/perf/p2000-sparse-operations.sh b/t/perf/p2000-sparse-operations.sh index bfd332120c..cb777c74a2 100755 --- a/t/perf/p2000-sparse-operations.sh +++ b/t/perf/p2000-sparse-operations.sh @@ -113,5 +113,9 @@ test_perf_on_all git checkout -f - test_perf_on_all git reset test_perf_on_all git reset --hard test_perf_on_all git reset -- does-not-exist +test_perf_on_all git diff +test_perf_on_all git diff --cached +test_perf_on_all git blame $SPARSE_CONE/a +test_perf_on_all git blame $SPARSE_CONE/f3/a test_done diff --git a/t/t1092-sparse-checkout-compatibility.sh b/t/t1092-sparse-checkout-compatibility.sh index f8270943aa..49f70a6569 100755 --- a/t/t1092-sparse-checkout-compatibility.sh +++ b/t/t1092-sparse-checkout-compatibility.sh @@ -415,7 +415,7 @@ test_expect_success 'checkout and reset --hard' ' test_all_match git reset --hard update-folder2 ' -test_expect_success 'diff --staged' ' +test_expect_success 'diff --cached' ' init_repos && write_script edit-contents <<-\EOF && @@ -424,10 +424,10 @@ test_expect_success 'diff --staged' ' run_on_all ../edit-contents && test_all_match git diff && - test_all_match git diff --staged && + test_all_match git diff --cached && test_all_match git add README.md && test_all_match git diff && - test_all_match git diff --staged + test_all_match git diff --cached ' # NEEDSWORK: sparse-checkout behaves differently from full-checkout when @@ -444,8 +444,8 @@ test_expect_success 'diff with renames and conflicts' ' test_all_match git checkout rename-base && test_all_match git checkout $branch -- . && test_all_match git status --porcelain=v2 && - test_all_match git diff --staged --no-renames && - test_all_match git diff --staged --find-renames || return 1 + test_all_match git diff --cached --no-renames && + test_all_match git diff --cached --find-renames || return 1 done ' @@ -464,8 +464,8 @@ test_expect_success 'diff with directory/file conflicts' ' test_all_match git checkout $branch && test_all_match git checkout rename-base -- . && test_all_match git status --porcelain=v2 && - test_all_match git diff --staged --no-renames && - test_all_match git diff --staged --find-renames || return 1 + test_all_match git diff --cached --no-renames && + test_all_match git diff --cached --find-renames || return 1 done ' @@ -486,21 +486,36 @@ test_expect_success 'log with pathspec outside sparse definition' ' test_expect_success 'blame with pathspec inside sparse definition' ' init_repos && - test_all_match git blame a && - test_all_match git blame deep/a && - test_all_match git blame deep/deeper1/a && - test_all_match git blame deep/deeper1/deepest/a + for file in a \ + deep/a \ + deep/deeper1/a \ + deep/deeper1/deepest/a + do + test_all_match git blame $file + done ' -# TODO: blame currently does not support blaming files outside of the -# sparse definition. It complains that the file doesn't exist locally. -test_expect_failure 'blame with pathspec outside sparse definition' ' +# Without a revision specified, blame will error if passed any file that +# is not present in the working directory (even if the file is tracked). +# Here we just verify that this is also true with sparse checkouts. +test_expect_success 'blame with pathspec outside sparse definition' ' init_repos && + test_sparse_match git sparse-checkout set && - test_all_match git blame folder1/a && - test_all_match git blame folder2/a && - test_all_match git blame deep/deeper2/a && - test_all_match git blame deep/deeper2/deepest/a + for file in a \ + deep/a \ + deep/deeper1/a \ + deep/deeper1/deepest/a + do + test_sparse_match test_must_fail git blame $file && + cat >expect <<-EOF && + fatal: Cannot lstat '"'"'$file'"'"': No such file or directory + EOF + # We compare sparse-checkout-err and sparse-index-err in + # `test_sparse_match`. Given we know they are the same, we + # only check the content of sparse-index-err here. + test_cmp expect sparse-index-err + done ' test_expect_success 'checkout and reset (mixed)' ' @@ -936,6 +951,64 @@ test_expect_success 'sparse-index is not expanded: merge conflict in cone' ' ) ' +test_expect_success 'sparse index is not expanded: diff' ' + init_repos && + + write_script edit-contents <<-\EOF && + echo text >>$1 + EOF + + # Add file within cone + test_sparse_match git sparse-checkout set deep && + run_on_all ../edit-contents deep/testfile && + test_all_match git add deep/testfile && + run_on_all ../edit-contents deep/testfile && + + test_all_match git diff && + test_all_match git diff --cached && + ensure_not_expanded diff && + ensure_not_expanded diff --cached && + + # Add file outside cone + test_all_match git reset --hard && + run_on_all mkdir newdirectory && + run_on_all ../edit-contents newdirectory/testfile && + test_sparse_match git sparse-checkout set newdirectory && + test_all_match git add newdirectory/testfile && + run_on_all ../edit-contents newdirectory/testfile && + test_sparse_match git sparse-checkout set && + + test_all_match git diff && + test_all_match git diff --cached && + ensure_not_expanded diff && + ensure_not_expanded diff --cached && + + # Merge conflict outside cone + # The sparse checkout will report a warning that is not in the + # full checkout, so we use `run_on_all` instead of + # `test_all_match` + run_on_all git reset --hard && + test_all_match git checkout merge-left && + test_all_match test_must_fail git merge merge-right && + + test_all_match git diff && + test_all_match git diff --cached && + ensure_not_expanded diff && + ensure_not_expanded diff --cached +' + +test_expect_success 'sparse index is not expanded: blame' ' + init_repos && + + for file in a \ + deep/a \ + deep/deeper1/a \ + deep/deeper1/deepest/a + do + ensure_not_expanded blame $file + done +' + # NEEDSWORK: a sparse-checkout behaves differently from a full checkout # in this scenario, but it shouldn't. test_expect_success 'reset mixed and checkout orphan' '