git/list[1] front-page[2] threads[3] people[4] search[5] about
 

Re: [PATCH 6/8] checkout: add --cached option

From
Thomas Gummerer <t.gummerer@gmail.com>
Date
Dec 11, 2018, 22:18 UTC
Message-ID
<20181211221825.GS4883@hank.intra.tgummerer.com>
In-Reply-To
<CABPp-BEyRNniNFBuMMFXjvuSz9RtP6R7TeBAWDJO+Y70oe7CBA@mail.gmail.com>
On 12/10, Elijah Newren wrote:
Show 21 quoted lines
> On Sun, Dec 9, 2018 at 12:05 PM Thomas Gummerer <t.gummerer@gmail.com> wrote:
> >
> > Add a new --cached option to git checkout, which works only on the
> > index, but not the working tree, similar to what 'git reset <tree-ish>
> > -- <pathspec>... does.  Indeed the tests are adapted from the 'git
> > reset' tests.
> >
> > In the longer term the idea is to potentially deprecate 'git reset
> > <tree-ish> -- <pathspec>...', so the 'git reset' command becomes only
> > about re-pointing the HEAD, and not also about copying entries from
> > <tree-ish> to the index.
> >
> > Note that 'git checkout' by default works in overlay mode, meaning
> > files that match the pathspec that don't exist in <tree-ish>, but
> > exist in the index would not be removed.  'git checkout --no-overlay
> > --cached' can be used to get the same behaviour as 'git reset
> > <tree-ish> -- <pathspec>'.
> 
> I think this argues _even more_ that --no-overlay should be the
> default.  Your series is valuable even if we don't push on that, I'm
> just being noisy about what I think would be an even better world.

I think just having that mode in 'git restore-files' Duy is working on may have to be enough for now.

