Re: Bug report: `git restore --source --staged` deals poorly with sparse-checkout

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



Hi Martin!

Please make sure you respond in plaintext - it looks like your message didn't
make it to the mailing list. I've reformatted it to render nicely in this 
reply.

Martin von Zweigbergk wrote:
>> On Tue, Oct 4, 2022 at 9:34 AM Victoria Dye <vdye@xxxxxxxxxx <mailto:vdye@xxxxxxxxxx>> wrote:
>> 
>> Glen Choo wrote:
>>> Filing a `git bugreport` on behalf of a user at $DAYJOB. I'm also pretty
>>> surprised by this behavior, perhaps someone who knows more could shed
>>> some light?
>>>
>>> What did you do before the bug happened? (Steps to reproduce your issue)
>>>
>>>   git clone git@xxxxxxxxxx:git/git.git . &&
>>>   git sparse-checkout set t &&
>>>   git restore --source v2.38.0-rc1 --staged Documentation &&
>>>   git status
>>> ...>
>>> What happened instead? (Actual behavior)
>>>
>>> I saw a staged modification (Documentation/cmd-list.perl) and the same
>>> file reported as deleted in the working copy. Specifically,
>>>
>>>   $ git status
>>>
>>>   On branch master
>>>   Your branch is up to date with 'origin/master'.
>>>
>>>   You are in a sparse checkout with 64% of tracked files present.
>>>
>>>   Changes to be committed:
>>>     (use "git restore --staged <file>..." to unstage)
>>>           modified:   Documentation/cmd-list.perl
>>>
>>>   Changes not staged for commit:
>>>     (use "git add/rm <file>..." to update what will be committed)
>>>     (use "git restore <file>..." to discard changes in working directory)
>>>           deleted:    Documentation/cmd-list.perl
>>>
>> 
>> Thanks for reporting this! There are a few confusing things going on with
>> 'restore' here.
>> 
>> First is that the out-of-cone was even restored in the first place.
>> 
> 
> I was actually happy that the out-of-cone paths were restored. I ran that
> command as an experiment while reading Elijah's doc because I was curious
> what would happen. The reason I think it should restore out-of-cone paths is
> so you can do `git restore --staged --source <some commit> && git commit -m
> "restore to old commit"` without caring about the sparse spec.

Conversely, that's behavior a user *wouldn't* want if they want to keep
their sparse cone intact (not to mention the performance impact of checking
out the entire worktree). I think it does more harm to those users than it
would benefit the ones that want to checkout out-of-cone files.

The use-case you're describing should be served by the
'--ignore-skip-worktree-bits' option (not the most intuitive name,
unfortunately). Luckily, there's an increasing desire to improve the naming
of sparse-related options, so the UX situation should improve in the future.

> 
>> Theoretically, 'restore' (like 'checkout') should be limited to pathspecs
>> inside the sparse-checkout patterns (per the documentation of
>> '--ignore-skip-worktree-bits'), but 'Documentation' does not match them.
>> Then, there's a difference between 'restore' and 'checkout' that doesn't
>> seem intentional; both remove the 'SKIP_WORKTREE' flag from the file, but
>> only 'checkout' creates the file on-disk (therefore avoiding the "deleted"
>> status).
> 
> Restoring only into the index (as I think `git restore --staged` is supposed
> to do) is weird. 

'git restore --staged' is intended to restore to both the worktree and index
(per 183fb44fd2 (restore: add --worktree and --staged, 2019-04-25)). The bug
you've identified is that it's not restoring to the worktree.

Assuming everything was working properly, users could still choose to
restore only to the index (using the '--no-worktree' option).

> Let's say we do a clean checkout of a commit with tree A
> (i.e. the root tree's hash is A). If we do `git sparse-checkout set
> non-existent`, the index and the working copy still logically contain state
> A, right? 

The index will, but the working tree will be empty because all index entries
not matching 'non-existent' will have SKIP_WORKTREE applied.

> If we now do `git restore --staged --source HEAD^` and that
> command doesn't remove the `SKIP_WORKTREE` flag on any paths, that logically
> means that we have modified the working copy, and I think `git
> sparse-checkout disable` would agree with me. 

If you aren't using '--ignore-skip-worktree-bits', the entries with
SKIP_WORKTREE shouldn't be touched in the first place. If you *do* specify
it, by virtue of restoring to the working tree, SKIP_WORKTREE must be
removed.

But suppose you're doing something like 'git restore --staged --no-worktree
--ignore-skip-worktree-bits --source HEAD^'. In that case:

- you are restoring to the index
- you are *not* restoring to the worktree
- you're restoring files with SKIP_WORKTREE applied

Right now, SKIP_WORKTREE is removed from the matched entries, but suppose
(per your comment) it wasn't. That wouldn't mean that we've "modified the
working copy"; the working tree is defined with respect to the index, and if
the index entry says "I don't care about the worktree", then there are no
differences to reflect. 

This raises an interesting question about the current behavior, though: if
you restore a SKIP_WORKTREE entry with '--staged' and '--no-worktree',
should we remove SKIP_WORKTREE? I'd lean towards "no", but I'm interested to
hear other contributors' thoughts.

> That's different from how `git
> restore --staged` without sparse-checkout would have worked (it would not
> have updated the working copy). So from that perspective, it might make
> sense to remove the `SKIP_WORKTREE` and add the old file contents back in
> the working (i.e. from state A in this example), and maybe that's why the
> commands do that? 

It's important to avoid restoring a file to the worktree when it has
SKIP_WORKTREE enabled. See af6a51875a (repo_read_index: clear SKIP_WORKTREE
bit from files present in worktree, 2022-01-14) and the corresponding
discussion in [1].

[1] https://lore.kernel.org/all/pull.1114.v2.git.1642175983.gitgitgadget@xxxxxxxxx/

> Actually, `git checkout HEAD^ .` would update both the
> index and the working copy to match
> HEAD^, so that shouldn't have to remove the `SKIP_WORKTREE`, maybe?
> 





[Index of Archives]     [Linux Kernel Development]     [Gcc Help]     [IETF Annouce]     [DCCP]     [Netdev]     [Networking]     [Security]     [V4L]     [Bugtraq]     [Yosemite]     [MIPS Linux]     [ARM Linux]     [Linux Security]     [Linux RAID]     [Linux SCSI]     [Fedora Users]

  Powered by Linux