reset: introduce --[no-]refresh option to --mixed

Add a new --[no-]refresh option that is intended to explicitly determine whether a mixed reset should end in an index refresh. Starting at 9ac8125d1a (reset: don't compute unstaged changes after reset when --quiet, 2018-10-23), using the '--quiet' option results in skipping the call to 'refresh_index(...)' at the end of a mixed reset with the goal of improving performance. However, by coupling behavior that modifies the index with the option that silences logs, there is no way for users to have one without the other (i.e., silenced logs with a refreshed index) without incurring the overhead of a separate call to 'git update-index --refresh'. Furthermore, there is minimal user-facing documentation indicating that --quiet skips the index refresh, potentially leading to unexpected issues executing commands after 'git reset --quiet' that do not themselves refresh the index (e.g., internals of 'git stash', 'git read-tree'). To mitigate these issues, '--[no-]refresh' and 'reset.refresh' are introduced to provide a dedicated mechanism for refreshing the index. When either is set, '--quiet' and 'reset.quiet' revert to controlling only whether logs are silenced and do not affect index refresh. Helped-by: Derrick Stolee <derrickstolee@github.com> Helped-by: Junio C Hamano <gitster@pobox.com> Signed-off-by: Victoria Dye <vdye@github.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Victoria Dye committed Mar 15, 2022 at 01:49 UTC fd56fba97f26bf668749207efd6a45aee2e2f57c
3 files changed +87 -8
Documentation/git-reset.txt
+9
@@ -110,6 +110,15 @@ OPTIONS
110 `reset.quiet` config option. `--quiet` and `--no-quiet` will
111 override the default behavior.
112
113 +--refresh::
114 +--no-refresh::
115 + Proactively refresh the index after a mixed reset. If unspecified, the
116 + behavior falls back on the `reset.refresh` config option. If neither
117 + `--[no-]refresh` nor `reset.refresh` are set, the default behavior is
118 + decided by the `--[no-]quiet` option and/or `reset.quiet` config.
119 + If `--quiet` is specified or `reset.quiet` is set with no command-line
120 + "quiet" setting, refresh is disabled. Otherwise, refresh is enabled.
121 +
122 --pathspec-from-file=<file>::
123 Pathspec is passed in `<file>` instead of commandline args. If
124 `<file>` is exactly `-` then standard input is used. Pathspec
builtin/reset.c
+12 -1
@@ -392,6 +392,7 @@ static int git_reset_config(const char *var, const char *value, void *cb)
392 int cmd_reset(int argc, const char **argv, const char *prefix)
393 {
394 int reset_type = NONE, update_ref_status = 0, quiet = 0;
395 + int refresh = -1;
396 int patch_mode = 0, pathspec_file_nul = 0, unborn;
397 const char *rev, *pathspec_from_file = NULL;
398 struct object_id oid;
@@ -399,6 +400,8 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
400 int intent_to_add = 0;
401 const struct option options[] = {
402 OPT__QUIET(&quiet, N_("be quiet, only report errors")),
403 + OPT_BOOL(0, "refresh", &refresh,
404 + N_("skip refreshing the index after reset")),
405 OPT_SET_INT(0, "mixed", &reset_type,
406 N_("reset HEAD and index"), MIXED),
407 OPT_SET_INT(0, "soft", &reset_type, N_("reset only HEAD"), SOFT),
@@ -421,11 +424,19 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
424
425 git_config(git_reset_config, NULL);
426 git_config_get_bool("reset.quiet", &quiet);
427 + git_config_get_bool("reset.refresh", &refresh);
428
429 argc = parse_options(argc, argv, prefix, options, git_reset_usage,
430 PARSE_OPT_KEEP_DASHDASH);
431 parse_args(&pathspec, argv, prefix, patch_mode, &rev);
432
433 + /*
434 + * If refresh is completely unspecified (either by config or by command
435 + * line option), decide based on 'quiet'.
436 + */
437 + if (refresh < 0)
438 + refresh = !quiet;
439 +
440 if (pathspec_from_file) {
441 if (patch_mode)
442 die(_("options '%s' and '%s' cannot be used together"), "--pathspec-from-file", "--patch");
@@ -517,7 +528,7 @@ int cmd_reset(int argc, const char **argv, const char *prefix)
528 if (read_from_tree(&pathspec, &oid, intent_to_add))
529 return 1;
530 the_index.updated_skipworktree = 1;
520 - if (!quiet && get_git_work_tree()) {
531 + if (refresh && get_git_work_tree()) {
532 uint64_t t_begin, t_delta_in_ms;
533
534 t_begin = getnanotime();
t/t7102-reset.sh
+66 -7
@@ -462,14 +462,73 @@ test_expect_success 'resetting an unmodified path is a no-op' '
462 git diff-index --cached --exit-code HEAD
463 '
464
465 +test_reset_refreshes_index () {
466 +
467 + # To test whether the index is refreshed in `git reset --mixed` with
468 + # the given options, create a scenario where we clearly see different
469 + # results depending on whether the refresh occurred or not.
470 +
471 + # Step 0: start with a clean index
472 + git reset --hard HEAD &&
473 +
474 + # Step 1: remove file2, but only in the index (no change to worktree)
475 + git rm --cached file2 &&
476 +
477 + # Step 2: reset index & leave worktree unchanged from HEAD
478 + git $1 reset $2 --mixed HEAD &&
479 +
480 + # Step 3: verify whether the index is refreshed by checking whether
481 + # file2 still has staged changes in the index differing from HEAD (if
482 + # the refresh occurred, there should be no such changes)
483 + git diff-files >output.log &&
484 + test_must_be_empty output.log
485 +}
486 +
487 test_expect_success '--mixed refreshes the index' '
466 - cat >expect <<-\EOF &&
467 - Unstaged changes after reset:
468 - M file2
469 - EOF
470 - echo 123 >>file2 &&
471 - git reset --mixed HEAD >output &&
472 - test_cmp expect output
488 + # Verify default behavior (with no config settings or command line
489 + # options)
490 + test_reset_refreshes_index
491 +'
492 +test_expect_success '--mixed --[no-]quiet sets default refresh behavior' '
493 + # Verify that --[no-]quiet and `reset.quiet` (without --[no-]refresh)
494 + # determine refresh behavior
495 +
496 + # Config setting
497 + ! test_reset_refreshes_index "-c reset.quiet=true" &&
498 + test_reset_refreshes_index "-c reset.quiet=false" &&
499 +
500 + # Command line option
501 + ! test_reset_refreshes_index "" --quiet &&
502 + test_reset_refreshes_index "" --no-quiet &&
503 +
504 + # Command line option overrides config setting
505 + ! test_reset_refreshes_index "-c reset.quiet=false" --quiet &&
506 + test_reset_refreshes_index "-c reset.refresh=true" --no-quiet
507 +'
508 +
509 +test_expect_success '--mixed --[no-]refresh sets refresh behavior' '
510 + # Verify that --[no-]refresh and `reset.refresh` control index refresh
511 +
512 + # Config setting
513 + test_reset_refreshes_index "-c reset.refresh=true" &&
514 + ! test_reset_refreshes_index "-c reset.refresh=false" &&
515 +
516 + # Command line option
517 + test_reset_refreshes_index "" --refresh &&
518 + ! test_reset_refreshes_index "" --no-refresh &&
519 +
520 + # Command line option overrides config setting
521 + test_reset_refreshes_index "-c reset.refresh=false" --refresh &&
522 + ! test_reset_refreshes_index "-c reset.refresh=true" --no-refresh
523 +'
524 +
525 +test_expect_success '--mixed --refresh overrides --quiet refresh behavior' '
526 + # Verify that *both* --refresh and `reset.refresh` override the
527 + # default non-refresh behavior of --quiet
528 + test_reset_refreshes_index "" "--quiet --refresh" &&
529 + test_reset_refreshes_index "-c reset.quiet=true" --refresh &&
530 + test_reset_refreshes_index "-c reset.refresh=true" --quiet &&
531 + test_reset_refreshes_index "-c reset.refresh=true -c reset.quiet=true"
532 '
533
534 test_expect_success '--mixed preserves skip-worktree' '