> Also, I don't think I've mentioned it yet, but I'm really excited
> about this series and what you're doing.  It's super cool.  (Which I
> expected when I saw the description of the desired behavior, but I'm
> also liking and contemplating re-using some code...)
Thanks :)
Show 32 quoted lines
> > One thing this patch doesn't currently deal with is conflicts.
> > Currently 'git checkout --{ours,theirs} -- <file-with-conflicts>'
> > doesn't do anything with the index, so the --cached option just
> > mirrors that behaviour.  But given it doesn't even deal with
> > conflicts, the '--cached' option doesn't make much sense when no
> > <tree-ish> is given.  As it operates only on the index, it's always a
> > no-op if no tree-ish is given.
> >
> > Signed-off-by: Thomas Gummerer <t.gummerer@gmail.com>
> > ---
> >
> > Maybe we can just disallow --cached without <tree-ish> given for now,
> > and possibly later allow it with some different behaviour for
> > conflicts, not sure what the best way forward here is.  We can also
> > just make it update the index as appropriate, and have it behave
> > different than 'git checkout' curerntly does when handling conflicts?
> 
> Huh?
>   "git checkout -- <path>"
> means update <path> from the index, meaning the index is left alone
> (it's the source) and only the working tree is touched.
> 
> When you add a flag named --cached to only update the index and not
> the working tree, then the index becomes the sole destination.
> 
> Now we combine: no tree is specified means the index is the source of
> the writing, and --cached being specified means the index is the sole
> destination of the writing.  Thus, you have a no-op.  If the user
> specifies --cached and no tree, you should immediately exit with a
> message along the lines of "Nothing to do; no tree given and --cached
> specified."  The presence of conflicts seems completely irrelevant to
> me here.

Ah yeah you're right, thanks for a sanity check. The command I was most worried about was 'git checkout --cached --{ours,theirs} -- <pathspec>', which I thought should update the index. But as we don't give any tree-ish, I'm not sure anymore it should. Maybe just always exiting with the message you mention above is the right thing to do.

Show 213 quoted lines
> >  builtin/checkout.c         |  26 ++++++++--
> >  t/t2016-checkout-patch.sh  |   8 +++
> >  t/t2026-checkout-cached.sh | 103 +++++++++++++++++++++++++++++++++++++
> >  t/t9902-completion.sh      |   1 +
> >  4 files changed, 135 insertions(+), 3 deletions(-)
> >  create mode 100755 t/t2026-checkout-cached.sh
> >
> > diff --git a/builtin/checkout.c b/builtin/checkout.c
> > index 0aef35bbc4..6ba85e9de5 100644
> > --- a/builtin/checkout.c
> > +++ b/builtin/checkout.c
> > @@ -45,6 +45,7 @@ struct checkout_opts {
> >         int ignore_other_worktrees;
> >         int show_progress;
> >         int overlay_mode;
> > +       int cached;
> >         /*
> >          * If new checkout options are added, skip_merge_working_tree
> >          * should be updated accordingly.
> > @@ -288,6 +289,10 @@ static int checkout_paths(const struct checkout_opts *opts,
> >                 die(_("Cannot update paths and switch to branch '%s' at the same time."),
> >                     opts->new_branch);
> >
> > +       if (opts->patch_mode && opts->cached)
> > +               return run_add_interactive(revision, "--patch=reset",
> > +                                          &opts->pathspec);
> > +
> >         if (opts->patch_mode)
> >                 return run_add_interactive(revision, "--patch=checkout",
> >                                            &opts->pathspec);
> > @@ -319,7 +324,9 @@ static int checkout_paths(const struct checkout_opts *opts,
> >                                  * the current index, which means that it should
> >                                  * be removed.
> >                                  */
> > -                               ce->ce_flags |= CE_MATCHED | CE_REMOVE | CE_WT_REMOVE;
> > +                               ce->ce_flags |= CE_MATCHED | CE_REMOVE;
> > +                               if (!opts->cached)
> > +                                       ce->ce_flags |= CE_WT_REMOVE;
> >                                 continue;
> >                         } else {
> >                                 /*
> > @@ -392,6 +399,9 @@ static int checkout_paths(const struct checkout_opts *opts,
> >         for (pos = 0; pos < active_nr; pos++) {
> >                 struct cache_entry *ce = active_cache[pos];
> >                 if (ce->ce_flags & CE_MATCHED) {
> > +                       if (opts->cached) {
> > +                               continue;
> > +                       }
> >                         if (!ce_stage(ce)) {
> >                                 errs |= checkout_entry(ce, &state, NULL);
> >                                 continue;
> > @@ -571,6 +581,11 @@ static int skip_merge_working_tree(const struct checkout_opts *opts,
> >          * not tested here
> >          */
> >
> > +       /*
> > +        * opts->cached cannot be used with switching branches so is
> > +        * not tested here
> > +        */
> > +
> >         /*
> >          * If we aren't creating a new branch any changes or updates will
> >          * happen in the existing branch.  Since that could only be updating
> > @@ -1207,9 +1222,13 @@ static int checkout_branch(struct checkout_opts *opts,
> >                 die(_("'%s' cannot be used with switching branches"),
> >                     "--patch");
> >
> > -       if (!opts->overlay_mode)
> > +       if (opts->overlay_mode != -1)
> > +               die(_("'%s' cannot be used with switching branches"),
> > +                   "--overlay/--no-overlay");
> > +
> > +       if (opts->cached)
> >                 die(_("'%s' cannot be used with switching branches"),
> > -                   "--no-overlay");
> > +                   "--cached");
> >
> >         if (opts->writeout_stage)
> >                 die(_("'%s' cannot be used with switching branches"),
> > @@ -1300,6 +1319,7 @@ int cmd_checkout(int argc, const char **argv, const char *prefix)
> >                             PARSE_OPT_OPTARG, option_parse_recurse_submodules_worktree_updater },
> >                 OPT_BOOL(0, "progress", &opts.show_progress, N_("force progress reporting")),
> >                 OPT_BOOL(0, "overlay", &opts.overlay_mode, N_("use overlay mode")),
> > +               OPT_BOOL(0, "cached", &opts.cached, N_("work on the index only")),
> >                 OPT_END(),
> >         };
> >
> > diff --git a/t/t2016-checkout-patch.sh b/t/t2016-checkout-patch.sh
> > index 47aeb0b167..e8774046e0 100755
> > --- a/t/t2016-checkout-patch.sh
> > +++ b/t/t2016-checkout-patch.sh
> > @@ -108,6 +108,14 @@ test_expect_success PERL 'path limiting works: foo inside dir' '
> >         verify_state dir/foo head head
> >  '
> >
> > +test_expect_success PERL 'git checkout --cached -p' '
> > +       set_and_save_state dir/foo work work &&
> > +       test_write_lines n y | git checkout --cached -p >output &&
> > +       verify_state dir/foo work head &&
> > +       verify_saved_state bar &&
> > +       test_i18ngrep "Unstage" output
> > +'
> > +
> >  test_expect_success PERL 'none of this moved HEAD' '
> >         verify_saved_head
> >  '
> > diff --git a/t/t2026-checkout-cached.sh b/t/t2026-checkout-cached.sh
> > new file mode 100755
> > index 0000000000..1b66192727
> > --- /dev/null
> > +++ b/t/t2026-checkout-cached.sh
> > @@ -0,0 +1,103 @@
> > +#!/bin/sh
> > +
> > +test_description='checkout --cached <pathspec>'
> > +
> > +. ./test-lib.sh
> > +
> > +test_expect_success 'checkout --cached <pathspec>' '
> > +       echo 1 >file1 &&
> > +       echo 2 >file2 &&
> > +       git add file1 file2 &&
> > +       test_tick &&
> > +       git commit -m files &&
> > +       git rm file2 &&
> > +       echo 3 >file3 &&
> > +       echo 4 >file1 &&
> > +       git add file1 file3 &&
> > +       git checkout --cached HEAD -- file1 file2 &&
> > +       test_must_fail git diff --quiet &&
> > +
> > +       cat >expect <<-\EOF &&
> > +       diff --git a/file1 b/file1
> > +       index d00491f..b8626c4 100644
> > +       --- a/file1
> > +       +++ b/file1
> > +       @@ -1 +1 @@
> > +       -1
> > +       +4
> > +       diff --git a/file2 b/file2
> > +       deleted file mode 100644
> > +       index 0cfbf08..0000000
> > +       --- a/file2
> > +       +++ /dev/null
> > +       @@ -1 +0,0 @@
> > +       -2
> > +       EOF
> > +       git diff >actual &&
> > +       test_cmp expect actual &&
> > +
> > +       cat >expect <<-\EOF &&
> > +       diff --git a/file3 b/file3
> > +       new file mode 100644
> > +       index 0000000..00750ed
> > +       --- /dev/null
> > +       +++ b/file3
> > +       @@ -0,0 +1 @@
> > +       +3
> > +       EOF
> > +       git diff --cached >actual &&
> > +       test_cmp expect actual
> > +'
> > +
> > +test_expect_success 'checking out an unmodified path is a no-op' '
> > +       git reset --hard &&
> > +       git checkout --cached HEAD -- file1 &&
> > +       git diff-files --exit-code &&
> > +       git diff-index --cached --exit-code HEAD
> > +'
> > +
> > +test_expect_success 'checking out specific path that is unmerged' '
> > +       test_commit file3 file3 &&
> > +       git rm --cached file2 &&
> > +       echo 1234 >file2 &&
> > +       F1=$(git rev-parse HEAD:file1) &&
> > +       F2=$(git rev-parse HEAD:file2) &&
> > +       F3=$(git rev-parse HEAD:file3) &&
> > +       {
> > +               echo "100644 $F1 1      file2" &&
> > +               echo "100644 $F2 2      file2" &&
> > +               echo "100644 $F3 3      file2"
> > +       } | git update-index --index-info &&
> > +       git ls-files -u &&
> > +       git checkout --cached HEAD file2 &&
> > +       test_must_fail git diff --quiet &&
> > +       git diff-index --exit-code --cached HEAD
> > +'
> > +
> > +test_expect_success '--cached without --no-overlay does not remove entry from index' '
> > +       test_must_fail git checkout --cached HEAD^ file3 &&
> > +       git ls-files --error-unmatch -- file3
> > +'
> > +
> > +test_expect_success 'file is removed from the index with --no-overlay' '
> > +       git checkout --cached --no-overlay HEAD^ file3 &&
> > +       test_path_is_file file3 &&
> > +       test_must_fail git ls-files --error-unmatch -- file3
> > +'
> > +
> > +test_expect_success 'test checkout --cached --no-overlay at given paths' '
> > +       mkdir sub &&
> > +       >sub/file1 &&
> > +       >sub/file2 &&
> > +       git update-index --add sub/file1 sub/file2 &&
> > +       T=$(git write-tree) &&
> > +       git checkout --cached --no-overlay HEAD sub/file2 &&
> > +       test_must_fail git diff --quiet &&
> > +       U=$(git write-tree) &&
> 
> Do we need to worry at all about losing the exit status of write-tree
> in either invocation?  In particular, if the second one for U fails
> somehow, we'd end up with $U being a blank string and we'd still
> probably get "$T" != "$U" below.
Hmm this seems to be a fairly common pattern in our test suite:

$ git grep -F '$(git write-tree)' t/* | wc -l 112

But maybe it's just something we used to do, but should move away from. Just writing the output to a file shouldn't be much harder either, I'll do that in the next iteration.

Show 26 quoted lines
> You also had some rev-parse invocations hidden in a sub-shell in both
> this patch and patch 5, but subsequent commands relied on non-empty
> output out of those, so I figured those were fine.  This one might be
> too, but I thought I'd at least mention it.
> 
> > +       echo "$T" &&
> > +       echo "$U" &&
> > +       test_must_fail git diff-index --cached --exit-code "$T" &&
> > +       test "$T" != "$U"
> > +'
> > +
> > +test_done
> > diff --git a/t/t9902-completion.sh b/t/t9902-completion.sh
> > index a3fd9a9630..cbc304ace8 100755
> > --- a/t/t9902-completion.sh
> > +++ b/t/t9902-completion.sh
> > @@ -1437,6 +1437,7 @@ test_expect_success 'double dash "git checkout"' '
> >         --no-quiet Z
> >         --no-... Z
> >         --overlay Z
> > +       --cached Z
> >         EOF
> >  '
> >
> > --
> > 2.20.0.405.gbc1bbc6f85
Previous: Elijah NewrenNext: Thomas Gummerer
Message 65 of 115 in “introduce no-overlay and cached mode in git checkout”
  1. 0/8 introduce no-overlay and cached mode in git checkoutThomas Gummerer, Dec 9, 2018
  2. 1/8 move worktree tests to t24*Thomas Gummerer, Dec 9, 2018
  3. Junio C HamanoDec 10, 2018
  4. Duy NguyenDec 10, 2018
  5. Thomas GummererDec 11, 2018
  6. Eric SunshineDec 12, 2018
  7. Duy NguyenDec 12, 2018
  8. 3/8 entry: support CE_WT_REMOVE flag in checkout_entryThomas Gummerer, Dec 9, 2018
  9. Duy NguyenDec 10, 2018
  10. Junio C HamanoDec 11, 2018
  11. Duy NguyenDec 12, 2018
  12. Junio C HamanoDec 12, 2018
  13. Elijah NewrenDec 10, 2018
  14. Thomas GummererDec 11, 2018
  15. 2/8 entry: factor out unlink_entry functionThomas Gummerer, Dec 9, 2018
  16. Duy NguyenDec 10, 2018
  17. Elijah NewrenDec 10, 2018
  18. Duy NguyenDec 10, 2018
  19. Junio C HamanoDec 11, 2018
  20. Thomas GummererDec 20, 2018
  21. 4/8 read-cache: add invalidate parameter to remove_marked_cache_entriesThomas Gummerer, Dec 9, 2018
  22. Duy NguyenDec 10, 2018
  23. Elijah NewrenDec 10, 2018
  24. Duy NguyenDec 10, 2018
  25. Elijah NewrenDec 10, 2018
  26. Duy NguyenDec 10, 2018
  27. Elijah NewrenDec 10, 2018
  28. Thomas GummererDec 11, 2018
  29. Junio C HamanoDec 11, 2018
  30. 5/8 checkout: introduce --{,no-}overlay optionThomas Gummerer, Dec 9, 2018
  31. Duy NguyenDec 10, 2018
  32. Thomas GummererDec 11, 2018
  33. Elijah NewrenDec 10, 2018
  34. Junio C HamanoDec 11, 2018
  35. Elijah NewrenDec 11, 2018
  36. Thomas GummererDec 11, 2018
  37. 6/8 checkout: add --cached optionThomas Gummerer, Dec 9, 2018
  38. Duy NguyenDec 10, 2018
  39. Junio C HamanoDec 11, 2018
  40. Elijah NewrenDec 11, 2018
  41. Duy NguyenDec 11, 2018
  42. Duy NguyenJan 31, 2019
  43. Junio C HamanoJan 31, 2019
  44. Duy NguyenFeb 1, 2019
  45. Junio C HamanoFeb 1, 2019
  46. Duy NguyenFeb 2, 2019
  47. Duy NguyenFeb 19, 2019
  48. Elijah NewrenFeb 19, 2019
  49. Duy NguyenFeb 19, 2019
  50. Junio C HamanoFeb 19, 2019
  51. Elijah NewrenFeb 19, 2019
  52. Junio C HamanoFeb 19, 2019
  53. Elijah NewrenFeb 19, 2019
  54. Duy NguyenFeb 20, 2019
  55. Junio C HamanoFeb 19, 2019
  56. Junio C HamanoFeb 19, 2019
  57. Elijah NewrenFeb 19, 2019
  58. Junio C HamanoFeb 19, 2019
  59. Duy NguyenFeb 20, 2019
  60. Duy NguyenFeb 20, 2019
  61. Duy NguyenFeb 20, 2019
  62. Elijah NewrenFeb 19, 2019
  63. Junio C HamanoFeb 19, 2019
  64. Elijah NewrenDec 10, 2018
  65. Thomas GummererDec 11, 2018
  66. 7/8 checkout: allow ignoring unmatched pathspecThomas Gummerer, Dec 9, 2018
  67. Duy NguyenDec 10, 2018
  68. Thomas GummererDec 11, 2018
  69. Elijah NewrenDec 10, 2018
  70. Thomas GummererDec 11, 2018
  71. 8/8 stash: use git checkout --no-overlayThomas Gummerer, Dec 9, 2018
  72. Elijah NewrenDec 10, 2018
  73. Junio C HamanoDec 10, 2018
  74. Thomas GummererDec 20, 2018
  75. Duy NguyenDec 10, 2018
  76. Elijah NewrenDec 10, 2018
  77. Duy NguyenDec 10, 2018
  78. Thomas GummererDec 11, 2018
  79. Junio C HamanoDec 12, 2018
  80. 0/8 introduce no-overlay mode in git checkoutThomas Gummerer, Dec 20, 2018
  81. 1/8 move worktree tests to t24*Thomas Gummerer, Dec 20, 2018
  82. 2/8 entry: factor out unlink_entry functionThomas Gummerer, Dec 20, 2018
  83. 3/8 entry: support CE_WT_REMOVE flag in checkout_entryThomas Gummerer, Dec 20, 2018
  84. 4/8 read-cache: add invalidate parameter to remove_marked_cache_entriesThomas Gummerer, Dec 20, 2018
  85. 5/8 checkout: clarify commentThomas Gummerer, Dec 20, 2018
  86. 6/8 checkout: factor out mark_cache_entry_for_checkout functionThomas Gummerer, Dec 20, 2018
  87. 7/8 checkout: introduce --{,no-}overlay optionThomas Gummerer, Dec 20, 2018
  88. Duy NguyenDec 23, 2018
  89. Eric SunshineDec 23, 2018
  90. Thomas GummererJan 6, 2019
  91. 8/8 checkout: introduce checkout.overlayMode configThomas Gummerer, Dec 20, 2018
  92. Junio C HamanoJan 2, 2019
  93. Thomas GummererJan 6, 2019
  94. Junio C HamanoJan 7, 2019
  95. 0/8 introduce no-overlay mode in git checkoutThomas Gummerer, Jan 8, 2019
  96. 2/8 entry: factor out unlink_entry functionThomas Gummerer, Jan 8, 2019
  97. 1/8 move worktree tests to t24*Thomas Gummerer, Jan 8, 2019
  98. 4/8 read-cache: add invalidate parameter to remove_marked_cache_entriesThomas Gummerer, Jan 8, 2019
  99. 3/8 entry: support CE_WT_REMOVE flag in checkout_entryThomas Gummerer, Jan 8, 2019
  100. 5/8 checkout: clarify commentThomas Gummerer, Jan 8, 2019
  101. 6/8 checkout: factor out mark_cache_entry_for_checkout functionThomas Gummerer, Jan 8, 2019
  102. 7/8 checkout: introduce --{,no-}overlay optionThomas Gummerer, Jan 8, 2019
  103. Jonathan NiederJan 22, 2019
  104. Junio C HamanoJan 23, 2019
  105. Thomas GummererJan 23, 2019
  106. Jonathan NiederJan 23, 2019
  107. Thomas GummererJan 24, 2019
  108. Junio C HamanoJan 23, 2019
  109. Jonathan NiederJan 24, 2019
  110. Thomas GummererJan 24, 2019
  111. Junio C HamanoJan 24, 2019
  112. Jonathan NiederJan 25, 2019
  113. Duy NguyenJan 25, 2019
  114. Philip OakleyFeb 9, 2019
  115. 8/8 checkout: introduce checkout.overlayMode configThomas Gummerer, Jan 8, 2019

Read the whole thread, see it on lore, or plain text.

$ cat FOOTERMessages come from the public archive at lore.kernel.org/git, fetched every hour. The front page is chosen and written each morning by an AI editor and can be wrong; the threads themselves are the record. About and API. For agents: an MCP server at https://gitlist.dev/mcp, and any thread, story or person page as Markdown by adding .md to its URL (or sending Accept: text/markdown). Details in /llms.txt.