Merge branch 'jh/fsmonitor-icase-corner-case-fix'
FSMonitor client code was confused when FSEvents were given in a different case on a case-insensitive filesystem, which has been corrected. Acked-by: Patrick Steinhardt <ps@pks.im> cf. <ZehofMaSZyUq8S1N@tanuki> * jh/fsmonitor-icase-corner-case-fix: fsmonitor: support case-insensitive events fsmonitor: refactor bit invalidation in refresh callback fsmonitor: trace the new invalidated cache-entry count fsmonitor: return invalidated cache-entry count on non-directory event fsmonitor: remove custom loop from non-directory path handler fsmonitor: return invalidated cache-entry count on directory event fsmonitor: move untracked-cache invalidation into helper functions fsmonitor: refactor untracked-cache invalidation dir: create untracked_cache_invalidate_trimmed_path() fsmonitor: refactor refresh callback for non-directory events fsmonitor: clarify handling of directory events in callback helper fsmonitor: refactor refresh callback on directory events t7527: add case-insensitve test for FSMonitor name-hash: add index_dir_find()
This commit is contained in:
20
dir.c
20
dir.c
@ -3918,6 +3918,26 @@ void untracked_cache_invalidate_path(struct index_state *istate,
|
||||
path, strlen(path));
|
||||
}
|
||||
|
||||
void untracked_cache_invalidate_trimmed_path(struct index_state *istate,
|
||||
const char *path,
|
||||
int safe_path)
|
||||
{
|
||||
size_t len = strlen(path);
|
||||
|
||||
if (!len)
|
||||
BUG("untracked_cache_invalidate_trimmed_path given zero length path");
|
||||
|
||||
if (path[len - 1] != '/') {
|
||||
untracked_cache_invalidate_path(istate, path, safe_path);
|
||||
} else {
|
||||
struct strbuf tmp = STRBUF_INIT;
|
||||
|
||||
strbuf_add(&tmp, path, len - 1);
|
||||
untracked_cache_invalidate_path(istate, tmp.buf, safe_path);
|
||||
strbuf_release(&tmp);
|
||||
}
|
||||
}
|
||||
|
||||
void untracked_cache_remove_from_index(struct index_state *istate,
|
||||
const char *path)
|
||||
{
|
||||
|
7
dir.h
7
dir.h
@ -576,6 +576,13 @@ int cmp_dir_entry(const void *p1, const void *p2);
|
||||
int check_dir_entry_contains(const struct dir_entry *out, const struct dir_entry *in);
|
||||
|
||||
void untracked_cache_invalidate_path(struct index_state *, const char *, int safe_path);
|
||||
/*
|
||||
* Invalidate the untracked-cache for this path, but first strip
|
||||
* off a trailing slash, if present.
|
||||
*/
|
||||
void untracked_cache_invalidate_trimmed_path(struct index_state *,
|
||||
const char *path,
|
||||
int safe_path);
|
||||
void untracked_cache_remove_from_index(struct index_state *, const char *);
|
||||
void untracked_cache_add_to_index(struct index_state *, const char *);
|
||||
|
||||
|
328
fsmonitor.c
328
fsmonitor.c
@ -5,6 +5,7 @@
|
||||
#include "ewah/ewok.h"
|
||||
#include "fsmonitor.h"
|
||||
#include "fsmonitor-ipc.h"
|
||||
#include "name-hash.h"
|
||||
#include "run-command.h"
|
||||
#include "strbuf.h"
|
||||
#include "trace2.h"
|
||||
@ -183,79 +184,282 @@ static int query_fsmonitor_hook(struct repository *r,
|
||||
return result;
|
||||
}
|
||||
|
||||
/*
|
||||
* Invalidate the FSM bit on this CE. This is like mark_fsmonitor_invalid()
|
||||
* but we've already handled the untracked-cache, so let's not repeat that
|
||||
* work. This also lets us have a different trace message so that we can
|
||||
* see everything that was done as part of the refresh-callback.
|
||||
*/
|
||||
static void invalidate_ce_fsm(struct cache_entry *ce)
|
||||
{
|
||||
if (ce->ce_flags & CE_FSMONITOR_VALID) {
|
||||
trace_printf_key(&trace_fsmonitor,
|
||||
"fsmonitor_refresh_callback INV: '%s'",
|
||||
ce->name);
|
||||
ce->ce_flags &= ~CE_FSMONITOR_VALID;
|
||||
}
|
||||
}
|
||||
|
||||
static size_t handle_path_with_trailing_slash(
|
||||
struct index_state *istate, const char *name, int pos);
|
||||
|
||||
/*
|
||||
* Use the name-hash to do a case-insensitive cache-entry lookup with
|
||||
* the pathname and invalidate the cache-entry.
|
||||
*
|
||||
* Returns the number of cache-entries that we invalidated.
|
||||
*/
|
||||
static size_t handle_using_name_hash_icase(
|
||||
struct index_state *istate, const char *name)
|
||||
{
|
||||
struct cache_entry *ce = NULL;
|
||||
|
||||
ce = index_file_exists(istate, name, strlen(name), 1);
|
||||
if (!ce)
|
||||
return 0;
|
||||
|
||||
/*
|
||||
* A case-insensitive search in the name-hash using the
|
||||
* observed pathname found a cache-entry, so the observed path
|
||||
* is case-incorrect. Invalidate the cache-entry and use the
|
||||
* correct spelling from the cache-entry to invalidate the
|
||||
* untracked-cache. Since we now have sparse-directories in
|
||||
* the index, the observed pathname may represent a regular
|
||||
* file or a sparse-index directory.
|
||||
*
|
||||
* Note that we should not have seen FSEvents for a
|
||||
* sparse-index directory, but we handle it just in case.
|
||||
*
|
||||
* Either way, we know that there are not any cache-entries for
|
||||
* children inside the cone of the directory, so we don't need to
|
||||
* do the usual scan.
|
||||
*/
|
||||
trace_printf_key(&trace_fsmonitor,
|
||||
"fsmonitor_refresh_callback MAP: '%s' '%s'",
|
||||
name, ce->name);
|
||||
|
||||
/*
|
||||
* NEEDSWORK: We used the name-hash to find the correct
|
||||
* case-spelling of the pathname in the cache-entry[], so
|
||||
* technically this is a tracked file or a sparse-directory.
|
||||
* It should not have any entries in the untracked-cache, so
|
||||
* we should not need to use the case-corrected spelling to
|
||||
* invalidate the the untracked-cache. So we may not need to
|
||||
* do this. For now, I'm going to be conservative and always
|
||||
* do it; we can revisit this later.
|
||||
*/
|
||||
untracked_cache_invalidate_trimmed_path(istate, ce->name, 0);
|
||||
|
||||
invalidate_ce_fsm(ce);
|
||||
return 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Use the dir-name-hash to find the correct-case spelling of the
|
||||
* directory. Use the canonical spelling to invalidate all of the
|
||||
* cache-entries within the matching cone.
|
||||
*
|
||||
* Returns the number of cache-entries that we invalidated.
|
||||
*/
|
||||
static size_t handle_using_dir_name_hash_icase(
|
||||
struct index_state *istate, const char *name)
|
||||
{
|
||||
struct strbuf canonical_path = STRBUF_INIT;
|
||||
int pos;
|
||||
size_t len = strlen(name);
|
||||
size_t nr_in_cone;
|
||||
|
||||
if (name[len - 1] == '/')
|
||||
len--;
|
||||
|
||||
if (!index_dir_find(istate, name, len, &canonical_path))
|
||||
return 0; /* name is untracked */
|
||||
|
||||
if (!memcmp(name, canonical_path.buf, canonical_path.len)) {
|
||||
strbuf_release(&canonical_path);
|
||||
/*
|
||||
* NEEDSWORK: Our caller already tried an exact match
|
||||
* and failed to find one. They called us to do an
|
||||
* ICASE match, so we should never get an exact match,
|
||||
* so we could promote this to a BUG() here if we
|
||||
* wanted to. It doesn't hurt anything to just return
|
||||
* 0 and go on because we should never get here. Or we
|
||||
* could just get rid of the memcmp() and this "if"
|
||||
* clause completely.
|
||||
*/
|
||||
BUG("handle_using_dir_name_hash_icase(%s) did not exact match",
|
||||
name);
|
||||
}
|
||||
|
||||
trace_printf_key(&trace_fsmonitor,
|
||||
"fsmonitor_refresh_callback MAP: '%s' '%s'",
|
||||
name, canonical_path.buf);
|
||||
|
||||
/*
|
||||
* The dir-name-hash only tells us the corrected spelling of
|
||||
* the prefix. We have to use this canonical path to do a
|
||||
* lookup in the cache-entry array so that we repeat the
|
||||
* original search using the case-corrected spelling.
|
||||
*/
|
||||
strbuf_addch(&canonical_path, '/');
|
||||
pos = index_name_pos(istate, canonical_path.buf,
|
||||
canonical_path.len);
|
||||
nr_in_cone = handle_path_with_trailing_slash(
|
||||
istate, canonical_path.buf, pos);
|
||||
strbuf_release(&canonical_path);
|
||||
return nr_in_cone;
|
||||
}
|
||||
|
||||
/*
|
||||
* The daemon sent an observed pathname without a trailing slash.
|
||||
* (This is the normal case.) We do not know if it is a tracked or
|
||||
* untracked file, a sparse-directory, or a populated directory (on a
|
||||
* platform such as Windows where FSEvents are not qualified).
|
||||
*
|
||||
* The pathname contains the observed case reported by the FS. We
|
||||
* do not know it is case-correct or -incorrect.
|
||||
*
|
||||
* Assume it is case-correct and try an exact match.
|
||||
*
|
||||
* Return the number of cache-entries that we invalidated.
|
||||
*/
|
||||
static size_t handle_path_without_trailing_slash(
|
||||
struct index_state *istate, const char *name, int pos)
|
||||
{
|
||||
/*
|
||||
* Mark the untracked cache dirty for this path (regardless of
|
||||
* whether or not we find an exact match for it in the index).
|
||||
* Since the path is unqualified (no trailing slash hint in the
|
||||
* FSEvent), it may refer to a file or directory. So we should
|
||||
* not assume one or the other and should always let the untracked
|
||||
* cache decide what needs to invalidated.
|
||||
*/
|
||||
untracked_cache_invalidate_trimmed_path(istate, name, 0);
|
||||
|
||||
if (pos >= 0) {
|
||||
/*
|
||||
* An exact match on a tracked file. We assume that we
|
||||
* do not need to scan forward for a sparse-directory
|
||||
* cache-entry with the same pathname, nor for a cone
|
||||
* at that directory. (That is, assume no D/F conflicts.)
|
||||
*/
|
||||
invalidate_ce_fsm(istate->cache[pos]);
|
||||
return 1;
|
||||
} else {
|
||||
size_t nr_in_cone;
|
||||
struct strbuf work_path = STRBUF_INIT;
|
||||
|
||||
/*
|
||||
* The negative "pos" gives us the suggested insertion
|
||||
* point for the pathname (without the trailing slash).
|
||||
* We need to see if there is a directory with that
|
||||
* prefix, but there can be lots of pathnames between
|
||||
* "foo" and "foo/" like "foo-" or "foo-bar", so we
|
||||
* don't want to do our own scan.
|
||||
*/
|
||||
strbuf_add(&work_path, name, strlen(name));
|
||||
strbuf_addch(&work_path, '/');
|
||||
pos = index_name_pos(istate, work_path.buf, work_path.len);
|
||||
nr_in_cone = handle_path_with_trailing_slash(
|
||||
istate, work_path.buf, pos);
|
||||
strbuf_release(&work_path);
|
||||
return nr_in_cone;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* The daemon can decorate directory events, such as a move or rename,
|
||||
* by adding a trailing slash to the observed name. Use this to
|
||||
* explicitly invalidate the entire cone under that directory.
|
||||
*
|
||||
* The daemon can only reliably do that if the OS FSEvent contains
|
||||
* sufficient information in the event.
|
||||
*
|
||||
* macOS FSEvents have enough information.
|
||||
*
|
||||
* Other platforms may or may not be able to do it (and it might
|
||||
* depend on the type of event (for example, a daemon could lstat() an
|
||||
* observed pathname after a rename, but not after a delete)).
|
||||
*
|
||||
* If we find an exact match in the index for a path with a trailing
|
||||
* slash, it means that we matched a sparse-index directory in a
|
||||
* cone-mode sparse-checkout (since that's the only time we have
|
||||
* directories in the index). We should never see this in practice
|
||||
* (because sparse directories should not be present and therefore
|
||||
* not generating FS events). Either way, we can treat them in the
|
||||
* same way and just invalidate the cache-entry and the untracked
|
||||
* cache (and in this case, the forward cache-entry scan won't find
|
||||
* anything and it doesn't hurt to let it run).
|
||||
*
|
||||
* Return the number of cache-entries that we invalidated. We will
|
||||
* use this later to determine if we need to attempt a second
|
||||
* case-insensitive search on case-insensitive file systems. That is,
|
||||
* if the search using the observed-case in the FSEvent yields any
|
||||
* results, we assume the prefix is case-correct. If there are no
|
||||
* matches, we still don't know if the observed path is simply
|
||||
* untracked or case-incorrect.
|
||||
*/
|
||||
static size_t handle_path_with_trailing_slash(
|
||||
struct index_state *istate, const char *name, int pos)
|
||||
{
|
||||
int i;
|
||||
size_t nr_in_cone = 0;
|
||||
|
||||
/*
|
||||
* Mark the untracked cache dirty for this directory path
|
||||
* (regardless of whether or not we find an exact match for it
|
||||
* in the index or find it to be proper prefix of one or more
|
||||
* files in the index), since the FSEvent is hinting that
|
||||
* there may be changes on or within the directory.
|
||||
*/
|
||||
untracked_cache_invalidate_trimmed_path(istate, name, 0);
|
||||
|
||||
if (pos < 0)
|
||||
pos = -pos - 1;
|
||||
|
||||
/* Mark all entries for the folder invalid */
|
||||
for (i = pos; i < istate->cache_nr; i++) {
|
||||
if (!starts_with(istate->cache[i]->name, name))
|
||||
break;
|
||||
invalidate_ce_fsm(istate->cache[i]);
|
||||
nr_in_cone++;
|
||||
}
|
||||
|
||||
return nr_in_cone;
|
||||
}
|
||||
|
||||
static void fsmonitor_refresh_callback(struct index_state *istate, char *name)
|
||||
{
|
||||
int i, len = strlen(name);
|
||||
int len = strlen(name);
|
||||
int pos = index_name_pos(istate, name, len);
|
||||
size_t nr_in_cone;
|
||||
|
||||
trace_printf_key(&trace_fsmonitor,
|
||||
"fsmonitor_refresh_callback '%s' (pos %d)",
|
||||
name, pos);
|
||||
|
||||
if (name[len - 1] == '/') {
|
||||
/*
|
||||
* The daemon can decorate directory events, such as
|
||||
* moves or renames, with a trailing slash if the OS
|
||||
* FS Event contains sufficient information, such as
|
||||
* MacOS.
|
||||
*
|
||||
* Use this to invalidate the entire cone under that
|
||||
* directory.
|
||||
*
|
||||
* We do not expect an exact match because the index
|
||||
* does not normally contain directory entries, so we
|
||||
* start at the insertion point and scan.
|
||||
*/
|
||||
if (pos < 0)
|
||||
pos = -pos - 1;
|
||||
|
||||
/* Mark all entries for the folder invalid */
|
||||
for (i = pos; i < istate->cache_nr; i++) {
|
||||
if (!starts_with(istate->cache[i]->name, name))
|
||||
break;
|
||||
istate->cache[i]->ce_flags &= ~CE_FSMONITOR_VALID;
|
||||
}
|
||||
|
||||
/*
|
||||
* We need to remove the traling "/" from the path
|
||||
* for the untracked cache.
|
||||
*/
|
||||
name[len - 1] = '\0';
|
||||
} else if (pos >= 0) {
|
||||
/*
|
||||
* We have an exact match for this path and can just
|
||||
* invalidate it.
|
||||
*/
|
||||
istate->cache[pos]->ce_flags &= ~CE_FSMONITOR_VALID;
|
||||
} else {
|
||||
/*
|
||||
* The path is not a tracked file -or- it is a
|
||||
* directory event on a platform that cannot
|
||||
* distinguish between file and directory events in
|
||||
* the event handler, such as Windows.
|
||||
*
|
||||
* Scan as if it is a directory and invalidate the
|
||||
* cone under it. (But remember to ignore items
|
||||
* between "name" and "name/", such as "name-" and
|
||||
* "name.".
|
||||
*/
|
||||
pos = -pos - 1;
|
||||
|
||||
for (i = pos; i < istate->cache_nr; i++) {
|
||||
if (!starts_with(istate->cache[i]->name, name))
|
||||
break;
|
||||
if ((unsigned char)istate->cache[i]->name[len] > '/')
|
||||
break;
|
||||
if (istate->cache[i]->name[len] == '/')
|
||||
istate->cache[i]->ce_flags &= ~CE_FSMONITOR_VALID;
|
||||
}
|
||||
}
|
||||
if (name[len - 1] == '/')
|
||||
nr_in_cone = handle_path_with_trailing_slash(istate, name, pos);
|
||||
else
|
||||
nr_in_cone = handle_path_without_trailing_slash(istate, name, pos);
|
||||
|
||||
/*
|
||||
* Mark the untracked cache dirty even if it wasn't found in the index
|
||||
* as it could be a new untracked file.
|
||||
* If we did not find an exact match for this pathname or any
|
||||
* cache-entries with this directory prefix and we're on a
|
||||
* case-insensitive file system, try again using the name-hash
|
||||
* and dir-name-hash.
|
||||
*/
|
||||
untracked_cache_invalidate_path(istate, name, 0);
|
||||
if (!nr_in_cone && ignore_case) {
|
||||
nr_in_cone = handle_using_name_hash_icase(istate, name);
|
||||
if (!nr_in_cone)
|
||||
nr_in_cone = handle_using_dir_name_hash_icase(
|
||||
istate, name);
|
||||
}
|
||||
|
||||
if (nr_in_cone)
|
||||
trace_printf_key(&trace_fsmonitor,
|
||||
"fsmonitor_refresh_callback CNT: %d",
|
||||
(int)nr_in_cone);
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -685,13 +685,20 @@ static int same_name(const struct cache_entry *ce, const char *name, int namelen
|
||||
return slow_same_name(name, namelen, ce->name, len);
|
||||
}
|
||||
|
||||
int index_dir_exists(struct index_state *istate, const char *name, int namelen)
|
||||
int index_dir_find(struct index_state *istate, const char *name, int namelen,
|
||||
struct strbuf *canonical_path)
|
||||
{
|
||||
struct dir_entry *dir;
|
||||
|
||||
lazy_init_name_hash(istate);
|
||||
expand_to_path(istate, name, namelen, 0);
|
||||
dir = find_dir_entry(istate, name, namelen);
|
||||
|
||||
if (canonical_path && dir && dir->nr) {
|
||||
strbuf_reset(canonical_path);
|
||||
strbuf_add(canonical_path, dir->name, dir->namelen);
|
||||
}
|
||||
|
||||
return dir && dir->nr;
|
||||
}
|
||||
|
||||
|
@ -4,7 +4,12 @@
|
||||
struct cache_entry;
|
||||
struct index_state;
|
||||
|
||||
int index_dir_exists(struct index_state *istate, const char *name, int namelen);
|
||||
|
||||
int index_dir_find(struct index_state *istate, const char *name, int namelen,
|
||||
struct strbuf *canonical_path);
|
||||
|
||||
#define index_dir_exists(i, n, l) index_dir_find((i), (n), (l), NULL)
|
||||
|
||||
void adjust_dirname_case(struct index_state *istate, char *name);
|
||||
struct cache_entry *index_file_exists(struct index_state *istate, const char *name, int namelen, int igncase);
|
||||
|
||||
|
@ -1037,4 +1037,227 @@ test_expect_success 'split-index and FSMonitor work well together' '
|
||||
)
|
||||
'
|
||||
|
||||
# The FSMonitor daemon reports the OBSERVED pathname of modified files
|
||||
# and thus contains the OBSERVED spelling on case-insensitive file
|
||||
# systems. The daemon does not (and should not) load the .git/index
|
||||
# file and therefore does not know the expected case-spelling. Since
|
||||
# it is possible for the user to create files/subdirectories with the
|
||||
# incorrect case, a modified file event for a tracked will not have
|
||||
# the EXPECTED case. This can cause `index_name_pos()` to incorrectly
|
||||
# report that the file is untracked. This causes the client to fail to
|
||||
# mark the file as possibly dirty (keeping the CE_FSMONITOR_VALID bit
|
||||
# set) so that `git status` will avoid inspecting it and thus not
|
||||
# present in the status output.
|
||||
#
|
||||
# The setup is a little contrived.
|
||||
#
|
||||
test_expect_success CASE_INSENSITIVE_FS 'fsmonitor subdir case wrong on disk' '
|
||||
test_when_finished "stop_daemon_delete_repo subdir_case_wrong" &&
|
||||
|
||||
git init subdir_case_wrong &&
|
||||
(
|
||||
cd subdir_case_wrong &&
|
||||
echo x >AAA &&
|
||||
echo x >BBB &&
|
||||
|
||||
mkdir dir1 &&
|
||||
echo x >dir1/file1 &&
|
||||
mkdir dir1/dir2 &&
|
||||
echo x >dir1/dir2/file2 &&
|
||||
mkdir dir1/dir2/dir3 &&
|
||||
echo x >dir1/dir2/dir3/file3 &&
|
||||
|
||||
echo x >yyy &&
|
||||
echo x >zzz &&
|
||||
git add . &&
|
||||
git commit -m "data" &&
|
||||
|
||||
# This will cause "dir1/" and everything under it
|
||||
# to be deleted.
|
||||
git sparse-checkout set --cone --sparse-index &&
|
||||
|
||||
# Create dir2 with the wrong case and then let Git
|
||||
# repopulate dir3 -- it will not correct the spelling
|
||||
# of dir2.
|
||||
mkdir dir1 &&
|
||||
mkdir dir1/DIR2 &&
|
||||
git sparse-checkout add dir1/dir2/dir3
|
||||
) &&
|
||||
|
||||
start_daemon -C subdir_case_wrong --tf "$PWD/subdir_case_wrong.trace" &&
|
||||
|
||||
# Enable FSMonitor in the client. Run enough commands for
|
||||
# the .git/index to sync up with the daemon with everything
|
||||
# marked clean.
|
||||
git -C subdir_case_wrong config core.fsmonitor true &&
|
||||
git -C subdir_case_wrong update-index --fsmonitor &&
|
||||
git -C subdir_case_wrong status &&
|
||||
|
||||
# Make some files dirty so that FSMonitor gets FSEvents for
|
||||
# each of them.
|
||||
echo xx >>subdir_case_wrong/AAA &&
|
||||
echo xx >>subdir_case_wrong/dir1/DIR2/dir3/file3 &&
|
||||
echo xx >>subdir_case_wrong/zzz &&
|
||||
|
||||
GIT_TRACE_FSMONITOR="$PWD/subdir_case_wrong.log" \
|
||||
git -C subdir_case_wrong --no-optional-locks status --short \
|
||||
>"$PWD/subdir_case_wrong.out" &&
|
||||
|
||||
# "git status" should have gotten file events for each of
|
||||
# the 3 files.
|
||||
#
|
||||
# "dir2" should be in the observed case on disk.
|
||||
grep "fsmonitor_refresh_callback" \
|
||||
<"$PWD/subdir_case_wrong.log" \
|
||||
>"$PWD/subdir_case_wrong.log1" &&
|
||||
|
||||
grep -q "AAA.*pos 0" "$PWD/subdir_case_wrong.log1" &&
|
||||
grep -q "zzz.*pos 6" "$PWD/subdir_case_wrong.log1" &&
|
||||
|
||||
grep -q "dir1/DIR2/dir3/file3.*pos -3" "$PWD/subdir_case_wrong.log1" &&
|
||||
|
||||
# Verify that we get a mapping event to correct the case.
|
||||
grep -q "MAP:.*dir1/DIR2/dir3/file3.*dir1/dir2/dir3/file3" \
|
||||
"$PWD/subdir_case_wrong.log1" &&
|
||||
|
||||
# The refresh-callbacks should have caused "git status" to clear
|
||||
# the CE_FSMONITOR_VALID bit on each of those files and caused
|
||||
# the worktree scan to visit them and mark them as modified.
|
||||
grep -q " M AAA" "$PWD/subdir_case_wrong.out" &&
|
||||
grep -q " M zzz" "$PWD/subdir_case_wrong.out" &&
|
||||
grep -q " M dir1/dir2/dir3/file3" "$PWD/subdir_case_wrong.out"
|
||||
'
|
||||
|
||||
test_expect_success CASE_INSENSITIVE_FS 'fsmonitor file case wrong on disk' '
|
||||
test_when_finished "stop_daemon_delete_repo file_case_wrong" &&
|
||||
|
||||
git init file_case_wrong &&
|
||||
(
|
||||
cd file_case_wrong &&
|
||||
echo x >AAA &&
|
||||
echo x >BBB &&
|
||||
|
||||
mkdir dir1 &&
|
||||
mkdir dir1/dir2 &&
|
||||
mkdir dir1/dir2/dir3 &&
|
||||
echo x >dir1/dir2/dir3/FILE-3-B &&
|
||||
echo x >dir1/dir2/dir3/XXXX-3-X &&
|
||||
echo x >dir1/dir2/dir3/file-3-a &&
|
||||
echo x >dir1/dir2/dir3/yyyy-3-y &&
|
||||
mkdir dir1/dir2/dir4 &&
|
||||
echo x >dir1/dir2/dir4/FILE-4-A &&
|
||||
echo x >dir1/dir2/dir4/XXXX-4-X &&
|
||||
echo x >dir1/dir2/dir4/file-4-b &&
|
||||
echo x >dir1/dir2/dir4/yyyy-4-y &&
|
||||
|
||||
echo x >yyy &&
|
||||
echo x >zzz &&
|
||||
git add . &&
|
||||
git commit -m "data"
|
||||
) &&
|
||||
|
||||
start_daemon -C file_case_wrong --tf "$PWD/file_case_wrong.trace" &&
|
||||
|
||||
# Enable FSMonitor in the client. Run enough commands for
|
||||
# the .git/index to sync up with the daemon with everything
|
||||
# marked clean.
|
||||
git -C file_case_wrong config core.fsmonitor true &&
|
||||
git -C file_case_wrong update-index --fsmonitor &&
|
||||
git -C file_case_wrong status &&
|
||||
|
||||
# Make some files dirty so that FSMonitor gets FSEvents for
|
||||
# each of them.
|
||||
echo xx >>file_case_wrong/AAA &&
|
||||
echo xx >>file_case_wrong/zzz &&
|
||||
|
||||
# Rename some files so that FSMonitor sees a create and delete
|
||||
# FSEvent for each. (A simple "mv foo FOO" is not portable
|
||||
# between macOS and Windows. It works on both platforms, but makes
|
||||
# the test messy, since (1) one platform updates "ctime" on the
|
||||
# moved file and one does not and (2) it causes a directory event
|
||||
# on one platform and not on the other which causes additional
|
||||
# scanning during "git status" which causes a "H" vs "h" discrepancy
|
||||
# in "git ls-files -f".) So old-school it and move it out of the
|
||||
# way and copy it to the case-incorrect name so that we get fresh
|
||||
# "ctime" and "mtime" values.
|
||||
|
||||
mv file_case_wrong/dir1/dir2/dir3/file-3-a file_case_wrong/dir1/dir2/dir3/ORIG &&
|
||||
cp file_case_wrong/dir1/dir2/dir3/ORIG file_case_wrong/dir1/dir2/dir3/FILE-3-A &&
|
||||
rm file_case_wrong/dir1/dir2/dir3/ORIG &&
|
||||
mv file_case_wrong/dir1/dir2/dir4/FILE-4-A file_case_wrong/dir1/dir2/dir4/ORIG &&
|
||||
cp file_case_wrong/dir1/dir2/dir4/ORIG file_case_wrong/dir1/dir2/dir4/file-4-a &&
|
||||
rm file_case_wrong/dir1/dir2/dir4/ORIG &&
|
||||
|
||||
# Run status enough times to fully sync.
|
||||
#
|
||||
# The first instance should get the create and delete FSEvents
|
||||
# for each pair. Status should update the index with a new FSM
|
||||
# token (so the next invocation will not see data for these
|
||||
# events).
|
||||
|
||||
GIT_TRACE_FSMONITOR="$PWD/file_case_wrong-try1.log" \
|
||||
git -C file_case_wrong status --short \
|
||||
>"$PWD/file_case_wrong-try1.out" &&
|
||||
grep -q "fsmonitor_refresh_callback.*FILE-3-A.*pos -3" "$PWD/file_case_wrong-try1.log" &&
|
||||
grep -q "fsmonitor_refresh_callback.*file-3-a.*pos 4" "$PWD/file_case_wrong-try1.log" &&
|
||||
grep -q "fsmonitor_refresh_callback.*FILE-4-A.*pos 6" "$PWD/file_case_wrong-try1.log" &&
|
||||
grep -q "fsmonitor_refresh_callback.*file-4-a.*pos -9" "$PWD/file_case_wrong-try1.log" &&
|
||||
|
||||
# FSM refresh will have invalidated the FSM bit and cause a regular
|
||||
# (real) scan of these tracked files, so they should have "H" status.
|
||||
# (We will not see a "h" status until the next refresh (on the next
|
||||
# command).)
|
||||
|
||||
git -C file_case_wrong ls-files -f >"$PWD/file_case_wrong-lsf1.out" &&
|
||||
grep -q "H dir1/dir2/dir3/file-3-a" "$PWD/file_case_wrong-lsf1.out" &&
|
||||
grep -q "H dir1/dir2/dir4/FILE-4-A" "$PWD/file_case_wrong-lsf1.out" &&
|
||||
|
||||
|
||||
# Try the status again. We assume that the above status command
|
||||
# advanced the token so that the next one will not see those events.
|
||||
|
||||
GIT_TRACE_FSMONITOR="$PWD/file_case_wrong-try2.log" \
|
||||
git -C file_case_wrong status --short \
|
||||
>"$PWD/file_case_wrong-try2.out" &&
|
||||
! grep -q "fsmonitor_refresh_callback.*FILE-3-A.*pos" "$PWD/file_case_wrong-try2.log" &&
|
||||
! grep -q "fsmonitor_refresh_callback.*file-3-a.*pos" "$PWD/file_case_wrong-try2.log" &&
|
||||
! grep -q "fsmonitor_refresh_callback.*FILE-4-A.*pos" "$PWD/file_case_wrong-try2.log" &&
|
||||
! grep -q "fsmonitor_refresh_callback.*file-4-a.*pos" "$PWD/file_case_wrong-try2.log" &&
|
||||
|
||||
# FSM refresh saw nothing, so it will mark all files as valid,
|
||||
# so they should now have "h" status.
|
||||
|
||||
git -C file_case_wrong ls-files -f >"$PWD/file_case_wrong-lsf2.out" &&
|
||||
grep -q "h dir1/dir2/dir3/file-3-a" "$PWD/file_case_wrong-lsf2.out" &&
|
||||
grep -q "h dir1/dir2/dir4/FILE-4-A" "$PWD/file_case_wrong-lsf2.out" &&
|
||||
|
||||
|
||||
# We now have files with clean content, but with case-incorrect
|
||||
# file names. Modify them to see if status properly reports
|
||||
# them.
|
||||
|
||||
echo xx >>file_case_wrong/dir1/dir2/dir3/FILE-3-A &&
|
||||
echo xx >>file_case_wrong/dir1/dir2/dir4/file-4-a &&
|
||||
|
||||
GIT_TRACE_FSMONITOR="$PWD/file_case_wrong-try3.log" \
|
||||
git -C file_case_wrong --no-optional-locks status --short \
|
||||
>"$PWD/file_case_wrong-try3.out" &&
|
||||
|
||||
# Verify that we get a mapping event to correct the case.
|
||||
grep -q "fsmonitor_refresh_callback MAP:.*dir1/dir2/dir3/FILE-3-A.*dir1/dir2/dir3/file-3-a" \
|
||||
"$PWD/file_case_wrong-try3.log" &&
|
||||
grep -q "fsmonitor_refresh_callback MAP:.*dir1/dir2/dir4/file-4-a.*dir1/dir2/dir4/FILE-4-A" \
|
||||
"$PWD/file_case_wrong-try3.log" &&
|
||||
|
||||
# FSEvents are in observed case.
|
||||
grep -q "fsmonitor_refresh_callback.*FILE-3-A.*pos -3" "$PWD/file_case_wrong-try3.log" &&
|
||||
grep -q "fsmonitor_refresh_callback.*file-4-a.*pos -9" "$PWD/file_case_wrong-try3.log" &&
|
||||
|
||||
# The refresh-callbacks should have caused "git status" to clear
|
||||
# the CE_FSMONITOR_VALID bit on each of those files and caused
|
||||
# the worktree scan to visit them and mark them as modified.
|
||||
grep -q " M dir1/dir2/dir3/file-3-a" "$PWD/file_case_wrong-try3.out" &&
|
||||
grep -q " M dir1/dir2/dir4/FILE-4-A" "$PWD/file_case_wrong-try3.out"
|
||||
'
|
||||
|
||||
test_done
|
||||
|
Reference in New Issue
Block a user