Skip to content

Commit 0822957

Browse files
Kevin Willforddscho
authored andcommitted
reset: fix mixed reset when using virtual filesystem
During the 2.35.0 rebase, we ejected 570f64b (Fix reset when using the sparse-checkout feature., 2017-03-15) because of a similar change upstream that actually works with the expected behavior of sparse-checkout. That commit only ever existed in microsoft/git, but when it was considered for upstream we realized that it behaved strangely for a sparse-checkout scenario. The root problem is that during a mixed reset, 'git reset <commit>' updates the index to aggree with <commit> but leaves the worktree the same as it was before. The issue with sparse-checkout is that some files might not be in the worktree and thus the information from those files would be "lost". The upstream decision was to leave these files as ignored, because that's what the SKIP_WORKTREE bit means: don't put these files in the worktree and ignore their contents. If there already were files in the worktree, then Git does not change them. The case for "losing" data is if a committed change outside of the sparse-checkout was in the previous HEAD position. However, this information could be recovered from the reflog. The case where this is different is in a virtualized filesystem. The virtualization is projecting the index contents onto the filesystem, so we need to do something different here. In a virtual environment, every file is considered "important" and we abuse the SKIP_WORKTREE bit to indicate that Git does not need to process a projected file. When a file is populated, the virtual filesystem hook provides the information for removing the SKIP_WORKTREE bit. In the case of these mixed resets, we have the issue where we change the projection of the worktree for these cache entries that change. If a file is populated in the worktree, then the populated file will persist and appear in a follow-up 'git status'. However, if the file is not populated and only projected, we change the projection from the current value to the new value, leaving a clean 'git status'. The previous version of this commit includes a call to checkout_entry(), which populates the file. This causes the file to be actually in the working tree and no longer projected. To make this work with the upstream changes, stop setting the skip-worktree bit for the new cache entry. This seemed to work fine without this change, but it's likely due to some indirection with the virtual filesystem. Better to do the best-possible thing here so we don't hide a corner-case bug by accident. Helped-by: Victoria Dye <vdye@github.com> Signed-off-by: Kevin Willford <kewillf@microsoft.com> Signed-off-by: Derrick Stolee <derrickstolee@github.com>
1 parent fe5c6a7 commit 0822957

File tree

1 file changed

+49
-2
lines changed

1 file changed

+49
-2
lines changed

builtin/reset.c

Lines changed: 49 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@
3838
#include "add-interactive.h"
3939
#include "strbuf.h"
4040
#include "quote.h"
41+
#include "dir.h"
42+
#include "entry.h"
4143

4244
#define REFRESH_INDEX_DELAY_WARNING_IN_MS (2 * 1000)
4345

@@ -158,9 +160,48 @@ static void update_index_from_diff(struct diff_queue_struct *q,
158160

159161
for (i = 0; i < q->nr; i++) {
160162
int pos;
163+
int respect_skip_worktree = 1;
161164
struct diff_filespec *one = q->queue[i]->one;
165+
struct diff_filespec *two = q->queue[i]->two;
162166
int is_in_reset_tree = one->mode && !is_null_oid(&one->oid);
167+
int is_missing = !(one->mode && !is_null_oid(&one->oid));
168+
int was_missing = !two->mode && is_null_oid(&two->oid);
163169
struct cache_entry *ce;
170+
struct cache_entry *ceBefore;
171+
struct checkout state = CHECKOUT_INIT;
172+
173+
/*
174+
* When using the virtual filesystem feature, the cache entries that are
175+
* added here will not have the skip-worktree bit set.
176+
*
177+
* Without this code there is data that is lost because the files that
178+
* would normally be in the working directory are not there and show as
179+
* deleted for the next status or in the case of added files just disappear.
180+
* We need to create the previous version of the files in the working
181+
* directory so that they will have the right content and the next
182+
* status call will show modified or untracked files correctly.
183+
*/
184+
if (core_virtualfilesystem && !file_exists(two->path))
185+
{
186+
respect_skip_worktree = 0;
187+
pos = index_name_pos(the_repository->index, two->path, strlen(two->path));
188+
189+
if ((pos >= 0 && ce_skip_worktree(the_repository->index->cache[pos])) &&
190+
(is_missing || !was_missing))
191+
{
192+
state.force = 1;
193+
state.refresh_cache = 1;
194+
state.istate = the_repository->index;
195+
ceBefore = make_cache_entry(the_repository->index, two->mode,
196+
&two->oid, two->path,
197+
0, 0);
198+
if (!ceBefore)
199+
die(_("make_cache_entry failed for path '%s'"),
200+
two->path);
201+
202+
checkout_entry(ceBefore, &state, NULL, NULL);
203+
}
204+
}
164205

165206
if (!is_in_reset_tree && !intent_to_add) {
166207
remove_file_from_index(the_repository->index, one->path);
@@ -179,8 +220,14 @@ static void update_index_from_diff(struct diff_queue_struct *q,
179220
* to properly construct the reset sparse directory.
180221
*/
181222
pos = index_name_pos(the_repository->index, one->path, strlen(one->path));
182-
if ((pos >= 0 && ce_skip_worktree(the_repository->index->cache[pos])) ||
183-
(pos < 0 && !path_in_sparse_checkout(one->path, the_repository->index)))
223+
224+
/*
225+
* Do not add the SKIP_WORKTREE bit back if we populated the
226+
* file on purpose in a virtual filesystem scenario.
227+
*/
228+
if (respect_skip_worktree &&
229+
((pos >= 0 && ce_skip_worktree(the_repository->index->cache[pos])) ||
230+
(pos < 0 && !path_in_sparse_checkout(one->path, the_repository->index))))
184231
ce->ce_flags |= CE_SKIP_WORKTREE;
185232

186233
if (!ce)

0 commit comments

Comments
 (0)