Builtin FSMonitor Part 3
PATCH v6 2/28 t7527: test FSMonitor on repos with Unicode root paths
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create some test repos with UTF8 characters in the pathname of the root directory and verify that the builtin FSMonitor can watch them.
This test is mainly for Windows where we need to avoid *A() routines.
--- t/t7527-builtin-fsmonitor.sh | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+)
t/t7527-builtin-fsmonitor.sh +23 -0
@@ -671,4 +671,27 @@ do done done +# Test Unicode UTF-8 characters in the pathname of the working +# directory root. Use of "*A()" routines rather than "*W()" routines +# on Windows can sometimes lead to odd failures. +# +u1=$(printf "u_c3_a6__\xC3\xA6") +u2=$(printf "u_e2_99_ab__\xE2\x99\xAB") +u_values="$u1 $u2" +for u in $u_values +do + test_expect_success "Unicode in repo root path: $u" ' + test_when_finished "stop_daemon_delete_repo $u" && + + git init "$u" && + echo 1 >"$u"/file1 && + git -C "$u" add file1 && + git -C "$u" config core.fsmonitor true && + + start_daemon -C "$u" && + git -C "$u" status >actual && + grep "new file: file1" actual + ' +done + test_done
Signature · 1 line
gitgitgadget
PATCH v6 3/28 t/helper/fsmonitor-client: create stress test
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create a stress test to hammer on the fsmonitor daemon. Create a client-side thread pool of n threads and have each of them make m requests as fast as they can.
We do not currently inspect the contents of the response. We're only interested in placing a heavy request load on the daemon.
This test is useful for interactive testing and various experimentation. For example, to place additional load on the daemon while another test is running. We currently do not have a test script that actually uses this helper. We might add such a test in the future.
--- t/helper/test-fsmonitor-client.c | 106 +++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+)
t/helper/test-fsmonitor-client.c +106 -0
@@ -7,6 +7,8 @@#include "cache.h" #include "parse-options.h" #include "fsmonitor-ipc.h" +#include "thread-utils.h" +#include "trace2.h" #ifndef HAVE_FSMONITOR_DAEMON_BACKEND int cmd__fsmonitor_client (int argc,const char **argv)
@@ -79,20 +81,121 @@ static int do_send_flush(void)return 0 ; } +struct hammer_thread_data +{ +pthread_t pthread_id; +int thread_nr; + +int nr_requests; +const char *token; + +int sum_successful; +int sum_errors; +}; + +static void *hammer_thread_proc (void *_hammer_thread_data) +{ +struct hammer_thread_data *data = _hammer_thread_data; +struct strbuf answer =STRBUF_INIT ; +int k; +int ret; + +trace2_thread_start ("hammer" ); + +for (k =0 ; k < data->nr_requests; k++) { +strbuf_reset (&answer); + + ret =fsmonitor_ipc__send_query (data->token, &answer); +if (ret <0 ) + data->sum_errors++; +else + data->sum_successful++; + } + +strbuf_release (&answer); +trace2_thread_exit (); +return NULL ; +} + +/* +* Start a pool of client threads that will each send a series of +* commands to the daemon. +* +* The goal is to overload the daemon with a sustained series of +* concurrent requests. +*/ +static int do_hammer (const char *token,int nr_threads,int nr_requests) +{ +struct hammer_thread_data *data =NULL ; +int k; +int sum_join_errors =0 ; +int sum_commands =0 ; +int sum_errors =0 ; + +if (!token || !*token) + token =get_token_from_index (); +if (nr_threads <1 ) + nr_threads =1 ; +if (nr_requests <1 ) + nr_requests =1 ; + +CALLOC_ARRAY (data, nr_threads); + +for (k =0 ; k < nr_threads; k++) { +struct hammer_thread_data *p = &data[k]; + p->thread_nr = k; + p->nr_requests = nr_requests; + p->token = token; + +if (pthread_create (&p->pthread_id,NULL , hammer_thread_proc, p)) { +warning ("failed to create thread[%d] skipping remainder" , k); + nr_threads = k; +break ; + } + } + +for (k =0 ; k < nr_threads; k++) { +struct hammer_thread_data *p = &data[k]; + +if (pthread_join (p->pthread_id,NULL )) + sum_join_errors++; + sum_commands += p->sum_successful; + sum_errors += p->sum_errors; + } + +fprintf (stderr,"HAMMER: [threads %d][requests %d] [ok %d][err %d][join %d]\n" , + nr_threads, nr_requests, sum_commands, sum_errors, sum_join_errors); + +free (data); + +/* +* Return an error if any of the _send_query requests failed. +* We don't care about thread create/join errors. +*/ +return sum_errors >0 ; +} +int cmd__fsmonitor_client (int argc,const char **argv) {const char *subcmd;const char *token =NULL ; +int nr_threads =1 ; +int nr_requests =1 ;const char *const fsmonitor_client_usage[] = {"test-tool fsmonitor-client query [<token>]" ,"test-tool fsmonitor-client flush" , +"test-tool fsmonitor-client hammer [<token>] [<threads>] [<requests>]" ,NULL , };struct option options[] = {OPT_STRING (0 ,"token" , &token,"token" ,"command token to send to the server" ), + +OPT_INTEGER (0 ,"threads" , &nr_threads,"number of client threads" ), +OPT_INTEGER (0 ,"requests" , &nr_requests,"number of requests per thread" ), +OPT_END () };
@@ -111,6 +214,9 @@ int cmd__fsmonitor_client(int argc, const char **argv)if (!strcmp (subcmd,"flush" ))return !!do_send_flush (); +if (!strcmp (subcmd,"hammer" )) +return !!do_hammer (token, nr_threads, nr_requests); + die("Unhandled subcommand: '%s'", subcmd); }#endif
Signature · 1 line
gitgitgadget
PATCH v6 4/28 fsmonitor-settings: bare repos are incompatible with FSMonitor
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Bare repos do not have a worktree, so there is nothing for the daemon watch.
--- builtin/fsmonitor--daemon.c | 18 +++++ builtin/update-index.c | 16 +++++ fsmonitor-settings.c | 133 ++++++++++++++++++++++++++++++------ fsmonitor-settings.h | 16 +++++ t/t7519-status-fsmonitor.sh | 23 +++++++ 5 files changed, 186 insertions(+), 20 deletions(-)
builtin/fsmonitor--daemon.c +18 -0
@@ -1423,6 +1423,7 @@ static int try_to_start_background_daemon(void)int cmd_fsmonitor__daemon (int argc,const char **argv,const char *prefix) {const char *subcmd; +enum fsmonitor_reason reason;int detach_console =0 ;struct option options[] = {
@@ -1449,6 +1450,23 @@ int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix)die (_ ("invalid 'ipc-threads' value (%d)" ), fsmonitor__ipc_threads); +prepare_repo_settings (the_repository); +/* +* If the repo is fsmonitor-compatible, explicitly set IPC-mode +* (without bothering to load the `core.fsmonitor` config settings). +* +* If the repo is not compatible, the repo-settings will be set to +* incompatible rather than IPC, so we can use one of the __get +* routines to detect the discrepancy. +*/ +fsm_settings__set_ipc (the_repository); + + reason =fsm_settings__get_reason (the_repository); +if (reason >FSMONITOR_REASON_OK ) +die ("%s" , +fsm_settings__get_incompatible_msg (the_repository, + reason)); +if (!strcmp (subcmd,"start" ))return !!try_to_start_background_daemon ();
builtin/update-index.c +16 -0
@@ -1237,6 +1237,22 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)if (fsmonitor >0 ) {enum fsmonitor_mode fsm_mode =fsm_settings__get_mode (r); +enum fsmonitor_reason reason =fsm_settings__get_reason (r); + +/* +* The user wants to turn on FSMonitor using the command +* line argument. (We don't know (or care) whether that +* is the IPC or HOOK version.) +* +* Use one of the __get routines to force load the FSMonitor +* config settings into the repo-settings. That will detect +* whether the file system is compatible so that we can stop +* here with a nice error message. +*/ +if (reason >FSMONITOR_REASON_OK ) +die ("%s" , +fsm_settings__get_incompatible_msg (r, reason)); +if (fsm_mode ==FSMONITOR_MODE_DISABLED ) { warning(_("core.fsmonitor is unset; " "set it if you really want to "
fsmonitor-settings.c +113 -20
200 lines not on this page · raw message
fsmonitor-settings.h +16 -0
@@ -4,18 +4,34 @@struct repository ;enum fsmonitor_mode { +FSMONITOR_MODE_INCOMPATIBLE =-1 ,/* see _reason */ FSMONITOR_MODE_DISABLED =0 ,FSMONITOR_MODE_HOOK =1 ,/* core.fsmonitor=<hook_path> */ FSMONITOR_MODE_IPC =2 ,/* core.fsmonitor=<true> */ }; +/* +* Incompatibility reasons. +*/ +enum fsmonitor_reason { +FSMONITOR_REASON_UNTESTED =0 , +FSMONITOR_REASON_OK ,/* no incompatibility or when disbled */ +FSMONITOR_REASON_BARE , +}; +void fsm_settings__set_ipc (struct repository *r);void fsm_settings__set_hook (struct repository *r,const char *path);void fsm_settings__set_disabled (struct repository *r); +void fsm_settings__set_incompatible (struct repository *r, +enum fsmonitor_reason reason);enum fsmonitor_mode fsm_settings__get_mode (struct repository *r);const char *fsm_settings__get_hook_path (struct repository *r); +enum fsmonitor_reason fsm_settings__get_reason (struct repository *r); +char *fsm_settings__get_incompatible_msg (const struct repository *r, +enum fsmonitor_reason reason); +struct fsmonitor_settings ;#endif /* FSMONITOR_SETTINGS_H */
t/t7519-status-fsmonitor.sh +23 -0
@@ -55,6 +55,29 @@ test_lazy_prereq UNTRACKED_CACHE ' test $ret -ne 1 ' +# Test that we detect and disallow repos that are incompatible with FSMonitor. +test_expect_success 'incompatible bare repo' ' + test_when_finished "rm -rf ./bare-clone actual expect" && + git init --bare bare-clone && + + test_must_fail \ + git -C ./bare-clone -c core.fsmonitor=foo \ + update-index --fsmonitor 2>actual && + grep "bare repository .* is incompatible with fsmonitor" actual && + + test_must_fail \ + git -C ./bare-clone -c core.fsmonitor=true \ + update-index --fsmonitor 2>actual && + grep "bare repository .* is incompatible with fsmonitor" actual +' + +test_expect_success FSMONITOR_DAEMON 'run fsmonitor-daemon in bare repo' ' + test_when_finished "rm -rf ./bare-clone actual" && + git init --bare bare-clone && + test_must_fail git -C ./bare-clone fsmonitor--daemon run 2>actual && + grep "bare repository .* is incompatible with fsmonitor" actual +' + test_expect_success 'setup' ' mkdir -p .git/hooks && : >tracked &&
Signature · 1 line
gitgitgadget
Re: [PATCH v6 04/28] fsmonitor-settings: bare repos are incompatible with FSMonitor
Hi Jeff,
Quoted 296 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
Bare repos do not have a worktree, so there is nothing for the daemon watch.
Signed-off-by Jeff Hostetler <[email protected]>--- builtin/fsmonitor--daemon.c | 18 +++++ builtin/update-index.c | 16 +++++ fsmonitor-settings.c | 133 ++++++++++++++++++++++++++++++------ fsmonitor-settings.h | 16 +++++ t/t7519-status-fsmonitor.sh | 23 +++++++ 5 files changed, 186 insertions(+), 20 deletions(-)builtin/fsmonitor--daemon.c +18
-0@@ -1423,6 +1423,7 @@ static int try_to_start_background_daemon(void)int cmd_fsmonitor__daemon (int argc,const char **argv,const char *prefix) {const char *subcmd; +enum fsmonitor_reason reason;int detach_console =0 ;struct option options[] = {@@ -1449,6 +1450,23 @@ int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix)die (_ ("invalid 'ipc-threads' value (%d)" ), fsmonitor__ipc_threads); +prepare_repo_settings (the_repository); +/* +* If the repo is fsmonitor-compatible, explicitly set IPC-mode +* (without bothering to load the `core.fsmonitor` config settings). +* +* If the repo is not compatible, the repo-settings will be set to +* incompatible rather than IPC, so we can use one of the __get +* routines to detect the discrepancy. +*/ +fsm_settings__set_ipc (the_repository); + + reason =fsm_settings__get_reason (the_repository); +if (reason >FSMONITOR_REASON_OK ) +die ("%s" , +fsm_settings__get_incompatible_msg (the_repository, + reason)); +if (!strcmp (subcmd,"start" ))return !!try_to_start_background_daemon ();builtin/update-index.c +16
-0@@ -1237,6 +1237,22 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)if (fsmonitor >0 ) {enum fsmonitor_mode fsm_mode =fsm_settings__get_mode (r); +enum fsmonitor_reason reason =fsm_settings__get_reason (r); + +/* +* The user wants to turn on FSMonitor using the command +* line argument. (We don't know (or care) whether that +* is the IPC or HOOK version.) +* +* Use one of the __get routines to force load the FSMonitor +* config settings into the repo-settings. That will detect +* whether the file system is compatible so that we can stop +* here with a nice error message. +*/ +if (reason >FSMONITOR_REASON_OK ) +die ("%s" , +fsm_settings__get_incompatible_msg (r, reason)); +if (fsm_mode ==FSMONITOR_MODE_DISABLED ) { warning(_("core.fsmonitor is unset; " "set it if you really want to "fsmonitor-settings.c +113
-20@@ -9,23 +9,42 @@*/ struct fsmonitor_settings {enum fsmonitor_mode mode; +enum fsmonitor_reason reason;char *hook_path; }; -static void lookup_fsmonitor_settings (struct repository *r) +static enum fsmonitor_reason check_for_incompatible (struct repository *r) +{ +if (!r->worktree) { +/* +* Bare repositories don't have a working directory and +* therefore have nothing to watch. +*/ +return FSMONITOR_REASON_BARE ; + } + +return FSMONITOR_REASON_OK ; +} + +static struct fsmonitor_settings *alloc_settings (void ) {struct fsmonitor_settings *s; + +CALLOC_ARRAY (s,1 ); + s->mode =FSMONITOR_MODE_DISABLED ; + s->reason =FSMONITOR_REASON_UNTESTED ; + +return s; +} + +static void lookup_fsmonitor_settings (struct repository *r) +{const char *const_str;int bool_value;if (r->settings.fsmonitor)return ; -CALLOC_ARRAY (s,1 ); - s->mode =FSMONITOR_MODE_DISABLED ; - - r->settings.fsmonitor = s; -/* * Overload the existing "core.fsmonitor" config setting (which * has historically been either unset or a hook pathname) to @@ -38,6 +57,8 @@ static void lookup_fsmonitor_settings(struct repository *r)case 0 :/* config value was set to <bool> */ if (bool_value)fsm_settings__set_ipc (r); +else +fsm_settings__set_disabled (r);return ;case 1 :/* config value was unset */ @@ -53,18 +74,18 @@ static void lookup_fsmonitor_settings(struct repository *r)return ; } -if (!const_str || !*const_str) -return ; - -fsm_settings__set_hook (r, const_str); +if (const_str && *const_str) +fsm_settings__set_hook (r, const_str); +else +fsm_settings__set_disabled (r); }enum fsmonitor_mode fsm_settings__get_mode (struct repository *r) {if (!r) r = the_repository; - -lookup_fsmonitor_settings (r); +if (!r->settings.fsmonitor) +lookup_fsmonitor_settings (r);return r->settings.fsmonitor->mode; }@@ -73,31 +94,55 @@ const char *fsm_settings__get_hook_path(struct repository *r) {if (!r) r = the_repository; - -lookup_fsmonitor_settings (r); +if (!r->settings.fsmonitor) +lookup_fsmonitor_settings (r);return r->settings.fsmonitor->hook_path; }void fsm_settings__set_ipc (struct repository *r) { +enum fsmonitor_reason reason =check_for_incompatible (r); + +if (reason !=FSMONITOR_REASON_OK ) { +fsm_settings__set_incompatible (r, reason); +return ; + } + +/* +* Caller requested IPC explicitly, so avoid (possibly +* recursive) config lookup. +*/ if (!r) r = the_repository; - -lookup_fsmonitor_settings (r); +if (!r->settings.fsmonitor) + r->settings.fsmonitor =alloc_settings (); r->settings.fsmonitor->mode =FSMONITOR_MODE_IPC ; + r->settings.fsmonitor->reason = reason;FREE_AND_NULL (r->settings.fsmonitor->hook_path); }void fsm_settings__set_hook (struct repository *r,const char *path) { +enum fsmonitor_reason reason =check_for_incompatible (r); + +if (reason !=FSMONITOR_REASON_OK ) { +fsm_settings__set_incompatible (r, reason); +return ; + } + +/* +* Caller requested hook explicitly, so avoid (possibly +* recursive) config lookup. +*/ if (!r) r = the_repository; - -lookup_fsmonitor_settings (r); +if (!r->settings.fsmonitor) + r->settings.fsmonitor =alloc_settings (); r->settings.fsmonitor->mode =FSMONITOR_MODE_HOOK ; + r->settings.fsmonitor->reason = reason;FREE_AND_NULL (r->settings.fsmonitor->hook_path); r->settings.fsmonitor->hook_path =strdup (path); }@@ -106,9 +151,57 @@ void fsm_settings__set_disabled(struct repository *r) {if (!r) r = the_repository; - -lookup_fsmonitor_settings (r); +if (!r->settings.fsmonitor) + r->settings.fsmonitor =alloc_settings (); r->settings.fsmonitor->mode =FSMONITOR_MODE_DISABLED ; + r->settings.fsmonitor->reason =FSMONITOR_REASON_OK ; +FREE_AND_NULL (r->settings.fsmonitor->hook_path); +} + +void fsm_settings__set_incompatible (struct repository *r, +enum fsmonitor_reason reason) +{ +if (!r) + r = the_repository; +if (!r->settings.fsmonitor) + r->settings.fsmonitor =alloc_settings (); + + r->settings.fsmonitor->mode =FSMONITOR_MODE_INCOMPATIBLE ; + r->settings.fsmonitor->reason = reason;FREE_AND_NULL (r->settings.fsmonitor->hook_path); } + +enum fsmonitor_reason fsm_settings__get_reason (struct repository *r) +{ +if (!r) + r = the_repository; +if (!r->settings.fsmonitor) +lookup_fsmonitor_settings (r); + +return r->settings.fsmonitor->reason; +} + +char *fsm_settings__get_incompatible_msg (const struct repository *r, +enum fsmonitor_reason reason) +{ +struct strbuf msg =STRBUF_INIT ; + +switch (reason) { +case FSMONITOR_REASON_UNTESTED : +case FSMONITOR_REASON_OK : + goto done; + +case FSMONITOR_REASON_BARE : +strbuf_addf (&msg, +_ ("bare repository '%s' is incompatible with fsmonitor" ), +xgetcwd ()); + goto done; + } + +BUG ("Unhandled case in fsm_settings__get_incompatible_msg: '%d'" , + reason); + +done: +return strbuf_detach (&msg,NULL ); +}fsmonitor-settings.h +1
-0@@ -4,18 +4,34 @@struct repository ;enum fsmonitor_mode { +FSMONITOR_MODE_INCOMPATIBLE =-1 ,/* see _reason */ FSMONITOR_MODE_DISABLED =0 ,
fsmonitor-settings.h line 18
Quoted 10 lines
fsmonitor-settings.h +6
-0FSMONITOR_MODE_HOOK =1 ,/* core.fsmonitor=<hook_path> */ FSMONITOR_MODE_IPC = 2, /* core.fsmonitor=<true> */ }; +/* +* Incompatibility reasons. +*/ +enum fsmonitor_reason { +FSMONITOR_REASON_UNTESTED =0 , + FSMONITOR_REASON_OK, /* no incompatibility or when disbled */
s/disbled/disabled/
I usually try to avoid pointing out speling erors, but I kind of wanted to show that I really studied this patch series for a couple hours.
The patch looks good to me,
Dscho
Quoted 57 lines
fsmonitor-settings.h +9
-0+ FSMONITOR_REASON_BARE, +}; +void fsm_settings__set_ipc (struct repository *r);void fsm_settings__set_hook (struct repository *r,const char *path);void fsm_settings__set_disabled (struct repository *r); +void fsm_settings__set_incompatible (struct repository *r, +enum fsmonitor_reason reason);enum fsmonitor_mode fsm_settings__get_mode (struct repository *r);const char *fsm_settings__get_hook_path (struct repository *r); +enum fsmonitor_reason fsm_settings__get_reason (struct repository *r); +char *fsm_settings__get_incompatible_msg (const struct repository *r, +enum fsmonitor_reason reason); +struct fsmonitor_settings ;#endif /* FSMONITOR_SETTINGS_H */ t/t7519-status-fsmonitor.sh +23
-1@@ -55,6 +55,29 @@ test_lazy_prereq UNTRACKED_CACHE ' test $ret -ne 1 ' +# Test that we detect and disallow repos that are incompatible with FSMonitor. +test_expect_success 'incompatible bare repo' ' + test_when_finished "rm -rf ./bare-clone actual expect" && + git init --bare bare-clone && + + test_must_fail \ + git -C ./bare-clone -c core.fsmonitor=foo \ + update-index --fsmonitor 2>actual && + grep "bare repository .* is incompatible with fsmonitor" actual && + + test_must_fail \ + git -C ./bare-clone -c core.fsmonitor=true \ + update-index --fsmonitor 2>actual && + grep "bare repository .* is incompatible with fsmonitor" actual +' + +test_expect_success FSMONITOR_DAEMON 'run fsmonitor-daemon in bare repo' ' + test_when_finished "rm -rf ./bare-clone actual" && + git init --bare bare-clone && + test_must_fail git -C ./bare-clone fsmonitor--daemon run 2>actual && + grep "bare repository .* is incompatible with fsmonitor" actual +' + test_expect_success 'setup' ' mkdir -p .git/hooks && : >tracked && --gitgitgadget
PATCH v6 5/28 fsmonitor-settings: stub in Win32-specific incompatibility checking
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Extend generic incompatibility checkout with platform-specific mechanism. Stub in Win32 version.
In the existing fsmonitor-settings code we have a way to mark types of repos as incompatible with fsmonitor (whether via the hook and IPC APIs). For example, we do this for bare repos, since there are no files to watch.
Extend this exclusion mechanism for platform-specific reasons. This commit just creates the framework and adds a stub for Win32.
--- Makefile | 13 +++++++++++++ compat/fsmonitor/fsm-settings-win32.c | 9 +++++++++ config.mak.uname | 4 ++++ contrib/buildsystems/CMakeLists.txt | 3 +++ fsmonitor-settings.c | 10 ++++++++++ fsmonitor-settings.h | 13 +++++++++++++ 6 files changed, 52 insertions(+) create mode 100644 compat/fsmonitor/fsm-settings-win32.c
Makefile +13 -0
@@ -475,6 +475,11 @@ all:: # `compat/fsmonitor/fsm-listen-<name>.c` that implements the # `fsm_listen__*()` routines. # +# If your platform has OS-specific ways to tell if a repo is incompatible with +# fsmonitor (whether the hook or IPC daemon version), set FSMONITOR_OS_SETTINGS +# to the "<name>" of the corresponding `compat/fsmonitor/fsm-settings-<name>.c` +# that implements the `fsm_os_settings__*()` routines. +# # Define DEVELOPER to enable more compiler warnings. Compiler version # and family are auto detected, but could be overridden by defining # COMPILER_FEATURES (see config.mak.dev). You can still set
@@ -1979,6 +1984,11 @@ ifdef FSMONITOR_DAEMON_BACKEND COMPAT_OBJS += compat/fsmonitor/fsm-listen-$(FSMONITOR_DAEMON_BACKEND).o endif +ifdef FSMONITOR_OS_SETTINGS + COMPAT_CFLAGS += -DHAVE_FSMONITOR_OS_SETTINGS + COMPAT_OBJS += compat/fsmonitor/fsm-settings-$(FSMONITOR_OS_SETTINGS).o +endif + ifeq ($(TCLTK_PATH),) NO_TCLTK = NoThanks endif
@@ -2901,6 +2911,9 @@ GIT-BUILD-OPTIONS: FORCE ifdef FSMONITOR_DAEMON_BACKEND @echo FSMONITOR_DAEMON_BACKEND=\''$(subst ','\'',$(subst ','\'',$(FSMONITOR_DAEMON_BACKEND)))'\' >>$@+ endif +ifdef FSMONITOR_OS_SETTINGS + @echo FSMONITOR_OS_SETTINGS=\''$(subst ','\'',$(subst ','\'',$(FSMONITOR_OS_SETTINGS)))'\' >>$@+ +endif ifdef TEST_OUTPUT_DIRECTORY @echo TEST_OUTPUT_DIRECTORY=\''$(subst ','\'',$(subst ','\'',$(TEST_OUTPUT_DIRECTORY)))'\' >>$@+ endif
compat/fsmonitor/fsm-settings-win32.c new file +9 -0
@@ -0,0 +1,9 @@ +#include "cache.h" +#include "config.h" +#include "repository.h" +#include "fsmonitor-settings.h" + +enum fsmonitor_reason fsm_os__incompatible (struct repository *r) +{ +return FSMONITOR_REASON_OK ; +}
config.mak.uname +4 -0
@@ -450,6 +450,8 @@ ifeq ($(uname_S),Windows) # These are always available, so we do not have to conditionally # support it. FSMONITOR_DAEMON_BACKEND = win32 + FSMONITOR_OS_SETTINGS = win32 + NO_SVN_TESTS = YesPlease RUNTIME_PREFIX = YesPlease HAVE_WPGMPTR = YesWeDo
@@ -639,6 +641,8 @@ ifeq ($(uname_S),MINGW) # These are always available, so we do not have to conditionally # support it. FSMONITOR_DAEMON_BACKEND = win32 + FSMONITOR_OS_SETTINGS = win32 + RUNTIME_PREFIX = YesPlease HAVE_WPGMPTR = YesWeDo NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
contrib/buildsystems/CMakeLists.txt +3 -0
@@ -289,6 +289,9 @@ if(SUPPORTS_SIMPLE_IPC) if(CMAKE_SYSTEM_NAME STREQUAL "Windows") add_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND) list(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-win32.c) + + add_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS) + list(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-win32.c) elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") add_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND) list(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-darwin.c)
fsmonitor-settings.c +10 -0
@@ -23,6 +23,16 @@ static enum fsmonitor_reason check_for_incompatible(struct repository *r)return FSMONITOR_REASON_BARE ; } +#ifdef HAVE_FSMONITOR_OS_SETTINGS + { +enum fsmonitor_reason reason; + + reason =fsm_os__incompatible (r); +if (reason !=FSMONITOR_REASON_OK ) +return reason; + } +#endif +return FSMONITOR_REASON_OK ; }
fsmonitor-settings.h +13 -0
@@ -34,4 +34,17 @@ char *fsm_settings__get_incompatible_msg(const struct repository *r,struct fsmonitor_settings ; +#ifdef HAVE_FSMONITOR_OS_SETTINGS +/* +* Ask platform-specific code whether the repository is incompatible +* with fsmonitor (both hook and ipc modes). For example, if the working +* directory is on a remote volume and mounted via a technology that does +* not support notification events, then we should not pretend to watch it. +* +* fsm_os__* routines should considered private to fsm_settings__ +* routines. +*/ +enum fsmonitor_reason fsm_os__incompatible (struct repository *r); +#endif /* HAVE_FSMONITOR_OS_SETTINGS */ +#endif /* FSMONITOR_SETTINGS_H */
Signature · 1 line
gitgitgadget
PATCH v6 6/28 fsmonitor-settings: VFS for Git virtual repos are incompatible
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
VFS for Git virtual repositories are incompatible with FSMonitor.
VFS for Git is a downstream fork of Git. It contains its own custom file system watcher that is aware of the virtualization. If a working directory is being managed by VFS for Git, we should not try to watch it because we may get incomplete results.
We do not know anything about how VFS for Git works, but we do know that VFS for Git working directories contain a well-defined config setting. If it is set, mark the working directory as incompatible.
--- compat/fsmonitor/fsm-settings-win32.c | 26 ++++++++++++++++++++++++++ fsmonitor-settings.c | 6 ++++++ fsmonitor-settings.h | 1 + t/t7519-status-fsmonitor.sh | 9 +++++++++ 4 files changed, 42 insertions(+)
compat/fsmonitor/fsm-settings-win32.c +26 -0
@@ -3,7 +3,33 @@#include "repository.h" #include "fsmonitor-settings.h" +/* +* VFS for Git is incompatible with FSMonitor. +* +* Granted, core Git does not know anything about VFS for Git and we +* shouldn't make assumptions about a downstream feature, but users +* can install both versions. And this can lead to incorrect results +* from core Git commands. So, without bringing in any of the VFS for +* Git code, do a simple config test for a published config setting. +* (We do not look at the various *_TEST_* environment variables.) +*/ +static enum fsmonitor_reason check_vfs4git (struct repository *r) +{ +const char *const_str; + +if (!repo_config_get_value (r,"core.virtualfilesystem" , &const_str)) +return FSMONITOR_REASON_VFS4GIT ; + +return FSMONITOR_REASON_OK ; +} +enum fsmonitor_reason fsm_os__incompatible (struct repository *r) { +enum fsmonitor_reason reason; + + reason =check_vfs4git (r); +if (reason !=FSMONITOR_REASON_OK ) +return reason; +return FSMONITOR_REASON_OK ; }
fsmonitor-settings.c +6 -0
@@ -207,6 +207,12 @@ char *fsm_settings__get_incompatible_msg(const struct repository *r,_ ("bare repository '%s' is incompatible with fsmonitor" ), xgetcwd()); goto done; + +case FSMONITOR_REASON_VFS4GIT : +strbuf_addf (&msg, +_ ("virtual repository '%s' is incompatible with fsmonitor" ), + r->worktree); + goto done; } BUG("Unhandled case in fsm_settings__get_incompatible_msg: '%d'",
fsmonitor-settings.h +1 -0
@@ -17,6 +17,7 @@ enum fsmonitor_reason {FSMONITOR_REASON_UNTESTED =0 ,FSMONITOR_REASON_OK ,/* no incompatibility or when disbled */ FSMONITOR_REASON_BARE , + FSMONITOR_REASON_VFS4GIT, /* VFS for Git virtualization */ };void fsm_settings__set_ipc (struct repository *r);
t/t7519-status-fsmonitor.sh +9 -0
@@ -78,6 +78,15 @@ test_expect_success FSMONITOR_DAEMON 'run fsmonitor-daemon in bare repo' ' grep "bare repository .* is incompatible with fsmonitor" actual ' +test_expect_success MINGW,FSMONITOR_DAEMON 'run fsmonitor-daemon in virtual repo' ' + test_when_finished "rm -rf ./fake-virtual-clone actual" && + git init fake-virtual-clone && + test_must_fail git -C ./fake-virtual-clone \ + -c core.virtualfilesystem=true \ + fsmonitor--daemon run 2>actual && + grep "virtual repository .* is incompatible with fsmonitor" actual +' + test_expect_success 'setup' ' mkdir -p .git/hooks && : >tracked &&
Signature · 1 line
gitgitgadget
PATCH v6 7/28 fsmonitor-settings: stub in macOS-specific incompatibility checking
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
--- compat/fsmonitor/fsm-settings-darwin.c | 9 +++++++++ config.mak.uname | 1 + contrib/buildsystems/CMakeLists.txt | 3 +++ 3 files changed, 13 insertions(+) create mode 100644 compat/fsmonitor/fsm-settings-darwin.c
compat/fsmonitor/fsm-settings-darwin.c new file +9 -0
@@ -0,0 +1,9 @@ +#include "cache.h" +#include "config.h" +#include "repository.h" +#include "fsmonitor-settings.h" + +enum fsmonitor_reason fsm_os__incompatible (struct repository *r) +{ +return FSMONITOR_REASON_OK ; +}
config.mak.uname +1 -0
@@ -163,6 +163,7 @@ ifeq ($(uname_S),Darwin) ifndef NO_PTHREADS ifndef NO_UNIX_SOCKETS FSMONITOR_DAEMON_BACKEND = darwin + FSMONITOR_OS_SETTINGS = darwin endif endif
contrib/buildsystems/CMakeLists.txt +3 -0
@@ -295,6 +295,9 @@ if(SUPPORTS_SIMPLE_IPC) elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") add_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND) list(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-darwin.c) + + add_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS) + list(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-darwin.c) endif() endif()
Signature · 1 line
gitgitgadget
PATCH v6 8/28 fsmonitor-settings: remote repos on macOS are incompatible
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Teach Git to detect remote working directories on macOS and mark them as incompatible with FSMonitor.
With this, git fsmonitor--daemon run will error out with a message like it does for bare repos.
Client commands, like git status, will not attempt to start the daemon.
--- compat/fsmonitor/fsm-settings-darwin.c | 66 ++++++++++++++++++++++++++ fsmonitor-settings.c | 12 +++++ fsmonitor-settings.h | 2 + 3 files changed, 80 insertions(+)
compat/fsmonitor/fsm-settings-darwin.c +66 -0
@@ -2,8 +2,74 @@#include "config.h" #include "repository.h" #include "fsmonitor-settings.h" +#include "fsmonitor.h" +#include <sys/param.h> +#include <sys/mount.h> + +/* +* Remote working directories are problematic for FSMonitor. +* +* The underlying file system on the server machine and/or the remote +* mount type (NFS, SAMBA, etc.) dictates whether notification events +* are available at all to remote client machines. +* +* Kernel differences between the server and client machines also +* dictate the how (buffering, frequency, de-dup) the events are +* delivered to client machine processes. +* +* A client machine (such as a laptop) may choose to suspend/resume +* and it is unclear (without lots of testing) whether the watcher can +* resync after a resume. We might be able to treat this as a normal +* "events were dropped by the kernel" event and do our normal "flush +* and resync" --or-- we might need to close the existing (zombie?) +* notification fd and create a new one. +* +* In theory, the above issues need to be addressed whether we are +* using the Hook or IPC API. +* +* For the builtin FSMonitor, we create the Unix domain socket for the +* IPC in the .git directory. If the working directory is remote, +* then the socket will be created on the remote file system. This +* can fail if the remote file system does not support UDS file types +* (e.g. smbfs to a Windows server) or if the remote kernel does not +* allow a non-local process to bind() the socket. (These problems +* could be fixed by moving the UDS out of the .git directory and to a +* well-known local directory on the client machine, but care should +* be taken to ensure that $HOME is actually local and not a managed +* file share.) +* +* So (for now at least), mark remote working directories as +* incompatible. +*/ +static enum fsmonitor_reason check_remote (struct repository *r) +{ +struct statfs fs; + +if (statfs (r->worktree, &fs) ==-1 ) { +int saved_errno = errno; +trace_printf_key (&trace_fsmonitor,"statfs('%s') failed: %s" , + r->worktree,strerror (saved_errno)); + errno = saved_errno; +return FSMONITOR_REASON_ERROR ; + } + +trace_printf_key (&trace_fsmonitor, +"statfs('%s') [type 0x%08x][flags 0x%08x] '%s'" , + r->worktree, fs.f_type, fs.f_flags, fs.f_fstypename); + +if (!(fs.f_flags &MNT_LOCAL )) +return FSMONITOR_REASON_REMOTE ; + +return FSMONITOR_REASON_OK ; +}enum fsmonitor_reason fsm_os__incompatible (struct repository *r) { +enum fsmonitor_reason reason; + + reason =check_remote (r); +if (reason !=FSMONITOR_REASON_OK ) +return reason; +return FSMONITOR_REASON_OK ; }
fsmonitor-settings.c +12 -0
@@ -208,6 +208,18 @@ char *fsm_settings__get_incompatible_msg(const struct repository *r, xgetcwd()); goto done; +case FSMONITOR_REASON_ERROR : +strbuf_addf (&msg, +_ ("repository '%s' is incompatible with fsmonitor due to errors" ), + r->worktree); + goto done; + +case FSMONITOR_REASON_REMOTE : +strbuf_addf (&msg, +_ ("remote repository '%s' is incompatible with fsmonitor" ), + r->worktree); + goto done; +case FSMONITOR_REASON_VFS4GIT : strbuf_addf(&msg, _("virtual repository '%s' is incompatible with fsmonitor"),
fsmonitor-settings.h +2 -0
@@ -17,6 +17,8 @@ enum fsmonitor_reason {FSMONITOR_REASON_UNTESTED =0 ,FSMONITOR_REASON_OK ,/* no incompatibility or when disbled */ FSMONITOR_REASON_BARE , +FSMONITOR_REASON_ERROR ,/* FS error probing for compatibility */ +FSMONITOR_REASON_REMOTE , FSMONITOR_REASON_VFS4GIT, /* VFS for Git virtualization */ };
Signature · 1 line
gitgitgadget
PATCH v6 9/28 fsmonitor-settings: remote repos on Windows are incompatible
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Teach Git to detect remote working directories on Windows and mark them as incompatible with FSMonitor.
With this git fsmonitor--daemon run will error out with a message like it does for bare repos.
Client commands, such as git status, will not attempt to start the daemon.
--- compat/fsmonitor/fsm-settings-win32.c | 102 ++++++++++++++++++++++++++ 1 file changed, 102 insertions(+)
compat/fsmonitor/fsm-settings-win32.c +102 -0
@@ -2,6 +2,7 @@#include "config.h" #include "repository.h" #include "fsmonitor-settings.h" +#include "fsmonitor.h" /* * VFS for Git is incompatible with FSMonitor.
@@ -23,6 +24,103 @@ static enum fsmonitor_reason check_vfs4git(struct repository *r)return FSMONITOR_REASON_OK ; } +/* +* Remote working directories are problematic for FSMonitor. +* +* The underlying file system on the server machine and/or the remote +* mount type dictates whether notification events are available at +* all to remote client machines. +* +* Kernel differences between the server and client machines also +* dictate the how (buffering, frequency, de-dup) the events are +* delivered to client machine processes. +* +* A client machine (such as a laptop) may choose to suspend/resume +* and it is unclear (without lots of testing) whether the watcher can +* resync after a resume. We might be able to treat this as a normal +* "events were dropped by the kernel" event and do our normal "flush +* and resync" --or-- we might need to close the existing (zombie?) +* notification fd and create a new one. +* +* In theory, the above issues need to be addressed whether we are +* using the Hook or IPC API. +* +* So (for now at least), mark remote working directories as +* incompatible. +* +* Notes for testing: +* +* (a) Windows allows a network share to be mapped to a drive letter. +* (This is the normal method to access it.) +* +* $ NET USE Z: \\server\share +* $ git -C Z:/repo status +* +* (b) Windows allows a network share to be referenced WITHOUT mapping +* it to drive letter. +* +* $ NET USE \\server\share\dir +* $ git -C //server/share/repo status +* +* (c) Windows allows "SUBST" to create a fake drive mapping to an +* arbitrary path (which may be remote) +* +* $ SUBST Q: Z:\repo +* $ git -C Q:/ status +* +* (d) Windows allows a directory symlink to be created on a local +* file system that points to a remote repo. +* +* $ mklink /d ./link //server/share/repo +* $ git -C ./link status +*/ +static enum fsmonitor_reason check_remote (struct repository *r) +{ +wchar_t wpath[MAX_PATH ]; +wchar_t wfullpath[MAX_PATH ]; +size_t wlen; +UINT driveType; + +/* +* Do everything in wide chars because the drive letter might be +* a multi-byte sequence. See win32_has_dos_drive_prefix(). +*/ +if (xutftowcs_path (wpath, r->worktree) <0 ) +return FSMONITOR_REASON_ERROR ; + +/* +* GetDriveTypeW() requires a final slash. We assume that the +* worktree pathname points to an actual directory. +*/ + wlen =wcslen (wpath); +if (wpath[wlen -1 ] !=L'\\' && wpath[wlen -1 ] !=L'/' ) { + wpath[wlen++] =L'\\' ; + wpath[wlen] =0 ; + } + +/* +* Normalize the path. If nothing else, this converts forward +* slashes to backslashes. This is essential to get GetDriveTypeW() +* correctly handle some UNC "\\server\share\..." paths. +*/ +if (!GetFullPathNameW (wpath,MAX_PATH , wfullpath,NULL )) +return FSMONITOR_REASON_ERROR ; + + driveType =GetDriveTypeW (wfullpath); +trace_printf_key (&trace_fsmonitor, +"DriveType '%s' L'%ls' (%u)" , + r->worktree, wfullpath, driveType); + +if (driveType ==DRIVE_REMOTE ) { +trace_printf_key (&trace_fsmonitor, +"check_remote('%s') true" , + r->worktree); +return FSMONITOR_REASON_REMOTE ; + } + +return FSMONITOR_REASON_OK ; +} +enum fsmonitor_reason fsm_os__incompatible (struct repository *r) {enum fsmonitor_reason reason;
@@ -31,5 +129,9 @@ enum fsmonitor_reason fsm_os__incompatible(struct repository *r)if (reason !=FSMONITOR_REASON_OK )return reason; + reason =check_remote (r); +if (reason !=FSMONITOR_REASON_OK ) +return reason; +return FSMONITOR_REASON_OK ; }
Signature · 1 line
gitgitgadget
PATCH v6 10/28 fsmonitor-settings: NTFS and FAT32 on MacOS are incompatible
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
On MacOS mark repos on NTFS or FAT32 volumes as incompatible.
The builtin FSMonitor used Unix domain sockets on MacOS for IPC with clients. These sockets are kept in the .git directory. Unix sockets are not supported by NTFS and FAT32, so the daemon cannot start up.
Test for this during our compatibility checking so that client commands do not keep trying to start the daemon.
--- compat/fsmonitor/fsm-settings-darwin.c | 20 +++++++++++++++++--- fsmonitor-settings.c | 6 ++++++ fsmonitor-settings.h | 1 + 3 files changed, 24 insertions(+), 3 deletions(-)
compat/fsmonitor/fsm-settings-darwin.c +17 -3
@@ -7,7 +7,7 @@#include <sys/mount.h> /* -* Remote working directories are problematic for FSMonitor. +* [1] Remote working directories are problematic for FSMonitor. * * The underlying file system on the server machine and/or the remote * mount type (NFS, SAMBA, etc.) dictates whether notification events
@@ -40,8 +40,16 @@* * So (for now at least), mark remote working directories as * incompatible. +* +* +* [2] FAT32 and NTFS working directories are problematic too. +* +* The builtin FSMonitor uses a Unix domain socket in the .git +* directory for IPC. These Windows drive formats do not support +* Unix domain sockets, so mark them as incompatible for the daemon. +* */ -static enum fsmonitor_reason check_remote (struct repository *r) +static enum fsmonitor_reason check_volume (struct repository *r) {struct statfs fs;
@@ -60,6 +68,12 @@ static enum fsmonitor_reason check_remote(struct repository *r)if (!(fs.f_flags &MNT_LOCAL ))return FSMONITOR_REASON_REMOTE ; +if (!strcmp (fs.f_fstypename,"msdos" ))/* aka FAT32 */ +return FSMONITOR_REASON_NOSOCKETS ; + +if (!strcmp (fs.f_fstypename,"ntfs" )) +return FSMONITOR_REASON_NOSOCKETS ; +return FSMONITOR_REASON_OK ; }
@@ -67,7 +81,7 @@ enum fsmonitor_reason fsm_os__incompatible(struct repository *r) {enum fsmonitor_reason reason; - reason =check_remote (r); + reason =check_volume (r);if (reason !=FSMONITOR_REASON_OK )return reason;
fsmonitor-settings.c +6 -0
@@ -225,6 +225,12 @@ char *fsm_settings__get_incompatible_msg(const struct repository *r,_ ("virtual repository '%s' is incompatible with fsmonitor" ), r->worktree); goto done; + +case FSMONITOR_REASON_NOSOCKETS : +strbuf_addf (&msg, +_ ("repository '%s' is incompatible with fsmonitor due to lack of Unix sockets" ), + r->worktree); + goto done; } BUG("Unhandled case in fsm_settings__get_incompatible_msg: '%d'",
fsmonitor-settings.h +1 -0
@@ -20,6 +20,7 @@ enum fsmonitor_reason {FSMONITOR_REASON_ERROR ,/* FS error probing for compatibility */ FSMONITOR_REASON_REMOTE ,FSMONITOR_REASON_VFS4GIT ,/* VFS for Git virtualization */ + FSMONITOR_REASON_NOSOCKETS, /* NTFS,FAT32 do not support Unix sockets */ };void fsm_settings__set_ipc (struct repository *r);
Signature · 1 line
gitgitgadget
PATCH v6 11/28 unpack-trees: initialize fsmonitor_has_run_once in o->result
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Initialize o->result.fsmonitor_has_run_once based upon value in o->src_index->fsmonitor_has_run_once to prevent a second fsmonitor query during the tree traversal and possibly getting a skewed view of the working directory.
The checkout code has already talked to the fsmonitor and the traversal is updating the index as it traverses, so there is no need to query the fsmonitor.
--- unpack-trees.c | 1 + 1 file changed, 1 insertion(+)
unpack-trees.c +1 -0
@@ -1772,6 +1772,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options o->result.fsmonitor_last_update =xstrdup_or_null (o->src_index->fsmonitor_last_update); + o->result.fsmonitor_has_run_once = o->src_index->fsmonitor_has_run_once;/* * Sparse checkout loop #1: set NEW_SKIP_WORKTREE on existing entries
Signature · 1 line
gitgitgadget
PATCH v6 12/28 fsm-listen-darwin: ignore FSEvents caused by xattr changes on macOS
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Ignore FSEvents resulting from xattr changes. Git does not care about xattr's or changes to xattr's, so don't waste time collecting these events in the daemon nor transmitting them to clients.
Various security tools add xattrs to files and/or directories, such as to mark them as having been downloaded. We should ignore these events since it doesn't affect the content of the file/directory or the normal meta-data that Git cares about.
--- compat/fsmonitor/fsm-listen-darwin.c | 34 +++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-)
compat/fsmonitor/fsm-listen-darwin.c +33 -1
@@ -100,7 +100,7 @@ static void log_flags_set(const char *path, const FSEventStreamEventFlags flag)if (flag & kFSEventStreamEventFlagItemCloned)strbuf_addstr (&msg,"ItemCloned|" ); -trace_printf_key (&trace_fsmonitor,"fsevent: '%s', flags=%u %s" , +trace_printf_key (&trace_fsmonitor,"fsevent: '%s', flags=0x%x %s" , path, flag, msg.buf);strbuf_release (&msg);
@@ -125,6 +125,31 @@ static int ef_is_dropped(const FSEventStreamEventFlags ef) ef & kFSEventStreamEventFlagUserDropped); } +/* +* If an `xattr` change is the only reason we received this event, +* then silently ignore it. Git doesn't care about xattr's. We +* have to be careful here because the kernel can combine multiple +* events for a single path. And because events always have certain +* bits set, such as `ItemIsFile` or `ItemIsDir`. +* +* Return 1 if we should ignore it. +*/ +static int ef_ignore_xattr (const FSEventStreamEventFlags ef) +{ +static const FSEventStreamEventFlags mask = + kFSEventStreamEventFlagItemChangeOwner | + kFSEventStreamEventFlagItemCreated | + kFSEventStreamEventFlagItemFinderInfoMod | + kFSEventStreamEventFlagItemInodeMetaMod | + kFSEventStreamEventFlagItemModified | + kFSEventStreamEventFlagItemRemoved | + kFSEventStreamEventFlagItemRenamed | + kFSEventStreamEventFlagItemXattrMod | + kFSEventStreamEventFlagItemCloned; + +return ((ef & mask) == kFSEventStreamEventFlagItemXattrMod); +} + static void fsevent_callback(ConstFSEventStreamRef streamRef, void *ctx, size_t num_of_events,
@@ -190,6 +215,13 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef,continue ; } +if (ef_ignore_xattr (event_flags[k])) { +trace_printf_key (&trace_fsmonitor, +"ignore-xattr: '%s', flags=0x%x" , + path_k, event_flags[k]); +continue ; + } +switch (fsmonitor_classify_path_absolute (state, path_k)) {case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX :
Signature · 1 line
gitgitgadget
PATCH v6 13/28 fsmonitor--daemon: cd out of worktree root
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Teach the fsmonitor--daemon to CD outside of the worktree before starting up.
The common Git startup mechanism causes the CWD of the daemon process to be in the root of the worktree. On Windows, this causes the daemon process to hold a locked handle on the CWD and prevents other processes from moving or deleting the worktree while the daemon is running.
CD to HOME before entering main event loops.
--- builtin/fsmonitor--daemon.c | 32 +++++++++++++++++++++++++++-- compat/fsmonitor/fsm-listen-win32.c | 22 ++++++++++++++------ fsmonitor--daemon.h | 1 + 3 files changed, 47 insertions(+), 8 deletions(-)
builtin/fsmonitor--daemon.c +30 -2
@@ -1181,11 +1181,11 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)* before we need it. */ if (ipc_server_run_async (&state->ipc_server_data, -fsmonitor_ipc__get_path (), &ipc_opts, + state->path_ipc.buf, &ipc_opts, handle_client, state))return error_errno (_ ("could not start IPC thread pool on '%s'" ), -fsmonitor_ipc__get_path ()); + state->path_ipc.buf);/* * Start the fsmonitor listener thread to collect filesystem
@@ -1220,6 +1220,7 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)static int fsmonitor_run_daemon (void ) {struct fsmonitor_daemon_state state; +const char *home;int err;memset (&state,0 ,sizeof (state));
@@ -1289,6 +1290,15 @@ static int fsmonitor_run_daemon(void)strbuf_addch (&state.path_cookie_prefix,'/' ); +/* +* We create a named-pipe or unix domain socket inside of the +* ".git" directory. (Well, on Windows, we base our named +* pipe in the NPFS on the absolute path of the git +* directory.) +*/ +strbuf_init (&state.path_ipc,0 ); +strbuf_addstr (&state.path_ipc,absolute_path (fsmonitor_ipc__get_path ())); +/* * Confirm that we can create platform-specific resources for the * filesystem listener before we bother starting all the threads.
@@ -1298,6 +1308,23 @@ static int fsmonitor_run_daemon(void) goto done; } +/* +* CD out of the worktree root directory. +* +* The common Git startup mechanism causes our CWD to be the +* root of the worktree. On Windows, this causes our process +* to hold a locked handle on the CWD. This prevents the +* worktree from being moved or deleted while the daemon is +* running. +* +* We assume that our FS and IPC listener threads have either +* opened all of the handles that they need or will do +* everything using absolute paths. +*/ + home =getenv ("HOME" ); +if (home && *home &&chdir (home)) +die_errno (_ ("could not cd home '%s'" ), home); + err =fsmonitor_run_daemon_1 (&state); done:
@@ -1310,6 +1337,7 @@ done:strbuf_release (&state.path_worktree_watch);strbuf_release (&state.path_gitdir_watch);strbuf_release (&state.path_cookie_prefix); +strbuf_release (&state.path_ipc);return err; }
compat/fsmonitor/fsm-listen-win32.c +16 -6
@@ -402,12 +402,22 @@ static int recv_rdcw_watch(struct one_watch *watch) }/* -* NEEDSWORK: If an external <gitdir> is deleted, the above -* returns an error. I'm not sure that there's anything that -* we can do here other than failing -- the <worktree>/.git -* link file would be broken anyway. We might try to check -* for that and return a better error message, but I'm not -* sure it is worth it. +* GetOverlappedResult() fails if the watched directory is +* deleted while we were waiting for an overlapped IO to +* complete. The documentation did not list specific errors, +* but I observed ERROR_ACCESS_DENIED (0x05) errors during +* testing. +* +* Note that we only get notificaiton events for events +* *within* the directory, not *on* the directory itself. +* (These might be properies of the parent directory, for +* example). +* +* NEEDSWORK: We might try to check for the deleted directory +* case and return a better error message, but I'm not sure it +* is worth it. +* +* Shutdown if we get any error. */ error(_("GetOverlappedResult failed on '%s' [GLE %ld]"),
fsmonitor--daemon.h +1 -0
@@ -54,6 +54,7 @@ struct fsmonitor_daemon_state {struct fsmonitor_daemon_backend_data *backend_data;struct ipc_server_data *ipc_server_data; +struct strbuf path_ipc; };/*
Signature · 1 line
gitgitgadget
PATCH v6 14/28 fsmonitor--daemon: prepare for adding health thread
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Refactor daemon thread startup to make it easier to start a third thread class to monitor the health of the daemon.
--- builtin/fsmonitor--daemon.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-)
builtin/fsmonitor--daemon.c +20 -7
@@ -1174,6 +1174,8 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)*/ .uds_disallow_chdir = 0 }; +int listener_started =0 ; +int err =0 ;/* * Start the IPC thread pool before the we've started the file
@@ -1194,15 +1196,20 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)if (pthread_create (&state->listener_thread,NULL , fsm_listen__thread_proc, state) <0 ) {ipc_server_stop_async (state->ipc_server_data); -ipc_server_await (state->ipc_server_data); - -return error (_ ("could not start fsmonitor listener thread" )); + err =error (_ ("could not start fsmonitor listener thread" )); + goto cleanup; } + listener_started =1 ;/* * The daemon is now fully functional in background threads. +* Our primary thread should now just wait while the threads +* do all the work. +*/ +cleanup: +/* * Wait for the IPC thread pool to shutdown (whether by client -* request or from filesystem activity). +* request, from filesystem activity, or an error). */ ipc_server_await (state->ipc_server_data);
@@ -1211,10 +1218,16 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)* event from the IPC thread pool, but it doesn't hurt to tell * it again. And wait for it to shutdown. */ -fsm_listen__stop_async (state); -pthread_join (state->listener_thread,NULL ); +if (listener_started) { +fsm_listen__stop_async (state); +pthread_join (state->listener_thread,NULL ); + } -return state->error_code; +if (err) +return err; +if (state->error_code) +return state->error_code; +return 0 ; }static int fsmonitor_run_daemon (void )
Signature · 1 line
gitgitgadget
PATCH v6 15/28 fsmonitor--daemon: rename listener thread related variables
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Rename platform-specific listener thread related variables and data types as we prepare to add another backend thread type.
[] struct fsmonitor_daemon_backend_data becomes struct fsm_listen_data [] state->backend_data becomes state->listen_data
[] state->error_code becomes state->listen_error_code
--- builtin/fsmonitor--daemon.c | 6 +++--- compat/fsmonitor/fsm-listen-darwin.c | 30 ++++++++++++++-------------- compat/fsmonitor/fsm-listen-win32.c | 28 +++++++++++++------------- compat/fsmonitor/fsm-listen.h | 2 +- fsmonitor--daemon.h | 6 +++--- 5 files changed, 36 insertions(+), 36 deletions(-)
builtin/fsmonitor--daemon.c +3 -3
@@ -1225,8 +1225,8 @@ cleanup:if (err)return err; -if (state->error_code) -return state->error_code; +if (state->listen_error_code) +return state->listen_error_code;return 0 ; }
@@ -1241,7 +1241,7 @@ static int fsmonitor_run_daemon(void)hashmap_init (&state.cookies, cookies_cmp,NULL ,0 );pthread_mutex_init (&state.main_lock,NULL );pthread_cond_init (&state.cookies_cond,NULL ); - state.error_code =0 ; + state.listen_error_code =0 ; state.current_token_data =fsmonitor_new_token_data ();/* Prepare to (recursively) watch the <worktree-root> directory. */
compat/fsmonitor/fsm-listen-darwin.c +15 -15
@@ -27,7 +27,7 @@#include "fsm-listen.h" #include "fsmonitor--daemon.h" -struct fsmonitor_daemon_backend_data +struct fsm_listen_data {CFStringRef cfsr_worktree_path;CFStringRef cfsr_gitdir_path;
@@ -158,7 +158,7 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef, const FSEventStreamEventId event_ids[]) {struct fsmonitor_daemon_state *state = ctx; -struct fsmonitor_daemon_backend_data *data = state->backend_data; +struct fsm_listen_data *data = state->listen_data;char **paths = (char **)event_paths;struct fsmonitor_batch *batch =NULL ;struct string_list cookie_list =STRING_LIST_INIT_DUP ;
@@ -350,11 +350,11 @@ int fsm_listen__ctor(struct fsmonitor_daemon_state *state)NULL , NULL }; -struct fsmonitor_daemon_backend_data *data; +struct fsm_listen_data *data;const void *dir_array[2 ];CALLOC_ARRAY (data,1 ); - state->backend_data = data; + state->listen_data = data; data->cfsr_worktree_path =CFStringCreateWithCString (NULL , state->path_worktree_watch.buf, kCFStringEncodingUTF8);
@@ -386,18 +386,18 @@ int fsm_listen__ctor(struct fsmonitor_daemon_state *state) failed:error (_ ("Unable to create FSEventStream." )); -FREE_AND_NULL (state->backend_data); +FREE_AND_NULL (state->listen_data);return -1 ; }void fsm_listen__dtor (struct fsmonitor_daemon_state *state) { -struct fsmonitor_daemon_backend_data *data; +struct fsm_listen_data *data; -if (!state || !state->backend_data) +if (!state || !state->listen_data)return ; - data = state->backend_data; + data = state->listen_data;if (data->stream) {if (data->stream_started)
@@ -407,14 +407,14 @@ void fsm_listen__dtor(struct fsmonitor_daemon_state *state)FSEventStreamRelease (data->stream); } -FREE_AND_NULL (state->backend_data); +FREE_AND_NULL (state->listen_data); }void fsm_listen__stop_async (struct fsmonitor_daemon_state *state) { -struct fsmonitor_daemon_backend_data *data; +struct fsm_listen_data *data; - data = state->backend_data; + data = state->listen_data; data->shutdown_style =SHUTDOWN_EVENT ;CFRunLoopStop (data->rl);
@@ -422,9 +422,9 @@ void fsm_listen__stop_async(struct fsmonitor_daemon_state *state)void fsm_listen__loop (struct fsmonitor_daemon_state *state) { -struct fsmonitor_daemon_backend_data *data; +struct fsm_listen_data *data; - data = state->backend_data; + data = state->listen_data; data->rl =CFRunLoopGetCurrent ();
@@ -441,7 +441,7 @@ void fsm_listen__loop(struct fsmonitor_daemon_state *state)switch (data->shutdown_style) {case FORCE_ERROR_STOP : - state->error_code =-1 ; + state->listen_error_code =-1 ;/* fall thru */ case FORCE_SHUTDOWN :ipc_server_stop_async (state->ipc_server_data);
@@ -453,7 +453,7 @@ void fsm_listen__loop(struct fsmonitor_daemon_state *state)return ; force_error_stop_without_loop: - state->error_code =-1 ; + state->listen_error_code =-1 ;ipc_server_stop_async (state->ipc_server_data);return ; }
compat/fsmonitor/fsm-listen-win32.c +14 -14
@@ -54,7 +54,7 @@ struct one_watchwchar_t dotgit_shortname[16 ];/* for 8.3 name */ }; -struct fsmonitor_daemon_backend_data +struct fsm_listen_data {struct one_watch *watch_worktree;struct one_watch *watch_gitdir;
@@ -263,7 +263,7 @@ static enum get_relative_result get_relative_longname(void fsm_listen__stop_async (struct fsmonitor_daemon_state *state) { -SetEvent (state->backend_data->hListener[LISTENER_SHUTDOWN ]); +SetEvent (state->listen_data->hListener[LISTENER_SHUTDOWN ]); } static struct one_watch *create_watch(struct fsmonitor_daemon_state *state,
@@ -337,7 +337,7 @@ static void destroy_watch(struct one_watch *watch)free (watch); } -static int start_rdcw_watch (struct fsmonitor_daemon_backend_data *data, +static int start_rdcw_watch (struct fsm_listen_data *data,struct one_watch *watch) { DWORD dwNotifyFilter =
@@ -516,7 +516,7 @@ static int process_1_worktree_event(*/ static int process_worktree_events (struct fsmonitor_daemon_state *state) { -struct fsmonitor_daemon_backend_data *data = state->backend_data; +struct fsm_listen_data *data = state->listen_data;struct one_watch *watch = data->watch_worktree;struct strbuf path =STRBUF_INIT ;struct string_list cookie_list =STRING_LIST_INIT_DUP ;
@@ -646,7 +646,7 @@ force_shutdown:*/ static int process_gitdir_events (struct fsmonitor_daemon_state *state) { -struct fsmonitor_daemon_backend_data *data = state->backend_data; +struct fsm_listen_data *data = state->listen_data;struct one_watch *watch = data->watch_gitdir;struct strbuf path =STRBUF_INIT ;struct string_list cookie_list =STRING_LIST_INIT_DUP ;
@@ -704,11 +704,11 @@ skip_this_path:void fsm_listen__loop (struct fsmonitor_daemon_state *state) { -struct fsmonitor_daemon_backend_data *data = state->backend_data; +struct fsm_listen_data *data = state->listen_data;DWORD dwWait;int result; - state->error_code =0 ; + state->listen_error_code =0 ;if (start_rdcw_watch (data, data->watch_worktree) ==-1 ) goto force_error_stop;
@@ -773,7 +773,7 @@ void fsm_listen__loop(struct fsmonitor_daemon_state *state) } force_error_stop: - state->error_code =-1 ; + state->listen_error_code =-1 ; force_shutdown:/*
@@ -790,7 +790,7 @@ clean_shutdown:int fsm_listen__ctor (struct fsmonitor_daemon_state *state) { -struct fsmonitor_daemon_backend_data *data; +struct fsm_listen_data *data;CALLOC_ARRAY (data,1 );
@@ -823,7 +823,7 @@ int fsm_listen__ctor(struct fsmonitor_daemon_state *state) data->nr_listener_handles++; } - state->backend_data = data; + state->listen_data = data;return 0 ; failed:
@@ -836,16 +836,16 @@ failed:void fsm_listen__dtor (struct fsmonitor_daemon_state *state) { -struct fsmonitor_daemon_backend_data *data; +struct fsm_listen_data *data; -if (!state || !state->backend_data) +if (!state || !state->listen_data)return ; - data = state->backend_data; + data = state->listen_data;CloseHandle (data->hEventShutdown);destroy_watch (data->watch_worktree);destroy_watch (data->watch_gitdir); -FREE_AND_NULL (state->backend_data); +FREE_AND_NULL (state->listen_data); }
compat/fsmonitor/fsm-listen.h +1 -1
@@ -33,7 +33,7 @@ void fsm_listen__dtor(struct fsmonitor_daemon_state *state);* do so if the listener thread receives a normal shutdown signal from * the IPC layer.) * -* It should set `state->error_code` to -1 if the daemon should exit +* It should set `state->listen_error_code` to -1 if the daemon should exit * with an error. */ void fsm_listen__loop (struct fsmonitor_daemon_state *state);
fsmonitor--daemon.h +3 -3
@@ -33,7 +33,7 @@ void fsmonitor_batch__free_list(struct fsmonitor_batch *batch);*/ void fsmonitor_batch__add_path (struct fsmonitor_batch *batch,const char *path); -struct fsmonitor_daemon_backend_data ;/* opaque platform-specific data */ +struct fsm_listen_data ;/* opaque platform-specific data for listener thread */ struct fsmonitor_daemon_state {pthread_t listener_thread;
@@ -50,8 +50,8 @@ struct fsmonitor_daemon_state {int cookie_seq;struct hashmap cookies; -int error_code; -struct fsmonitor_daemon_backend_data *backend_data; +int listen_error_code; +struct fsm_listen_data *listen_data;struct ipc_server_data *ipc_server_data;struct strbuf path_ipc;
Signature · 1 line
gitgitgadget
PATCH v6 16/28 fsmonitor--daemon: stub in health thread
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create another thread to watch over the daemon process and automatically shut it down if necessary.
This commit creates the basic framework for a "health" thread to monitor the daemon and/or the file system. Later commits will add platform-specific code to do the actual work.
The "health" thread is intended to monitor conditions that would be difficult to track inside the IPC thread pool and/or the file system listener threads. For example, when there are file system events outside of the watched worktree root or if we want to have an idle-timeout auto-shutdown feature.
This commit creates the health thread itself, defines the thread-proc and sets up the thread's event loop. It integrates this new thread into the existing IPC and Listener thread models.
This commit defines the API to the platform-specific code where all of the monitoring will actually happen.
The platform-specific code for MacOS is just stubs. Meaning that the health thread will immediately exit on MacOS, but that is OK and expected. Future work can define MacOS-specific monitoring.
The platform-specific code for Windows sets up enough of the WaitForMultipleObjects() machinery to watch for system and/or custom events. Currently, the set of wait handles only includes our custom shutdown event (sent from our other theads). Later commits in this series will extend the set of wait handles to monitor other conditions.
--- Makefile | 6 ++- builtin/fsmonitor--daemon.c | 39 +++++++++++++++ compat/fsmonitor/fsm-health-darwin.c | 24 ++++++++++ compat/fsmonitor/fsm-health-win32.c | 72 ++++++++++++++++++++++++++++ compat/fsmonitor/fsm-health.h | 47 ++++++++++++++++++ contrib/buildsystems/CMakeLists.txt | 2 + fsmonitor--daemon.h | 4 ++ 7 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 compat/fsmonitor/fsm-health-darwin.c create mode 100644 compat/fsmonitor/fsm-health-win32.c create mode 100644 compat/fsmonitor/fsm-health.h
Makefile +4 -2
@@ -472,8 +472,9 @@ all:: # # If your platform supports a built-in fsmonitor backend, set # FSMONITOR_DAEMON_BACKEND to the "<name>" of the corresponding -# `compat/fsmonitor/fsm-listen-<name>.c` that implements the -# `fsm_listen__*()` routines. +# `compat/fsmonitor/fsm-listen-<name>.c` and +# `compat/fsmonitor/fsm-health-<name>.c` files +# that implement the `fsm_listen__*()` and `fsm_health__*()` routines. # # If your platform has OS-specific ways to tell if a repo is incompatible with # fsmonitor (whether the hook or IPC daemon version), set FSMONITOR_OS_SETTINGS
@@ -1982,6 +1983,7 @@ endif ifdef FSMONITOR_DAEMON_BACKEND COMPAT_CFLAGS += -DHAVE_FSMONITOR_DAEMON_BACKEND COMPAT_OBJS += compat/fsmonitor/fsm-listen-$(FSMONITOR_DAEMON_BACKEND).o + COMPAT_OBJS += compat/fsmonitor/fsm-health-$(FSMONITOR_DAEMON_BACKEND).o endif ifdef FSMONITOR_OS_SETTINGS
builtin/fsmonitor--daemon.c +39 -0
@@ -3,6 +3,7 @@#include "parse-options.h" #include "fsmonitor.h" #include "fsmonitor-ipc.h" +#include "compat/fsmonitor/fsm-health.h" #include "compat/fsmonitor/fsm-listen.h" #include "fsmonitor--daemon.h" #include "simple-ipc.h"
@@ -1136,6 +1137,18 @@ void fsmonitor_publish(struct fsmonitor_daemon_state *state,pthread_mutex_unlock (&state->main_lock); } +static void *fsm_health__thread_proc (void *_state) +{ +struct fsmonitor_daemon_state *state = _state; + +trace2_thread_start ("fsm-health" ); + +fsm_health__loop (state); + +trace2_thread_exit (); +return NULL ; +} +static void *fsm_listen__thread_proc (void *_state) {struct fsmonitor_daemon_state *state = _state;
@@ -1174,6 +1187,7 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)*/ .uds_disallow_chdir = 0 }; +int health_started =0 ;int listener_started =0 ;int err =0 ;
@@ -1201,6 +1215,17 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state) } listener_started =1 ; +/* +* Start the health thread to watch over our process. +*/ +if (pthread_create (&state->health_thread,NULL , + fsm_health__thread_proc, state) <0 ) { +ipc_server_stop_async (state->ipc_server_data); + err =error (_ ("could not start fsmonitor health thread" )); + goto cleanup; + } + health_started =1 ; +/* * The daemon is now fully functional in background threads. * Our primary thread should now just wait while the threads
@@ -1223,10 +1248,17 @@ cleanup:pthread_join (state->listener_thread,NULL ); } +if (health_started) { +fsm_health__stop_async (state); +pthread_join (state->health_thread,NULL ); + } +if (err)return err;if (state->listen_error_code)return state->listen_error_code; +if (state->health_error_code) +return state->health_error_code;return 0 ; }
@@ -1242,6 +1274,7 @@ static int fsmonitor_run_daemon(void)pthread_mutex_init (&state.main_lock,NULL );pthread_cond_init (&state.cookies_cond,NULL ); state.listen_error_code =0 ; + state.health_error_code =0 ; state.current_token_data =fsmonitor_new_token_data ();/* Prepare to (recursively) watch the <worktree-root> directory. */
@@ -1321,6 +1354,11 @@ static int fsmonitor_run_daemon(void) goto done; } +if (fsm_health__ctor (&state)) { + err =error (_ ("could not initialize health thread" )); + goto done; + } +/* * CD out of the worktree root directory. *
@@ -1344,6 +1382,7 @@ done:pthread_cond_destroy (&state.cookies_cond);pthread_mutex_destroy (&state.main_lock);fsm_listen__dtor (&state); +fsm_health__dtor (&state);ipc_server_free (state.ipc_server_data);
compat/fsmonitor/fsm-health-darwin.c new file +24 -0
@@ -0,0 +1,24 @@ +#include "cache.h" +#include "config.h" +#include "fsmonitor.h" +#include "fsm-health.h" +#include "fsmonitor--daemon.h" + +int fsm_health__ctor (struct fsmonitor_daemon_state *state) +{ +return 0 ; +} + +void fsm_health__dtor (struct fsmonitor_daemon_state *state) +{ +return ; +} + +void fsm_health__loop (struct fsmonitor_daemon_state *state) +{ +return ; +} + +void fsm_health__stop_async (struct fsmonitor_daemon_state *state) +{ +}
compat/fsmonitor/fsm-health-win32.c new file +72 -0
72 lines not on this page · raw message
compat/fsmonitor/fsm-health.h new file +47 -0
47 lines not on this page · raw message
contrib/buildsystems/CMakeLists.txt +2 -0
@@ -289,12 +289,14 @@ if(SUPPORTS_SIMPLE_IPC) if(CMAKE_SYSTEM_NAME STREQUAL "Windows") add_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND) list(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-win32.c) + list(APPEND compat_SOURCES compat/fsmonitor/fsm-health-win32.c) add_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS) list(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-win32.c) elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") add_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND) list(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-darwin.c) + list(APPEND compat_SOURCES compat/fsmonitor/fsm-health-darwin.c) add_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS) list(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-darwin.c)
fsmonitor--daemon.h +4 -0
@@ -34,9 +34,11 @@ void fsmonitor_batch__free_list(struct fsmonitor_batch *batch);void fsmonitor_batch__add_path (struct fsmonitor_batch *batch,const char *path);struct fsm_listen_data ;/* opaque platform-specific data for listener thread */ +struct fsm_health_data ;/* opaque platform-specific data for health thread */ struct fsmonitor_daemon_state {pthread_t listener_thread; +pthread_t health_thread;pthread_mutex_t main_lock;struct strbuf path_worktree_watch;
@@ -51,7 +53,9 @@ struct fsmonitor_daemon_state {struct hashmap cookies;int listen_error_code; +int health_error_code;struct fsm_listen_data *listen_data; +struct fsm_health_data *health_data;struct ipc_server_data *ipc_server_data;struct strbuf path_ipc;
Signature · 1 line
gitgitgadget
Re: [PATCH v6 16/28] fsmonitor--daemon: stub in health thread
Hi Jeff,
Quoted 212 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
Create another thread to watch over the daemon process and automatically shut it down if necessary.
This commit creates the basic framework for a "health" thread to monitor the daemon and/or the file system. Later commits will add platform-specific code to do the actual work.
The "health" thread is intended to monitor conditions that would be difficult to track inside the IPC thread pool and/or the file system listener threads. For example, when there are file system events outside of the watched worktree root or if we want to have an idle-timeout auto-shutdown feature.
This commit creates the health thread itself, defines the thread-proc and sets up the thread's event loop. It integrates this new thread into the existing IPC and Listener thread models.
This commit defines the API to the platform-specific code where all of the monitoring will actually happen.
The platform-specific code for MacOS is just stubs. Meaning that the health thread will immediately exit on MacOS, but that is OK and expected. Future work can define MacOS-specific monitoring.
The platform-specific code for Windows sets up enough of the
WaitForMultipleObjects()machinery to watch for system and/or custom events. Currently, the set of wait handles only includes our custom shutdown event (sent from our other theads). Later commits in this series will extend the set of wait handles to monitor other conditions.Signed-off-by Jeff Hostetler <[email protected]>--- Makefile | 6 ++- builtin/fsmonitor--daemon.c | 39 +++++++++++++++ compat/fsmonitor/fsm-health-darwin.c | 24 ++++++++++ compat/fsmonitor/fsm-health-win32.c | 72 ++++++++++++++++++++++++++++ compat/fsmonitor/fsm-health.h | 47 ++++++++++++++++++ contrib/buildsystems/CMakeLists.txt | 2 + fsmonitor--daemon.h | 4 ++ 7 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 compat/fsmonitor/fsm-health-darwin.c create mode 100644 compat/fsmonitor/fsm-health-win32.c create mode 100644 compat/fsmonitor/fsm-health.hMakefile +4
-2@@ -472,8 +472,9 @@ all:: # # If your platform supports a built-in fsmonitor backend, set # FSMONITOR_DAEMON_BACKEND to the "<name>" of the corresponding -# `compat/fsmonitor/fsm-listen-<name>.c` that implements the -# `fsm_listen__*()` routines. +# `compat/fsmonitor/fsm-listen-<name>.c` and +# `compat/fsmonitor/fsm-health-<name>.c` files +# that implement the `fsm_listen__*()` and `fsm_health__*()` routines. # # If your platform has OS-specific ways to tell if a repo is incompatible with # fsmonitor (whether the hook or IPC daemon version), set FSMONITOR_OS_SETTINGS@@ -1982,6 +1983,7 @@ endif ifdef FSMONITOR_DAEMON_BACKEND COMPAT_CFLAGS += -DHAVE_FSMONITOR_DAEMON_BACKEND COMPAT_OBJS += compat/fsmonitor/fsm-listen-$(FSMONITOR_DAEMON_BACKEND).o + COMPAT_OBJS += compat/fsmonitor/fsm-health-$(FSMONITOR_DAEMON_BACKEND).o endif ifdef FSMONITOR_OS_SETTINGSbuiltin/fsmonitor--daemon.c +39
-0@@ -3,6 +3,7 @@#include "parse-options.h" #include "fsmonitor.h" #include "fsmonitor-ipc.h" +#include "compat/fsmonitor/fsm-health.h" #include "compat/fsmonitor/fsm-listen.h" #include "fsmonitor--daemon.h" #include "simple-ipc.h" @@ -1136,6 +1137,18 @@ void fsmonitor_publish(struct fsmonitor_daemon_state *state,pthread_mutex_unlock (&state->main_lock); } +static void *fsm_health__thread_proc (void *_state) +{ +struct fsmonitor_daemon_state *state = _state; + +trace2_thread_start ("fsm-health" ); + +fsm_health__loop (state); + +trace2_thread_exit (); +return NULL ; +} +static void *fsm_listen__thread_proc (void *_state) {struct fsmonitor_daemon_state *state = _state;@@ -1174,6 +1187,7 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)*/ .uds_disallow_chdir = 0 }; +int health_started =0 ;int listener_started =0 ;int err =0 ;@@ -1201,6 +1215,17 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state) } listener_started =1 ; +/* +* Start the health thread to watch over our process. +*/ +if (pthread_create (&state->health_thread,NULL , + fsm_health__thread_proc, state) <0 ) { +ipc_server_stop_async (state->ipc_server_data); + err =error (_ ("could not start fsmonitor health thread" )); + goto cleanup; + } + health_started =1 ; +/* * The daemon is now fully functional in background threads. * Our primary thread should now just wait while the threads @@ -1223,10 +1248,17 @@ cleanup:pthread_join (state->listener_thread,NULL ); } +if (health_started) { +fsm_health__stop_async (state); +pthread_join (state->health_thread,NULL ); + } +if (err)return err;if (state->listen_error_code)return state->listen_error_code; +if (state->health_error_code) +return state->health_error_code;return 0 ; }@@ -1242,6 +1274,7 @@ static int fsmonitor_run_daemon(void)pthread_mutex_init (&state.main_lock,NULL );pthread_cond_init (&state.cookies_cond,NULL ); state.listen_error_code =0 ; + state.health_error_code =0 ; state.current_token_data =fsmonitor_new_token_data ();/* Prepare to (recursively) watch the <worktree-root> directory. */ @@ -1321,6 +1354,11 @@ static int fsmonitor_run_daemon(void) goto done; } +if (fsm_health__ctor (&state)) { + err =error (_ ("could not initialize health thread" )); + goto done; + } +/* * CD out of the worktree root directory. * @@ -1344,6 +1382,7 @@ done:pthread_cond_destroy (&state.cookies_cond);pthread_mutex_destroy (&state.main_lock);fsm_listen__dtor (&state); +fsm_health__dtor (&state);ipc_server_free (state.ipc_server_data);compat/fsmonitor/fsm-health-darwin.c new file +24
-0@@ -0,0 +1,24 @@ +#include "cache.h" +#include "config.h" +#include "fsmonitor.h" +#include "fsm-health.h" +#include "fsmonitor--daemon.h" + +int fsm_health__ctor (struct fsmonitor_daemon_state *state) +{ +return 0 ; +} + +void fsm_health__dtor (struct fsmonitor_daemon_state *state) +{ +return ; +} + +void fsm_health__loop (struct fsmonitor_daemon_state *state) +{ +return ; +} + +void fsm_health__stop_async (struct fsmonitor_daemon_state *state) +{ +}compat/fsmonitor/fsm-health-win32.c new file +2
-0@@ -0,0 +1,72 @@ +#include "cache.h" +#include "config.h"
compat/fsmonitor/fsm-health-win32.c line 12
Quoted 10 lines
compat/fsmonitor/fsm-health-win32.c new file +10
-0+#include "fsmonitor.h" +#include "fsm-health.h" +#include "fsmonitor--daemon.h" + +struct fsm_health_data +{ +HANDLE hEventShutdown; + +HANDLE hHandles[1 ];/* the array does not own these handles */ +#define HEALTH_SHUTDOWN 0
How about defining HANDLE hHandles[HEALTH_SHUTDOWN + 1] to indicate that the constant is used as an offset into hHandles?
compat/fsmonitor/fsm-health-win32.c line 20
Quoted 8 lines
compat/fsmonitor/fsm-health-win32.c new file +8
-0+int nr_handles;/* number of active event handles */ +}; + +int fsm_health__ctor (struct fsmonitor_daemon_state *state) +{ +struct fsm_health_data *data; + +CALLOC_ARRAY (data,1 );
I _guess_ that this is okay, even if data is not actually an array. But it's a convenient construct to get the parameters right.
Thank you!
Dscho
Quoted 154 lines
compat/fsmonitor/fsm-health-win32.c new file +52
-0+ + data->hEventShutdown =CreateEvent (NULL , TRUE, FALSE,NULL ); + + data->hHandles[HEALTH_SHUTDOWN ] = data->hEventShutdown; + data->nr_handles++; + + state->health_data = data; +return 0 ; +} + +void fsm_health__dtor (struct fsmonitor_daemon_state *state) +{ +struct fsm_health_data *data; + +if (!state || !state->health_data) +return ; + + data = state->health_data; + +CloseHandle (data->hEventShutdown); + +FREE_AND_NULL (state->health_data); +} + +void fsm_health__loop (struct fsmonitor_daemon_state *state) +{ +struct fsm_health_data *data = state->health_data; + +for (;;) { +DWORD dwWait =WaitForMultipleObjects (data->nr_handles, + data->hHandles, + FALSE,INFINITE ); + +if (dwWait ==WAIT_OBJECT_0 +HEALTH_SHUTDOWN ) + goto clean_shutdown; + +error (_ ("health thread wait failed [GLE %ld]" ), +GetLastError ()); + goto force_error_stop; + } + +force_error_stop: + state->health_error_code =-1 ; +ipc_server_stop_async (state->ipc_server_data); +clean_shutdown: +return ; +} + +void fsm_health__stop_async (struct fsmonitor_daemon_state *state) +{ +SetEvent (state->health_data->hHandles[HEALTH_SHUTDOWN ]); +}compat/fsmonitor/fsm-health.h new file +47
-0@@ -0,0 +1,47 @@ +#ifndef FSM_HEALTH_H +#define FSM_HEALTH_H + +/* This needs to be implemented by each backend */ + +#ifdef HAVE_FSMONITOR_DAEMON_BACKEND + +struct fsmonitor_daemon_state ; + +/* +* Initialize platform-specific data for the fsmonitor health thread. +* This will be called from the main thread PRIOR to staring the +* thread. +* +* Returns 0 if successful. +* Returns -1 otherwise. +*/ +int fsm_health__ctor (struct fsmonitor_daemon_state *state); + +/* +* Cleanup platform-specific data for the health thread. +* This will be called from the main thread AFTER joining the thread. +*/ +void fsm_health__dtor (struct fsmonitor_daemon_state *state); + +/* +* The main body of the platform-specific event loop to monitor the +* health of the daemon process. This will run in the health thread. +* +* The health thread should call `ipc_server_stop_async()` if it needs +* to cause a shutdown. (It should NOT do so if it receives a shutdown +* shutdown signal.) +* +* It should set `state->health_error_code` to -1 if the daemon should exit +* with an error. +*/ +void fsm_health__loop (struct fsmonitor_daemon_state *state); + +/* +* Gently request that the health thread shutdown. +* It does not wait for it to stop. The caller should do a JOIN +* to wait for it. +*/ +void fsm_health__stop_async (struct fsmonitor_daemon_state *state); + +#endif /* HAVE_FSMONITOR_DAEMON_BACKEND */ +#endif /* FSM_HEALTH_H */ contrib/buildsystems/CMakeLists.txt +2
-0@@ -289,12 +289,14 @@ if(SUPPORTS_SIMPLE_IPC) if(CMAKE_SYSTEM_NAME STREQUAL "Windows") add_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND) list(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-win32.c) + list(APPEND compat_SOURCES compat/fsmonitor/fsm-health-win32.c) add_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS) list(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-win32.c) elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") add_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND) list(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-darwin.c) + list(APPEND compat_SOURCES compat/fsmonitor/fsm-health-darwin.c) add_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS) list(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-darwin.c)fsmonitor--daemon.h +4
-1@@ -34,9 +34,11 @@ void fsmonitor_batch__free_list(struct fsmonitor_batch *batch);void fsmonitor_batch__add_path (struct fsmonitor_batch *batch,const char *path);struct fsm_listen_data ;/* opaque platform-specific data for listener thread */ +struct fsm_health_data ;/* opaque platform-specific data for health thread */ struct fsmonitor_daemon_state {pthread_t listener_thread; +pthread_t health_thread;pthread_mutex_t main_lock;struct strbuf path_worktree_watch;@@ -51,7 +53,9 @@ struct fsmonitor_daemon_state {struct hashmap cookies;int listen_error_code; +int health_error_code;struct fsm_listen_data *listen_data; +struct fsm_health_data *health_data;struct ipc_server_data *ipc_server_data;struct strbuf path_ipc; --gitgitgadget
Re: [PATCH v6 16/28] fsmonitor--daemon: stub in health thread
Quoted 9 lines · Johannes Schindelin
Hi Jeff,
Quoted 5 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
Create another thread to watch over the daemon process and automatically shut it down if necessary.
[...]
Quoted 6 lines
Quoted 6 lines
The platform-specific code for Windows sets up enough of the
WaitForMultipleObjects()machinery to watch for system and/or custom events. Currently, the set of wait handles only includes our custom shutdown event (sent from our other theads). Later commits in this series will extend the set of wait handles to monitor other conditions.
[...]
Quoted 22 lines
Quoted 8 lines
compat/fsmonitor/fsm-health-win32.c new file +2
-0@@ -0,0 +1,72 @@ +#include "cache.h" +#include "config.h" compat/fsmonitor/fsm-health-win32.c line 12
Quoted 10 lines
compat/fsmonitor/fsm-health-win32.c new file +10
-0+#include "fsmonitor.h" +#include "fsm-health.h" +#include "fsmonitor--daemon.h" + +struct fsm_health_data +{ +HANDLE hEventShutdown; + +HANDLE hHandles[1 ];/* the array does not own these handles */ +#define HEALTH_SHUTDOWN 0How about defining
HANDLE hHandles[HEALTH_SHUTDOWN + 1]to indicate that the constant is used as an offset intohHandles?
Quoted 1 line
compat/fsmonitor/fsm-health-win32.c line 13
Quoted 1 line
compat/fsmonitor/fsm-health-win32.c new file +1
-0+int nr_handles;/* number of active event handles */
I think I'd like to keep this one as is. It matches the style that I used in fsm-listen-win32.c where I have 3 listener handles.
Granted, it does look a little odd when there is only 1 handle in the array. But the idea was to allow new handles to be added as we want to watch more things.
It might be clearer (in both of them) to define the array in terms of an enum rather than a local list of #define's. But I'm not sure it matters.
Jeff
Re: [PATCH v6 16/28] fsmonitor--daemon: stub in health thread
Hi Jeff,
Quoted 47 lines · Jeff Hostetler
Quoted 8 lines · Johannes Schindelin
Quoted 5 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
Create another thread to watch over the daemon process and automatically shut it down if necessary.
[...]
Quoted 6 lines
Quoted 6 lines
The platform-specific code for Windows sets up enough of the
WaitForMultipleObjects()machinery to watch for system and/or custom events. Currently, the set of wait handles only includes our custom shutdown event (sent from our other theads). Later commits in this series will extend the set of wait handles to monitor other conditions.[...]
Quoted 23 lines
Quoted 9 lines
compat/fsmonitor/fsm-health-win32.c new file +2
-0@@ -0,0 +1,72 @@ +#include "cache.h" +#include "config.h" compat/fsmonitor/fsm-health-win32.c line 12
Quoted 10 lines
compat/fsmonitor/fsm-health-win32.c new file +10
-0+#include "fsmonitor.h" +#include "fsm-health.h" +#include "fsmonitor--daemon.h" + +struct fsm_health_data +{ +HANDLE hEventShutdown; + +HANDLE hHandles[1 ];/* the array does not own these handles */ +#define HEALTH_SHUTDOWN 0How about defining
HANDLE hHandles[HEALTH_SHUTDOWN + 1]to indicate that the constant is used as an offset intohHandles?Quoted 1 line
compat/fsmonitor/fsm-health-win32.c line 13
Quoted 1 line
compat/fsmonitor/fsm-health-win32.c new file +1
-0+int nr_handles;/* number of active event handles */ I think I'd like to keep this one as is. It matches the style that I used in
fsm-listen-win32.cwhere I have 3 listener handles.
Quoted 7 lines
Granted, it does look a little odd when there is only 1 handle in the array. But the idea was to allow new handles to be added as we want to watch more things.
It might be clearer (in both of them) to define the array in terms of an enum rather than a local list of #define's. But I'm not sure it matters.
Your explanation makes sense to me, I have no objections against keeping the code as-is.
Thanks,
Dscho
PATCH v6 17/28 fsm-health-win32: add polling framework to monitor daemon health
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Extend the Windows version of the "health" thread to periodically inspect the system and shutdown if warranted.
This commit updates the thread's wait loop to use a timeout and defines a (currently empty) table of functions to poll the system.
A later commit will add functions to the table to actually inspect the system.
--- compat/fsmonitor/fsm-health-win32.c | 65 ++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-)
compat/fsmonitor/fsm-health-win32.c +64 -1
@@ -4,6 +4,24 @@#include "fsm-health.h" #include "fsmonitor--daemon.h" +/* +* Every minute wake up and test our health. +*/ +#define WAIT_FREQ_MS (60 * 1000) + +/* +* State machine states for each of the interval functions +* used for polling our health. +*/ +enum interval_fn_ctx { +CTX_INIT =0 , +CTX_TERM , +CTX_TIMER +}; + +typedef int (interval_fn )(struct fsmonitor_daemon_state *state, +enum interval_fn_ctx ctx); +struct fsm_health_data {HANDLE hEventShutdown;
@@ -42,18 +60,61 @@ void fsm_health__dtor(struct fsmonitor_daemon_state *state)FREE_AND_NULL (state->health_data); } +/* +* A table of the polling functions. +*/ +static interval_fn *table[] = { +NULL ,/* must be last */ +}; + +/* +* Call all of the polling functions in the table. +* Shortcut and return first error. +* +* Return 0 if all succeeded. +*/ +static int call_all (struct fsmonitor_daemon_state *state, +enum interval_fn_ctx ctx) +{ +int k; + +for (k =0 ; table[k]; k++) { +int r = table[k](state, ctx); +if (r) +return r; + } + +return 0 ; +} +void fsm_health__loop (struct fsmonitor_daemon_state *state) {struct fsm_health_data *data = state->health_data; +int r; + + r =call_all (state,CTX_INIT ); +if (r <0 ) + goto force_error_stop; +if (r >0 ) + goto force_shutdown;for (;;) {DWORD dwWait =WaitForMultipleObjects (data->nr_handles, data->hHandles, - FALSE,INFINITE ); + FALSE,WAIT_FREQ_MS );if (dwWait ==WAIT_OBJECT_0 +HEALTH_SHUTDOWN ) goto clean_shutdown; +if (dwWait ==WAIT_TIMEOUT ) { + r =call_all (state,CTX_TIMER ); +if (r <0 ) + goto force_error_stop; +if (r >0 ) + goto force_shutdown; +continue ; + } +error (_ ("health thread wait failed [GLE %ld]" ),GetLastError ()); goto force_error_stop;
@@ -61,8 +122,10 @@ void fsm_health__loop(struct fsmonitor_daemon_state *state) force_error_stop: state->health_error_code =-1 ; +force_shutdown:ipc_server_stop_async (state->ipc_server_data); clean_shutdown: +call_all (state,CTX_TERM );return ; }
Signature · 1 line
gitgitgadget
PATCH v6 18/28 fsm-health-win32: force shutdown daemon if worktree root moves
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Force shutdown fsmonitor daemon if the worktree root directory is moved, renamed, or deleted.
Use Windows low-level GetFileInformationByHandle() to get and compare the Windows system unique ID for the directory with a cached version when we started up. This lets us detect the case where someone renames the directory that we are watching and then creates a new directory with the original pathname.
This is important because we are listening to a named pipe for requests and they are stored in the Named Pipe File System (NPFS) which a kernel-resident pseudo filesystem not associated with the actual NTFS directory.
For example, if the daemon was watching "~/foo/", it would have a directory-watch handle on that directory and a named-pipe handle for "//./pipe/...foo". Moving the directory to "~/bar/" does not invalidate the directory handle. (So the daemon would actually be watching "~/bar" but listening on "//./pipe/...foo". If the user then does "git init ~/foo" and causes another daemon to start, the first daemon will still have ownership of the pipe and the second daemon instance will fail to start. "git status" clients in "~/foo" will ask "//./pipe/...foo" about changes and the first daemon instance will tell them about "~/bar".
This commit causes the first daemon to shutdown if the system unique ID for "~/foo" changes (changes from what it was when the daemon started). Shutdown occurs after a periodic poll. After the first daemon exits and releases the lock on the named pipe, subsequent Git commands may cause another daemon to be started on "~/foo". Similarly, a subsequent Git command may cause another daemon to be started on "~/bar".
--- compat/fsmonitor/fsm-health-win32.c | 143 ++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+)
compat/fsmonitor/fsm-health-win32.c +143 -0
@@ -29,8 +29,150 @@ struct fsm_health_dataHANDLE hHandles[1 ];/* the array does not own these handles */ #define HEALTH_SHUTDOWN 0int nr_handles;/* number of active event handles */ + +struct wt_moved + { +wchar_t wpath[MAX_PATH +1 ]; +BY_HANDLE_FILE_INFORMATION bhfi; + } wt_moved; }; +/* +* Lookup the system unique ID for the path. This is as close as +* we get to an inode number, but this also contains volume info, +* so it is a little stronger. +*/ +static int lookup_bhfi (wchar_t *wpath, +BY_HANDLE_FILE_INFORMATION *bhfi) +{ +DWORD desired_access =FILE_LIST_DIRECTORY ; +DWORD share_mode = +FILE_SHARE_WRITE |FILE_SHARE_READ |FILE_SHARE_DELETE ; +HANDLE hDir; + + hDir =CreateFileW (wpath, desired_access, share_mode,NULL , +OPEN_EXISTING ,FILE_FLAG_BACKUP_SEMANTICS ,NULL ); +if (hDir ==INVALID_HANDLE_VALUE ) { +error (_ ("[GLE %ld] health thread could not open '%ls'" ), +GetLastError (), wpath); +return -1 ; + } + +if (!GetFileInformationByHandle (hDir, bhfi)) { +error (_ ("[GLE %ld] health thread getting BHFI for '%ls'" ), +GetLastError (), wpath); +CloseHandle (hDir); +return -1 ; + } + +CloseHandle (hDir); +return 0 ; +} + +/* +* Compare the relevant fields from two system unique IDs. +* We use this to see if two different handles to the same +* path actually refer to the same *instance* of the file +* or directory. +*/ +static int bhfi_eq (const BY_HANDLE_FILE_INFORMATION *bhfi_1, +const BY_HANDLE_FILE_INFORMATION *bhfi_2) +{ +return (bhfi_1->dwVolumeSerialNumber == bhfi_2->dwVolumeSerialNumber && + bhfi_1->nFileIndexHigh == bhfi_2->nFileIndexHigh && + bhfi_1->nFileIndexLow == bhfi_2->nFileIndexLow); +} + +/* +* Shutdown if the original worktree root directory been deleted, +* moved, or renamed? +* +* Since the main thread did a "chdir(getenv($HOME))" and our CWD +* is not in the worktree root directory and because the listener +* thread added FILE_SHARE_DELETE to the watch handle, it is possible +* for the root directory to be moved or deleted while we are still +* watching it. We want to detect that here and force a shutdown. +* +* Granted, a delete MAY cause some operations to fail, such as +* GetOverlappedResult(), but it is not guaranteed. And because +* ReadDirectoryChangesW() only reports on changes *WITHIN* the +* directory, not changes *ON* the directory, our watch will not +* receive a delete event for it. +* +* A move/rename of the worktree root will also not generate an event. +* And since the listener thread already has an open handle, it may +* continue to receive events for events within the directory. +* However, the pathname of the named-pipe was constructed using the +* original location of the worktree root. (Remember named-pipes are +* stored in the NPFS and not in the actual file system.) Clients +* trying to talk to the worktree after the move/rename will not +* reach our daemon process, since we're still listening on the +* pipe with original path. +* +* Furthermore, if the user does something like: +* +* $ mv repo repo.old +* $ git init repo +* +* A new daemon cannot be started in the new instance of "repo" +* because the named-pipe is still being used by the daemon on +* the original instance. +* +* So, detect move/rename/delete and shutdown. This should also +* handle unsafe drive removal. +* +* We use the file system unique ID to distinguish the original +* directory instance from a new instance and force a shutdown +* if the unique ID changes. +* +* Since a worktree move/rename/delete/unmount doesn't happen +* that often (and we can't get an immediate event anyway), we +* use a timeout and periodically poll it. +*/ +static int has_worktree_moved (struct fsmonitor_daemon_state *state, +enum interval_fn_ctx ctx) +{ +struct fsm_health_data *data = state->health_data; +BY_HANDLE_FILE_INFORMATION bhfi; +int r; + +switch (ctx) { +case CTX_TERM : +return 0 ; + +case CTX_INIT : +if (xutftowcs_path (data->wt_moved.wpath, + state->path_worktree_watch.buf) <0 ) { +error (_ ("could not convert to wide characters: '%s'" ), + state->path_worktree_watch.buf); +return -1 ; + } + +/* +* On the first call we lookup the unique sequence ID for +* the worktree root directory. +*/ +return lookup_bhfi (data->wt_moved.wpath, &data->wt_moved.bhfi); + +case CTX_TIMER : + r =lookup_bhfi (data->wt_moved.wpath, &bhfi); +if (r) +return r; +if (!bhfi_eq (&data->wt_moved.bhfi, &bhfi)) { +error (_ ("BHFI changed '%ls'" ), data->wt_moved.wpath); +return -1 ; + } +return 0 ; + +default : +die (_ ("unhandled case in 'has_worktree_moved': %d" ), + (int )ctx); + } + +return 0 ; +} + +int fsm_health__ctor (struct fsmonitor_daemon_state *state) {struct fsm_health_data *data;
@@ -64,6 +206,7 @@ void fsm_health__dtor(struct fsmonitor_daemon_state *state)* A table of the polling functions. */ static interval_fn *table[] = { + has_worktree_moved,NULL ,/* must be last */ };
Signature · 1 line
gitgitgadget
PATCH v6 19/28 fsm-listen-darwin: shutdown daemon if worktree root is moved/renamed
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Teach the listener thread to shutdown the daemon if the spelling of the worktree root directory changes.
--- compat/fsmonitor/fsm-listen-darwin.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+)
compat/fsmonitor/fsm-listen-darwin.c +25 -0
@@ -106,6 +106,11 @@ static void log_flags_set(const char *path, const FSEventStreamEventFlags flag)strbuf_release (&msg); } +static int ef_is_root_changed (const FSEventStreamEventFlags ef) +{ +return (ef & kFSEventStreamEventFlagRootChanged); +} +static int ef_is_root_delete (const FSEventStreamEventFlags ef) { return (ef & kFSEventStreamEventFlagItemIsDir &&
@@ -215,6 +220,26 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef,continue ; } +if (ef_is_root_changed (event_flags[k])) { +/* +* The spelling of the pathname of the root directory +* has changed. This includes the name of the root +* directory itself or of any parent directory in the +* path. +* +* (There may be other conditions that throw this, +* but I couldn't find any information on it.) +* +* Force a shutdown now and avoid things getting +* out of sync. The Unix domain socket is inside +* the .git directory and a spelling change will make +* it hard for clients to rendezvous with us. +*/ +trace_printf_key (&trace_fsmonitor, +"event: root changed" ); + goto force_shutdown; + } +if (ef_ignore_xattr (event_flags[k])) { trace_printf_key(&trace_fsmonitor, "ignore-xattr: '%s', flags=0x%x",
Signature · 1 line
gitgitgadget
PATCH v6 20/28 fsmonitor: optimize processing of directory events
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Teach Git to perform binary search over the cache-entries for a directory notification and then linearly scan forward to find the immediate children.
Previously, when the FSMonitor reported a modified directory Git would perform a linear search on the entire cache-entry array for all entries matching that directory prefix and invalidate them. Since the cache-entry array is already sorted, we can use a binary search to find the first matching entry and then only linearly walk forward and invalidate entries until the prefix changes.
Also, the original code would invalidate anything having the same directory prefix. Since a directory event should only be received for items that are immediately within the directory (and not within sub-directories of it), only invalidate those entries and not the whole subtree.
--- fsmonitor.c | 71 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 54 insertions(+), 17 deletions(-)
fsmonitor.c +54 -17
@@ -184,30 +184,68 @@ static int query_fsmonitor_hook(struct repository *r,static void fsmonitor_refresh_callback (struct index_state *istate,char *name) {int i, len =strlen (name); -if (name[len -1 ] =='/' ) { +int pos =index_name_pos (istate, name, len); + +trace_printf_key (&trace_fsmonitor, +"fsmonitor_refresh_callback '%s' (pos %d)" , + name, pos); +if (name[len -1 ] =='/' ) {/* -* TODO We should binary search to find the first path with -* TODO this directory prefix. Then linearly update entries -* TODO while the prefix matches. Taking care to search without -* TODO the trailing slash -- because '/' sorts after a few -* TODO interesting special chars, like '.' and ' '. +* 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 =0 ; i < istate->cache_nr; i++) { -if (istate->cache[i]->ce_flags &CE_FSMONITOR_VALID && -starts_with (istate->cache[i]->name, name)) - istate->cache[i]->ce_flags &= ~CE_FSMONITOR_VALID ; +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 ; } -/* Need to remove the / from the path for the untracked cache */ + +/* +* 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 { -int pos =index_name_pos (istate, name,strlen (name)); - -if (pos >=0 ) { -struct cache_entry *ce = istate->cache[pos]; - ce->ce_flags &= ~CE_FSMONITOR_VALID ; +/* +* 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 ; } }
@@ -215,7 +253,6 @@ static void fsmonitor_refresh_callback(struct index_state *istate, char *name)* Mark the untracked cache dirty even if it wasn't found in the index * as it could be a new untracked file. */ -trace_printf_key (&trace_fsmonitor,"fsmonitor_refresh_callback '%s'" , name);untracked_cache_invalidate_path (istate, name,0 ); }
Signature · 1 line
gitgitgadget
Re: [PATCH v6 20/28] fsmonitor: optimize processing of directory events
Hi Jeff,
Quoted 96 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
Teach Git to perform binary search over the cache-entries for a directory notification and then linearly scan forward to find the immediate children.
Previously, when the FSMonitor reported a modified directory Git would perform a linear search on the entire cache-entry array for all entries matching that directory prefix and invalidate them. Since the cache-entry array is already sorted, we can use a binary search to find the first matching entry and then only linearly walk forward and invalidate entries until the prefix changes.
Also, the original code would invalidate anything having the same directory prefix. Since a directory event should only be received for items that are immediately within the directory (and not within sub-directories of it), only invalidate those entries and not the whole subtree.
Signed-off-by Jeff Hostetler <[email protected]>--- fsmonitor.c | 71 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 54 insertions(+), 17 deletions(-)fsmonitor.c +41
-16@@ -184,30 +184,68 @@ static int query_fsmonitor_hook(struct repository *r,static void fsmonitor_refresh_callback (struct index_state *istate,char *name) {int i, len =strlen (name); -if (name[len -1 ] =='/' ) { +int pos =index_name_pos (istate, name, len); + +trace_printf_key (&trace_fsmonitor, +"fsmonitor_refresh_callback '%s' (pos %d)" , + name, pos); +if (name[len -1 ] =='/' ) {/* -* TODO We should binary search to find the first path with -* TODO this directory prefix. Then linearly update entries -* TODO while the prefix matches. Taking care to search without -* TODO the trailing slash -- because '/' sorts after a few -* TODO interesting special chars, like '.' and ' '. +* 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 =0 ; i < istate->cache_nr; i++) { -if (istate->cache[i]->ce_flags &CE_FSMONITOR_VALID && -starts_with (istate->cache[i]->name, name)) - istate->cache[i]->ce_flags &= ~CE_FSMONITOR_VALID ; +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 ; } -/* Need to remove the / from the path for the untracked cache */ + +/* +* 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 { -int pos =index_name_pos (istate, name,strlen (name)); - -if (pos >=0 ) { -struct cache_entry *ce = istate->cache[pos]; - ce->ce_flags &= ~CE_FSMONITOR_VALID ; +/* +* 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
fsmonitor.c static int query_fsmonitor_hook(struct repository *r, · line 245
Quoted 10 lines
fsmonitor.c +10
-0+* 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] >'/' )
Nice attention to detail casting istate->cache[i]->name[len] to (unsigned char) before comparing to '/'!
fsmonitor.c static void fsmonitor_refresh_callback(struct index_state *istate, char *name) · line 218
Quoted 11 lines
fsmonitor.c +3
-1+break ; +if (istate->cache[i]->name[len] =='/' ) + istate->cache[i]->ce_flags &= ~CE_FSMONITOR_VALID ; } }@@ -215,7 +253,6 @@ static void fsmonitor_refresh_callback(struct index_state *istate, char *name)* Mark the untracked cache dirty even if it wasn't found in the index * as it could be a new untracked file. */ -trace_printf_key (&trace_fsmonitor,"fsmonitor_refresh_callback '%s'" , name);
Did you mean to remove this statement in this patch? Not a big issue, but I wonder what the rationale for it is, and since I have an inquisitive mind, I figured I'd just ask.
Thanks,
Dscho
Quoted 7 lines
fsmonitor.c +0
-1untracked_cache_invalidate_path (istate, name,0 ); } --gitgitgadget
Re: [PATCH v6 20/28] fsmonitor: optimize processing of directory events
Quoted 9 lines · Johannes Schindelin
Hi Jeff,
Quoted 5 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
Teach Git to perform binary search over the cache-entries for a directory notification and then linearly scan forward to find the immediate children.
[...]
Quoted 10 lines
Quoted 10 lines
static void fsmonitor_refresh_callback(struct index_state *istate, char *name) { int i, len = strlen(name);fsmonitor.c +5
-1-if (name[len -1 ] =='/' ) { +int pos =index_name_pos (istate, name, len); + +trace_printf_key (&trace_fsmonitor, +"fsmonitor_refresh_callback '%s' (pos %d)" , + name, pos);
[...]
Quoted 1 line
Quoted 1 line
fsmonitor.c +1
-0+if (name[len -1 ] =='/' ) {
[...]
Quoted 11 lines
fsmonitor.c static void fsmonitor_refresh_callback(struct index_state *istate, char *name) · line 218
Quoted 7 lines
}fsmonitor.c +0
-1@@ -215,7 +253,6 @@ static void fsmonitor_refresh_callback(struct index_state *istate, char *name)* Mark the untracked cache dirty even if it wasn't found in the index * as it could be a new untracked file. */ -trace_printf_key (&trace_fsmonitor,"fsmonitor_refresh_callback '%s'" , name);Did you mean to remove this statement in this patch? Not a big issue, but I wonder what the rationale for it is, and since I have an inquisitive mind, I figured I'd just ask.
I just moved it to the top of the function. That lets me see name before it is modified in one of the else arms (it was helpful to see whether the daemon sent a trailing slash or not). And I also wanted to see the computed value of pos (before the "-pos - 1" tricks).
Jeff
Re: [PATCH v6 20/28] fsmonitor: optimize processing of directory events
Hi Jeff,
Quoted 41 lines · Jeff Hostetler
Quoted 9 lines · Johannes Schindelin
Quoted 6 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
Teach Git to perform binary search over the cache-entries for a directory notification and then linearly scan forward to find the immediate children.
[...]
Quoted 11 lines
Quoted 11 lines
static void fsmonitor_refresh_callback(struct index_state *istate, char *name) { int i, len = strlen(name);fsmonitor.c +5
-1-if (name[len -1 ] =='/' ) { +int pos =index_name_pos (istate, name, len); + +trace_printf_key (&trace_fsmonitor, +"fsmonitor_refresh_callback '%s' (pos %d)" , + name, pos);[...]
Quoted 1 line
Quoted 1 line
fsmonitor.c +1
-0+if (name[len -1 ] =='/' ) {[...]
Quoted 13 lines
fsmonitor.c static void fsmonitor_refresh_callback(struct · line 257
Quoted 9 lines
}fsmonitor.c +0
-1@@ -215,7 +253,6 @@ static void fsmonitor_refresh_callback(structindex_state *istate, char *name) * Mark the untracked cache dirty even if it wasn't found in the index * as it could be a new untracked file. */ -trace_printf_key (&trace_fsmonitor,"fsmonitor_refresh_callback '%s'" , name);Did you mean to remove this statement in this patch? Not a big issue, but I wonder what the rationale for it is, and since I have an inquisitive mind, I figured I'd just ask.
Quoted 4 lines
I just moved it to the top of the function. That lets me see
namebefore it is modified in one of the else arms (it was helpful to see whether the daemon sent a trailing slash or not). And I also wanted to see the computed value ofpos(before the "-pos - 1" tricks).
Oh, I missed that it was moved rather than removed. Sorry for that!
Thank you,
Dscho
PATCH v6 21/28 t7527: FSMonitor tests for directory moves
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create unit tests to move a directory. Verify that git status gives the same result with and without FSMonitor enabled.
--- t/t7527-builtin-fsmonitor.sh | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+)
t/t7527-builtin-fsmonitor.sh +27 -0
@@ -274,6 +274,16 @@ test_expect_success 'setup' ' trace* EOF + mkdir -p T1/T2/T3/T4 && + echo 1 >T1/F1 && + echo 1 >T1/T2/F1 && + echo 1 >T1/T2/T3/F1 && + echo 1 >T1/T2/T3/T4/F1 && + echo 2 >T1/F2 && + echo 2 >T1/T2/F2 && + echo 2 >T1/T2/T3/F2 && + echo 2 >T1/T2/T3/T4/F2 && + git -c core.fsmonitor=false add . && test_tick && git -c core.fsmonitor=false commit -m initial &&
@@ -356,6 +366,19 @@ directory_to_file () { echo 1 >dir1 } +move_directory_contents_deeper() { + mkdir T1/_new_ && + mv T1/[A-Z]* T1/_new_ +} + +move_directory_up() { + mv T1/T2/T3 T1 +} + +move_directory() { + mv T1/T2/T3 T1/T2/NewT3 +} + # The next few test cases confirm that our fsmonitor daemon sees each type # of OS filesystem notification that we care about. At this layer we just # ensure we are getting the OS notifications and do not try to confirm what
@@ -660,6 +683,10 @@ do matrix_try $uc_val $fsm_val file_to_directory matrix_try $uc_val $fsm_val directory_to_file + matrix_try $uc_val $fsm_val move_directory_contents_deeper + matrix_try $uc_val $fsm_val move_directory_up + matrix_try $uc_val $fsm_val move_directory + if test $fsm_val = true then test_expect_success "Matrix[uc:$uc_val][fsm:$fsm_val] disable fsmonitor at end" '
Signature · 1 line
gitgitgadget
PATCH v6 22/28 t/perf/p7527: add perf test for builtin FSMonitor
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
--- t/perf/p7527-builtin-fsmonitor.sh | 257 ++++++++++++++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100755 t/perf/p7527-builtin-fsmonitor.sh
t/perf/p7527-builtin-fsmonitor.sh new file +257 -0
@@ -0,0 +1,257 @@ +#!/bin/sh + +test_description="Perf test for the builtin FSMonitor" + +. ./perf-lib.sh + +if ! test_have_prereq FSMONITOR_DAEMON +then + skip_all="fsmonitor--daemon is not supported on this platform" + test_done +fi + +test_lazy_prereq UNTRACKED_CACHE ' + { git update-index --test-untracked-cache; ret=$?; } && + test $ret -ne 1 +' + +# Lie to perf-lib and ask for a new empty repo and avoid +# the complaints about GIT_PERF_REPO not being big enough +# the perf hit when GIT_PERF_LARGE_REPO is copied into +# the trash directory. +# +# NEEDSWORK: It would be nice if perf-lib had an option to +# "borrow" an existing large repo (especially for gigantic +# monorepos) and use it in-place. For now, fake it here. +# +test_perf_fresh_repo + + +# Use a generated synthetic monorepo. If it doesn't exist, we will +# generate it. If it does exist, we will put it in a known state +# before we start our timings. +# +PARAM_D=5 +PARAM_W=10 +PARAM_F=9 + +PARAMS="$PARAM_D"."$PARAM_W"."$PARAM_F" + +BALLAST_BR=p0006-ballast +export BALLAST_BR + +TMP_BR=tmp_br +export TMP_BR + +REPO=../repos/gen-many-files-"$PARAMS".git +export REPO + +if ! test -d $REPO +then + (cd ../repos; ./many-files.sh -d $PARAM_D -w $PARAM_W -f $PARAM_F) +fi + + +enable_uc () { + git -C $REPO config core.untrackedcache true + git -C $REPO update-index --untracked-cache + git -C $REPO status >/dev/null 2>&1 +} + +disable_uc () { + git -C $REPO config core.untrackedcache false + git -C $REPO update-index --no-untracked-cache + git -C $REPO status >/dev/null 2>&1 +} + +start_fsm () { + git -C $REPO fsmonitor--daemon start + git -C $REPO fsmonitor--daemon status + git -C $REPO config core.fsmonitor true + git -C $REPO update-index --fsmonitor + git -C $REPO status >/dev/null 2>&1 +} + +stop_fsm () { + git -C $REPO config --unset core.fsmonitor + git -C $REPO update-index --no-fsmonitor + test_might_fail git -C $REPO fsmonitor--daemon stop 2>/dev/null + git -C $REPO status >/dev/null 2>&1 +} + + +# Ensure that FSMonitor is turned off on the borrowed repo. +# +test_expect_success "Setup borrowed repo (fsm+uc)" " + stop_fsm && + disable_uc +" + +# Also ensure that it starts in a known state. +# +# Because we assume that $GIT_PERF_REPEAT_COUNT > 1, we are not going to time +# the ballast checkout, since only the first invocation does any work and the +# subsequent ones just print "already on branch" and quit, so the reported +# time is not useful. +# +# Create a temp branch and do all work relative to it so that we don't +# accidentially alter the real ballast branch. +# +test_expect_success "Setup borrowed repo (temp ballast branch)" " + test_might_fail git -C $REPO checkout $BALLAST_BR && + test_might_fail git -C $REPO reset --hard && + git -C $REPO clean -d -f && + test_might_fail git -C $REPO branch -D $TMP_BR && + git -C $REPO branch $TMP_BR $BALLAST_BR && + git -C $REPO checkout $TMP_BR +" + + +echo Data >data.txt + +# NEEDSWORK: We assume that $GIT_PERF_REPEAT_COUNT > 1. With +# FSMonitor enabled, we can get a skewed view of status times, since +# the index MAY (or may not) be updated after the first invocation +# which will update the FSMonitor Token, so the subsequent invocations +# may get a smaller response from the daemon. +# +do_status () { + msg=$1 + + test_perf "$msg" " + git -C $REPO status >/dev/null 2>&1 + " +} + +do_matrix () { + uc=$1 + fsm=$2 + + t="[uc $uc][fsm $fsm]" + MATRIX_BR="$TMP_BR-$uc-$fsm" + + test_expect_success "$t Setup matrix branch" " + git -C $REPO clean -d -f && + git -C $REPO checkout $TMP_BR && + test_might_fail git -C $REPO branch -D $MATRIX_BR && + git -C $REPO branch $MATRIX_BR $TMP_BR && + git -C $REPO checkout $MATRIX_BR + " + + if test $uc = true + then + enable_uc + else + disable_uc + fi + + if test $fsm = true + then + start_fsm + else + stop_fsm + fi + + do_status "$t status after checkout" + + # Modify many files in the matrix branch. + # Stage them. + # Commit them. + # Rollback. + # + test_expect_success "$t modify tracked files" " + find $REPO -name file1 -exec cp data.txt {} \\; + " + + do_status "$t status after big change" + + # Don't bother timing the "add" because _REPEAT_COUNT + # issue described above. + # + test_expect_success "$t add all" " + git -C $REPO add -A + " + + do_status "$t status after add all" + + test_expect_success "$t add dot" " + git -C $REPO add . + " + + do_status "$t status after add dot" + + test_expect_success "$t commit staged" " + git -C $REPO commit -a -m data + " + + do_status "$t status after commit" + + test_expect_success "$t reset HEAD~1 hard" " + git -C $REPO reset --hard HEAD~1 >/dev/null 2>&1 + " + + do_status "$t status after reset hard" + + # Create some untracked files. + # + test_expect_success "$t create untracked files" " + cp -R $REPO/ballast/dir1 $REPO/ballast/xxx1 + " + + do_status "$t status after create untracked files" + + # Remove the new untracked files. + # + test_expect_success "$t clean -df" " + git -C $REPO clean -d -f + " + + do_status "$t status after clean" + + if test $fsm = true + then + stop_fsm + fi +} + +# Begin testing each case in the matrix that we care about. +# +uc_values="false" +test_have_prereq UNTRACKED_CACHE && uc_values="false true" + +fsm_values="false true" + +for uc_val in $uc_values +do + for fsm_val in $fsm_values + do + do_matrix $uc_val $fsm_val + done +done + +cleanup () { + uc=$1 + fsm=$2 + + MATRIX_BR="$TMP_BR-$uc-$fsm" + + test_might_fail git -C $REPO branch -D $MATRIX_BR +} + + +# We're borrowing this repo. We should leave it in a clean state. +# +test_expect_success "Cleanup temp and matrix branches" " + git -C $REPO clean -d -f && + test_might_fail git -C $REPO checkout $BALLAST_BR && + test_might_fail git -C $REPO branch -D $TMP_BR && + for uc_val in $uc_values + do + for fsm_val in $fsm_values + do + cleanup $uc_val $fsm_val + done + done +" + +test_done
Signature · 1 line
gitgitgadget
PATCH v6 23/28 fsmonitor: never set CE_FSMONITOR_VALID on submodules
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Never set CE_FSMONITOR_VALID on the cache-entry of submodule directories.
During a client command like 'git status', we may need to recurse into each submodule to compute a status summary for the submodule. Since the purpose of the ce_flag is to let Git avoid scanning a cache-entry, setting the flag causes the recursive call to be avoided and we report incorrect (no status) for the submodule.
We created an OS watch on the root directory of our working directory and we receive events for everything in the cone under it. When submodules are present inside our working directory, we receive events for both our repo (the super) and any subs within it. Since our index doesn't have any information for items within the submodules, we can't use those events.
We could try to truncate the paths of those events back to the submodule boundary and mark the GITLINK as dirty, but that feels expensive since we would have to prefix compare every FS event that we receive against a list of submodule roots. And it still wouldn't be sufficient to correctly report status on the submodule, since we don't have any space in the cache-entry to cache the submodule's status (the 'SCMU' bits in porcelain V2 speak). That is, the CE_FSMONITOR_VALID bit just says that we don't need to scan/inspect it because we already know the answer -- it doesn't say that the item is clean -- and we don't have space in the cache-entry to store those answers. So we should always do the recursive scan.
Therefore, we should never set the flag on GITLINK cache-entries.
--- fsmonitor.c | 2 + fsmonitor.h | 11 ++++ t/t7527-builtin-fsmonitor.sh | 111 +++++++++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+)
fsmonitor.c +2 -0
@@ -580,6 +580,8 @@ void tweak_fsmonitor(struct index_state *istate)if (fsmonitor_enabled) {/* Mark all entries valid */ for (i =0 ; i < istate->cache_nr; i++) { +if (S_ISGITLINK (istate->cache[i]->ce_mode)) +continue ; istate->cache[i]->ce_flags |=CE_FSMONITOR_VALID ; }
fsmonitor.h +11 -0
@@ -68,6 +68,15 @@ static inline int is_fsmonitor_refreshed(const struct index_state *istate)* Set the given cache entries CE_FSMONITOR_VALID bit. This should be * called any time the cache entry has been updated to reflect the * current state of the file on disk. +* +* However, never mark submodules as valid. When commands like "git +* status" run they might need to recurse into the submodule (using a +* child process) to get a summary of the submodule state. We don't +* have (and don't want to create) the facility to translate every +* FS event that we receive and that happens to be deep inside of a +* submodule back to the submodule root, so we cannot correctly keep +* track of this bit on the gitlink directory. Therefore, we never +* set it on submodules. */ static inline void mark_fsmonitor_valid (struct index_state *istate,struct cache_entry *ce) {
@@ -75,6 +84,8 @@ static inline void mark_fsmonitor_valid(struct index_state *istate, struct cacheif (fsm_mode >FSMONITOR_MODE_DISABLED && !(ce->ce_flags &CE_FSMONITOR_VALID )) { +if (S_ISGITLINK (ce->ce_mode)) +return ; istate->cache_changed =1 ; ce->ce_flags |=CE_FSMONITOR_VALID ;trace_printf_key (&trace_fsmonitor,"mark_fsmonitor_clean '%s'" , ce->name);
t/t7527-builtin-fsmonitor.sh +111 -0
115 lines not on this page · raw message
Signature · 1 line
gitgitgadget
Re: [PATCH v6 23/28] fsmonitor: never set CE_FSMONITOR_VALID on submodules
Hi Jeff,
Quoted 149 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
Never set
CE_FSMONITOR_VALIDon the cache-entry of submodule directories.During a client command like 'git status', we may need to recurse into each submodule to compute a status summary for the submodule. Since the purpose of the
ce_flagis to let Git avoid scanning a cache-entry, setting the flag causes the recursive call to be avoided and we report incorrect (no status) for the submodule.We created an OS watch on the root directory of our working directory and we receive events for everything in the cone under it. When submodules are present inside our working directory, we receive events for both our repo (the super) and any subs within it. Since our index doesn't have any information for items within the submodules, we can't use those events.
We could try to truncate the paths of those events back to the submodule boundary and mark the GITLINK as dirty, but that feels expensive since we would have to prefix compare every FS event that we receive against a list of submodule roots. And it still wouldn't be sufficient to correctly report status on the submodule, since we don't have any space in the cache-entry to cache the submodule's status (the 'SCMU' bits in porcelain V2 speak). That is, the
CE_FSMONITOR_VALIDbit just says that we don't need to scan/inspect it because we already know the answer -- it doesn't say that the item is clean -- and we don't have space in the cache-entry to store those answers. So we should always do the recursive scan.Therefore, we should never set the flag on GITLINK cache-entries.
Signed-off-by Jeff Hostetler <[email protected]>--- fsmonitor.c | 2 + fsmonitor.h | 11 ++++ t/t7527-builtin-fsmonitor.sh | 111 +++++++++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+)fsmonitor.c +2
-0@@ -580,6 +580,8 @@ void tweak_fsmonitor(struct index_state *istate)if (fsmonitor_enabled) {/* Mark all entries valid */ for (i =0 ; i < istate->cache_nr; i++) { +if (S_ISGITLINK (istate->cache[i]->ce_mode)) +continue ; istate->cache[i]->ce_flags |=CE_FSMONITOR_VALID ; }fsmonitor.h +11
-0@@ -68,6 +68,15 @@ static inline int is_fsmonitor_refreshed(const struct index_state *istate)* Set the given cache entries CE_FSMONITOR_VALID bit. This should be * called any time the cache entry has been updated to reflect the * current state of the file on disk. +* +* However, never mark submodules as valid. When commands like "git +* status" run they might need to recurse into the submodule (using a +* child process) to get a summary of the submodule state. We don't +* have (and don't want to create) the facility to translate every +* FS event that we receive and that happens to be deep inside of a +* submodule back to the submodule root, so we cannot correctly keep +* track of this bit on the gitlink directory. Therefore, we never +* set it on submodules. */ static inline void mark_fsmonitor_valid (struct index_state *istate,struct cache_entry *ce) {@@ -75,6 +84,8 @@ static inline void mark_fsmonitor_valid(struct index_state *istate, struct cacheif (fsm_mode >FSMONITOR_MODE_DISABLED && !(ce->ce_flags &CE_FSMONITOR_VALID )) { +if (S_ISGITLINK (ce->ce_mode)) +return ; istate->cache_changed =1 ; ce->ce_flags |=CE_FSMONITOR_VALID ;trace_printf_key (&trace_fsmonitor,"mark_fsmonitor_clean '%s'" , ce->name);t/t7527-builtin-fsmonitor.sh +59
-0@@ -721,4 +721,115 @@ do ' done +# Test fsmonitor interaction with submodules. +# +# If we start the daemon in the super, it will see FS events for +# everything in the working directory cone and this includes any +# files/directories contained *within* the submodules. +# +# A `git status` at top level will get events for items within the +# submodule and ignore them, since they aren't named in the index +# of the super repo. This makes the fsmonitor response a little +# noisy, but it doesn't alter the correctness of the state of the +# super-proper. +# +# When we have submodules, `git status` normally does a recursive +# status on each of the submodules and adds a summary row for any +# dirty submodules. (See the "S..." bits in porcelain V2 output.) +# +# It is therefore important that the top level status not be tricked +# by the FSMonitor response to skip those recursive calls. That is, +# even if FSMonitor says that the mtime of the submodule directory +# hasn't changed and it could be implicitly marked valid, we must +# not take that shortcut. We need to force the recusion into the +# submodule so that we get a summary of the status *within* the +# submodule. + +create_super () { + super="$1" && + + git init "$super" && + echo x >"$super/file_1" && + echo y >"$super/file_2" && + echo z >"$super/file_3" && + mkdir "$super/dir_1" && + echo a >"$super/dir_1/file_11" && + echo b >"$super/dir_1/file_12" && + mkdir "$super/dir_1/dir_2" && + echo a >"$super/dir_1/dir_2/file_21" && + echo b >"$super/dir_1/dir_2/file_22" && + git -C "$super" add . && + git -C "$super" commit -m "initial $super commit" +} + +create_sub () { + sub="$1" && + + git init "$sub" && + echo x >"$sub/file_x" && + echo y >"$sub/file_y" && + echo z >"$sub/file_z" && + mkdir "$sub/dir_x" && + echo a >"$sub/dir_x/file_a" && + echo b >"$sub/dir_x/file_b" && + mkdir "$sub/dir_x/dir_y" && + echo a >"$sub/dir_x/dir_y/file_a" && + echo b >"$sub/dir_x/dir_y/file_b" && + git -C "$sub" add . && + git -C "$sub" commit -m "initial $sub commit" +} + +my_match_and_clean () {
t/t7527-builtin-fsmonitor.sh do · line 792
Quoted 10 lines
t/t7527-builtin-fsmonitor.sh +10
-0+ git -C super --no-optional-locks status --porcelain=v2 >actual.with && + git -C super --no-optional-locks -c core.fsmonitor=false \ + status --porcelain=v2 >actual.without && + test_cmp actual.with actual.without && + + git -C super/dir_1/dir_2/sub reset --hard && + git -C super/dir_1/dir_2/sub clean -d -f +} + +test_expect_success "Submodule always visited" '
I almost feel bad offering this nit: could you use single-quotes, and start with a lower-case s?
Thanks,
Dscho
Quoted 47 lines
t/t7527-builtin-fsmonitor.sh +42
-1+ test_when_finished "git -C super fsmonitor--daemon stop; \ + rm -rf super; \ + rm -rf sub" && + + create_super super && + create_sub sub && + + git -C super submodule add ../sub ./dir_1/dir_2/sub && + git -C super commit -m "add sub" && + + start_daemon -C super && + git -C super config core.fsmonitor true && + git -C super update-index --fsmonitor && + git -C super status && + + # Now run pairs of commands w/ and w/o FSMonitor while we make + # some dirt in the submodule and confirm matching output. + + # Completely clean status. + my_match_and_clean && + + # .M S..U + echo z >super/dir_1/dir_2/sub/dir_x/dir_y/foobar_u && + my_match_and_clean && + + # .M S.M. + echo z >super/dir_1/dir_2/sub/dir_x/dir_y/foobar_m && + git -C super/dir_1/dir_2/sub add . && + my_match_and_clean && + + # .M S.M. + echo z >>super/dir_1/dir_2/sub/dir_x/dir_y/file_a && + git -C super/dir_1/dir_2/sub add . && + my_match_and_clean && + + # .M SC.. + echo z >>super/dir_1/dir_2/sub/dir_x/dir_y/file_a && + git -C super/dir_1/dir_2/sub add . && + git -C super/dir_1/dir_2/sub commit -m "SC.." && + my_match_and_clean +' + test_done --gitgitgadget
PATCH v6 24/28 t7527: test FSMonitor on case insensitive+preserving file system
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Test that FS events from the OS are received using the preserved, on-disk spelling of files/directories rather than spelling used to make the change.
--- t/t7527-builtin-fsmonitor.sh | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+)
t/t7527-builtin-fsmonitor.sh +36 -0
@@ -832,4 +832,40 @@ test_expect_success "Submodule always visited" ' my_match_and_clean ' +# On a case-insensitive file system, confirm that the daemon +# notices when the .git directory is moved/renamed/deleted +# regardless of how it is spelled in the the FS event. +# That is, does the FS event receive the spelling of the +# operation or does it receive the spelling preserved with +# the file/directory. +# +test_expect_success CASE_INSENSITIVE_FS 'case insensitive+preserving' ' +# test_when_finished "stop_daemon_delete_repo test_insensitive" && + + git init test_insensitive && + + start_daemon -C test_insensitive --tf "$PWD/insensitive.trace" && + + mkdir -p test_insensitive/abc/def && + echo xyz >test_insensitive/ABC/DEF/xyz && + + test_path_is_dir test_insensitive/.git && + test_path_is_dir test_insensitive/.GIT && + + # Rename .git using an alternate spelling to verify that that + # daemon detects it and automatically shuts down. + mv test_insensitive/.GIT test_insensitive/.FOO && + sleep 1 && + mv test_insensitive/.FOO test_insensitive/.git && + test_must_fail git -C test_insensitive fsmonitor--daemon status && + + # Verify that events were reported using on-disk spellings of the + # directories and files that we touched. We may or may not get a + # trailing slash on modified directories. + # + egrep "^event: abc/?$" ./insensitive.trace && + egrep "^event: abc/def/?$" ./insensitive.trace && + egrep "^event: abc/def/xyz$" ./insensitive.trace +' + test_done
Signature · 1 line
gitgitgadget
PATCH v6 25/28 fsmonitor: on macOS also emit NFC spelling for NFD pathname
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Emit NFC or NFC and NFD spellings of pathnames on macOS.
MacOS is Unicode composition insensitive, so NFC and NFD spellings are treated as aliases and collide. While the spelling of pathnames in filesystem events depends upon the underlying filesystem, such as APFS, HFS+ or FAT32, the OS enforces such collisions regardless of filesystem.
Teach the daemon to always report the NFC spelling and to report the NFD spelling when stored in that format on the disk.
This is slightly more general than "core.precomposeUnicode".
--- compat/fsmonitor/fsm-listen-darwin.c | 33 ++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-)
compat/fsmonitor/fsm-listen-darwin.c +31 -2
@@ -155,6 +155,35 @@ static int ef_ignore_xattr(const FSEventStreamEventFlags ef)return ((ef & mask) == kFSEventStreamEventFlagItemXattrMod); } +/* +* On MacOS we have to adjust for Unicode composition insensitivity +* (where NFC and NFD spellings are not respected). The different +* spellings are essentially aliases regardless of how the path is +* actually stored on the disk. +* +* This is related to "core.precomposeUnicode" (which wants to try +* to hide NFD completely and treat everything as NFC). Here, we +* don't know what the value the client has (or will have) for this +* config setting when they make a query, so assume the worst and +* emit both when the OS gives us an NFD path. +*/ +static void my_add_path (struct fsmonitor_batch *batch,const char *path) +{ +char *composed; + +/* add the NFC or NFD path as received from the OS */ +fsmonitor_batch__add_path (batch, path); + +/* if NFD, also add the corresponding NFC spelling */ + composed = (char *)precompose_string_if_needed (path); +if (!composed || composed == path) +return ; + +fsmonitor_batch__add_path (batch, composed); +free (composed); +} + + static void fsevent_callback(ConstFSEventStreamRef streamRef, void *ctx, size_t num_of_events,
@@ -305,7 +334,7 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef,if (!batch) batch =fsmonitor_batch__new (); -fsmonitor_batch__add_path (batch, rel); +my_add_path (batch, rel); }if (event_flags[k] & kFSEventStreamEventFlagItemIsDir) {
@@ -318,7 +347,7 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef,if (!batch) batch =fsmonitor_batch__new (); -fsmonitor_batch__add_path (batch, tmp.buf); +my_add_path (batch, tmp.buf); }break ;
Signature · 1 line
gitgitgadget
PATCH v6 26/28 t/lib-unicode-nfc-nfd: helper prereqs for testing unicode nfc/nfd
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create a set of prereqs to help understand how file names are handled by the filesystem when they contain NFC and NFD Unicode characters.
--- t/lib-unicode-nfc-nfd.sh | 167 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100755 t/lib-unicode-nfc-nfd.sh
t/lib-unicode-nfc-nfd.sh new file +167 -0
@@ -0,0 +1,167 @@ +# Help detect how Unicode NFC and NFD are handled on the filesystem. + +# A simple character that has a NFD form. +# +# NFC: U+00e9 LATIN SMALL LETTER E WITH ACUTE +# UTF8(NFC): \xc3 \xa9 +# +# NFD: U+0065 LATIN SMALL LETTER E +# U+0301 COMBINING ACUTE ACCENT +# UTF8(NFD): \x65 + \xcc \x81 +# +utf8_nfc=$(printf "\xc3\xa9") +utf8_nfd=$(printf "\x65\xcc\x81") + +# Is the OS or the filesystem "Unicode composition sensitive"? +# +# That is, does the OS or the filesystem allow files to exist with +# both the NFC and NFD spellings? Or, does the OS/FS lie to us and +# tell us that the NFC and NFD forms are equivalent. +# +# This is or may be independent of what type of filesystem we have, +# since it might be handled by the OS at a layer above the FS. +# Testing shows on MacOS using APFS, HFS+, and FAT32 reports a +# collision, for example. +# +# This does not tell us how the Unicode pathname will be spelled +# on disk, but rather only that the two spelling "collide". We +# will examine the actual on disk spelling in a later prereq. +# +test_lazy_prereq UNICODE_COMPOSITION_SENSITIVE ' + mkdir trial_${utf8_nfc} && + mkdir trial_${utf8_nfd} +' + +# Is the spelling of an NFC pathname preserved on disk? +# +# On MacOS with HFS+ and FAT32, NFC paths are converted into NFD +# and on APFS, NFC paths are preserved. As we have established +# above, this is independent of "composition sensitivity". +# +# 0000000 63 5f c3 a9 +# +# (/usr/bin/od output contains different amount of whitespace +# on different platforms, so we need the wildcards here.) +# +test_lazy_prereq UNICODE_NFC_PRESERVED ' + mkdir c_${utf8_nfc} && + ls | od -t x1 | grep "63 *5f *c3 *a9" +' + +# Is the spelling of an NFD pathname preserved on disk? +# +# 0000000 64 5f 65 cc 81 +# +test_lazy_prereq UNICODE_NFD_PRESERVED ' + mkdir d_${utf8_nfd} && + ls | od -t x1 | grep "64 *5f *65 *cc *81" +' + mkdir c_${utf8_nfc} && + mkdir d_${utf8_nfd} && + +# The following _DOUBLE_ forms are more for my curiosity, +# but there may be quirks lurking when there are multiple +# combining characters in non-canonical order. + +# Unicode also allows multiple combining characters +# that can be decomposed in pieces. +# +# NFC: U+1f67 GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI +# UTF8(NFC): \xe1 \xbd \xa7 +# +# NFD1: U+1f61 GREEK SMALL LETTER OMEGA WITH DASIA +# U+0342 COMBINING GREEK PERISPOMENI +# UTF8(NFD1): \xe1 \xbd \xa1 + \xcd \x82 +# +# But U+1f61 decomposes into +# NFD2: U+03c9 GREEK SMALL LETTER OMEGA +# U+0314 COMBINING REVERSED COMMA ABOVE +# UTF8(NFD2): \xcf \x89 + \xcc \x94 +# +# Yielding: \xcf \x89 + \xcc \x94 + \xcd \x82 +# +# Note that I've used the canonical ordering of the +# combinining characters. It is also possible to +# swap them. My testing shows that that non-standard +# ordering also causes a collision in mkdir. However, +# the resulting names don't draw correctly on the +# terminal (implying that the on-disk format also has +# them out of order). +# +greek_nfc=$(printf "\xe1\xbd\xa7") +greek_nfd1=$(printf "\xe1\xbd\xa1\xcd\x82") +greek_nfd2=$(printf "\xcf\x89\xcc\x94\xcd\x82") + +# See if a double decomposition also collides. +# +test_lazy_prereq UNICODE_DOUBLE_COMPOSITION_SENSITIVE ' + mkdir trial_${greek_nfc} && + mkdir trial_${greek_nfd2} +' + +# See if the NFC spelling appears on the disk. +# +test_lazy_prereq UNICODE_DOUBLE_NFC_PRESERVED ' + mkdir c_${greek_nfc} && + ls | od -t x1 | grep "63 *5f *e1 *bd *a7" +' + +# See if the NFD spelling appears on the disk. +# +test_lazy_prereq UNICODE_DOUBLE_NFD_PRESERVED ' + mkdir d_${greek_nfd2} && + ls | od -t x1 | grep "64 *5f *cf *89 *cc *94 *cd *82" +' + +# The following is for debugging. I found it useful when +# trying to understand the various (OS, FS) quirks WRT +# Unicode and how composition/decomposition is handled. +# For example, when trying to understand how (macOS, APFS) +# and (macOS, HFS) and (macOS, FAT32) compare. +# +# It is rather noisy, so it is disabled by default. +# +if test "$unicode_debug" = "true" +then + if test_have_prereq UNICODE_COMPOSITION_SENSITIVE + then + echo NFC and NFD are distinct on this OS/filesystem. + else + echo NFC and NFD are aliases on this OS/filesystem. + fi + + if test_have_prereq UNICODE_NFC_PRESERVED + then + echo NFC maintains original spelling. + else + echo NFC is modified. + fi + + if test_have_prereq UNICODE_NFD_PRESERVED + then + echo NFD maintains original spelling. + else + echo NFD is modified. + fi + + if test_have_prereq UNICODE_DOUBLE_COMPOSITION_SENSITIVE + then + echo DOUBLE NFC and NFD are distinct on this OS/filesystem. + else + echo DOUBLE NFC and NFD are aliases on this OS/filesystem. + fi + + if test_have_prereq UNICODE_DOUBLE_NFC_PRESERVED + then + echo Double NFC maintains original spelling. + else + echo Double NFC is modified. + fi + + if test_have_prereq UNICODE_DOUBLE_NFD_PRESERVED + then + echo Double NFD maintains original spelling. + else + echo Double NFD is modified. + fi +fi
Signature · 1 line
gitgitgadget
Re: [PATCH v6 26/28] t/lib-unicode-nfc-nfd: helper prereqs for testing unicode nfc/nfd
Hi Jeff,
Quoted 56 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
Create a set of prereqs to help understand how file names are handled by the filesystem when they contain NFC and NFD Unicode characters.
Signed-off-by Jeff Hostetler <[email protected]>--- t/lib-unicode-nfc-nfd.sh | 167 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 167 insertions(+) create mode 100755 t/lib-unicode-nfc-nfd.sht/lib-unicode-nfc-nfd.sh new file +38
-0@@ -0,0 +1,167 @@ +# Help detect how Unicode NFC and NFD are handled on the filesystem. + +# A simple character that has a NFD form. +# +# NFC: U+00e9 LATIN SMALL LETTER E WITH ACUTE +# UTF8(NFC): \xc3 \xa9 +# +# NFD: U+0065 LATIN SMALL LETTER E +# U+0301 COMBINING ACUTE ACCENT +# UTF8(NFD): \x65 + \xcc \x81 +# +utf8_nfc=$(printf "\xc3\xa9") +utf8_nfd=$(printf "\x65\xcc\x81") + +# Is the OS or the filesystem "Unicode composition sensitive"? +# +# That is, does the OS or the filesystem allow files to exist with +# both the NFC and NFD spellings? Or, does the OS/FS lie to us and +# tell us that the NFC and NFD forms are equivalent. +# +# This is or may be independent of what type of filesystem we have, +# since it might be handled by the OS at a layer above the FS. +# Testing shows on MacOS using APFS, HFS+, and FAT32 reports a +# collision, for example. +# +# This does not tell us how the Unicode pathname will be spelled +# on disk, but rather only that the two spelling "collide". We +# will examine the actual on disk spelling in a later prereq. +# +test_lazy_prereq UNICODE_COMPOSITION_SENSITIVE ' + mkdir trial_${utf8_nfc} && + mkdir trial_${utf8_nfd} +' + +# Is the spelling of an NFC pathname preserved on disk? +# +# On MacOS with HFS+ and FAT32, NFC paths are converted into NFD +# and on APFS, NFC paths are preserved. As we have established
t/lib-unicode-nfc-nfd.sh line 48
Quoted 10 lines
t/lib-unicode-nfc-nfd.sh new file +10
-0+# above, this is independent of "composition sensitivity". +# +# 0000000 63 5f c3 a9 +# +# (/usr/bin/od output contains different amount of whitespace +# on different platforms, so we need the wildcards here.) +# +test_lazy_prereq UNICODE_NFC_PRESERVED ' + mkdir c_${utf8_nfc} && + ls | od -t x1 | grep "63 *5f *c3 *a9"
As far as I can see, this would be the first usage of od in the test suite. I'd actually like to reduce our dependency on Unix-y tools, not increase it.
One thing we could do would be to imitate t4030, and introduce a shell function that calls Perl, something like:
bin2hex () {
perl -e '
$/ = undef;
$_ = <>;
s/./sprintf("%02x ", ord($&))/ge;
print $_
'
}But it is a thorn in my side for quite a few years already that we _require_ Perl, even in NO_PERL builds.
So maybe a much better idea would be to introduce a small helper in t/helper/ that converts binary data on stdin to hex on stdout? Something like this:
-- snip --
From bee2a3c43c90683b3e86e1739361570cce76d382 Mon Sep 17 00:00:00 2001
From: Johannes Schindelin <[email protected]>
Date: Thu, 12 May 2022 17:24:50 +0200
Subject: [PATCH] tests: add a helped to print a hexdump
--- Makefile | 1 + t/helper/test-hexdump.c | 24 ++++++++++++++++++++++++ t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + 4 files changed, 27 insertions(+) create mode 100644 t/helper/test-hexdump.c
Makefile +1 -0
@@ -708,6 +708,7 @@ TEST_BUILTINS_OBJS += test-getcwd.o TEST_BUILTINS_OBJS += test-hash-speed.o TEST_BUILTINS_OBJS += test-hash.o TEST_BUILTINS_OBJS += test-hashmap.o +TEST_BUILTINS_OBJS += test-hexdump.o TEST_BUILTINS_OBJS += test-index-version.o TEST_BUILTINS_OBJS += test-json-writer.o TEST_BUILTINS_OBJS += test-lazy-init-name-hash.o
t/helper/test-hexdump.c new file +24 -0
@@ -0,0 +1,24 @@ +#include "test-tool.h" +#include "git-compat-util.h" + +/* +* Read stdin and print a hexdump to stdout. +*/ +int cmd__hexdump (int argc,const char **argv) +{ +char buf[1024 ]; +ssize_t i, len; + +for (;;) { + len =xread (0 , buf,sizeof (buf)); +if (len <0 ) +die_errno ("failure reading stdin" ); +if (!len) +break ; + +for (i =0 ; i < len; i++) +printf ("%02x " , buf[i]); + } + +return 0 ; +}
t/helper/test-tool.c +1 -0
@@ -35,6 +35,7 @@ static struct test_cmd cmds[] = { { "genzeros", cmd__genzeros }, { "getcwd", cmd__getcwd }, { "hashmap", cmd__hashmap }, + { "hexdump", cmd__hexdump }, { "hash-speed", cmd__hash_speed }, { "index-version", cmd__index_version }, { "json-writer", cmd__json_writer },
t/helper/test-tool.h +1 -0
@@ -25,6 +25,7 @@ int cmd__genrandom(int argc, const char **argv);int cmd__genzeros (int argc,const char **argv);int cmd__getcwd (int argc,const char **argv);int cmd__hashmap (int argc,const char **argv); +int cmd__hexdump (int argc,const char **argv);int cmd__hash_speed (int argc,const char **argv);int cmd__index_version (int argc,const char **argv);int cmd__json_writer (int argc,const char **argv);
-- snap --
Other than the od usage, this patch looks good to me.
Thank you very much for driving FSMonitor forward! Dscho
Quoted 123 lines
t/lib-unicode-nfc-nfd.sh new file +119
-1+' + +# Is the spelling of an NFD pathname preserved on disk? +# +# 0000000 64 5f 65 cc 81 +# +test_lazy_prereq UNICODE_NFD_PRESERVED ' + mkdir d_${utf8_nfd} && + ls | od -t x1 | grep "64 *5f *65 *cc *81" +' + mkdir c_${utf8_nfc} && + mkdir d_${utf8_nfd} && + +# The following _DOUBLE_ forms are more for my curiosity, +# but there may be quirks lurking when there are multiple +# combining characters in non-canonical order. + +# Unicode also allows multiple combining characters +# that can be decomposed in pieces. +# +# NFC: U+1f67 GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI +# UTF8(NFC): \xe1 \xbd \xa7 +# +# NFD1: U+1f61 GREEK SMALL LETTER OMEGA WITH DASIA +# U+0342 COMBINING GREEK PERISPOMENI +# UTF8(NFD1): \xe1 \xbd \xa1 + \xcd \x82 +# +# But U+1f61 decomposes into +# NFD2: U+03c9 GREEK SMALL LETTER OMEGA +# U+0314 COMBINING REVERSED COMMA ABOVE +# UTF8(NFD2): \xcf \x89 + \xcc \x94 +# +# Yielding: \xcf \x89 + \xcc \x94 + \xcd \x82 +# +# Note that I've used the canonical ordering of the +# combinining characters. It is also possible to +# swap them. My testing shows that that non-standard +# ordering also causes a collision in mkdir. However, +# the resulting names don't draw correctly on the +# terminal (implying that the on-disk format also has +# them out of order). +# +greek_nfc=$(printf "\xe1\xbd\xa7") +greek_nfd1=$(printf "\xe1\xbd\xa1\xcd\x82") +greek_nfd2=$(printf "\xcf\x89\xcc\x94\xcd\x82") + +# See if a double decomposition also collides. +# +test_lazy_prereq UNICODE_DOUBLE_COMPOSITION_SENSITIVE ' + mkdir trial_${greek_nfc} && + mkdir trial_${greek_nfd2} +' + +# See if the NFC spelling appears on the disk. +# +test_lazy_prereq UNICODE_DOUBLE_NFC_PRESERVED ' + mkdir c_${greek_nfc} && + ls | od -t x1 | grep "63 *5f *e1 *bd *a7" +' + +# See if the NFD spelling appears on the disk. +# +test_lazy_prereq UNICODE_DOUBLE_NFD_PRESERVED ' + mkdir d_${greek_nfd2} && + ls | od -t x1 | grep "64 *5f *cf *89 *cc *94 *cd *82" +' + +# The following is for debugging. I found it useful when +# trying to understand the various (OS, FS) quirks WRT +# Unicode and how composition/decomposition is handled. +# For example, when trying to understand how (macOS, APFS) +# and (macOS, HFS) and (macOS, FAT32) compare. +# +# It is rather noisy, so it is disabled by default. +# +if test "$unicode_debug" = "true" +then + if test_have_prereq UNICODE_COMPOSITION_SENSITIVE + then + echo NFC and NFD are distinct on this OS/filesystem. + else + echo NFC and NFD are aliases on this OS/filesystem. + fi + + if test_have_prereq UNICODE_NFC_PRESERVED + then + echo NFC maintains original spelling. + else + echo NFC is modified. + fi + + if test_have_prereq UNICODE_NFD_PRESERVED + then + echo NFD maintains original spelling. + else + echo NFD is modified. + fi + + if test_have_prereq UNICODE_DOUBLE_COMPOSITION_SENSITIVE + then + echo DOUBLE NFC and NFD are distinct on this OS/filesystem. + else + echo DOUBLE NFC and NFD are aliases on this OS/filesystem. + fi + + if test_have_prereq UNICODE_DOUBLE_NFC_PRESERVED + then + echo Double NFC maintains original spelling. + else + echo Double NFC is modified. + fi + + if test_have_prereq UNICODE_DOUBLE_NFD_PRESERVED + then + echo Double NFD maintains original spelling. + else + echo Double NFD is modified. + fi +fi --gitgitgadget
Re: [PATCH v6 26/28] t/lib-unicode-nfc-nfd: helper prereqs for testing unicode nfc/nfd
Quoted 6 lines · Johannes Schindelin
Hi Jeff,
Quoted 2 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
[...]
Quoted 21 lines
Review
Quoted 4 lines
(quoted diff) +4
-0+# +test_lazy_prereq UNICODE_NFC_PRESERVED ' + mkdir c_${utf8_nfc} && + ls | od -t x1 | grep "63 *5f *c3 *a9"As far as I can see, this would be the first usage of
odin the test suite. I'd actually like to reduce our dependency on Unix-y tools, not increase it.One thing we could do would be to imitate t4030, and introduce a shell function that calls Perl, something like:
bin2hex () { perl -e ' $/ = undef; $_ = <>; s/./sprintf("%02x ", ord($&))/ge; print $_ ' }
Quoted 7 lines
But it is a thorn in my side for quite a few years already that we _require_ Perl, even in
NO_PERLbuilds.So maybe a much better idea would be to introduce a small helper in
t/helper/that converts binary data on stdin to hex on stdout? Something like this:
Yeah, lets add the hexdump helper.
Thanks
Jeff
PATCH v6 27/28 t7527: test Unicode NFC/NFD handling on MacOS
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Confirm that the daemon reports events using the on-disk spelling for Unicode NFC/NFD characters. On APFS we still have Unicode aliasing, so we cannot create two files that only differ by NFC/NFD, but the on-disk format preserves the spelling used to create the file. On HFS+ we also have aliasing, but the path is always stored on disk in NFD.
--- t/t7527-builtin-fsmonitor.sh | 55 ++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+)
t/t7527-builtin-fsmonitor.sh +55 -0
@@ -868,4 +868,59 @@ test_expect_success CASE_INSENSITIVE_FS 'case insensitive+preserving' ' egrep "^event: abc/def/xyz$" ./insensitive.trace ' +# The variable "unicode_debug" is defined in the following library +# script to dump information about how the (OS, FS) handles Unicode +# composition. Uncomment the following line if you want to enable it. +# +# unicode_debug=true + +. "$TEST_DIRECTORY/lib-unicode-nfc-nfd.sh" + +# See if the OS or filesystem does NFC/NFD aliasing/munging. +# +# The daemon should err on the side of caution and send BOTH the +# NFC and NFD forms. It does not know the original spelling of +# the pathname (how the user thinks it should be spelled), so +# emit both and let the client decide (when necessary). This is +# similar to "core.precomposeUnicode". +# +test_expect_success !UNICODE_COMPOSITION_SENSITIVE 'Unicode nfc/nfd' ' + test_when_finished "stop_daemon_delete_repo test_unicode" && + + git init test_unicode && + + start_daemon -C test_unicode --tf "$PWD/unicode.trace" && + + # Create a directory using an NFC spelling. + # + mkdir test_unicode/nfc && + mkdir test_unicode/nfc/c_${utf8_nfc} && + + # Create a directory using an NFD spelling. + # + mkdir test_unicode/nfd && + mkdir test_unicode/nfd/d_${utf8_nfd} && + + git -C test_unicode fsmonitor--daemon stop && + + if test_have_prereq UNICODE_NFC_PRESERVED + then + # We should have seen NFC event from OS. + # We should not have synthesized an NFD event. + egrep "^event: nfc/c_${utf8_nfc}/?$" ./unicode.trace && + egrep -v "^event: nfc/c_${utf8_nfd}/?$" ./unicode.trace + else + # We should have seen NFD event from OS. + # We should have synthesized an NFC event. + egrep "^event: nfc/c_${utf8_nfd}/?$" ./unicode.trace && + egrep "^event: nfc/c_${utf8_nfc}/?$" ./unicode.trace + fi && + + # We assume UNICODE_NFD_PRESERVED. + # We should have seen explicit NFD from OS. + # We should have synthesized an NFC event. + egrep "^event: nfd/d_${utf8_nfd}/?$" ./unicode.trace && + egrep "^event: nfd/d_${utf8_nfc}/?$" ./unicode.trace +' + test_done
Signature · 1 line
gitgitgadget
PATCH v6 28/28 fsmonitor--daemon: allow --super-prefix argument
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create a test in t7527 to verify that we get a stray warning from git fsmonitor--daemon start when indirectly called from git submodule absorbgitdirs.
Update git fsmonitor--daemon to take (and ignore) the --super-prefix argument to suppress the warning.
When we have:
1. a submodule with a sub/.git/ directory (rather than a sub/.git file).
2. core.fsmonitor is turned on in the submodule, but the daemon is not yet started in the submodule.
3. and someone does a git submodule absorbgitdirs in the super.
Git will recursively invoke git submodule--helper absorb-git-dirs in the submodule. This will read the index and may attempt to start the fsmonitor--daemon with the --super-prefix argument.
git fsmonitor--daemon start does not accept the --super-prefix argument and causes a warning to be issued.
This does not cause a problem because the refresh_index() code assumes a trivial response if the daemon does not start.
The net-net is a harmelss, but stray warning. Lets eliminate the warning.
--- git.c | 2 +- t/t7527-builtin-fsmonitor.sh | 50 ++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-)
git.c +1 -1
@@ -537,7 +537,7 @@ static struct cmd_struct commands[] = { { "format-patch", cmd_format_patch, RUN_SETUP }, { "fsck", cmd_fsck, RUN_SETUP }, { "fsck-objects", cmd_fsck, RUN_SETUP }, - { "fsmonitor--daemon", cmd_fsmonitor__daemon, RUN_SETUP }, + { "fsmonitor--daemon", cmd_fsmonitor__daemon, SUPPORT_SUPER_PREFIX | RUN_SETUP }, { "gc", cmd_gc, RUN_SETUP }, { "get-tar-commit-id", cmd_get_tar_commit_id, NO_PARSEOPT }, { "grep", cmd_grep, RUN_SETUP_GENTLY },
t/t7527-builtin-fsmonitor.sh +50 -0
56 lines not on this page · raw message
Signature · 1 line
gitgitgadget
Hi Jeff,
Quoted 21 lines · Jeff Hostetler via GitGitGadget
Here is version 6 of part 3 of FSMonitor.
This version addresses:
1. Junio's comments on V5 23/28 WRT shell variable references and quoting pathnames in the
create_superandcreate_subhelper functions.2. Ævar's comments on V4 4/27 (sorry I didn't see them until after I sent V5) WRT somewhat blurry logic around the fsmonitor-settings and detecting incompatible worktrees. I simplified things, but not to the level that Ævar was suggesting. For example, in
builtin/update-index.cthe suggestion was to detect incompatible FS before taking the lock on the index, but the lock is taken before the CL args are parsed (because update-index uses a custom version ofparse_options_start()), so we don't know yet whether the user passed --fsmonitor until much later and that is what triggers the error/warning. I did replace the return 1 with adie()so hopefully, we'll release the lock on the index like all of the other errors in that function. I did try to better document the code inupdate-index.cand inbuiltin/fsmonitor--daemon.cto explain how things are supposed to work. So hopefully it'll be easier to review.
Quoted 4 lines
3. Also, in update-index and fsmonitor--daemon, I redid how the error messages are printed, so that I could use
die()in the cmd_*() functions rather than having calls toerror()hidden insidefsmonitor-settings.c. I think that helped with the above cleanup.
Thank you _so_ much for keeping on the ball. I do see how much effort you had to put into FSMonitor, what with three large patch series, plenty of reviews that necessitated plenty of iterations, but I heard from a couple of sides now just how important this feature is for users who work with large repositories.
Your work truly has a great impact on Git users!
I offered a couple of suggestions in my replies to individual patches, nothing majorly critical (except maybe the wcscpy()/wcsncpy() calls that _might_ overrun their buffer in cornercases).
Hopefully you find the suggestions useful rather than annoying at this late stage (you're already at iteration 6 of the third large patch series, after all). I just didn't want us to run into any big surprises like when the second FSMonitor patch series was integrated into next (finally!!!), only to be dropped and needing to be replaced by yet another iteration (not so yay :-( ).
After studying your patches for a few hours and then writing up my review (only being interrupted _once_ today, briefly, yay!), I am fairly happy even with the current shape of the series, and if you want to address my suggestions and send out a seventh iteration of the patch series, I am certain that it is ready for next.
Again, thank you _so much_ for keeping up the good work, Dscho
v7 cover Builtin FSMonitor Part 3
In reply to Jeff Hostetler via GitGitGadgetHere is version 7 of part 3 of FSMonitor.
This version mainly addresses comments received on V6.
1. A few typos.
2. Cleanup of some wide-char string manipulations in Windows code.
3. Fix some coding-style issues in the unit tests: don't capitalize test case description and single-quote the descriptions when possible.
4. Add t/helper/test-hexdump.c and use that in t7527 rather than /usr/bin/od in the Unicode/UTF8 compatibility tests for macOS.
5. Refactor the implicit daemon shutdown tests to retry a few times rather than a fixed sleep. This helps avoid races in the CI build runs.
Here is a range-diff against V6:
1: 8b7c5f4e23 ! 1: 26144c5865 fsm-listen-win32: handle shortnames
@@ compat/fsmonitor/fsm-listen-win32.c: struct one_watch
+ * clients.)
+ */
+ BOOL has_shortnames;
-+ BOOL has_tilda;
++ BOOL has_tilde;
+ wchar_t dotgit_shortname[16]; /* for 8.3 name */ };
@@ compat/fsmonitor/fsm-listen-win32.c: static int normalize_path_in_utf8(FILE_NOTI
+{
+ wchar_t buf_in[MAX_PATH + 1];
+ wchar_t buf_out[MAX_PATH + 1];
-+ wchar_t *last_slash = NULL;
-+ wchar_t *last_bslash = NULL;
+ wchar_t *last;
++ wchar_t *p;
+
+ /* build L"<wt-root-path>/.git" */
-+ wcscpy(buf_in, watch->wpath_longname);
-+ wcscpy(buf_in + watch->wpath_longname_len, L".git");
++ swprintf(buf_in, ARRAY_SIZE(buf_in) - 1, L"%s.git",
++ watch->wpath_longname);
+
-+ if (!GetShortPathNameW(buf_in, buf_out, MAX_PATH))
++ if (!GetShortPathNameW(buf_in, buf_out, ARRAY_SIZE(buf_out)))
+ return;
+
-+ last_slash = wcsrchr(buf_out, L'/');
-+ last_bslash = wcsrchr(buf_out, L'\\');
-+ if (last_slash > last_bslash)
-+ last = last_slash + 1;
-+ else if (last_bslash)
-+ last = last_bslash + 1;
-+ else
-+ last = buf_out;
++ /*
++ * Get the final filename component of the shortpath.
++ * We know that the path does not have a final slash.
++ */
++ for (last = p = buf_out; *p; p++)
++ if (*p == L'/' || *p == '\\')
++ last = p + 1;
+
+ if (!wcscmp(last, L".git"))
+ return;
@@ compat/fsmonitor/fsm-listen-win32.c: static int normalize_path_in_utf8(FILE_NOTI
+ * Lets test this.
+ */
+ if (wcschr(watch->dotgit_shortname, L'~'))
-+ watch->has_tilda = 1;
++ watch->has_tilde = 1;
+}
+
+enum get_relative_result {
@@ compat/fsmonitor/fsm-listen-win32.c: static int normalize_path_in_utf8(FILE_NOTI
+static enum get_relative_result get_relative_longname(
+ struct one_watch *watch,
+ const wchar_t *wpath, DWORD wpath_len,
-+ wchar_t *wpath_longname)
++ wchar_t *wpath_longname, size_t bufsize_wpath_longname)
+{
+ wchar_t buf_in[2 * MAX_PATH + 1];
+ wchar_t buf_out[MAX_PATH + 1];
+ DWORD root_len;
++ DWORD out_len;
+
-+ /* Build L"<wt-root-path>/<event-rel-path>" */
++ /*
++ * Build L"<wt-root-path>/<event-rel-path>"
++ * Note that the <event-rel-path> might not be null terminated
++ * so we avoid swprintf() constructions.
++ */
+ root_len = watch->wpath_longname_len;
++ if (root_len + wpath_len >= ARRAY_SIZE(buf_in)) {
++ /*
++ * This should not happen. We cannot append the observed
++ * relative path onto the end of the worktree root path
++ * without overflowing the buffer. Just give up.
++ */
++ return GRR_SHUTDOWN;
++ }
+ wcsncpy(buf_in, watch->wpath_longname, root_len);
+ wcsncpy(buf_in + root_len, wpath, wpath_len);
+ buf_in[root_len + wpath_len] = 0;
+
+ /*
+ * We don't actually know if the source pathname is a
-+ * shortname or a longname. This routine allows either to be
-+ * given as input.
++ * shortname or a longname. This Windows routine allows
++ * either to be given as input.
+ */
-+ if (!GetLongPathNameW(buf_in, buf_out, MAX_PATH)) {
++ out_len = GetLongPathNameW(buf_in, buf_out, ARRAY_SIZE(buf_out)); ++ if (!out_len) {
+ /*
+ * The shortname to longname conversion can fail for
+ * various reasons, for example if the file has been
@@ compat/fsmonitor/fsm-listen-win32.c: static int normalize_path_in_utf8(FILE_NOTI
+ return GRR_SHUTDOWN;
+ }
+
++ if (out_len - root_len >= bufsize_wpath_longname) {
++ /*
++ * This should not happen. We cannot copy the root-relative
++ * portion of the path into the provided buffer without an
++ * overrun. Just give up.
++ */
++ return GRR_SHUTDOWN;
++ }
++
+ /* Return the worktree root-relative portion of the longname. */ +
+ wcscpy(wpath_longname, buf_out + root_len);
@@ compat/fsmonitor/fsm-listen-win32.c: static struct one_watch *create_watch(struc
return NULL;
}
-+ if (!GetLongPathNameW(wpath, wpath_longname, MAX_PATH)) {
++ len_longname = GetLongPathNameW(wpath, wpath_longname,
++ ARRAY_SIZE(wpath_longname));
++ if (!len_longname) {
+ error(_("[GLE %ld] could not get longname of '%s'"),
+ GetLastError(), path);
+ CloseHandle(hDir);
+ return NULL;
+ }
+
-+ len_longname = wcslen(wpath_longname);
+ if (wpath_longname[len_longname - 1] != L'/' &&
+ wpath_longname[len_longname - 1] != L'\\') {
+ wpath_longname[len_longname++] = L'/';
@@ compat/fsmonitor/fsm-listen-win32.c: static int process_worktree_events(struct f
- case IS_INSIDE_DOT_GIT:
- /* ignore everything inside of "<worktree>/.git/" */
- break;
-+ if (watch->has_tilda && !wcschr(wpath, L'~')) {
++ if (watch->has_tilde && !wcschr(wpath, L'~')) {
+ /*
-+ * Shortnames on this filesystem have tildas
++ * Shortnames on this filesystem have tildes
+ * and the notification path does not have
+ * one, so we assume that it is a longname.
+ */
@@ compat/fsmonitor/fsm-listen-win32.c: static int process_worktree_events(struct f
- "fsm-listen/dotgit",
- "removed");
+ grr = get_relative_longname(watch, wpath, wpath_len,
-+ wpath_longname);
++ wpath_longname,
++ ARRAY_SIZE(wpath_longname));
+ switch (grr) {
+ case GRR_NO_CONVERSION_NEEDED: /* use info buffer as is */
+ break;
2: 5b246bec24 ! 2: 1bf2e36b6a t7527: test FSMonitor on repos with Unicode root paths
@@ t/t7527-builtin-fsmonitor.sh: do
+u_values="$u1 $u2"
+for u in $u_values
+do
-+ test_expect_success "Unicode in repo root path: $u" '
++ test_expect_success "unicode in repo root path: $u" '
+ test_when_finished "stop_daemon_delete_repo $u" &&
+
+ git init "$u" &&
3: 8a474d6999 = 3: 4bca494bb2 t/helper/fsmonitor-client: create stress test
4: 72b94acd5f ! 4: 663deabc3f fsmonitor-settings: bare repos are incompatible with FSMonitor
@@ fsmonitor-settings.h
+ */
+enum fsmonitor_reason {
+ FSMONITOR_REASON_UNTESTED = 0,
-+ FSMONITOR_REASON_OK, /* no incompatibility or when disbled */
++ FSMONITOR_REASON_OK, /* no incompatibility or when disabled */
+ FSMONITOR_REASON_BARE,
+};
+
5: 2e225c3f4f = 5: 7cb0180a1e fsmonitor-settings: stub in Win32-specific incompatibility checking
6: e0d3bdf755 ! 6: 9774faddc4 fsmonitor-settings: VFS for Git virtual repos are incompatible
@@ fsmonitor-settings.c: char *fsm_settings__get_incompatible_msg(const struct repo
## fsmonitor-settings.h ##
@@ fsmonitor-settings.h: enum fsmonitor_reason {
FSMONITOR_REASON_UNTESTED = 0,
- FSMONITOR_REASON_OK, /* no incompatibility or when disbled */
+ FSMONITOR_REASON_OK, /* no incompatibility or when disabled */
FSMONITOR_REASON_BARE,
+ FSMONITOR_REASON_VFS4GIT, /* VFS for Git virtualization */
};
7: c50ed29a31 = 7: f7ef7dcffc fsmonitor-settings: stub in macOS-specific incompatibility checking
8: 1f5b772d42 ! 8: dc2dfd6793 fsmonitor-settings: remote repos on macOS are incompatible
@@ fsmonitor-settings.c: char *fsm_settings__get_incompatible_msg(const struct repo
## fsmonitor-settings.h ##
@@ fsmonitor-settings.h: enum fsmonitor_reason {
FSMONITOR_REASON_UNTESTED = 0,
- FSMONITOR_REASON_OK, /* no incompatibility or when disbled */
+ FSMONITOR_REASON_OK, /* no incompatibility or when disabled */
FSMONITOR_REASON_BARE,
+ FSMONITOR_REASON_ERROR, /* FS error probing for compatibility */
+ FSMONITOR_REASON_REMOTE,
9: 495e54049b = 9: 5627038aaa fsmonitor-settings: remote repos on Windows are incompatible
10: 4b52083698 = 10: 9a12cc78b5 fsmonitor-settings: NTFS and FAT32 on MacOS are incompatible
11: d4a4263d37 = 11: aaff000cec unpack-trees: initialize fsmonitor_has_run_once in o->result
12: f4feb00ec2 = 12: 4f2b15d3d1 fsm-listen-darwin: ignore FSEvents caused by xattr changes on macOS
13: dbb983fd9d = 13: 427dec412a fsmonitor--daemon: cd out of worktree root
14: ae90b99ea9 = 14: 51b266b06e fsmonitor--daemon: prepare for adding health thread
15: b6c5800095 = 15: 594e0ae243 fsmonitor--daemon: rename listener thread related variables
16: 32fc6ba743 = 16: c2b5c02ed3 fsmonitor--daemon: stub in health thread
17: 77bc037481 = 17: 46a5ae2a63 fsm-health-win32: add polling framework to monitor daemon health
18: b06edd995e = 18: 7cf1be5f8e fsm-health-win32: force shutdown daemon if worktree root moves
19: 1bd5f34624 = 19: 95cf1299d4 fsm-listen-darwin: shutdown daemon if worktree root is moved/renamed
20: 48af0813de = 20: b020bfb456 fsmonitor: optimize processing of directory events
21: a9b35e770f = 21: d058d7e0c0 t7527: FSMonitor tests for directory moves
22: 26308936af = 22: f5dac28681 t/perf/p7527: add perf test for builtin FSMonitor
23: d0e25f6bac ! 23: 92f5c0d2c8 fsmonitor: never set CE_FSMONITOR_VALID on submodules @@ t/t7527-builtin-fsmonitor.sh: do
+ git -C super/dir_1/dir_2/sub clean -d -f
+}
+
-+test_expect_success "Submodule always visited" '
++test_expect_success 'submodule always visited' '
+ test_when_finished "git -C super fsmonitor--daemon stop; \
+ rm -rf super; \
+ rm -rf sub" &&
24: 410dd2d292 ! 24: 40b80adbb3 t7527: test FSMonitor on case insensitive+preserving file system
@@ Commit message
## t/t7527-builtin-fsmonitor.sh ##
-@@ t/t7527-builtin-fsmonitor.sh: test_expect_success "Submodule always visited" ' +@@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'submodule always visited' '
my_match_and_clean
'
25: cd7c55b0d3 = 25: b93f064269 fsmonitor: on macOS also emit NFC spelling for NFD pathname
-: ---------- > 26: 6f2e935f14 t/helper/hexdump: add helper to print hexdump of stdin
26: 8278f32c4d ! 27: 6a83086995 t/lib-unicode-nfc-nfd: helper prereqs for testing unicode nfc/nfd
@@ t/lib-unicode-nfc-nfd.sh (new)
+# and on APFS, NFC paths are preserved. As we have established
+# above, this is independent of "composition sensitivity".
+#
-+# 0000000 63 5f c3 a9
-+#
-+# (/usr/bin/od output contains different amount of whitespace
-+# on different platforms, so we need the wildcards here.)
-+#
+test_lazy_prereq UNICODE_NFC_PRESERVED '
+ mkdir c_${utf8_nfc} &&
-+ ls | od -t x1 | grep "63 *5f *c3 *a9"
++ ls | test-tool hexdump | grep "63 5f c3 a9"
+'
+
+# Is the spelling of an NFD pathname preserved on disk?
+#
-+# 0000000 64 5f 65 cc 81
-+#
+test_lazy_prereq UNICODE_NFD_PRESERVED '
+ mkdir d_${utf8_nfd} &&
-+ ls | od -t x1 | grep "64 *5f *65 *cc *81"
++ ls | test-tool hexdump | grep "64 5f 65 cc 81"
+'
-+ mkdir c_${utf8_nfc} &&
-+ mkdir d_${utf8_nfd} &&
+
+# The following _DOUBLE_ forms are more for my curiosity,
+# but there may be quirks lurking when there are multiple
@@ t/lib-unicode-nfc-nfd.sh (new)
+#
+test_lazy_prereq UNICODE_DOUBLE_NFC_PRESERVED '
+ mkdir c_${greek_nfc} &&
-+ ls | od -t x1 | grep "63 *5f *e1 *bd *a7"
++ ls | test-tool hexdump | grep "63 5f e1 bd a7"
+'
+
+# See if the NFD spelling appears on the disk.
+#
+test_lazy_prereq UNICODE_DOUBLE_NFD_PRESERVED '
+ mkdir d_${greek_nfd2} &&
-+ ls | od -t x1 | grep "64 *5f *cf *89 *cc *94 *cd *82"
++ ls | test-tool hexdump | grep "64 5f cf 89 cc 94 cd 82"
+'
+
+# The following is for debugging. I found it useful when
27: 4efb3a4383 = 28: f9a7869d20 t7527: test Unicode NFC/NFD handling on MacOS
28: df1b4f3a80 ! 29: 9fc7c97092 fsmonitor--daemon: allow --super-prefix argument @@ git.c: static struct cmd_struct commands[] = {
{ "grep", cmd_grep, RUN_SETUP_GENTLY },
## t/t7527-builtin-fsmonitor.sh ##
-@@ t/t7527-builtin-fsmonitor.sh: test_expect_success "Submodule always visited" ' +@@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'submodule always visited' '
my_match_and_clean
'
@@ t/t7527-builtin-fsmonitor.sh: test_expect_success "Submodule always visited" '
+ tr '\047' Q <$1 | grep -e "$msg"
+}
+
-+test_expect_success "Stray Submodule super-prefix warning" ' ++test_expect_success "stray submodule super-prefix warning" '
+ test_when_finished "rm -rf super; \
+ rm -rf sub; \
+ rm super-sub.trace" &&
-: ---------- > 30: ca833ecc7a t7527: improve implicit shutdown testing in fsmonitor--daemon
Jeff Hostetler (30):
fsm-listen-win32: handle shortnames
t7527: test FSMonitor on repos with Unicode root paths t/helper/fsmonitor-client: create stress test
fsmonitor-settings: bare repos are incompatible with FSMonitor
fsmonitor-settings: stub in Win32-specific incompatibility checking
fsmonitor-settings: VFS for Git virtual repos are incompatible
fsmonitor-settings: stub in macOS-specific incompatibility checking
fsmonitor-settings: remote repos on macOS are incompatible
fsmonitor-settings: remote repos on Windows are incompatible
fsmonitor-settings: NTFS and FAT32 on MacOS are incompatible
unpack-trees: initialize fsmonitor_has_run_once in o->result
fsm-listen-darwin: ignore FSEvents caused by xattr changes on macOS
fsmonitor--daemon: cd out of worktree root
fsmonitor--daemon: prepare for adding health thread
fsmonitor--daemon: rename listener thread related variables
fsmonitor--daemon: stub in health thread
fsm-health-win32: add polling framework to monitor daemon health
fsm-health-win32: force shutdown daemon if worktree root moves
fsm-listen-darwin: shutdown daemon if worktree root is moved/renamed
fsmonitor: optimize processing of directory events
t7527: FSMonitor tests for directory moves
t/perf/p7527: add perf test for builtin FSMonitor
fsmonitor: never set CE_FSMONITOR_VALID on submodules
t7527: test FSMonitor on case insensitive+preserving file system
fsmonitor: on macOS also emit NFC spelling for NFD pathname t/helper/hexdump: add helper to print hexdump of stdin t/lib-unicode-nfc-nfd: helper prereqs for testing unicode nfc/nfd
t7527: test Unicode NFC/NFD handling on MacOS
fsmonitor--daemon: allow --super-prefix argument
t7527: improve implicit shutdown testing in fsmonitor--daemon
Makefile | 20 +- builtin/fsmonitor--daemon.c | 116 ++++++- builtin/update-index.c | 16 + compat/fsmonitor/fsm-health-darwin.c | 24 ++ compat/fsmonitor/fsm-health-win32.c | 278 ++++++++++++++++ compat/fsmonitor/fsm-health.h | 47 +++ compat/fsmonitor/fsm-listen-darwin.c | 122 ++++++- compat/fsmonitor/fsm-listen-win32.c | 436 ++++++++++++++++++++----- compat/fsmonitor/fsm-listen.h | 2 +- compat/fsmonitor/fsm-settings-darwin.c | 89 +++++ compat/fsmonitor/fsm-settings-win32.c | 137 ++++++++ config.mak.uname | 5 + contrib/buildsystems/CMakeLists.txt | 8 + fsmonitor--daemon.h | 11 +- fsmonitor-settings.c | 167 ++++++++-- fsmonitor-settings.h | 33 ++ fsmonitor.c | 73 ++++- fsmonitor.h | 11 + git.c | 2 +- t/helper/test-fsmonitor-client.c | 106 ++++++ t/helper/test-hexdump.c | 24 ++ t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + t/lib-unicode-nfc-nfd.sh | 158 +++++++++ t/perf/p7527-builtin-fsmonitor.sh | 257 +++++++++++++++ t/t7519-status-fsmonitor.sh | 32 ++ t/t7527-builtin-fsmonitor.sh | 401 ++++++++++++++++++++++- unpack-trees.c | 1 + 28 files changed, 2429 insertions(+), 149 deletions(-) create mode 100644 compat/fsmonitor/fsm-health-darwin.c create mode 100644 compat/fsmonitor/fsm-health-win32.c create mode 100644 compat/fsmonitor/fsm-health.h create mode 100644 compat/fsmonitor/fsm-settings-darwin.c create mode 100644 compat/fsmonitor/fsm-settings-win32.c create mode 100644 t/helper/test-hexdump.c create mode 100755 t/lib-unicode-nfc-nfd.sh create mode 100755 t/perf/p7527-builtin-fsmonitor.sh
base-commit: 5eb696daba2fe108d4d9ba2ccf4b357447ef9946
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1143%2Fjeffhostetler%2Fbuiltin-fsmonitor-part3-v7
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1143/jeffhostetler/builtin-fsmonitor-part3-v7
Pull-Request: https://github.com/gitgitgadget/git/pull/1143
Range-diff vs v6:
1: 8b7c5f4e234 ! 1: 26144c58659 fsm-listen-win32: handle shortnames
@@ compat/fsmonitor/fsm-listen-win32.c: struct one_watch
+ * clients.)
+ */
+ BOOL has_shortnames;
-+ BOOL has_tilda;
++ BOOL has_tilde;
+ wchar_t dotgit_shortname[16]; /* for 8.3 name */
}; @@ compat/fsmonitor/fsm-listen-win32.c: normalize:
+{
+ wchar_t buf_in[MAX_PATH + 1];
+ wchar_t buf_out[MAX_PATH + 1];
-+ wchar_t *last_slash = NULL;
-+ wchar_t *last_bslash = NULL;
+ wchar_t *last;
++ wchar_t *p;
+
+ /* build L"<wt-root-path>/.git" */
-+ wcscpy(buf_in, watch->wpath_longname);
-+ wcscpy(buf_in + watch->wpath_longname_len, L".git");
++ swprintf(buf_in, ARRAY_SIZE(buf_in) - 1, L"%s.git",
++ watch->wpath_longname);
+
-+ if (!GetShortPathNameW(buf_in, buf_out, MAX_PATH))
++ if (!GetShortPathNameW(buf_in, buf_out, ARRAY_SIZE(buf_out)))
+ return;
+
-+ last_slash = wcsrchr(buf_out, L'/');
-+ last_bslash = wcsrchr(buf_out, L'\\');
-+ if (last_slash > last_bslash)
-+ last = last_slash + 1;
-+ else if (last_bslash)
-+ last = last_bslash + 1;
-+ else
-+ last = buf_out;
++ /*
++ * Get the final filename component of the shortpath.
++ * We know that the path does not have a final slash.
++ */
++ for (last = p = buf_out; *p; p++)
++ if (*p == L'/' || *p == '\\')
++ last = p + 1;
+
+ if (!wcscmp(last, L".git"))
+ return;
@@ compat/fsmonitor/fsm-listen-win32.c: normalize:
+ * Lets test this.
+ */
+ if (wcschr(watch->dotgit_shortname, L'~'))
-+ watch->has_tilda = 1;
++ watch->has_tilde = 1;
+}
+
+enum get_relative_result {
@@ compat/fsmonitor/fsm-listen-win32.c: normalize:
+static enum get_relative_result get_relative_longname(
+ struct one_watch *watch,
+ const wchar_t *wpath, DWORD wpath_len,
-+ wchar_t *wpath_longname)
++ wchar_t *wpath_longname, size_t bufsize_wpath_longname)
+{
+ wchar_t buf_in[2 * MAX_PATH + 1];
+ wchar_t buf_out[MAX_PATH + 1];
+ DWORD root_len;
++ DWORD out_len;
+
-+ /* Build L"<wt-root-path>/<event-rel-path>" */
++ /*
++ * Build L"<wt-root-path>/<event-rel-path>"
++ * Note that the <event-rel-path> might not be null terminated
++ * so we avoid swprintf() constructions.
++ */
+ root_len = watch->wpath_longname_len;
++ if (root_len + wpath_len >= ARRAY_SIZE(buf_in)) {
++ /*
++ * This should not happen. We cannot append the observed
++ * relative path onto the end of the worktree root path
++ * without overflowing the buffer. Just give up.
++ */
++ return GRR_SHUTDOWN;
++ }
+ wcsncpy(buf_in, watch->wpath_longname, root_len);
+ wcsncpy(buf_in + root_len, wpath, wpath_len);
+ buf_in[root_len + wpath_len] = 0;
+
+ /*
+ * We don't actually know if the source pathname is a
-+ * shortname or a longname. This routine allows either to be
-+ * given as input.
++ * shortname or a longname. This Windows routine allows
++ * either to be given as input.
+ */
-+ if (!GetLongPathNameW(buf_in, buf_out, MAX_PATH)) {
++ out_len = GetLongPathNameW(buf_in, buf_out, ARRAY_SIZE(buf_out));
++ if (!out_len) {
+ /*
+ * The shortname to longname conversion can fail for
+ * various reasons, for example if the file has been
@@ compat/fsmonitor/fsm-listen-win32.c: normalize:
+ return GRR_SHUTDOWN;
+ }
+
++ if (out_len - root_len >= bufsize_wpath_longname) {
++ /*
++ * This should not happen. We cannot copy the root-relative
++ * portion of the path into the provided buffer without an
++ * overrun. Just give up.
++ */
++ return GRR_SHUTDOWN;
++ }
++
+ /* Return the worktree root-relative portion of the longname. */
+
+ wcscpy(wpath_longname, buf_out + root_len);
@@ compat/fsmonitor/fsm-listen-win32.c: static struct one_watch *create_watch(struc
return NULL;
}
-+ if (!GetLongPathNameW(wpath, wpath_longname, MAX_PATH)) {
++ len_longname = GetLongPathNameW(wpath, wpath_longname,
++ ARRAY_SIZE(wpath_longname));
++ if (!len_longname) {
+ error(_("[GLE %ld] could not get longname of '%s'"),
+ GetLastError(), path);
+ CloseHandle(hDir);
+ return NULL;
+ }
+
-+ len_longname = wcslen(wpath_longname);
+ if (wpath_longname[len_longname - 1] != L'/' &&
+ wpath_longname[len_longname - 1] != L'\\') {
+ wpath_longname[len_longname++] = L'/';
@@ compat/fsmonitor/fsm-listen-win32.c: static int process_worktree_events(struct f
- case IS_INSIDE_DOT_GIT:
- /* ignore everything inside of "<worktree>/.git/" */
- break;
-+ if (watch->has_tilda && !wcschr(wpath, L'~')) {
++ if (watch->has_tilde && !wcschr(wpath, L'~')) {
+ /*
-+ * Shortnames on this filesystem have tildas
++ * Shortnames on this filesystem have tildes
+ * and the notification path does not have
+ * one, so we assume that it is a longname.
+ */
@@ compat/fsmonitor/fsm-listen-win32.c: static int process_worktree_events(struct f
- "fsm-listen/dotgit",
- "removed");
+ grr = get_relative_longname(watch, wpath, wpath_len,
-+ wpath_longname);
++ wpath_longname,
++ ARRAY_SIZE(wpath_longname));
+ switch (grr) {
+ case GRR_NO_CONVERSION_NEEDED: /* use info buffer as is */
+ break;
2: 5b246bec247 ! 2: 1bf2e36b6ad t7527: test FSMonitor on repos with Unicode root paths
@@ t/t7527-builtin-fsmonitor.sh: do
+u_values="$u1 $u2"
+for u in $u_values
+do
-+ test_expect_success "Unicode in repo root path: $u" '
++ test_expect_success "unicode in repo root path: $u" '
+ test_when_finished "stop_daemon_delete_repo $u" &&
+
+ git init "$u" &&
3: 8a474d69999 = 3: 4bca494bb22 t/helper/fsmonitor-client: create stress test
4: 72b94acd5fe ! 4: 663deabc3f6 fsmonitor-settings: bare repos are incompatible with FSMonitor
@@ fsmonitor-settings.h
+ */
+enum fsmonitor_reason {
+ FSMONITOR_REASON_UNTESTED = 0,
-+ FSMONITOR_REASON_OK, /* no incompatibility or when disbled */
++ FSMONITOR_REASON_OK, /* no incompatibility or when disabled */
+ FSMONITOR_REASON_BARE,
+};
+
5: 2e225c3f4f2 = 5: 7cb0180a1ed fsmonitor-settings: stub in Win32-specific incompatibility checking
6: e0d3bdf7556 ! 6: 9774faddc45 fsmonitor-settings: VFS for Git virtual repos are incompatible
@@ fsmonitor-settings.c: char *fsm_settings__get_incompatible_msg(const struct repo
## fsmonitor-settings.h ##
@@ fsmonitor-settings.h: enum fsmonitor_reason {
FSMONITOR_REASON_UNTESTED = 0,
- FSMONITOR_REASON_OK, /* no incompatibility or when disbled */
+ FSMONITOR_REASON_OK, /* no incompatibility or when disabled */
FSMONITOR_REASON_BARE,
+ FSMONITOR_REASON_VFS4GIT, /* VFS for Git virtualization */
};
7: c50ed29a310 = 7: f7ef7dcffc8 fsmonitor-settings: stub in macOS-specific incompatibility checking
8: 1f5b772d42a ! 8: dc2dfd67931 fsmonitor-settings: remote repos on macOS are incompatible
@@ fsmonitor-settings.c: char *fsm_settings__get_incompatible_msg(const struct repo
## fsmonitor-settings.h ##
@@ fsmonitor-settings.h: enum fsmonitor_reason {
FSMONITOR_REASON_UNTESTED = 0,
- FSMONITOR_REASON_OK, /* no incompatibility or when disbled */
+ FSMONITOR_REASON_OK, /* no incompatibility or when disabled */
FSMONITOR_REASON_BARE,
+ FSMONITOR_REASON_ERROR, /* FS error probing for compatibility */
+ FSMONITOR_REASON_REMOTE,
9: 495e54049b4 = 9: 5627038aaa3 fsmonitor-settings: remote repos on Windows are incompatible
10: 4b52083698c = 10: 9a12cc78b5d fsmonitor-settings: NTFS and FAT32 on MacOS are incompatible
11: d4a4263d379 = 11: aaff000cecb unpack-trees: initialize fsmonitor_has_run_once in o->result
12: f4feb00ec2b = 12: 4f2b15d3d1f fsm-listen-darwin: ignore FSEvents caused by xattr changes on macOS
13: dbb983fd9d0 = 13: 427dec412a5 fsmonitor--daemon: cd out of worktree root
14: ae90b99ea9b = 14: 51b266b06e1 fsmonitor--daemon: prepare for adding health thread
15: b6c5800095f = 15: 594e0ae243d fsmonitor--daemon: rename listener thread related variables
16: 32fc6ba7437 = 16: c2b5c02ed38 fsmonitor--daemon: stub in health thread
17: 77bc037481a = 17: 46a5ae2a635 fsm-health-win32: add polling framework to monitor daemon health
18: b06edd995ea = 18: 7cf1be5f8e2 fsm-health-win32: force shutdown daemon if worktree root moves
19: 1bd5f346248 = 19: 95cf1299d44 fsm-listen-darwin: shutdown daemon if worktree root is moved/renamed
20: 48af0813dec = 20: b020bfb4568 fsmonitor: optimize processing of directory events
21: a9b35e770f3 = 21: d058d7e0c08 t7527: FSMonitor tests for directory moves
22: 26308936af9 = 22: f5dac286812 t/perf/p7527: add perf test for builtin FSMonitor
23: d0e25f6bac6 ! 23: 92f5c0d2c8b fsmonitor: never set CE_FSMONITOR_VALID on submodules
@@ t/t7527-builtin-fsmonitor.sh: do
+ git -C super/dir_1/dir_2/sub clean -d -f
+}
+
-+test_expect_success "Submodule always visited" '
++test_expect_success 'submodule always visited' '
+ test_when_finished "git -C super fsmonitor--daemon stop; \
+ rm -rf super; \
+ rm -rf sub" && 24: 410dd2d2920 ! 24: 40b80adbb31 t7527: test FSMonitor on case insensitive+preserving file system
@@ Commit message
## t/t7527-builtin-fsmonitor.sh ##
-@@ t/t7527-builtin-fsmonitor.sh: test_expect_success "Submodule always visited" ' +@@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'submodule always visited' '
my_match_and_clean
'
25: cd7c55b0d38 = 25: b93f0642699 fsmonitor: on macOS also emit NFC spelling for NFD pathname
-: ----------- > 26: 6f2e935f148 t/helper/hexdump: add helper to print hexdump of stdin
26: 8278f32c4d8 ! 27: 6a830869954 t/lib-unicode-nfc-nfd: helper prereqs for testing unicode nfc/nfd
@@ t/lib-unicode-nfc-nfd.sh (new)
+# and on APFS, NFC paths are preserved. As we have established
+# above, this is independent of "composition sensitivity".
+#
-+# 0000000 63 5f c3 a9
-+#
-+# (/usr/bin/od output contains different amount of whitespace
-+# on different platforms, so we need the wildcards here.)
-+#
+test_lazy_prereq UNICODE_NFC_PRESERVED '
+ mkdir c_${utf8_nfc} &&
-+ ls | od -t x1 | grep "63 *5f *c3 *a9"
++ ls | test-tool hexdump | grep "63 5f c3 a9"
+'
+
+# Is the spelling of an NFD pathname preserved on disk?
+#
-+# 0000000 64 5f 65 cc 81
-+#
+test_lazy_prereq UNICODE_NFD_PRESERVED '
+ mkdir d_${utf8_nfd} &&
-+ ls | od -t x1 | grep "64 *5f *65 *cc *81"
++ ls | test-tool hexdump | grep "64 5f 65 cc 81"
+'
-+ mkdir c_${utf8_nfc} &&
-+ mkdir d_${utf8_nfd} &&
+
+# The following _DOUBLE_ forms are more for my curiosity,
+# but there may be quirks lurking when there are multiple
@@ t/lib-unicode-nfc-nfd.sh (new)
+#
+test_lazy_prereq UNICODE_DOUBLE_NFC_PRESERVED '
+ mkdir c_${greek_nfc} &&
-+ ls | od -t x1 | grep "63 *5f *e1 *bd *a7"
++ ls | test-tool hexdump | grep "63 5f e1 bd a7"
+'
+
+# See if the NFD spelling appears on the disk.
+#
+test_lazy_prereq UNICODE_DOUBLE_NFD_PRESERVED '
+ mkdir d_${greek_nfd2} &&
-+ ls | od -t x1 | grep "64 *5f *cf *89 *cc *94 *cd *82"
++ ls | test-tool hexdump | grep "64 5f cf 89 cc 94 cd 82"
+'
+
+# The following is for debugging. I found it useful when
27: 4efb3a43838 = 28: f9a7869d202 t7527: test Unicode NFC/NFD handling on MacOS
28: df1b4f3a80f ! 29: 9fc7c970929 fsmonitor--daemon: allow --super-prefix argument
@@ git.c: static struct cmd_struct commands[] = {
{ "grep", cmd_grep, RUN_SETUP_GENTLY },
## t/t7527-builtin-fsmonitor.sh ##
-@@ t/t7527-builtin-fsmonitor.sh: test_expect_success "Submodule always visited" '
+@@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'submodule always visited' '
my_match_and_clean
'
@@ t/t7527-builtin-fsmonitor.sh: test_expect_success "Submodule always visited" '
+ tr '\047' Q <$1 | grep -e "$msg"
+}
+
-+test_expect_success "Stray Submodule super-prefix warning" '
++test_expect_success "stray submodule super-prefix warning" '
+ test_when_finished "rm -rf super; \
+ rm -rf sub; \
+ rm super-sub.trace" && -: ----------- > 30: ca833ecc7a1 t7527: improve implicit shutdown testing in fsmonitor--daemon
Signature · 1 line
gitgitgadget
PATCH v7 1/30 fsm-listen-win32: handle shortnames
From: Jeff Hostetler <[email protected]>
Teach FSMonitor daemon on Windows to recognize shortname paths as aliases of normal longname paths. FSMonitor clients, such as git status, should receive the longname spelling of changed files (when possible).
Sometimes we receive FS events using the shortname, such as when a CMD shell runs "RENAME GIT~1 FOO" or "RMDIR GIT~1". The FS notification arrives using whatever combination of long and shortnames were used by the other process. (Shortnames do seem to be case normalized, however.)
Use Windows GetLongPathNameW() to try to map the pathname spelling in the notification event into the normalized longname spelling. (This can fail if the file/directory is deleted, moved, or renamed, because we are asking the FS for the mapping in response to the event and after it has already happened, but we try.)
Special case the shortname spelling of ".git" to avoid under-reporting these events.
--- compat/fsmonitor/fsm-listen-win32.c | 386 ++++++++++++++++++++++++---- t/t7527-builtin-fsmonitor.sh | 65 +++++ 2 files changed, 397 insertions(+), 54 deletions(-)
compat/fsmonitor/fsm-listen-win32.c +332 -54
@@ -25,6 +25,9 @@ struct one_watchDWORD count;struct strbuf path; +wchar_t wpath_longname[MAX_PATH +1 ]; +DWORD wpath_longname_len; +HANDLE hDir;HANDLE hEvent;OVERLAPPED overlapped;
@@ -34,6 +37,21 @@ struct one_watch* need to later call GetOverlappedResult() and possibly CancelIoEx(). */ BOOL is_active; + +/* +* Are shortnames enabled on the containing drive? This is +* always true for "C:/" drives and usually never true for +* other drives. +* +* We only set this for the worktree because we only need to +* convert shortname paths to longname paths for items we send +* to clients. (We don't care about shortname expansion for +* paths inside a GITDIR because we never send them to +* clients.) +*/ +BOOL has_shortnames; +BOOL has_tilde; +wchar_t dotgit_shortname[16 ];/* for 8.3 name */ };struct fsmonitor_daemon_backend_data
@@ -51,17 +69,18 @@ struct fsmonitor_daemon_backend_data };/* -* Convert the WCHAR path from the notification into UTF8 and -* then normalize it. +* Convert the WCHAR path from the event into UTF8 and normalize it. +* +* `wpath_len` is in WCHARS not bytes. */ -static int normalize_path_in_utf8 (FILE_NOTIFY_INFORMATION *info, +static int normalize_path_in_utf8 (wchar_t *wpath,DWORD wpath_len,struct strbuf *normalized_path) {int reserve;int len =0 ;strbuf_reset (normalized_path); -if (!info->FileNameLength) +if (!wpath_len) goto normalize;/*
@@ -70,12 +89,12 @@ static int normalize_path_in_utf8(FILE_NOTIFY_INFORMATION *info,* sequence of 2 UTF8 characters. That should let us * avoid ERROR_INSUFFICIENT_BUFFER 99.9+% of the time. */ - reserve = info->FileNameLength +1 ; + reserve =2 * wpath_len +1 ;strbuf_grow (normalized_path, reserve);for (;;) { - len =WideCharToMultiByte (CP_UTF8 ,0 , info->FileName, - info->FileNameLength /sizeof (WCHAR ), + len =WideCharToMultiByte (CP_UTF8 ,0 , + wpath, wpath_len, normalized_path->buf,strbuf_avail (normalized_path) -1 ,NULL ,NULL );
@@ -83,9 +102,7 @@ static int normalize_path_in_utf8(FILE_NOTIFY_INFORMATION *info, goto normalize;if (GetLastError () !=ERROR_INSUFFICIENT_BUFFER ) {error (_ ("[GLE %ld] could not convert path to UTF-8: '%.*ls'" ), -GetLastError (), - (int )(info->FileNameLength /sizeof (WCHAR )), - info->FileName); +GetLastError (), (int )wpath_len, wpath);return -1 ; }
@@ -98,6 +115,173 @@ normalize:return strbuf_normalize_path (normalized_path); } +/* +* See if the worktree root directory has shortnames enabled. +* This will help us decide if we need to do an expensive shortname +* to longname conversion on every notification event. +* +* We do not want to create a file to test this, so we assume that the +* root directory contains a ".git" file or directory. (Our caller +* only calls us for the worktree root, so this should be fine.) +* +* Remember the spelling of the shortname for ".git" if it exists. +*/ +static void check_for_shortnames (struct one_watch *watch) +{ +wchar_t buf_in[MAX_PATH +1 ]; +wchar_t buf_out[MAX_PATH +1 ]; +wchar_t *last; +wchar_t *p; + +/* build L"<wt-root-path>/.git" */ +swprintf (buf_in,ARRAY_SIZE (buf_in) -1 ,L"%s.git" , + watch->wpath_longname); + +if (!GetShortPathNameW (buf_in, buf_out,ARRAY_SIZE (buf_out))) +return ; + +/* +* Get the final filename component of the shortpath. +* We know that the path does not have a final slash. +*/ +for (last = p = buf_out; *p; p++) +if (*p ==L'/' || *p =='\\' ) + last = p +1 ; + +if (!wcscmp (last,L".git" )) +return ; + + watch->has_shortnames =1 ; +wcsncpy (watch->dotgit_shortname, last, +ARRAY_SIZE (watch->dotgit_shortname)); + +/* +* The shortname for ".git" is usually of the form "GIT~1", so +* we should be able to avoid shortname to longname mapping on +* every notification event if the source string does not +* contain a "~". +* +* However, the documentation for GetLongPathNameW() says +* that there are filesystems that don't follow that pattern +* and warns against this optimization. +* +* Lets test this. +*/ +if (wcschr (watch->dotgit_shortname,L'~' )) + watch->has_tilde =1 ; +} + +enum get_relative_result { +GRR_NO_CONVERSION_NEEDED , +GRR_HAVE_CONVERSION , +GRR_SHUTDOWN , +}; + +/* +* Info notification paths are relative to the root of the watch. +* If our CWD is still at the root, then we can use relative paths +* to convert from shortnames to longnames. If our process has a +* different CWD, then we need to construct an absolute path, do +* the conversion, and then return the root-relative portion. +* +* We use the longname form of the root as our basis and assume that +* it already has a trailing slash. +* +* `wpath_len` is in WCHARS not bytes. +*/ +static enum get_relative_result get_relative_longname ( +struct one_watch *watch, +const wchar_t *wpath,DWORD wpath_len, +wchar_t *wpath_longname,size_t bufsize_wpath_longname) +{ +wchar_t buf_in[2 *MAX_PATH +1 ]; +wchar_t buf_out[MAX_PATH +1 ]; +DWORD root_len; +DWORD out_len; + +/* +* Build L"<wt-root-path>/<event-rel-path>" +* Note that the <event-rel-path> might not be null terminated +* so we avoid swprintf() constructions. +*/ + root_len = watch->wpath_longname_len; +if (root_len + wpath_len >=ARRAY_SIZE (buf_in)) { +/* +* This should not happen. We cannot append the observed +* relative path onto the end of the worktree root path +* without overflowing the buffer. Just give up. +*/ +return GRR_SHUTDOWN ; + } +wcsncpy (buf_in, watch->wpath_longname, root_len); +wcsncpy (buf_in + root_len, wpath, wpath_len); + buf_in[root_len + wpath_len] =0 ; + +/* +* We don't actually know if the source pathname is a +* shortname or a longname. This Windows routine allows +* either to be given as input. +*/ + out_len =GetLongPathNameW (buf_in, buf_out,ARRAY_SIZE (buf_out)); +if (!out_len) { +/* +* The shortname to longname conversion can fail for +* various reasons, for example if the file has been +* deleted. (That is, if we just received a +* delete-file notification event and the file is +* already gone, we can't ask the file system to +* lookup the longname for it. Likewise, for moves +* and renames where we are given the old name.) +* +* Since deleting or moving a file or directory by its +* shortname is rather obscure, I'm going ignore the +* failure and ask the caller to report the original +* relative path. This seems kinder than failing here +* and forcing a resync. Besides, forcing a resync on +* every file/directory delete would effectively +* cripple monitoring. +* +* We might revisit this in the future. +*/ +return GRR_NO_CONVERSION_NEEDED ; + } + +if (!wcscmp (buf_in, buf_out)) { +/* +* The path does not have a shortname alias. +*/ +return GRR_NO_CONVERSION_NEEDED ; + } + +if (wcsncmp (buf_in, buf_out, root_len)) { +/* +* The spelling of the root directory portion of the computed +* longname has changed. This should not happen. Basically, +* it means that we don't know where (without recomputing the +* longname of just the root directory) to split out the +* relative path. Since this should not happen, I'm just +* going to let this fail and force a shutdown (because all +* subsequent events are probably going to see the same +* mismatch). +*/ +return GRR_SHUTDOWN ; + } + +if (out_len - root_len >= bufsize_wpath_longname) { +/* +* This should not happen. We cannot copy the root-relative +* portion of the path into the provided buffer without an +* overrun. Just give up. +*/ +return GRR_SHUTDOWN ; + } + +/* Return the worktree root-relative portion of the longname. */ + +wcscpy (wpath_longname, buf_out + root_len); +return GRR_HAVE_CONVERSION ; +} +void fsm_listen__stop_async (struct fsmonitor_daemon_state *state) {SetEvent (state->backend_data->hListener[LISTENER_SHUTDOWN ]);
@@ -111,7 +295,9 @@ static struct one_watch *create_watch(struct fsmonitor_daemon_state *state,DWORD share_mode =FILE_SHARE_WRITE |FILE_SHARE_READ |FILE_SHARE_DELETE ;HANDLE hDir; -wchar_t wpath[MAX_PATH ]; +DWORD len_longname; +wchar_t wpath[MAX_PATH +1 ]; +wchar_t wpath_longname[MAX_PATH +1 ];if (xutftowcs_path (wpath, path) <0 ) {error (_ ("could not convert to wide characters: '%s'" ), path);
@@ -128,6 +314,21 @@ static struct one_watch *create_watch(struct fsmonitor_daemon_state *state,return NULL ; } + len_longname =GetLongPathNameW (wpath, wpath_longname, +ARRAY_SIZE (wpath_longname)); +if (!len_longname) { +error (_ ("[GLE %ld] could not get longname of '%s'" ), +GetLastError (), path); +CloseHandle (hDir); +return NULL ; + } + +if (wpath_longname[len_longname -1 ] !=L'/' && + wpath_longname[len_longname -1 ] !=L'\\' ) { + wpath_longname[len_longname++] =L'/' ; + wpath_longname[len_longname] =0 ; + } +CALLOC_ARRAY (watch,1 ); watch->buf_len =sizeof (watch->buffer);/* assume full MAX_RDCW_BUF */
@@ -135,6 +336,9 @@ static struct one_watch *create_watch(struct fsmonitor_daemon_state *state,strbuf_init (&watch->path,0 );strbuf_addstr (&watch->path, path); +wcscpy (watch->wpath_longname, wpath_longname); + watch->wpath_longname_len = len_longname; + watch->hDir = hDir; watch->hEvent =CreateEvent (NULL , TRUE, FALSE,NULL );
@@ -258,6 +462,62 @@ static void cancel_rdcw_watch(struct one_watch *watch) watch->is_active = FALSE; } +/* +* Process a single relative pathname event. +* Return 1 if we should shutdown. +*/ +static int process_1_worktree_event ( +struct string_list *cookie_list, +struct fsmonitor_batch **batch, +const struct strbuf *path, +enum fsmonitor_path_type t, +DWORD info_action) +{ +const char *slash; + +switch (t) { +case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX : +/* special case cookie files within .git */ + +/* Use just the filename of the cookie file. */ + slash =find_last_dir_sep (path->buf); +string_list_append (cookie_list, + slash ? slash +1 : path->buf); +break ; + +case IS_INSIDE_DOT_GIT : +/* ignore everything inside of "<worktree>/.git/" */ +break ; + +case IS_DOT_GIT : +/* "<worktree>/.git" was deleted (or renamed away) */ +if ((info_action ==FILE_ACTION_REMOVED ) || + (info_action ==FILE_ACTION_RENAMED_OLD_NAME )) { +trace2_data_string ("fsmonitor" ,NULL , +"fsm-listen/dotgit" , +"removed" ); +return 1 ; + } +break ; + +case IS_WORKDIR_PATH : +/* queue normal pathname */ +if (!*batch) + *batch =fsmonitor_batch__new (); +fsmonitor_batch__add_path (*batch, path->buf); +break ; + +case IS_GITDIR : +case IS_INSIDE_GITDIR : +case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX : +default : +BUG ("unexpected path classification '%d' for '%s'" , + t, path->buf); + } + +return 0 ; +} +/* * Process filesystem events that happen anywhere (recursively) under the * <worktree> root directory. For a normal working directory, this includes
@@ -274,6 +534,7 @@ static int process_worktree_events(struct fsmonitor_daemon_state *state)struct string_list cookie_list =STRING_LIST_INIT_DUP ;struct fsmonitor_batch *batch =NULL ;const char *p = watch->buffer; +wchar_t wpath_longname[MAX_PATH +1 ];/* * If the kernel gets more events than will fit in the kernel
@@ -306,54 +567,64 @@ static int process_worktree_events(struct fsmonitor_daemon_state *state)*/ for (;;) {FILE_NOTIFY_INFORMATION *info = (void *)p; -const char *slash; +wchar_t *wpath = info->FileName; +DWORD wpath_len = info->FileNameLength /sizeof (WCHAR );enum fsmonitor_path_type t; +enum get_relative_result grr; + +if (watch->has_shortnames) { +if (!wcscmp (wpath, watch->dotgit_shortname)) { +/* +* This event exactly matches the +* spelling of the shortname of +* ".git", so we can skip some steps. +* +* (This case is odd because the user +* can "rm -rf GIT~1" and we cannot +* use the filesystem to map it back +* to ".git".) +*/ +strbuf_reset (&path); +strbuf_addstr (&path,".git" ); + t =IS_DOT_GIT ; + goto process_it; + } -strbuf_reset (&path); -if (normalize_path_in_utf8 (info, &path) ==-1 ) - goto skip_this_path; - - t =fsmonitor_classify_path_workdir_relative (path.buf); - -switch (t) { -case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX : -/* special case cookie files within .git */ - -/* Use just the filename of the cookie file. */ - slash =find_last_dir_sep (path.buf); -string_list_append (&cookie_list, - slash ? slash +1 : path.buf); -break ; - -case IS_INSIDE_DOT_GIT : -/* ignore everything inside of "<worktree>/.git/" */ -break ; +if (watch->has_tilde && !wcschr (wpath,L'~' )) { +/* +* Shortnames on this filesystem have tildes +* and the notification path does not have +* one, so we assume that it is a longname. +*/ + goto normalize_it; + } -case IS_DOT_GIT : -/* "<worktree>/.git" was deleted (or renamed away) */ -if ((info->Action ==FILE_ACTION_REMOVED ) || - (info->Action ==FILE_ACTION_RENAMED_OLD_NAME )) { -trace2_data_string ("fsmonitor" ,NULL , -"fsm-listen/dotgit" , -"removed" ); + grr =get_relative_longname (watch, wpath, wpath_len, + wpath_longname, +ARRAY_SIZE (wpath_longname)); +switch (grr) { +case GRR_NO_CONVERSION_NEEDED :/* use info buffer as is */ +break ; +case GRR_HAVE_CONVERSION : + wpath = wpath_longname; + wpath_len =wcslen (wpath); +break ; +default : +case GRR_SHUTDOWN : goto force_shutdown; } -break ; + } -case IS_WORKDIR_PATH : -/* queue normal pathname */ -if (!batch) - batch =fsmonitor_batch__new (); -fsmonitor_batch__add_path (batch, path.buf); -break ; +normalize_it: +if (normalize_path_in_utf8 (wpath, wpath_len, &path) ==-1 ) + goto skip_this_path; -case IS_GITDIR : -case IS_INSIDE_GITDIR : -case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX : -default : -BUG ("unexpected path classification '%d' for '%s'" , - t, path.buf); - } + t =fsmonitor_classify_path_workdir_relative (path.buf); + +process_it: +if (process_1_worktree_event (&cookie_list, &batch, &path, t, + info->Action)) + goto force_shutdown; skip_this_path:if (!info->NextEntryOffset)
@@ -382,6 +653,9 @@ force_shutdown:* Note that we DO NOT get filesystem events on the external <gitdir> * itself (it is not inside something that we are watching). In particular, * we do not get an event if the external <gitdir> is deleted. +* +* Also, we do not care about shortnames within the external <gitdir>, since +* we never send these paths to clients. */ static int process_gitdir_events (struct fsmonitor_daemon_state *state) {
@@ -403,8 +677,10 @@ static int process_gitdir_events(struct fsmonitor_daemon_state *state)const char *slash;enum fsmonitor_path_type t; -strbuf_reset (&path); -if (normalize_path_in_utf8 (info, &path) ==-1 ) +if (normalize_path_in_utf8 ( + info->FileName, + info->FileNameLength /sizeof (WCHAR ), + &path) ==-1 ) goto skip_this_path; t =fsmonitor_classify_path_gitdir_relative (path.buf);
@@ -538,6 +814,8 @@ int fsm_listen__ctor(struct fsmonitor_daemon_state *state)if (!data->watch_worktree) goto failed; +check_for_shortnames (data->watch_worktree); +if (state->nr_paths_watching >1 ) { data->watch_gitdir =create_watch (state, state->path_gitdir_watch.buf);
t/t7527-builtin-fsmonitor.sh +65 -0
71 lines not on this page · raw message
Signature · 1 line
gitgitgadget
PATCH v7 2/30 t7527: test FSMonitor on repos with Unicode root paths
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create some test repos with UTF8 characters in the pathname of the root directory and verify that the builtin FSMonitor can watch them.
This test is mainly for Windows where we need to avoid *A() routines.
--- t/t7527-builtin-fsmonitor.sh | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+)
t/t7527-builtin-fsmonitor.sh +23 -0
@@ -671,4 +671,27 @@ do done done +# Test Unicode UTF-8 characters in the pathname of the working +# directory root. Use of "*A()" routines rather than "*W()" routines +# on Windows can sometimes lead to odd failures. +# +u1=$(printf "u_c3_a6__\xC3\xA6") +u2=$(printf "u_e2_99_ab__\xE2\x99\xAB") +u_values="$u1 $u2" +for u in $u_values +do + test_expect_success "unicode in repo root path: $u" ' + test_when_finished "stop_daemon_delete_repo $u" && + + git init "$u" && + echo 1 >"$u"/file1 && + git -C "$u" add file1 && + git -C "$u" config core.fsmonitor true && + + start_daemon -C "$u" && + git -C "$u" status >actual && + grep "new file: file1" actual + ' +done + test_done
Signature · 1 line
gitgitgadget
PATCH v7 3/30 t/helper/fsmonitor-client: create stress test
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create a stress test to hammer on the fsmonitor daemon. Create a client-side thread pool of n threads and have each of them make m requests as fast as they can.
We do not currently inspect the contents of the response. We're only interested in placing a heavy request load on the daemon.
This test is useful for interactive testing and various experimentation. For example, to place additional load on the daemon while another test is running. We currently do not have a test script that actually uses this helper. We might add such a test in the future.
--- t/helper/test-fsmonitor-client.c | 106 +++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+)
t/helper/test-fsmonitor-client.c +106 -0
@@ -7,6 +7,8 @@#include "cache.h" #include "parse-options.h" #include "fsmonitor-ipc.h" +#include "thread-utils.h" +#include "trace2.h" #ifndef HAVE_FSMONITOR_DAEMON_BACKEND int cmd__fsmonitor_client (int argc,const char **argv)
@@ -79,20 +81,121 @@ static int do_send_flush(void)return 0 ; } +struct hammer_thread_data +{ +pthread_t pthread_id; +int thread_nr; + +int nr_requests; +const char *token; + +int sum_successful; +int sum_errors; +}; + +static void *hammer_thread_proc (void *_hammer_thread_data) +{ +struct hammer_thread_data *data = _hammer_thread_data; +struct strbuf answer =STRBUF_INIT ; +int k; +int ret; + +trace2_thread_start ("hammer" ); + +for (k =0 ; k < data->nr_requests; k++) { +strbuf_reset (&answer); + + ret =fsmonitor_ipc__send_query (data->token, &answer); +if (ret <0 ) + data->sum_errors++; +else + data->sum_successful++; + } + +strbuf_release (&answer); +trace2_thread_exit (); +return NULL ; +} + +/* +* Start a pool of client threads that will each send a series of +* commands to the daemon. +* +* The goal is to overload the daemon with a sustained series of +* concurrent requests. +*/ +static int do_hammer (const char *token,int nr_threads,int nr_requests) +{ +struct hammer_thread_data *data =NULL ; +int k; +int sum_join_errors =0 ; +int sum_commands =0 ; +int sum_errors =0 ; + +if (!token || !*token) + token =get_token_from_index (); +if (nr_threads <1 ) + nr_threads =1 ; +if (nr_requests <1 ) + nr_requests =1 ; + +CALLOC_ARRAY (data, nr_threads); + +for (k =0 ; k < nr_threads; k++) { +struct hammer_thread_data *p = &data[k]; + p->thread_nr = k; + p->nr_requests = nr_requests; + p->token = token; + +if (pthread_create (&p->pthread_id,NULL , hammer_thread_proc, p)) { +warning ("failed to create thread[%d] skipping remainder" , k); + nr_threads = k; +break ; + } + } + +for (k =0 ; k < nr_threads; k++) { +struct hammer_thread_data *p = &data[k]; + +if (pthread_join (p->pthread_id,NULL )) + sum_join_errors++; + sum_commands += p->sum_successful; + sum_errors += p->sum_errors; + } + +fprintf (stderr,"HAMMER: [threads %d][requests %d] [ok %d][err %d][join %d]\n" , + nr_threads, nr_requests, sum_commands, sum_errors, sum_join_errors); + +free (data); + +/* +* Return an error if any of the _send_query requests failed. +* We don't care about thread create/join errors. +*/ +return sum_errors >0 ; +} +int cmd__fsmonitor_client (int argc,const char **argv) {const char *subcmd;const char *token =NULL ; +int nr_threads =1 ; +int nr_requests =1 ;const char *const fsmonitor_client_usage[] = {"test-tool fsmonitor-client query [<token>]" ,"test-tool fsmonitor-client flush" , +"test-tool fsmonitor-client hammer [<token>] [<threads>] [<requests>]" ,NULL , };struct option options[] = {OPT_STRING (0 ,"token" , &token,"token" ,"command token to send to the server" ), + +OPT_INTEGER (0 ,"threads" , &nr_threads,"number of client threads" ), +OPT_INTEGER (0 ,"requests" , &nr_requests,"number of requests per thread" ), +OPT_END () };
@@ -111,6 +214,9 @@ int cmd__fsmonitor_client(int argc, const char **argv)if (!strcmp (subcmd,"flush" ))return !!do_send_flush (); +if (!strcmp (subcmd,"hammer" )) +return !!do_hammer (token, nr_threads, nr_requests); + die("Unhandled subcommand: '%s'", subcmd); }#endif
Signature · 1 line
gitgitgadget
PATCH v7 4/30 fsmonitor-settings: bare repos are incompatible with FSMonitor
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Bare repos do not have a worktree, so there is nothing for the daemon watch.
--- builtin/fsmonitor--daemon.c | 18 +++++ builtin/update-index.c | 16 +++++ fsmonitor-settings.c | 133 ++++++++++++++++++++++++++++++------ fsmonitor-settings.h | 16 +++++ t/t7519-status-fsmonitor.sh | 23 +++++++ 5 files changed, 186 insertions(+), 20 deletions(-)
builtin/fsmonitor--daemon.c +18 -0
@@ -1423,6 +1423,7 @@ static int try_to_start_background_daemon(void)int cmd_fsmonitor__daemon (int argc,const char **argv,const char *prefix) {const char *subcmd; +enum fsmonitor_reason reason;int detach_console =0 ;struct option options[] = {
@@ -1449,6 +1450,23 @@ int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix)die (_ ("invalid 'ipc-threads' value (%d)" ), fsmonitor__ipc_threads); +prepare_repo_settings (the_repository); +/* +* If the repo is fsmonitor-compatible, explicitly set IPC-mode +* (without bothering to load the `core.fsmonitor` config settings). +* +* If the repo is not compatible, the repo-settings will be set to +* incompatible rather than IPC, so we can use one of the __get +* routines to detect the discrepancy. +*/ +fsm_settings__set_ipc (the_repository); + + reason =fsm_settings__get_reason (the_repository); +if (reason >FSMONITOR_REASON_OK ) +die ("%s" , +fsm_settings__get_incompatible_msg (the_repository, + reason)); +if (!strcmp (subcmd,"start" ))return !!try_to_start_background_daemon ();
builtin/update-index.c +16 -0
@@ -1237,6 +1237,22 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)if (fsmonitor >0 ) {enum fsmonitor_mode fsm_mode =fsm_settings__get_mode (r); +enum fsmonitor_reason reason =fsm_settings__get_reason (r); + +/* +* The user wants to turn on FSMonitor using the command +* line argument. (We don't know (or care) whether that +* is the IPC or HOOK version.) +* +* Use one of the __get routines to force load the FSMonitor +* config settings into the repo-settings. That will detect +* whether the file system is compatible so that we can stop +* here with a nice error message. +*/ +if (reason >FSMONITOR_REASON_OK ) +die ("%s" , +fsm_settings__get_incompatible_msg (r, reason)); +if (fsm_mode ==FSMONITOR_MODE_DISABLED ) { warning(_("core.fsmonitor is unset; " "set it if you really want to "
fsmonitor-settings.c +113 -20
200 lines not on this page · raw message
fsmonitor-settings.h +16 -0
@@ -4,18 +4,34 @@struct repository ;enum fsmonitor_mode { +FSMONITOR_MODE_INCOMPATIBLE =-1 ,/* see _reason */ FSMONITOR_MODE_DISABLED =0 ,FSMONITOR_MODE_HOOK =1 ,/* core.fsmonitor=<hook_path> */ FSMONITOR_MODE_IPC =2 ,/* core.fsmonitor=<true> */ }; +/* +* Incompatibility reasons. +*/ +enum fsmonitor_reason { +FSMONITOR_REASON_UNTESTED =0 , +FSMONITOR_REASON_OK ,/* no incompatibility or when disabled */ +FSMONITOR_REASON_BARE , +}; +void fsm_settings__set_ipc (struct repository *r);void fsm_settings__set_hook (struct repository *r,const char *path);void fsm_settings__set_disabled (struct repository *r); +void fsm_settings__set_incompatible (struct repository *r, +enum fsmonitor_reason reason);enum fsmonitor_mode fsm_settings__get_mode (struct repository *r);const char *fsm_settings__get_hook_path (struct repository *r); +enum fsmonitor_reason fsm_settings__get_reason (struct repository *r); +char *fsm_settings__get_incompatible_msg (const struct repository *r, +enum fsmonitor_reason reason); +struct fsmonitor_settings ;#endif /* FSMONITOR_SETTINGS_H */
t/t7519-status-fsmonitor.sh +23 -0
@@ -55,6 +55,29 @@ test_lazy_prereq UNTRACKED_CACHE ' test $ret -ne 1 ' +# Test that we detect and disallow repos that are incompatible with FSMonitor. +test_expect_success 'incompatible bare repo' ' + test_when_finished "rm -rf ./bare-clone actual expect" && + git init --bare bare-clone && + + test_must_fail \ + git -C ./bare-clone -c core.fsmonitor=foo \ + update-index --fsmonitor 2>actual && + grep "bare repository .* is incompatible with fsmonitor" actual && + + test_must_fail \ + git -C ./bare-clone -c core.fsmonitor=true \ + update-index --fsmonitor 2>actual && + grep "bare repository .* is incompatible with fsmonitor" actual +' + +test_expect_success FSMONITOR_DAEMON 'run fsmonitor-daemon in bare repo' ' + test_when_finished "rm -rf ./bare-clone actual" && + git init --bare bare-clone && + test_must_fail git -C ./bare-clone fsmonitor--daemon run 2>actual && + grep "bare repository .* is incompatible with fsmonitor" actual +' + test_expect_success 'setup' ' mkdir -p .git/hooks && : >tracked &&
Signature · 1 line
gitgitgadget
PATCH v7 5/30 fsmonitor-settings: stub in Win32-specific incompatibility checking
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Extend generic incompatibility checkout with platform-specific mechanism. Stub in Win32 version.
In the existing fsmonitor-settings code we have a way to mark types of repos as incompatible with fsmonitor (whether via the hook and IPC APIs). For example, we do this for bare repos, since there are no files to watch.
Extend this exclusion mechanism for platform-specific reasons. This commit just creates the framework and adds a stub for Win32.
--- Makefile | 13 +++++++++++++ compat/fsmonitor/fsm-settings-win32.c | 9 +++++++++ config.mak.uname | 4 ++++ contrib/buildsystems/CMakeLists.txt | 3 +++ fsmonitor-settings.c | 10 ++++++++++ fsmonitor-settings.h | 13 +++++++++++++ 6 files changed, 52 insertions(+) create mode 100644 compat/fsmonitor/fsm-settings-win32.c
Makefile +13 -0
@@ -475,6 +475,11 @@ all:: # `compat/fsmonitor/fsm-listen-<name>.c` that implements the # `fsm_listen__*()` routines. # +# If your platform has OS-specific ways to tell if a repo is incompatible with +# fsmonitor (whether the hook or IPC daemon version), set FSMONITOR_OS_SETTINGS +# to the "<name>" of the corresponding `compat/fsmonitor/fsm-settings-<name>.c` +# that implements the `fsm_os_settings__*()` routines. +# # Define DEVELOPER to enable more compiler warnings. Compiler version # and family are auto detected, but could be overridden by defining # COMPILER_FEATURES (see config.mak.dev). You can still set
@@ -1979,6 +1984,11 @@ ifdef FSMONITOR_DAEMON_BACKEND COMPAT_OBJS += compat/fsmonitor/fsm-listen-$(FSMONITOR_DAEMON_BACKEND).o endif +ifdef FSMONITOR_OS_SETTINGS + COMPAT_CFLAGS += -DHAVE_FSMONITOR_OS_SETTINGS + COMPAT_OBJS += compat/fsmonitor/fsm-settings-$(FSMONITOR_OS_SETTINGS).o +endif + ifeq ($(TCLTK_PATH),) NO_TCLTK = NoThanks endif
@@ -2901,6 +2911,9 @@ GIT-BUILD-OPTIONS: FORCE ifdef FSMONITOR_DAEMON_BACKEND @echo FSMONITOR_DAEMON_BACKEND=\''$(subst ','\'',$(subst ','\'',$(FSMONITOR_DAEMON_BACKEND)))'\' >>$@+ endif +ifdef FSMONITOR_OS_SETTINGS + @echo FSMONITOR_OS_SETTINGS=\''$(subst ','\'',$(subst ','\'',$(FSMONITOR_OS_SETTINGS)))'\' >>$@+ +endif ifdef TEST_OUTPUT_DIRECTORY @echo TEST_OUTPUT_DIRECTORY=\''$(subst ','\'',$(subst ','\'',$(TEST_OUTPUT_DIRECTORY)))'\' >>$@+ endif
compat/fsmonitor/fsm-settings-win32.c new file +9 -0
@@ -0,0 +1,9 @@ +#include "cache.h" +#include "config.h" +#include "repository.h" +#include "fsmonitor-settings.h" + +enum fsmonitor_reason fsm_os__incompatible (struct repository *r) +{ +return FSMONITOR_REASON_OK ; +}
config.mak.uname +4 -0
@@ -450,6 +450,8 @@ ifeq ($(uname_S),Windows) # These are always available, so we do not have to conditionally # support it. FSMONITOR_DAEMON_BACKEND = win32 + FSMONITOR_OS_SETTINGS = win32 + NO_SVN_TESTS = YesPlease RUNTIME_PREFIX = YesPlease HAVE_WPGMPTR = YesWeDo
@@ -639,6 +641,8 @@ ifeq ($(uname_S),MINGW) # These are always available, so we do not have to conditionally # support it. FSMONITOR_DAEMON_BACKEND = win32 + FSMONITOR_OS_SETTINGS = win32 + RUNTIME_PREFIX = YesPlease HAVE_WPGMPTR = YesWeDo NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
contrib/buildsystems/CMakeLists.txt +3 -0
@@ -289,6 +289,9 @@ if(SUPPORTS_SIMPLE_IPC) if(CMAKE_SYSTEM_NAME STREQUAL "Windows") add_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND) list(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-win32.c) + + add_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS) + list(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-win32.c) elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") add_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND) list(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-darwin.c)
fsmonitor-settings.c +10 -0
@@ -23,6 +23,16 @@ static enum fsmonitor_reason check_for_incompatible(struct repository *r)return FSMONITOR_REASON_BARE ; } +#ifdef HAVE_FSMONITOR_OS_SETTINGS + { +enum fsmonitor_reason reason; + + reason =fsm_os__incompatible (r); +if (reason !=FSMONITOR_REASON_OK ) +return reason; + } +#endif +return FSMONITOR_REASON_OK ; }
fsmonitor-settings.h +13 -0
@@ -34,4 +34,17 @@ char *fsm_settings__get_incompatible_msg(const struct repository *r,struct fsmonitor_settings ; +#ifdef HAVE_FSMONITOR_OS_SETTINGS +/* +* Ask platform-specific code whether the repository is incompatible +* with fsmonitor (both hook and ipc modes). For example, if the working +* directory is on a remote volume and mounted via a technology that does +* not support notification events, then we should not pretend to watch it. +* +* fsm_os__* routines should considered private to fsm_settings__ +* routines. +*/ +enum fsmonitor_reason fsm_os__incompatible (struct repository *r); +#endif /* HAVE_FSMONITOR_OS_SETTINGS */ +#endif /* FSMONITOR_SETTINGS_H */
Signature · 1 line
gitgitgadget
PATCH v7 6/30 fsmonitor-settings: VFS for Git virtual repos are incompatible
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
VFS for Git virtual repositories are incompatible with FSMonitor.
VFS for Git is a downstream fork of Git. It contains its own custom file system watcher that is aware of the virtualization. If a working directory is being managed by VFS for Git, we should not try to watch it because we may get incomplete results.
We do not know anything about how VFS for Git works, but we do know that VFS for Git working directories contain a well-defined config setting. If it is set, mark the working directory as incompatible.
--- compat/fsmonitor/fsm-settings-win32.c | 26 ++++++++++++++++++++++++++ fsmonitor-settings.c | 6 ++++++ fsmonitor-settings.h | 1 + t/t7519-status-fsmonitor.sh | 9 +++++++++ 4 files changed, 42 insertions(+)
compat/fsmonitor/fsm-settings-win32.c +26 -0
@@ -3,7 +3,33 @@#include "repository.h" #include "fsmonitor-settings.h" +/* +* VFS for Git is incompatible with FSMonitor. +* +* Granted, core Git does not know anything about VFS for Git and we +* shouldn't make assumptions about a downstream feature, but users +* can install both versions. And this can lead to incorrect results +* from core Git commands. So, without bringing in any of the VFS for +* Git code, do a simple config test for a published config setting. +* (We do not look at the various *_TEST_* environment variables.) +*/ +static enum fsmonitor_reason check_vfs4git (struct repository *r) +{ +const char *const_str; + +if (!repo_config_get_value (r,"core.virtualfilesystem" , &const_str)) +return FSMONITOR_REASON_VFS4GIT ; + +return FSMONITOR_REASON_OK ; +} +enum fsmonitor_reason fsm_os__incompatible (struct repository *r) { +enum fsmonitor_reason reason; + + reason =check_vfs4git (r); +if (reason !=FSMONITOR_REASON_OK ) +return reason; +return FSMONITOR_REASON_OK ; }
fsmonitor-settings.c +6 -0
@@ -207,6 +207,12 @@ char *fsm_settings__get_incompatible_msg(const struct repository *r,_ ("bare repository '%s' is incompatible with fsmonitor" ), xgetcwd()); goto done; + +case FSMONITOR_REASON_VFS4GIT : +strbuf_addf (&msg, +_ ("virtual repository '%s' is incompatible with fsmonitor" ), + r->worktree); + goto done; } BUG("Unhandled case in fsm_settings__get_incompatible_msg: '%d'",
fsmonitor-settings.h +1 -0
@@ -17,6 +17,7 @@ enum fsmonitor_reason {FSMONITOR_REASON_UNTESTED =0 ,FSMONITOR_REASON_OK ,/* no incompatibility or when disabled */ FSMONITOR_REASON_BARE , + FSMONITOR_REASON_VFS4GIT, /* VFS for Git virtualization */ };void fsm_settings__set_ipc (struct repository *r);
t/t7519-status-fsmonitor.sh +9 -0
@@ -78,6 +78,15 @@ test_expect_success FSMONITOR_DAEMON 'run fsmonitor-daemon in bare repo' ' grep "bare repository .* is incompatible with fsmonitor" actual ' +test_expect_success MINGW,FSMONITOR_DAEMON 'run fsmonitor-daemon in virtual repo' ' + test_when_finished "rm -rf ./fake-virtual-clone actual" && + git init fake-virtual-clone && + test_must_fail git -C ./fake-virtual-clone \ + -c core.virtualfilesystem=true \ + fsmonitor--daemon run 2>actual && + grep "virtual repository .* is incompatible with fsmonitor" actual +' + test_expect_success 'setup' ' mkdir -p .git/hooks && : >tracked &&
Signature · 1 line
gitgitgadget
PATCH v7 7/30 fsmonitor-settings: stub in macOS-specific incompatibility checking
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
--- compat/fsmonitor/fsm-settings-darwin.c | 9 +++++++++ config.mak.uname | 1 + contrib/buildsystems/CMakeLists.txt | 3 +++ 3 files changed, 13 insertions(+) create mode 100644 compat/fsmonitor/fsm-settings-darwin.c
compat/fsmonitor/fsm-settings-darwin.c new file +9 -0
@@ -0,0 +1,9 @@ +#include "cache.h" +#include "config.h" +#include "repository.h" +#include "fsmonitor-settings.h" + +enum fsmonitor_reason fsm_os__incompatible (struct repository *r) +{ +return FSMONITOR_REASON_OK ; +}
config.mak.uname +1 -0
@@ -163,6 +163,7 @@ ifeq ($(uname_S),Darwin) ifndef NO_PTHREADS ifndef NO_UNIX_SOCKETS FSMONITOR_DAEMON_BACKEND = darwin + FSMONITOR_OS_SETTINGS = darwin endif endif
contrib/buildsystems/CMakeLists.txt +3 -0
@@ -295,6 +295,9 @@ if(SUPPORTS_SIMPLE_IPC) elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") add_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND) list(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-darwin.c) + + add_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS) + list(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-darwin.c) endif() endif()
Signature · 1 line
gitgitgadget
PATCH v7 8/30 fsmonitor-settings: remote repos on macOS are incompatible
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Teach Git to detect remote working directories on macOS and mark them as incompatible with FSMonitor.
With this, git fsmonitor--daemon run will error out with a message like it does for bare repos.
Client commands, like git status, will not attempt to start the daemon.
--- compat/fsmonitor/fsm-settings-darwin.c | 66 ++++++++++++++++++++++++++ fsmonitor-settings.c | 12 +++++ fsmonitor-settings.h | 2 + 3 files changed, 80 insertions(+)
compat/fsmonitor/fsm-settings-darwin.c +66 -0
@@ -2,8 +2,74 @@#include "config.h" #include "repository.h" #include "fsmonitor-settings.h" +#include "fsmonitor.h" +#include <sys/param.h> +#include <sys/mount.h> + +/* +* Remote working directories are problematic for FSMonitor. +* +* The underlying file system on the server machine and/or the remote +* mount type (NFS, SAMBA, etc.) dictates whether notification events +* are available at all to remote client machines. +* +* Kernel differences between the server and client machines also +* dictate the how (buffering, frequency, de-dup) the events are +* delivered to client machine processes. +* +* A client machine (such as a laptop) may choose to suspend/resume +* and it is unclear (without lots of testing) whether the watcher can +* resync after a resume. We might be able to treat this as a normal +* "events were dropped by the kernel" event and do our normal "flush +* and resync" --or-- we might need to close the existing (zombie?) +* notification fd and create a new one. +* +* In theory, the above issues need to be addressed whether we are +* using the Hook or IPC API. +* +* For the builtin FSMonitor, we create the Unix domain socket for the +* IPC in the .git directory. If the working directory is remote, +* then the socket will be created on the remote file system. This +* can fail if the remote file system does not support UDS file types +* (e.g. smbfs to a Windows server) or if the remote kernel does not +* allow a non-local process to bind() the socket. (These problems +* could be fixed by moving the UDS out of the .git directory and to a +* well-known local directory on the client machine, but care should +* be taken to ensure that $HOME is actually local and not a managed +* file share.) +* +* So (for now at least), mark remote working directories as +* incompatible. +*/ +static enum fsmonitor_reason check_remote (struct repository *r) +{ +struct statfs fs; + +if (statfs (r->worktree, &fs) ==-1 ) { +int saved_errno = errno; +trace_printf_key (&trace_fsmonitor,"statfs('%s') failed: %s" , + r->worktree,strerror (saved_errno)); + errno = saved_errno; +return FSMONITOR_REASON_ERROR ; + } + +trace_printf_key (&trace_fsmonitor, +"statfs('%s') [type 0x%08x][flags 0x%08x] '%s'" , + r->worktree, fs.f_type, fs.f_flags, fs.f_fstypename); + +if (!(fs.f_flags &MNT_LOCAL )) +return FSMONITOR_REASON_REMOTE ; + +return FSMONITOR_REASON_OK ; +}enum fsmonitor_reason fsm_os__incompatible (struct repository *r) { +enum fsmonitor_reason reason; + + reason =check_remote (r); +if (reason !=FSMONITOR_REASON_OK ) +return reason; +return FSMONITOR_REASON_OK ; }
fsmonitor-settings.c +12 -0
@@ -208,6 +208,18 @@ char *fsm_settings__get_incompatible_msg(const struct repository *r, xgetcwd()); goto done; +case FSMONITOR_REASON_ERROR : +strbuf_addf (&msg, +_ ("repository '%s' is incompatible with fsmonitor due to errors" ), + r->worktree); + goto done; + +case FSMONITOR_REASON_REMOTE : +strbuf_addf (&msg, +_ ("remote repository '%s' is incompatible with fsmonitor" ), + r->worktree); + goto done; +case FSMONITOR_REASON_VFS4GIT : strbuf_addf(&msg, _("virtual repository '%s' is incompatible with fsmonitor"),
fsmonitor-settings.h +2 -0
@@ -17,6 +17,8 @@ enum fsmonitor_reason {FSMONITOR_REASON_UNTESTED =0 ,FSMONITOR_REASON_OK ,/* no incompatibility or when disabled */ FSMONITOR_REASON_BARE , +FSMONITOR_REASON_ERROR ,/* FS error probing for compatibility */ +FSMONITOR_REASON_REMOTE , FSMONITOR_REASON_VFS4GIT, /* VFS for Git virtualization */ };
Signature · 1 line
gitgitgadget
PATCH v7 9/30 fsmonitor-settings: remote repos on Windows are incompatible
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Teach Git to detect remote working directories on Windows and mark them as incompatible with FSMonitor.
With this git fsmonitor--daemon run will error out with a message like it does for bare repos.
Client commands, such as git status, will not attempt to start the daemon.
--- compat/fsmonitor/fsm-settings-win32.c | 102 ++++++++++++++++++++++++++ 1 file changed, 102 insertions(+)
compat/fsmonitor/fsm-settings-win32.c +102 -0
@@ -2,6 +2,7 @@#include "config.h" #include "repository.h" #include "fsmonitor-settings.h" +#include "fsmonitor.h" /* * VFS for Git is incompatible with FSMonitor.
@@ -23,6 +24,103 @@ static enum fsmonitor_reason check_vfs4git(struct repository *r)return FSMONITOR_REASON_OK ; } +/* +* Remote working directories are problematic for FSMonitor. +* +* The underlying file system on the server machine and/or the remote +* mount type dictates whether notification events are available at +* all to remote client machines. +* +* Kernel differences between the server and client machines also +* dictate the how (buffering, frequency, de-dup) the events are +* delivered to client machine processes. +* +* A client machine (such as a laptop) may choose to suspend/resume +* and it is unclear (without lots of testing) whether the watcher can +* resync after a resume. We might be able to treat this as a normal +* "events were dropped by the kernel" event and do our normal "flush +* and resync" --or-- we might need to close the existing (zombie?) +* notification fd and create a new one. +* +* In theory, the above issues need to be addressed whether we are +* using the Hook or IPC API. +* +* So (for now at least), mark remote working directories as +* incompatible. +* +* Notes for testing: +* +* (a) Windows allows a network share to be mapped to a drive letter. +* (This is the normal method to access it.) +* +* $ NET USE Z: \\server\share +* $ git -C Z:/repo status +* +* (b) Windows allows a network share to be referenced WITHOUT mapping +* it to drive letter. +* +* $ NET USE \\server\share\dir +* $ git -C //server/share/repo status +* +* (c) Windows allows "SUBST" to create a fake drive mapping to an +* arbitrary path (which may be remote) +* +* $ SUBST Q: Z:\repo +* $ git -C Q:/ status +* +* (d) Windows allows a directory symlink to be created on a local +* file system that points to a remote repo. +* +* $ mklink /d ./link //server/share/repo +* $ git -C ./link status +*/ +static enum fsmonitor_reason check_remote (struct repository *r) +{ +wchar_t wpath[MAX_PATH ]; +wchar_t wfullpath[MAX_PATH ]; +size_t wlen; +UINT driveType; + +/* +* Do everything in wide chars because the drive letter might be +* a multi-byte sequence. See win32_has_dos_drive_prefix(). +*/ +if (xutftowcs_path (wpath, r->worktree) <0 ) +return FSMONITOR_REASON_ERROR ; + +/* +* GetDriveTypeW() requires a final slash. We assume that the +* worktree pathname points to an actual directory. +*/ + wlen =wcslen (wpath); +if (wpath[wlen -1 ] !=L'\\' && wpath[wlen -1 ] !=L'/' ) { + wpath[wlen++] =L'\\' ; + wpath[wlen] =0 ; + } + +/* +* Normalize the path. If nothing else, this converts forward +* slashes to backslashes. This is essential to get GetDriveTypeW() +* correctly handle some UNC "\\server\share\..." paths. +*/ +if (!GetFullPathNameW (wpath,MAX_PATH , wfullpath,NULL )) +return FSMONITOR_REASON_ERROR ; + + driveType =GetDriveTypeW (wfullpath); +trace_printf_key (&trace_fsmonitor, +"DriveType '%s' L'%ls' (%u)" , + r->worktree, wfullpath, driveType); + +if (driveType ==DRIVE_REMOTE ) { +trace_printf_key (&trace_fsmonitor, +"check_remote('%s') true" , + r->worktree); +return FSMONITOR_REASON_REMOTE ; + } + +return FSMONITOR_REASON_OK ; +} +enum fsmonitor_reason fsm_os__incompatible (struct repository *r) {enum fsmonitor_reason reason;
@@ -31,5 +129,9 @@ enum fsmonitor_reason fsm_os__incompatible(struct repository *r)if (reason !=FSMONITOR_REASON_OK )return reason; + reason =check_remote (r); +if (reason !=FSMONITOR_REASON_OK ) +return reason; +return FSMONITOR_REASON_OK ; }
Signature · 1 line
gitgitgadget
PATCH v7 10/30 fsmonitor-settings: NTFS and FAT32 on MacOS are incompatible
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
On MacOS mark repos on NTFS or FAT32 volumes as incompatible.
The builtin FSMonitor used Unix domain sockets on MacOS for IPC with clients. These sockets are kept in the .git directory. Unix sockets are not supported by NTFS and FAT32, so the daemon cannot start up.
Test for this during our compatibility checking so that client commands do not keep trying to start the daemon.
--- compat/fsmonitor/fsm-settings-darwin.c | 20 +++++++++++++++++--- fsmonitor-settings.c | 6 ++++++ fsmonitor-settings.h | 1 + 3 files changed, 24 insertions(+), 3 deletions(-)
compat/fsmonitor/fsm-settings-darwin.c +17 -3
@@ -7,7 +7,7 @@#include <sys/mount.h> /* -* Remote working directories are problematic for FSMonitor. +* [1] Remote working directories are problematic for FSMonitor. * * The underlying file system on the server machine and/or the remote * mount type (NFS, SAMBA, etc.) dictates whether notification events
@@ -40,8 +40,16 @@* * So (for now at least), mark remote working directories as * incompatible. +* +* +* [2] FAT32 and NTFS working directories are problematic too. +* +* The builtin FSMonitor uses a Unix domain socket in the .git +* directory for IPC. These Windows drive formats do not support +* Unix domain sockets, so mark them as incompatible for the daemon. +* */ -static enum fsmonitor_reason check_remote (struct repository *r) +static enum fsmonitor_reason check_volume (struct repository *r) {struct statfs fs;
@@ -60,6 +68,12 @@ static enum fsmonitor_reason check_remote(struct repository *r)if (!(fs.f_flags &MNT_LOCAL ))return FSMONITOR_REASON_REMOTE ; +if (!strcmp (fs.f_fstypename,"msdos" ))/* aka FAT32 */ +return FSMONITOR_REASON_NOSOCKETS ; + +if (!strcmp (fs.f_fstypename,"ntfs" )) +return FSMONITOR_REASON_NOSOCKETS ; +return FSMONITOR_REASON_OK ; }
@@ -67,7 +81,7 @@ enum fsmonitor_reason fsm_os__incompatible(struct repository *r) {enum fsmonitor_reason reason; - reason =check_remote (r); + reason =check_volume (r);if (reason !=FSMONITOR_REASON_OK )return reason;
fsmonitor-settings.c +6 -0
@@ -225,6 +225,12 @@ char *fsm_settings__get_incompatible_msg(const struct repository *r,_ ("virtual repository '%s' is incompatible with fsmonitor" ), r->worktree); goto done; + +case FSMONITOR_REASON_NOSOCKETS : +strbuf_addf (&msg, +_ ("repository '%s' is incompatible with fsmonitor due to lack of Unix sockets" ), + r->worktree); + goto done; } BUG("Unhandled case in fsm_settings__get_incompatible_msg: '%d'",
fsmonitor-settings.h +1 -0
@@ -20,6 +20,7 @@ enum fsmonitor_reason {FSMONITOR_REASON_ERROR ,/* FS error probing for compatibility */ FSMONITOR_REASON_REMOTE ,FSMONITOR_REASON_VFS4GIT ,/* VFS for Git virtualization */ + FSMONITOR_REASON_NOSOCKETS, /* NTFS,FAT32 do not support Unix sockets */ };void fsm_settings__set_ipc (struct repository *r);
Signature · 1 line
gitgitgadget
PATCH v7 11/30 unpack-trees: initialize fsmonitor_has_run_once in o->result
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Initialize o->result.fsmonitor_has_run_once based upon value in o->src_index->fsmonitor_has_run_once to prevent a second fsmonitor query during the tree traversal and possibly getting a skewed view of the working directory.
The checkout code has already talked to the fsmonitor and the traversal is updating the index as it traverses, so there is no need to query the fsmonitor.
--- unpack-trees.c | 1 + 1 file changed, 1 insertion(+)
unpack-trees.c +1 -0
@@ -1772,6 +1772,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options o->result.fsmonitor_last_update =xstrdup_or_null (o->src_index->fsmonitor_last_update); + o->result.fsmonitor_has_run_once = o->src_index->fsmonitor_has_run_once;/* * Sparse checkout loop #1: set NEW_SKIP_WORKTREE on existing entries
Signature · 1 line
gitgitgadget
PATCH v7 12/30 fsm-listen-darwin: ignore FSEvents caused by xattr changes on macOS
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Ignore FSEvents resulting from xattr changes. Git does not care about xattr's or changes to xattr's, so don't waste time collecting these events in the daemon nor transmitting them to clients.
Various security tools add xattrs to files and/or directories, such as to mark them as having been downloaded. We should ignore these events since it doesn't affect the content of the file/directory or the normal meta-data that Git cares about.
--- compat/fsmonitor/fsm-listen-darwin.c | 34 +++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-)
compat/fsmonitor/fsm-listen-darwin.c +33 -1
@@ -100,7 +100,7 @@ static void log_flags_set(const char *path, const FSEventStreamEventFlags flag)if (flag & kFSEventStreamEventFlagItemCloned)strbuf_addstr (&msg,"ItemCloned|" ); -trace_printf_key (&trace_fsmonitor,"fsevent: '%s', flags=%u %s" , +trace_printf_key (&trace_fsmonitor,"fsevent: '%s', flags=0x%x %s" , path, flag, msg.buf);strbuf_release (&msg);
@@ -125,6 +125,31 @@ static int ef_is_dropped(const FSEventStreamEventFlags ef) ef & kFSEventStreamEventFlagUserDropped); } +/* +* If an `xattr` change is the only reason we received this event, +* then silently ignore it. Git doesn't care about xattr's. We +* have to be careful here because the kernel can combine multiple +* events for a single path. And because events always have certain +* bits set, such as `ItemIsFile` or `ItemIsDir`. +* +* Return 1 if we should ignore it. +*/ +static int ef_ignore_xattr (const FSEventStreamEventFlags ef) +{ +static const FSEventStreamEventFlags mask = + kFSEventStreamEventFlagItemChangeOwner | + kFSEventStreamEventFlagItemCreated | + kFSEventStreamEventFlagItemFinderInfoMod | + kFSEventStreamEventFlagItemInodeMetaMod | + kFSEventStreamEventFlagItemModified | + kFSEventStreamEventFlagItemRemoved | + kFSEventStreamEventFlagItemRenamed | + kFSEventStreamEventFlagItemXattrMod | + kFSEventStreamEventFlagItemCloned; + +return ((ef & mask) == kFSEventStreamEventFlagItemXattrMod); +} + static void fsevent_callback(ConstFSEventStreamRef streamRef, void *ctx, size_t num_of_events,
@@ -190,6 +215,13 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef,continue ; } +if (ef_ignore_xattr (event_flags[k])) { +trace_printf_key (&trace_fsmonitor, +"ignore-xattr: '%s', flags=0x%x" , + path_k, event_flags[k]); +continue ; + } +switch (fsmonitor_classify_path_absolute (state, path_k)) {case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX :
Signature · 1 line
gitgitgadget
PATCH v7 13/30 fsmonitor--daemon: cd out of worktree root
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Teach the fsmonitor--daemon to CD outside of the worktree before starting up.
The common Git startup mechanism causes the CWD of the daemon process to be in the root of the worktree. On Windows, this causes the daemon process to hold a locked handle on the CWD and prevents other processes from moving or deleting the worktree while the daemon is running.
CD to HOME before entering main event loops.
--- builtin/fsmonitor--daemon.c | 32 +++++++++++++++++++++++++++-- compat/fsmonitor/fsm-listen-win32.c | 22 ++++++++++++++------ fsmonitor--daemon.h | 1 + 3 files changed, 47 insertions(+), 8 deletions(-)
builtin/fsmonitor--daemon.c +30 -2
@@ -1181,11 +1181,11 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)* before we need it. */ if (ipc_server_run_async (&state->ipc_server_data, -fsmonitor_ipc__get_path (), &ipc_opts, + state->path_ipc.buf, &ipc_opts, handle_client, state))return error_errno (_ ("could not start IPC thread pool on '%s'" ), -fsmonitor_ipc__get_path ()); + state->path_ipc.buf);/* * Start the fsmonitor listener thread to collect filesystem
@@ -1220,6 +1220,7 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)static int fsmonitor_run_daemon (void ) {struct fsmonitor_daemon_state state; +const char *home;int err;memset (&state,0 ,sizeof (state));
@@ -1289,6 +1290,15 @@ static int fsmonitor_run_daemon(void)strbuf_addch (&state.path_cookie_prefix,'/' ); +/* +* We create a named-pipe or unix domain socket inside of the +* ".git" directory. (Well, on Windows, we base our named +* pipe in the NPFS on the absolute path of the git +* directory.) +*/ +strbuf_init (&state.path_ipc,0 ); +strbuf_addstr (&state.path_ipc,absolute_path (fsmonitor_ipc__get_path ())); +/* * Confirm that we can create platform-specific resources for the * filesystem listener before we bother starting all the threads.
@@ -1298,6 +1308,23 @@ static int fsmonitor_run_daemon(void) goto done; } +/* +* CD out of the worktree root directory. +* +* The common Git startup mechanism causes our CWD to be the +* root of the worktree. On Windows, this causes our process +* to hold a locked handle on the CWD. This prevents the +* worktree from being moved or deleted while the daemon is +* running. +* +* We assume that our FS and IPC listener threads have either +* opened all of the handles that they need or will do +* everything using absolute paths. +*/ + home =getenv ("HOME" ); +if (home && *home &&chdir (home)) +die_errno (_ ("could not cd home '%s'" ), home); + err =fsmonitor_run_daemon_1 (&state); done:
@@ -1310,6 +1337,7 @@ done:strbuf_release (&state.path_worktree_watch);strbuf_release (&state.path_gitdir_watch);strbuf_release (&state.path_cookie_prefix); +strbuf_release (&state.path_ipc);return err; }
compat/fsmonitor/fsm-listen-win32.c +16 -6
@@ -424,12 +424,22 @@ static int recv_rdcw_watch(struct one_watch *watch) }/* -* NEEDSWORK: If an external <gitdir> is deleted, the above -* returns an error. I'm not sure that there's anything that -* we can do here other than failing -- the <worktree>/.git -* link file would be broken anyway. We might try to check -* for that and return a better error message, but I'm not -* sure it is worth it. +* GetOverlappedResult() fails if the watched directory is +* deleted while we were waiting for an overlapped IO to +* complete. The documentation did not list specific errors, +* but I observed ERROR_ACCESS_DENIED (0x05) errors during +* testing. +* +* Note that we only get notificaiton events for events +* *within* the directory, not *on* the directory itself. +* (These might be properies of the parent directory, for +* example). +* +* NEEDSWORK: We might try to check for the deleted directory +* case and return a better error message, but I'm not sure it +* is worth it. +* +* Shutdown if we get any error. */ error(_("GetOverlappedResult failed on '%s' [GLE %ld]"),
fsmonitor--daemon.h +1 -0
@@ -54,6 +54,7 @@ struct fsmonitor_daemon_state {struct fsmonitor_daemon_backend_data *backend_data;struct ipc_server_data *ipc_server_data; +struct strbuf path_ipc; };/*
Signature · 1 line
gitgitgadget
PATCH v7 14/30 fsmonitor--daemon: prepare for adding health thread
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Refactor daemon thread startup to make it easier to start a third thread class to monitor the health of the daemon.
--- builtin/fsmonitor--daemon.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-)
builtin/fsmonitor--daemon.c +20 -7
@@ -1174,6 +1174,8 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)*/ .uds_disallow_chdir = 0 }; +int listener_started =0 ; +int err =0 ;/* * Start the IPC thread pool before the we've started the file
@@ -1194,15 +1196,20 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)if (pthread_create (&state->listener_thread,NULL , fsm_listen__thread_proc, state) <0 ) {ipc_server_stop_async (state->ipc_server_data); -ipc_server_await (state->ipc_server_data); - -return error (_ ("could not start fsmonitor listener thread" )); + err =error (_ ("could not start fsmonitor listener thread" )); + goto cleanup; } + listener_started =1 ;/* * The daemon is now fully functional in background threads. +* Our primary thread should now just wait while the threads +* do all the work. +*/ +cleanup: +/* * Wait for the IPC thread pool to shutdown (whether by client -* request or from filesystem activity). +* request, from filesystem activity, or an error). */ ipc_server_await (state->ipc_server_data);
@@ -1211,10 +1218,16 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)* event from the IPC thread pool, but it doesn't hurt to tell * it again. And wait for it to shutdown. */ -fsm_listen__stop_async (state); -pthread_join (state->listener_thread,NULL ); +if (listener_started) { +fsm_listen__stop_async (state); +pthread_join (state->listener_thread,NULL ); + } -return state->error_code; +if (err) +return err; +if (state->error_code) +return state->error_code; +return 0 ; }static int fsmonitor_run_daemon (void )
Signature · 1 line
gitgitgadget
PATCH v7 15/30 fsmonitor--daemon: rename listener thread related variables
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Rename platform-specific listener thread related variables and data types as we prepare to add another backend thread type.
[] struct fsmonitor_daemon_backend_data becomes struct fsm_listen_data [] state->backend_data becomes state->listen_data
[] state->error_code becomes state->listen_error_code
--- builtin/fsmonitor--daemon.c | 6 +++--- compat/fsmonitor/fsm-listen-darwin.c | 30 ++++++++++++++-------------- compat/fsmonitor/fsm-listen-win32.c | 28 +++++++++++++------------- compat/fsmonitor/fsm-listen.h | 2 +- fsmonitor--daemon.h | 6 +++--- 5 files changed, 36 insertions(+), 36 deletions(-)
builtin/fsmonitor--daemon.c +3 -3
@@ -1225,8 +1225,8 @@ cleanup:if (err)return err; -if (state->error_code) -return state->error_code; +if (state->listen_error_code) +return state->listen_error_code;return 0 ; }
@@ -1241,7 +1241,7 @@ static int fsmonitor_run_daemon(void)hashmap_init (&state.cookies, cookies_cmp,NULL ,0 );pthread_mutex_init (&state.main_lock,NULL );pthread_cond_init (&state.cookies_cond,NULL ); - state.error_code =0 ; + state.listen_error_code =0 ; state.current_token_data =fsmonitor_new_token_data ();/* Prepare to (recursively) watch the <worktree-root> directory. */
compat/fsmonitor/fsm-listen-darwin.c +15 -15
@@ -27,7 +27,7 @@#include "fsm-listen.h" #include "fsmonitor--daemon.h" -struct fsmonitor_daemon_backend_data +struct fsm_listen_data {CFStringRef cfsr_worktree_path;CFStringRef cfsr_gitdir_path;
@@ -158,7 +158,7 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef, const FSEventStreamEventId event_ids[]) {struct fsmonitor_daemon_state *state = ctx; -struct fsmonitor_daemon_backend_data *data = state->backend_data; +struct fsm_listen_data *data = state->listen_data;char **paths = (char **)event_paths;struct fsmonitor_batch *batch =NULL ;struct string_list cookie_list =STRING_LIST_INIT_DUP ;
@@ -350,11 +350,11 @@ int fsm_listen__ctor(struct fsmonitor_daemon_state *state)NULL , NULL }; -struct fsmonitor_daemon_backend_data *data; +struct fsm_listen_data *data;const void *dir_array[2 ];CALLOC_ARRAY (data,1 ); - state->backend_data = data; + state->listen_data = data; data->cfsr_worktree_path =CFStringCreateWithCString (NULL , state->path_worktree_watch.buf, kCFStringEncodingUTF8);
@@ -386,18 +386,18 @@ int fsm_listen__ctor(struct fsmonitor_daemon_state *state) failed:error (_ ("Unable to create FSEventStream." )); -FREE_AND_NULL (state->backend_data); +FREE_AND_NULL (state->listen_data);return -1 ; }void fsm_listen__dtor (struct fsmonitor_daemon_state *state) { -struct fsmonitor_daemon_backend_data *data; +struct fsm_listen_data *data; -if (!state || !state->backend_data) +if (!state || !state->listen_data)return ; - data = state->backend_data; + data = state->listen_data;if (data->stream) {if (data->stream_started)
@@ -407,14 +407,14 @@ void fsm_listen__dtor(struct fsmonitor_daemon_state *state)FSEventStreamRelease (data->stream); } -FREE_AND_NULL (state->backend_data); +FREE_AND_NULL (state->listen_data); }void fsm_listen__stop_async (struct fsmonitor_daemon_state *state) { -struct fsmonitor_daemon_backend_data *data; +struct fsm_listen_data *data; - data = state->backend_data; + data = state->listen_data; data->shutdown_style =SHUTDOWN_EVENT ;CFRunLoopStop (data->rl);
@@ -422,9 +422,9 @@ void fsm_listen__stop_async(struct fsmonitor_daemon_state *state)void fsm_listen__loop (struct fsmonitor_daemon_state *state) { -struct fsmonitor_daemon_backend_data *data; +struct fsm_listen_data *data; - data = state->backend_data; + data = state->listen_data; data->rl =CFRunLoopGetCurrent ();
@@ -441,7 +441,7 @@ void fsm_listen__loop(struct fsmonitor_daemon_state *state)switch (data->shutdown_style) {case FORCE_ERROR_STOP : - state->error_code =-1 ; + state->listen_error_code =-1 ;/* fall thru */ case FORCE_SHUTDOWN :ipc_server_stop_async (state->ipc_server_data);
@@ -453,7 +453,7 @@ void fsm_listen__loop(struct fsmonitor_daemon_state *state)return ; force_error_stop_without_loop: - state->error_code =-1 ; + state->listen_error_code =-1 ;ipc_server_stop_async (state->ipc_server_data);return ; }
compat/fsmonitor/fsm-listen-win32.c +14 -14
@@ -54,7 +54,7 @@ struct one_watchwchar_t dotgit_shortname[16 ];/* for 8.3 name */ }; -struct fsmonitor_daemon_backend_data +struct fsm_listen_data {struct one_watch *watch_worktree;struct one_watch *watch_gitdir;
@@ -284,7 +284,7 @@ static enum get_relative_result get_relative_longname(void fsm_listen__stop_async (struct fsmonitor_daemon_state *state) { -SetEvent (state->backend_data->hListener[LISTENER_SHUTDOWN ]); +SetEvent (state->listen_data->hListener[LISTENER_SHUTDOWN ]); } static struct one_watch *create_watch(struct fsmonitor_daemon_state *state,
@@ -359,7 +359,7 @@ static void destroy_watch(struct one_watch *watch)free (watch); } -static int start_rdcw_watch (struct fsmonitor_daemon_backend_data *data, +static int start_rdcw_watch (struct fsm_listen_data *data,struct one_watch *watch) { DWORD dwNotifyFilter =
@@ -538,7 +538,7 @@ static int process_1_worktree_event(*/ static int process_worktree_events (struct fsmonitor_daemon_state *state) { -struct fsmonitor_daemon_backend_data *data = state->backend_data; +struct fsm_listen_data *data = state->listen_data;struct one_watch *watch = data->watch_worktree;struct strbuf path =STRBUF_INIT ;struct string_list cookie_list =STRING_LIST_INIT_DUP ;
@@ -669,7 +669,7 @@ force_shutdown:*/ static int process_gitdir_events (struct fsmonitor_daemon_state *state) { -struct fsmonitor_daemon_backend_data *data = state->backend_data; +struct fsm_listen_data *data = state->listen_data;struct one_watch *watch = data->watch_gitdir;struct strbuf path =STRBUF_INIT ;struct string_list cookie_list =STRING_LIST_INIT_DUP ;
@@ -727,11 +727,11 @@ skip_this_path:void fsm_listen__loop (struct fsmonitor_daemon_state *state) { -struct fsmonitor_daemon_backend_data *data = state->backend_data; +struct fsm_listen_data *data = state->listen_data;DWORD dwWait;int result; - state->error_code =0 ; + state->listen_error_code =0 ;if (start_rdcw_watch (data, data->watch_worktree) ==-1 ) goto force_error_stop;
@@ -796,7 +796,7 @@ void fsm_listen__loop(struct fsmonitor_daemon_state *state) } force_error_stop: - state->error_code =-1 ; + state->listen_error_code =-1 ; force_shutdown:/*
@@ -813,7 +813,7 @@ clean_shutdown:int fsm_listen__ctor (struct fsmonitor_daemon_state *state) { -struct fsmonitor_daemon_backend_data *data; +struct fsm_listen_data *data;CALLOC_ARRAY (data,1 );
@@ -846,7 +846,7 @@ int fsm_listen__ctor(struct fsmonitor_daemon_state *state) data->nr_listener_handles++; } - state->backend_data = data; + state->listen_data = data;return 0 ; failed:
@@ -859,16 +859,16 @@ failed:void fsm_listen__dtor (struct fsmonitor_daemon_state *state) { -struct fsmonitor_daemon_backend_data *data; +struct fsm_listen_data *data; -if (!state || !state->backend_data) +if (!state || !state->listen_data)return ; - data = state->backend_data; + data = state->listen_data;CloseHandle (data->hEventShutdown);destroy_watch (data->watch_worktree);destroy_watch (data->watch_gitdir); -FREE_AND_NULL (state->backend_data); +FREE_AND_NULL (state->listen_data); }
compat/fsmonitor/fsm-listen.h +1 -1
@@ -33,7 +33,7 @@ void fsm_listen__dtor(struct fsmonitor_daemon_state *state);* do so if the listener thread receives a normal shutdown signal from * the IPC layer.) * -* It should set `state->error_code` to -1 if the daemon should exit +* It should set `state->listen_error_code` to -1 if the daemon should exit * with an error. */ void fsm_listen__loop (struct fsmonitor_daemon_state *state);
fsmonitor--daemon.h +3 -3
@@ -33,7 +33,7 @@ void fsmonitor_batch__free_list(struct fsmonitor_batch *batch);*/ void fsmonitor_batch__add_path (struct fsmonitor_batch *batch,const char *path); -struct fsmonitor_daemon_backend_data ;/* opaque platform-specific data */ +struct fsm_listen_data ;/* opaque platform-specific data for listener thread */ struct fsmonitor_daemon_state {pthread_t listener_thread;
@@ -50,8 +50,8 @@ struct fsmonitor_daemon_state {int cookie_seq;struct hashmap cookies; -int error_code; -struct fsmonitor_daemon_backend_data *backend_data; +int listen_error_code; +struct fsm_listen_data *listen_data;struct ipc_server_data *ipc_server_data;struct strbuf path_ipc;
Signature · 1 line
gitgitgadget
PATCH v7 16/30 fsmonitor--daemon: stub in health thread
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create another thread to watch over the daemon process and automatically shut it down if necessary.
This commit creates the basic framework for a "health" thread to monitor the daemon and/or the file system. Later commits will add platform-specific code to do the actual work.
The "health" thread is intended to monitor conditions that would be difficult to track inside the IPC thread pool and/or the file system listener threads. For example, when there are file system events outside of the watched worktree root or if we want to have an idle-timeout auto-shutdown feature.
This commit creates the health thread itself, defines the thread-proc and sets up the thread's event loop. It integrates this new thread into the existing IPC and Listener thread models.
This commit defines the API to the platform-specific code where all of the monitoring will actually happen.
The platform-specific code for MacOS is just stubs. Meaning that the health thread will immediately exit on MacOS, but that is OK and expected. Future work can define MacOS-specific monitoring.
The platform-specific code for Windows sets up enough of the WaitForMultipleObjects() machinery to watch for system and/or custom events. Currently, the set of wait handles only includes our custom shutdown event (sent from our other theads). Later commits in this series will extend the set of wait handles to monitor other conditions.
--- Makefile | 6 ++- builtin/fsmonitor--daemon.c | 39 +++++++++++++++ compat/fsmonitor/fsm-health-darwin.c | 24 ++++++++++ compat/fsmonitor/fsm-health-win32.c | 72 ++++++++++++++++++++++++++++ compat/fsmonitor/fsm-health.h | 47 ++++++++++++++++++ contrib/buildsystems/CMakeLists.txt | 2 + fsmonitor--daemon.h | 4 ++ 7 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 compat/fsmonitor/fsm-health-darwin.c create mode 100644 compat/fsmonitor/fsm-health-win32.c create mode 100644 compat/fsmonitor/fsm-health.h
Makefile +4 -2
@@ -472,8 +472,9 @@ all:: # # If your platform supports a built-in fsmonitor backend, set # FSMONITOR_DAEMON_BACKEND to the "<name>" of the corresponding -# `compat/fsmonitor/fsm-listen-<name>.c` that implements the -# `fsm_listen__*()` routines. +# `compat/fsmonitor/fsm-listen-<name>.c` and +# `compat/fsmonitor/fsm-health-<name>.c` files +# that implement the `fsm_listen__*()` and `fsm_health__*()` routines. # # If your platform has OS-specific ways to tell if a repo is incompatible with # fsmonitor (whether the hook or IPC daemon version), set FSMONITOR_OS_SETTINGS
@@ -1982,6 +1983,7 @@ endif ifdef FSMONITOR_DAEMON_BACKEND COMPAT_CFLAGS += -DHAVE_FSMONITOR_DAEMON_BACKEND COMPAT_OBJS += compat/fsmonitor/fsm-listen-$(FSMONITOR_DAEMON_BACKEND).o + COMPAT_OBJS += compat/fsmonitor/fsm-health-$(FSMONITOR_DAEMON_BACKEND).o endif ifdef FSMONITOR_OS_SETTINGS
builtin/fsmonitor--daemon.c +39 -0
@@ -3,6 +3,7 @@#include "parse-options.h" #include "fsmonitor.h" #include "fsmonitor-ipc.h" +#include "compat/fsmonitor/fsm-health.h" #include "compat/fsmonitor/fsm-listen.h" #include "fsmonitor--daemon.h" #include "simple-ipc.h"
@@ -1136,6 +1137,18 @@ void fsmonitor_publish(struct fsmonitor_daemon_state *state,pthread_mutex_unlock (&state->main_lock); } +static void *fsm_health__thread_proc (void *_state) +{ +struct fsmonitor_daemon_state *state = _state; + +trace2_thread_start ("fsm-health" ); + +fsm_health__loop (state); + +trace2_thread_exit (); +return NULL ; +} +static void *fsm_listen__thread_proc (void *_state) {struct fsmonitor_daemon_state *state = _state;
@@ -1174,6 +1187,7 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)*/ .uds_disallow_chdir = 0 }; +int health_started =0 ;int listener_started =0 ;int err =0 ;
@@ -1201,6 +1215,17 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state) } listener_started =1 ; +/* +* Start the health thread to watch over our process. +*/ +if (pthread_create (&state->health_thread,NULL , + fsm_health__thread_proc, state) <0 ) { +ipc_server_stop_async (state->ipc_server_data); + err =error (_ ("could not start fsmonitor health thread" )); + goto cleanup; + } + health_started =1 ; +/* * The daemon is now fully functional in background threads. * Our primary thread should now just wait while the threads
@@ -1223,10 +1248,17 @@ cleanup:pthread_join (state->listener_thread,NULL ); } +if (health_started) { +fsm_health__stop_async (state); +pthread_join (state->health_thread,NULL ); + } +if (err)return err;if (state->listen_error_code)return state->listen_error_code; +if (state->health_error_code) +return state->health_error_code;return 0 ; }
@@ -1242,6 +1274,7 @@ static int fsmonitor_run_daemon(void)pthread_mutex_init (&state.main_lock,NULL );pthread_cond_init (&state.cookies_cond,NULL ); state.listen_error_code =0 ; + state.health_error_code =0 ; state.current_token_data =fsmonitor_new_token_data ();/* Prepare to (recursively) watch the <worktree-root> directory. */
@@ -1321,6 +1354,11 @@ static int fsmonitor_run_daemon(void) goto done; } +if (fsm_health__ctor (&state)) { + err =error (_ ("could not initialize health thread" )); + goto done; + } +/* * CD out of the worktree root directory. *
@@ -1344,6 +1382,7 @@ done:pthread_cond_destroy (&state.cookies_cond);pthread_mutex_destroy (&state.main_lock);fsm_listen__dtor (&state); +fsm_health__dtor (&state);ipc_server_free (state.ipc_server_data);
compat/fsmonitor/fsm-health-darwin.c new file +24 -0
@@ -0,0 +1,24 @@ +#include "cache.h" +#include "config.h" +#include "fsmonitor.h" +#include "fsm-health.h" +#include "fsmonitor--daemon.h" + +int fsm_health__ctor (struct fsmonitor_daemon_state *state) +{ +return 0 ; +} + +void fsm_health__dtor (struct fsmonitor_daemon_state *state) +{ +return ; +} + +void fsm_health__loop (struct fsmonitor_daemon_state *state) +{ +return ; +} + +void fsm_health__stop_async (struct fsmonitor_daemon_state *state) +{ +}
compat/fsmonitor/fsm-health-win32.c new file +72 -0
72 lines not on this page · raw message
compat/fsmonitor/fsm-health.h new file +47 -0
47 lines not on this page · raw message
contrib/buildsystems/CMakeLists.txt +2 -0
@@ -289,12 +289,14 @@ if(SUPPORTS_SIMPLE_IPC) if(CMAKE_SYSTEM_NAME STREQUAL "Windows") add_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND) list(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-win32.c) + list(APPEND compat_SOURCES compat/fsmonitor/fsm-health-win32.c) add_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS) list(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-win32.c) elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") add_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND) list(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-darwin.c) + list(APPEND compat_SOURCES compat/fsmonitor/fsm-health-darwin.c) add_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS) list(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-darwin.c)
fsmonitor--daemon.h +4 -0
@@ -34,9 +34,11 @@ void fsmonitor_batch__free_list(struct fsmonitor_batch *batch);void fsmonitor_batch__add_path (struct fsmonitor_batch *batch,const char *path);struct fsm_listen_data ;/* opaque platform-specific data for listener thread */ +struct fsm_health_data ;/* opaque platform-specific data for health thread */ struct fsmonitor_daemon_state {pthread_t listener_thread; +pthread_t health_thread;pthread_mutex_t main_lock;struct strbuf path_worktree_watch;
@@ -51,7 +53,9 @@ struct fsmonitor_daemon_state {struct hashmap cookies;int listen_error_code; +int health_error_code;struct fsm_listen_data *listen_data; +struct fsm_health_data *health_data;struct ipc_server_data *ipc_server_data;struct strbuf path_ipc;
Signature · 1 line
gitgitgadget
PATCH v7 17/30 fsm-health-win32: add polling framework to monitor daemon health
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Extend the Windows version of the "health" thread to periodically inspect the system and shutdown if warranted.
This commit updates the thread's wait loop to use a timeout and defines a (currently empty) table of functions to poll the system.
A later commit will add functions to the table to actually inspect the system.
--- compat/fsmonitor/fsm-health-win32.c | 65 ++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-)
compat/fsmonitor/fsm-health-win32.c +64 -1
@@ -4,6 +4,24 @@#include "fsm-health.h" #include "fsmonitor--daemon.h" +/* +* Every minute wake up and test our health. +*/ +#define WAIT_FREQ_MS (60 * 1000) + +/* +* State machine states for each of the interval functions +* used for polling our health. +*/ +enum interval_fn_ctx { +CTX_INIT =0 , +CTX_TERM , +CTX_TIMER +}; + +typedef int (interval_fn )(struct fsmonitor_daemon_state *state, +enum interval_fn_ctx ctx); +struct fsm_health_data {HANDLE hEventShutdown;
@@ -42,18 +60,61 @@ void fsm_health__dtor(struct fsmonitor_daemon_state *state)FREE_AND_NULL (state->health_data); } +/* +* A table of the polling functions. +*/ +static interval_fn *table[] = { +NULL ,/* must be last */ +}; + +/* +* Call all of the polling functions in the table. +* Shortcut and return first error. +* +* Return 0 if all succeeded. +*/ +static int call_all (struct fsmonitor_daemon_state *state, +enum interval_fn_ctx ctx) +{ +int k; + +for (k =0 ; table[k]; k++) { +int r = table[k](state, ctx); +if (r) +return r; + } + +return 0 ; +} +void fsm_health__loop (struct fsmonitor_daemon_state *state) {struct fsm_health_data *data = state->health_data; +int r; + + r =call_all (state,CTX_INIT ); +if (r <0 ) + goto force_error_stop; +if (r >0 ) + goto force_shutdown;for (;;) {DWORD dwWait =WaitForMultipleObjects (data->nr_handles, data->hHandles, - FALSE,INFINITE ); + FALSE,WAIT_FREQ_MS );if (dwWait ==WAIT_OBJECT_0 +HEALTH_SHUTDOWN ) goto clean_shutdown; +if (dwWait ==WAIT_TIMEOUT ) { + r =call_all (state,CTX_TIMER ); +if (r <0 ) + goto force_error_stop; +if (r >0 ) + goto force_shutdown; +continue ; + } +error (_ ("health thread wait failed [GLE %ld]" ),GetLastError ()); goto force_error_stop;
@@ -61,8 +122,10 @@ void fsm_health__loop(struct fsmonitor_daemon_state *state) force_error_stop: state->health_error_code =-1 ; +force_shutdown:ipc_server_stop_async (state->ipc_server_data); clean_shutdown: +call_all (state,CTX_TERM );return ; }
Signature · 1 line
gitgitgadget
PATCH v7 18/30 fsm-health-win32: force shutdown daemon if worktree root moves
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Force shutdown fsmonitor daemon if the worktree root directory is moved, renamed, or deleted.
Use Windows low-level GetFileInformationByHandle() to get and compare the Windows system unique ID for the directory with a cached version when we started up. This lets us detect the case where someone renames the directory that we are watching and then creates a new directory with the original pathname.
This is important because we are listening to a named pipe for requests and they are stored in the Named Pipe File System (NPFS) which a kernel-resident pseudo filesystem not associated with the actual NTFS directory.
For example, if the daemon was watching "~/foo/", it would have a directory-watch handle on that directory and a named-pipe handle for "//./pipe/...foo". Moving the directory to "~/bar/" does not invalidate the directory handle. (So the daemon would actually be watching "~/bar" but listening on "//./pipe/...foo". If the user then does "git init ~/foo" and causes another daemon to start, the first daemon will still have ownership of the pipe and the second daemon instance will fail to start. "git status" clients in "~/foo" will ask "//./pipe/...foo" about changes and the first daemon instance will tell them about "~/bar".
This commit causes the first daemon to shutdown if the system unique ID for "~/foo" changes (changes from what it was when the daemon started). Shutdown occurs after a periodic poll. After the first daemon exits and releases the lock on the named pipe, subsequent Git commands may cause another daemon to be started on "~/foo". Similarly, a subsequent Git command may cause another daemon to be started on "~/bar".
--- compat/fsmonitor/fsm-health-win32.c | 143 ++++++++++++++++++++++++++++ 1 file changed, 143 insertions(+)
compat/fsmonitor/fsm-health-win32.c +143 -0
@@ -29,8 +29,150 @@ struct fsm_health_dataHANDLE hHandles[1 ];/* the array does not own these handles */ #define HEALTH_SHUTDOWN 0int nr_handles;/* number of active event handles */ + +struct wt_moved + { +wchar_t wpath[MAX_PATH +1 ]; +BY_HANDLE_FILE_INFORMATION bhfi; + } wt_moved; }; +/* +* Lookup the system unique ID for the path. This is as close as +* we get to an inode number, but this also contains volume info, +* so it is a little stronger. +*/ +static int lookup_bhfi (wchar_t *wpath, +BY_HANDLE_FILE_INFORMATION *bhfi) +{ +DWORD desired_access =FILE_LIST_DIRECTORY ; +DWORD share_mode = +FILE_SHARE_WRITE |FILE_SHARE_READ |FILE_SHARE_DELETE ; +HANDLE hDir; + + hDir =CreateFileW (wpath, desired_access, share_mode,NULL , +OPEN_EXISTING ,FILE_FLAG_BACKUP_SEMANTICS ,NULL ); +if (hDir ==INVALID_HANDLE_VALUE ) { +error (_ ("[GLE %ld] health thread could not open '%ls'" ), +GetLastError (), wpath); +return -1 ; + } + +if (!GetFileInformationByHandle (hDir, bhfi)) { +error (_ ("[GLE %ld] health thread getting BHFI for '%ls'" ), +GetLastError (), wpath); +CloseHandle (hDir); +return -1 ; + } + +CloseHandle (hDir); +return 0 ; +} + +/* +* Compare the relevant fields from two system unique IDs. +* We use this to see if two different handles to the same +* path actually refer to the same *instance* of the file +* or directory. +*/ +static int bhfi_eq (const BY_HANDLE_FILE_INFORMATION *bhfi_1, +const BY_HANDLE_FILE_INFORMATION *bhfi_2) +{ +return (bhfi_1->dwVolumeSerialNumber == bhfi_2->dwVolumeSerialNumber && + bhfi_1->nFileIndexHigh == bhfi_2->nFileIndexHigh && + bhfi_1->nFileIndexLow == bhfi_2->nFileIndexLow); +} + +/* +* Shutdown if the original worktree root directory been deleted, +* moved, or renamed? +* +* Since the main thread did a "chdir(getenv($HOME))" and our CWD +* is not in the worktree root directory and because the listener +* thread added FILE_SHARE_DELETE to the watch handle, it is possible +* for the root directory to be moved or deleted while we are still +* watching it. We want to detect that here and force a shutdown. +* +* Granted, a delete MAY cause some operations to fail, such as +* GetOverlappedResult(), but it is not guaranteed. And because +* ReadDirectoryChangesW() only reports on changes *WITHIN* the +* directory, not changes *ON* the directory, our watch will not +* receive a delete event for it. +* +* A move/rename of the worktree root will also not generate an event. +* And since the listener thread already has an open handle, it may +* continue to receive events for events within the directory. +* However, the pathname of the named-pipe was constructed using the +* original location of the worktree root. (Remember named-pipes are +* stored in the NPFS and not in the actual file system.) Clients +* trying to talk to the worktree after the move/rename will not +* reach our daemon process, since we're still listening on the +* pipe with original path. +* +* Furthermore, if the user does something like: +* +* $ mv repo repo.old +* $ git init repo +* +* A new daemon cannot be started in the new instance of "repo" +* because the named-pipe is still being used by the daemon on +* the original instance. +* +* So, detect move/rename/delete and shutdown. This should also +* handle unsafe drive removal. +* +* We use the file system unique ID to distinguish the original +* directory instance from a new instance and force a shutdown +* if the unique ID changes. +* +* Since a worktree move/rename/delete/unmount doesn't happen +* that often (and we can't get an immediate event anyway), we +* use a timeout and periodically poll it. +*/ +static int has_worktree_moved (struct fsmonitor_daemon_state *state, +enum interval_fn_ctx ctx) +{ +struct fsm_health_data *data = state->health_data; +BY_HANDLE_FILE_INFORMATION bhfi; +int r; + +switch (ctx) { +case CTX_TERM : +return 0 ; + +case CTX_INIT : +if (xutftowcs_path (data->wt_moved.wpath, + state->path_worktree_watch.buf) <0 ) { +error (_ ("could not convert to wide characters: '%s'" ), + state->path_worktree_watch.buf); +return -1 ; + } + +/* +* On the first call we lookup the unique sequence ID for +* the worktree root directory. +*/ +return lookup_bhfi (data->wt_moved.wpath, &data->wt_moved.bhfi); + +case CTX_TIMER : + r =lookup_bhfi (data->wt_moved.wpath, &bhfi); +if (r) +return r; +if (!bhfi_eq (&data->wt_moved.bhfi, &bhfi)) { +error (_ ("BHFI changed '%ls'" ), data->wt_moved.wpath); +return -1 ; + } +return 0 ; + +default : +die (_ ("unhandled case in 'has_worktree_moved': %d" ), + (int )ctx); + } + +return 0 ; +} + +int fsm_health__ctor (struct fsmonitor_daemon_state *state) {struct fsm_health_data *data;
@@ -64,6 +206,7 @@ void fsm_health__dtor(struct fsmonitor_daemon_state *state)* A table of the polling functions. */ static interval_fn *table[] = { + has_worktree_moved,NULL ,/* must be last */ };
Signature · 1 line
gitgitgadget
PATCH v7 19/30 fsm-listen-darwin: shutdown daemon if worktree root is moved/renamed
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Teach the listener thread to shutdown the daemon if the spelling of the worktree root directory changes.
--- compat/fsmonitor/fsm-listen-darwin.c | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+)
compat/fsmonitor/fsm-listen-darwin.c +25 -0
@@ -106,6 +106,11 @@ static void log_flags_set(const char *path, const FSEventStreamEventFlags flag)strbuf_release (&msg); } +static int ef_is_root_changed (const FSEventStreamEventFlags ef) +{ +return (ef & kFSEventStreamEventFlagRootChanged); +} +static int ef_is_root_delete (const FSEventStreamEventFlags ef) { return (ef & kFSEventStreamEventFlagItemIsDir &&
@@ -215,6 +220,26 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef,continue ; } +if (ef_is_root_changed (event_flags[k])) { +/* +* The spelling of the pathname of the root directory +* has changed. This includes the name of the root +* directory itself or of any parent directory in the +* path. +* +* (There may be other conditions that throw this, +* but I couldn't find any information on it.) +* +* Force a shutdown now and avoid things getting +* out of sync. The Unix domain socket is inside +* the .git directory and a spelling change will make +* it hard for clients to rendezvous with us. +*/ +trace_printf_key (&trace_fsmonitor, +"event: root changed" ); + goto force_shutdown; + } +if (ef_ignore_xattr (event_flags[k])) { trace_printf_key(&trace_fsmonitor, "ignore-xattr: '%s', flags=0x%x",
Signature · 1 line
gitgitgadget
PATCH v7 20/30 fsmonitor: optimize processing of directory events
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Teach Git to perform binary search over the cache-entries for a directory notification and then linearly scan forward to find the immediate children.
Previously, when the FSMonitor reported a modified directory Git would perform a linear search on the entire cache-entry array for all entries matching that directory prefix and invalidate them. Since the cache-entry array is already sorted, we can use a binary search to find the first matching entry and then only linearly walk forward and invalidate entries until the prefix changes.
Also, the original code would invalidate anything having the same directory prefix. Since a directory event should only be received for items that are immediately within the directory (and not within sub-directories of it), only invalidate those entries and not the whole subtree.
--- fsmonitor.c | 71 ++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 54 insertions(+), 17 deletions(-)
fsmonitor.c +54 -17
@@ -184,30 +184,68 @@ static int query_fsmonitor_hook(struct repository *r,static void fsmonitor_refresh_callback (struct index_state *istate,char *name) {int i, len =strlen (name); -if (name[len -1 ] =='/' ) { +int pos =index_name_pos (istate, name, len); + +trace_printf_key (&trace_fsmonitor, +"fsmonitor_refresh_callback '%s' (pos %d)" , + name, pos); +if (name[len -1 ] =='/' ) {/* -* TODO We should binary search to find the first path with -* TODO this directory prefix. Then linearly update entries -* TODO while the prefix matches. Taking care to search without -* TODO the trailing slash -- because '/' sorts after a few -* TODO interesting special chars, like '.' and ' '. +* 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 =0 ; i < istate->cache_nr; i++) { -if (istate->cache[i]->ce_flags &CE_FSMONITOR_VALID && -starts_with (istate->cache[i]->name, name)) - istate->cache[i]->ce_flags &= ~CE_FSMONITOR_VALID ; +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 ; } -/* Need to remove the / from the path for the untracked cache */ + +/* +* 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 { -int pos =index_name_pos (istate, name,strlen (name)); - -if (pos >=0 ) { -struct cache_entry *ce = istate->cache[pos]; - ce->ce_flags &= ~CE_FSMONITOR_VALID ; +/* +* 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 ; } }
@@ -215,7 +253,6 @@ static void fsmonitor_refresh_callback(struct index_state *istate, char *name)* Mark the untracked cache dirty even if it wasn't found in the index * as it could be a new untracked file. */ -trace_printf_key (&trace_fsmonitor,"fsmonitor_refresh_callback '%s'" , name);untracked_cache_invalidate_path (istate, name,0 ); }
Signature · 1 line
gitgitgadget
PATCH v7 21/30 t7527: FSMonitor tests for directory moves
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create unit tests to move a directory. Verify that git status gives the same result with and without FSMonitor enabled.
--- t/t7527-builtin-fsmonitor.sh | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+)
t/t7527-builtin-fsmonitor.sh +27 -0
@@ -274,6 +274,16 @@ test_expect_success 'setup' ' trace* EOF + mkdir -p T1/T2/T3/T4 && + echo 1 >T1/F1 && + echo 1 >T1/T2/F1 && + echo 1 >T1/T2/T3/F1 && + echo 1 >T1/T2/T3/T4/F1 && + echo 2 >T1/F2 && + echo 2 >T1/T2/F2 && + echo 2 >T1/T2/T3/F2 && + echo 2 >T1/T2/T3/T4/F2 && + git -c core.fsmonitor=false add . && test_tick && git -c core.fsmonitor=false commit -m initial &&
@@ -356,6 +366,19 @@ directory_to_file () { echo 1 >dir1 } +move_directory_contents_deeper() { + mkdir T1/_new_ && + mv T1/[A-Z]* T1/_new_ +} + +move_directory_up() { + mv T1/T2/T3 T1 +} + +move_directory() { + mv T1/T2/T3 T1/T2/NewT3 +} + # The next few test cases confirm that our fsmonitor daemon sees each type # of OS filesystem notification that we care about. At this layer we just # ensure we are getting the OS notifications and do not try to confirm what
@@ -660,6 +683,10 @@ do matrix_try $uc_val $fsm_val file_to_directory matrix_try $uc_val $fsm_val directory_to_file + matrix_try $uc_val $fsm_val move_directory_contents_deeper + matrix_try $uc_val $fsm_val move_directory_up + matrix_try $uc_val $fsm_val move_directory + if test $fsm_val = true then test_expect_success "Matrix[uc:$uc_val][fsm:$fsm_val] disable fsmonitor at end" '
Signature · 1 line
gitgitgadget
PATCH v7 22/30 t/perf/p7527: add perf test for builtin FSMonitor
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
--- t/perf/p7527-builtin-fsmonitor.sh | 257 ++++++++++++++++++++++++++++++ 1 file changed, 257 insertions(+) create mode 100755 t/perf/p7527-builtin-fsmonitor.sh
t/perf/p7527-builtin-fsmonitor.sh new file +257 -0
@@ -0,0 +1,257 @@ +#!/bin/sh + +test_description="Perf test for the builtin FSMonitor" + +. ./perf-lib.sh + +if ! test_have_prereq FSMONITOR_DAEMON +then + skip_all="fsmonitor--daemon is not supported on this platform" + test_done +fi + +test_lazy_prereq UNTRACKED_CACHE ' + { git update-index --test-untracked-cache; ret=$?; } && + test $ret -ne 1 +' + +# Lie to perf-lib and ask for a new empty repo and avoid +# the complaints about GIT_PERF_REPO not being big enough +# the perf hit when GIT_PERF_LARGE_REPO is copied into +# the trash directory. +# +# NEEDSWORK: It would be nice if perf-lib had an option to +# "borrow" an existing large repo (especially for gigantic +# monorepos) and use it in-place. For now, fake it here. +# +test_perf_fresh_repo + + +# Use a generated synthetic monorepo. If it doesn't exist, we will +# generate it. If it does exist, we will put it in a known state +# before we start our timings. +# +PARAM_D=5 +PARAM_W=10 +PARAM_F=9 + +PARAMS="$PARAM_D"."$PARAM_W"."$PARAM_F" + +BALLAST_BR=p0006-ballast +export BALLAST_BR + +TMP_BR=tmp_br +export TMP_BR + +REPO=../repos/gen-many-files-"$PARAMS".git +export REPO + +if ! test -d $REPO +then + (cd ../repos; ./many-files.sh -d $PARAM_D -w $PARAM_W -f $PARAM_F) +fi + + +enable_uc () { + git -C $REPO config core.untrackedcache true + git -C $REPO update-index --untracked-cache + git -C $REPO status >/dev/null 2>&1 +} + +disable_uc () { + git -C $REPO config core.untrackedcache false + git -C $REPO update-index --no-untracked-cache + git -C $REPO status >/dev/null 2>&1 +} + +start_fsm () { + git -C $REPO fsmonitor--daemon start + git -C $REPO fsmonitor--daemon status + git -C $REPO config core.fsmonitor true + git -C $REPO update-index --fsmonitor + git -C $REPO status >/dev/null 2>&1 +} + +stop_fsm () { + git -C $REPO config --unset core.fsmonitor + git -C $REPO update-index --no-fsmonitor + test_might_fail git -C $REPO fsmonitor--daemon stop 2>/dev/null + git -C $REPO status >/dev/null 2>&1 +} + + +# Ensure that FSMonitor is turned off on the borrowed repo. +# +test_expect_success "Setup borrowed repo (fsm+uc)" " + stop_fsm && + disable_uc +" + +# Also ensure that it starts in a known state. +# +# Because we assume that $GIT_PERF_REPEAT_COUNT > 1, we are not going to time +# the ballast checkout, since only the first invocation does any work and the +# subsequent ones just print "already on branch" and quit, so the reported +# time is not useful. +# +# Create a temp branch and do all work relative to it so that we don't +# accidentially alter the real ballast branch. +# +test_expect_success "Setup borrowed repo (temp ballast branch)" " + test_might_fail git -C $REPO checkout $BALLAST_BR && + test_might_fail git -C $REPO reset --hard && + git -C $REPO clean -d -f && + test_might_fail git -C $REPO branch -D $TMP_BR && + git -C $REPO branch $TMP_BR $BALLAST_BR && + git -C $REPO checkout $TMP_BR +" + + +echo Data >data.txt + +# NEEDSWORK: We assume that $GIT_PERF_REPEAT_COUNT > 1. With +# FSMonitor enabled, we can get a skewed view of status times, since +# the index MAY (or may not) be updated after the first invocation +# which will update the FSMonitor Token, so the subsequent invocations +# may get a smaller response from the daemon. +# +do_status () { + msg=$1 + + test_perf "$msg" " + git -C $REPO status >/dev/null 2>&1 + " +} + +do_matrix () { + uc=$1 + fsm=$2 + + t="[uc $uc][fsm $fsm]" + MATRIX_BR="$TMP_BR-$uc-$fsm" + + test_expect_success "$t Setup matrix branch" " + git -C $REPO clean -d -f && + git -C $REPO checkout $TMP_BR && + test_might_fail git -C $REPO branch -D $MATRIX_BR && + git -C $REPO branch $MATRIX_BR $TMP_BR && + git -C $REPO checkout $MATRIX_BR + " + + if test $uc = true + then + enable_uc + else + disable_uc + fi + + if test $fsm = true + then + start_fsm + else + stop_fsm + fi + + do_status "$t status after checkout" + + # Modify many files in the matrix branch. + # Stage them. + # Commit them. + # Rollback. + # + test_expect_success "$t modify tracked files" " + find $REPO -name file1 -exec cp data.txt {} \\; + " + + do_status "$t status after big change" + + # Don't bother timing the "add" because _REPEAT_COUNT + # issue described above. + # + test_expect_success "$t add all" " + git -C $REPO add -A + " + + do_status "$t status after add all" + + test_expect_success "$t add dot" " + git -C $REPO add . + " + + do_status "$t status after add dot" + + test_expect_success "$t commit staged" " + git -C $REPO commit -a -m data + " + + do_status "$t status after commit" + + test_expect_success "$t reset HEAD~1 hard" " + git -C $REPO reset --hard HEAD~1 >/dev/null 2>&1 + " + + do_status "$t status after reset hard" + + # Create some untracked files. + # + test_expect_success "$t create untracked files" " + cp -R $REPO/ballast/dir1 $REPO/ballast/xxx1 + " + + do_status "$t status after create untracked files" + + # Remove the new untracked files. + # + test_expect_success "$t clean -df" " + git -C $REPO clean -d -f + " + + do_status "$t status after clean" + + if test $fsm = true + then + stop_fsm + fi +} + +# Begin testing each case in the matrix that we care about. +# +uc_values="false" +test_have_prereq UNTRACKED_CACHE && uc_values="false true" + +fsm_values="false true" + +for uc_val in $uc_values +do + for fsm_val in $fsm_values + do + do_matrix $uc_val $fsm_val + done +done + +cleanup () { + uc=$1 + fsm=$2 + + MATRIX_BR="$TMP_BR-$uc-$fsm" + + test_might_fail git -C $REPO branch -D $MATRIX_BR +} + + +# We're borrowing this repo. We should leave it in a clean state. +# +test_expect_success "Cleanup temp and matrix branches" " + git -C $REPO clean -d -f && + test_might_fail git -C $REPO checkout $BALLAST_BR && + test_might_fail git -C $REPO branch -D $TMP_BR && + for uc_val in $uc_values + do + for fsm_val in $fsm_values + do + cleanup $uc_val $fsm_val + done + done +" + +test_done
Signature · 1 line
gitgitgadget
PATCH v7 23/30 fsmonitor: never set CE_FSMONITOR_VALID on submodules
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Never set CE_FSMONITOR_VALID on the cache-entry of submodule directories.
During a client command like 'git status', we may need to recurse into each submodule to compute a status summary for the submodule. Since the purpose of the ce_flag is to let Git avoid scanning a cache-entry, setting the flag causes the recursive call to be avoided and we report incorrect (no status) for the submodule.
We created an OS watch on the root directory of our working directory and we receive events for everything in the cone under it. When submodules are present inside our working directory, we receive events for both our repo (the super) and any subs within it. Since our index doesn't have any information for items within the submodules, we can't use those events.
We could try to truncate the paths of those events back to the submodule boundary and mark the GITLINK as dirty, but that feels expensive since we would have to prefix compare every FS event that we receive against a list of submodule roots. And it still wouldn't be sufficient to correctly report status on the submodule, since we don't have any space in the cache-entry to cache the submodule's status (the 'SCMU' bits in porcelain V2 speak). That is, the CE_FSMONITOR_VALID bit just says that we don't need to scan/inspect it because we already know the answer -- it doesn't say that the item is clean -- and we don't have space in the cache-entry to store those answers. So we should always do the recursive scan.
Therefore, we should never set the flag on GITLINK cache-entries.
--- fsmonitor.c | 2 + fsmonitor.h | 11 ++++ t/t7527-builtin-fsmonitor.sh | 111 +++++++++++++++++++++++++++++++++++ 3 files changed, 124 insertions(+)
fsmonitor.c +2 -0
@@ -580,6 +580,8 @@ void tweak_fsmonitor(struct index_state *istate)if (fsmonitor_enabled) {/* Mark all entries valid */ for (i =0 ; i < istate->cache_nr; i++) { +if (S_ISGITLINK (istate->cache[i]->ce_mode)) +continue ; istate->cache[i]->ce_flags |=CE_FSMONITOR_VALID ; }
fsmonitor.h +11 -0
@@ -68,6 +68,15 @@ static inline int is_fsmonitor_refreshed(const struct index_state *istate)* Set the given cache entries CE_FSMONITOR_VALID bit. This should be * called any time the cache entry has been updated to reflect the * current state of the file on disk. +* +* However, never mark submodules as valid. When commands like "git +* status" run they might need to recurse into the submodule (using a +* child process) to get a summary of the submodule state. We don't +* have (and don't want to create) the facility to translate every +* FS event that we receive and that happens to be deep inside of a +* submodule back to the submodule root, so we cannot correctly keep +* track of this bit on the gitlink directory. Therefore, we never +* set it on submodules. */ static inline void mark_fsmonitor_valid (struct index_state *istate,struct cache_entry *ce) {
@@ -75,6 +84,8 @@ static inline void mark_fsmonitor_valid(struct index_state *istate, struct cacheif (fsm_mode >FSMONITOR_MODE_DISABLED && !(ce->ce_flags &CE_FSMONITOR_VALID )) { +if (S_ISGITLINK (ce->ce_mode)) +return ; istate->cache_changed =1 ; ce->ce_flags |=CE_FSMONITOR_VALID ;trace_printf_key (&trace_fsmonitor,"mark_fsmonitor_clean '%s'" , ce->name);
t/t7527-builtin-fsmonitor.sh +111 -0
115 lines not on this page · raw message
Signature · 1 line
gitgitgadget
PATCH v7 24/30 t7527: test FSMonitor on case insensitive+preserving file system
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Test that FS events from the OS are received using the preserved, on-disk spelling of files/directories rather than spelling used to make the change.
--- t/t7527-builtin-fsmonitor.sh | 36 ++++++++++++++++++++++++++++++++++++ 1 file changed, 36 insertions(+)
t/t7527-builtin-fsmonitor.sh +36 -0
@@ -832,4 +832,40 @@ test_expect_success 'submodule always visited' ' my_match_and_clean ' +# On a case-insensitive file system, confirm that the daemon +# notices when the .git directory is moved/renamed/deleted +# regardless of how it is spelled in the the FS event. +# That is, does the FS event receive the spelling of the +# operation or does it receive the spelling preserved with +# the file/directory. +# +test_expect_success CASE_INSENSITIVE_FS 'case insensitive+preserving' ' +# test_when_finished "stop_daemon_delete_repo test_insensitive" && + + git init test_insensitive && + + start_daemon -C test_insensitive --tf "$PWD/insensitive.trace" && + + mkdir -p test_insensitive/abc/def && + echo xyz >test_insensitive/ABC/DEF/xyz && + + test_path_is_dir test_insensitive/.git && + test_path_is_dir test_insensitive/.GIT && + + # Rename .git using an alternate spelling to verify that that + # daemon detects it and automatically shuts down. + mv test_insensitive/.GIT test_insensitive/.FOO && + sleep 1 && + mv test_insensitive/.FOO test_insensitive/.git && + test_must_fail git -C test_insensitive fsmonitor--daemon status && + + # Verify that events were reported using on-disk spellings of the + # directories and files that we touched. We may or may not get a + # trailing slash on modified directories. + # + egrep "^event: abc/?$" ./insensitive.trace && + egrep "^event: abc/def/?$" ./insensitive.trace && + egrep "^event: abc/def/xyz$" ./insensitive.trace +' + test_done
Signature · 1 line
gitgitgadget
PATCH v7 25/30 fsmonitor: on macOS also emit NFC spelling for NFD pathname
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Emit NFC or NFC and NFD spellings of pathnames on macOS.
MacOS is Unicode composition insensitive, so NFC and NFD spellings are treated as aliases and collide. While the spelling of pathnames in filesystem events depends upon the underlying filesystem, such as APFS, HFS+ or FAT32, the OS enforces such collisions regardless of filesystem.
Teach the daemon to always report the NFC spelling and to report the NFD spelling when stored in that format on the disk.
This is slightly more general than "core.precomposeUnicode".
--- compat/fsmonitor/fsm-listen-darwin.c | 33 ++++++++++++++++++++++++++-- 1 file changed, 31 insertions(+), 2 deletions(-)
compat/fsmonitor/fsm-listen-darwin.c +31 -2
@@ -155,6 +155,35 @@ static int ef_ignore_xattr(const FSEventStreamEventFlags ef)return ((ef & mask) == kFSEventStreamEventFlagItemXattrMod); } +/* +* On MacOS we have to adjust for Unicode composition insensitivity +* (where NFC and NFD spellings are not respected). The different +* spellings are essentially aliases regardless of how the path is +* actually stored on the disk. +* +* This is related to "core.precomposeUnicode" (which wants to try +* to hide NFD completely and treat everything as NFC). Here, we +* don't know what the value the client has (or will have) for this +* config setting when they make a query, so assume the worst and +* emit both when the OS gives us an NFD path. +*/ +static void my_add_path (struct fsmonitor_batch *batch,const char *path) +{ +char *composed; + +/* add the NFC or NFD path as received from the OS */ +fsmonitor_batch__add_path (batch, path); + +/* if NFD, also add the corresponding NFC spelling */ + composed = (char *)precompose_string_if_needed (path); +if (!composed || composed == path) +return ; + +fsmonitor_batch__add_path (batch, composed); +free (composed); +} + + static void fsevent_callback(ConstFSEventStreamRef streamRef, void *ctx, size_t num_of_events,
@@ -305,7 +334,7 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef,if (!batch) batch =fsmonitor_batch__new (); -fsmonitor_batch__add_path (batch, rel); +my_add_path (batch, rel); }if (event_flags[k] & kFSEventStreamEventFlagItemIsDir) {
@@ -318,7 +347,7 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef,if (!batch) batch =fsmonitor_batch__new (); -fsmonitor_batch__add_path (batch, tmp.buf); +my_add_path (batch, tmp.buf); }break ;
Signature · 1 line
gitgitgadget
PATCH v7 26/30 t/helper/hexdump: add helper to print hexdump of stdin
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
--- Makefile | 1 + t/helper/test-hexdump.c | 24 ++++++++++++++++++++++++ t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + 4 files changed, 27 insertions(+) create mode 100644 t/helper/test-hexdump.c
Makefile +1 -0
@@ -729,6 +729,7 @@ TEST_BUILTINS_OBJS += test-getcwd.o TEST_BUILTINS_OBJS += test-hash-speed.o TEST_BUILTINS_OBJS += test-hash.o TEST_BUILTINS_OBJS += test-hashmap.o +TEST_BUILTINS_OBJS += test-hexdump.o TEST_BUILTINS_OBJS += test-index-version.o TEST_BUILTINS_OBJS += test-json-writer.o TEST_BUILTINS_OBJS += test-lazy-init-name-hash.o
t/helper/test-hexdump.c new file +24 -0
@@ -0,0 +1,24 @@ +#include "test-tool.h" +#include "git-compat-util.h" + +/* +* Read stdin and print a hexdump to stdout. +*/ +int cmd__hexdump (int argc,const char **argv) +{ +char buf[1024 ]; +ssize_t i, len; + +for (;;) { + len =xread (0 , buf,sizeof (buf)); +if (len <0 ) +die_errno ("failure reading stdin" ); +if (!len) +break ; + +for (i =0 ; i < len; i++) +printf ("%02x " , (unsigned char )buf[i]); + } + +return 0 ; +}
t/helper/test-tool.c +1 -0
@@ -38,6 +38,7 @@ static struct test_cmd cmds[] = { { "getcwd", cmd__getcwd }, { "hashmap", cmd__hashmap }, { "hash-speed", cmd__hash_speed }, + { "hexdump", cmd__hexdump }, { "index-version", cmd__index_version }, { "json-writer", cmd__json_writer }, { "lazy-init-name-hash", cmd__lazy_init_name_hash },
t/helper/test-tool.h +1 -0
@@ -29,6 +29,7 @@ int cmd__genzeros(int argc, const char **argv);int cmd__getcwd (int argc,const char **argv);int cmd__hashmap (int argc,const char **argv);int cmd__hash_speed (int argc,const char **argv); +int cmd__hexdump (int argc,const char **argv);int cmd__index_version (int argc,const char **argv);int cmd__json_writer (int argc,const char **argv);int cmd__lazy_init_name_hash (int argc,const char **argv);
Signature · 1 line
gitgitgadget
Re: [PATCH v7 26/30] t/helper/hexdump: add helper to print hexdump of stdin
Quoted 8 lines · Jeff Hostetler via GitGitGadget
t/helper/test-hexdump.c new file +8
-0+int cmd__hexdump (int argc,const char **argv) +{ +char buf[1024 ]; +ssize_t i, len; + +for (;;) { + len =xread (0 , buf,sizeof (buf)); +if (len <0 )
t/helper/test-hexdump.c line 24
Quoted 10 lines
t/helper/test-hexdump.c new file +10
-0+die_errno ("failure reading stdin" ); +if (!len) +break ; + +for (i =0 ; i < len; i++) +printf ("%02x " , (unsigned char )buf[i]); + } + +return 0 ; +}
It is meant to be consumed by machine, so I do not think we would mind too much about a single long line, but given that consumers include "grep", it would probably be better to avoid emitting an incomplete line, especially since addition of this tool is all about portability across platforms.
An extra putchar('\n'); after the loop would fix it easily.
Thanks.
Quoted 24 lines
t/helper/test-tool.c +1
-0@@ -38,6 +38,7 @@ static struct test_cmd cmds[] = { { "getcwd", cmd__getcwd }, { "hashmap", cmd__hashmap }, { "hash-speed", cmd__hash_speed }, + { "hexdump", cmd__hexdump }, { "index-version", cmd__index_version }, { "json-writer", cmd__json_writer }, { "lazy-init-name-hash", cmd__lazy_init_name_hash },t/helper/test-tool.h +1
-0@@ -29,6 +29,7 @@ int cmd__genzeros(int argc, const char **argv);int cmd__getcwd (int argc,const char **argv);int cmd__hashmap (int argc,const char **argv);int cmd__hash_speed (int argc,const char **argv); +int cmd__hexdump (int argc,const char **argv);int cmd__index_version (int argc,const char **argv);int cmd__json_writer (int argc,const char **argv);int cmd__lazy_init_name_hash (int argc,const char **argv);
Re: [PATCH v7 26/30] t/helper/hexdump: add helper to print hexdump of stdin
Hi Junio,
Quoted 18 lines · Junio C Hamano
Quoted 16 lines · Jeff Hostetler via GitGitGadget
t/helper/test-hexdump.c new file +16
-0+int cmd__hexdump (int argc,const char **argv) +{ +char buf[1024 ]; +ssize_t i, len; + +for (;;) { + len =xread (0 , buf,sizeof (buf)); +if (len <0 ) +die_errno ("failure reading stdin" ); +if (!len) +break ; + +for (i =0 ; i < len; i++) +printf ("%02x " , (unsigned char )buf[i]); + } +
Quoted 8 lines
t/helper/test-hexdump.c line 24
Quoted 2 lines
t/helper/test-hexdump.c new file +2
-0+return 0 ; +}It is meant to be consumed by machine, so I do not think we would mind too much about a single long line, but given that consumers include "grep", it would probably be better to avoid emitting an incomplete line, especially since addition of this tool is all about portability across platforms.
Do you know of any grep implementation that has problems with text missing the usual trailing newlines?
Ciao,
Dscho
Re: [PATCH v7 26/30] t/helper/hexdump: add helper to print hexdump of stdin
Quoted 2 lines · Johannes Schindelin
Do you know of any
grepimplementation that has problems with text missing the usual trailing newlines?
I recall that we had reports on BSD variants, but please do not quote me on that. Perhaps all the BSD variants are good now, or perhaps some aren't.
In any case, I do not take the "if it works on Windows and Linux, we do not care about the rest of the world" world view, so finding the answer to that question unfortunately does not give much input to the issue in either way.
And in this particular case, it is much simpler to mak sure that the file does not end in an incomplete line than us exchanging e-mails back and forth, so that would be the most economical solution I would prefer.
Thanks.
Re: [PATCH v7 26/30] t/helper/hexdump: add helper to print hexdump of stdin
Hi Junio,
Quoted 8 lines · Junio C Hamano
Quoted 2 lines · Johannes Schindelin
Do you know of any
grepimplementation that has problems with text missing the usual trailing newlines?I recall that we had reports on BSD variants, but please do not quote me on that. Perhaps all the BSD variants are good now, or perhaps some aren't.
I did not recall any such reports, but take your word for it.
Quoted 2 lines
In any case, I do not take the "if it works on Windows and Linux, we do not care about the rest of the world" world view,
Just in case it was unclear to you: we're on the same page here.
Quoted 7 lines
so finding the answer to that question unfortunately does not give much input to the issue in either way.
And in this particular case, it is much simpler to mak sure that the file does not end in an incomplete line than us exchanging e-mails back and forth, so that would be the most economical solution I would prefer.
Indeed, the trailing newline is easily added and fixes a known issue, so I'm all for it.
Ciao,
Dscho
Re: [PATCH v7 26/30] t/helper/hexdump: add helper to print hexdump of stdin
In reply to Junio C HamanoQuoted 27 lines · Junio C Hamano
Quoted 8 lines · Jeff Hostetler via GitGitGadget
t/helper/test-hexdump.c new file +8
-0+int cmd__hexdump (int argc,const char **argv) +{ +char buf[1024 ]; +ssize_t i, len; + +for (;;) { + len =xread (0 , buf,sizeof (buf)); +if (len <0 )t/helper/test-hexdump.c line 24
Quoted 10 lines
t/helper/test-hexdump.c new file +10
-0+die_errno ("failure reading stdin" ); +if (!len) +break ; + +for (i =0 ; i < len; i++) +printf ("%02x " , (unsigned char )buf[i]); + } + +return 0 ; +}It is meant to be consumed by machine, so I do not think we would mind too much about a single long line, but given that consumers include "grep", it would probably be better to avoid emitting an incomplete line, especially since addition of this tool is all about portability across platforms.
Quoted 1 line
An extra putchar('\n'); after the loop would fix it easily.
Yes, I should have added a final LF. I was more focused on cleaning up the test cases.
Would you prefer a send a V8 or would you be willing to push a fixup commit on top?
Jeff
Re: [PATCH v7 26/30] t/helper/hexdump: add helper to print hexdump of stdin
Quoted 2 lines · Jeff Hostetler
Would you prefer a send a V8 or would you be willing to push a fixup commit on top?
If "test-tool hexgrep" does not turn out to be a better solution, and if there is no other changes needed, then I do not mind locally amending, but I'd rather avoid touching a middle step in multi dozen patch series myself if I can.
Thanks.
Re: [PATCH v7 26/30] t/helper/hexdump: add helper to print hexdump of stdin
Quoted 5 lines · Junio C Hamano
Quoted 2 lines · Jeff Hostetler
Would you prefer a send a V8 or would you be willing to push a fixup commit on top?
Quoted 7 lines
If "test-tool hexgrep" does not turn out to be a better solution, and if there is no other changes needed, then I do not mind locally amending, but I'd rather avoid touching a middle step in multi dozen patch series myself if I can.
Thanks.
That's fine. I'll fix it and send V8.
Thanks
Jeff
PATCH v7 27/30 t/lib-unicode-nfc-nfd: helper prereqs for testing unicode nfc/nfd
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create a set of prereqs to help understand how file names are handled by the filesystem when they contain NFC and NFD Unicode characters.
--- t/lib-unicode-nfc-nfd.sh | 158 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 158 insertions(+) create mode 100755 t/lib-unicode-nfc-nfd.sh
t/lib-unicode-nfc-nfd.sh new file +158 -0
@@ -0,0 +1,158 @@ +# Help detect how Unicode NFC and NFD are handled on the filesystem. + +# A simple character that has a NFD form. +# +# NFC: U+00e9 LATIN SMALL LETTER E WITH ACUTE +# UTF8(NFC): \xc3 \xa9 +# +# NFD: U+0065 LATIN SMALL LETTER E +# U+0301 COMBINING ACUTE ACCENT +# UTF8(NFD): \x65 + \xcc \x81 +# +utf8_nfc=$(printf "\xc3\xa9") +utf8_nfd=$(printf "\x65\xcc\x81") + +# Is the OS or the filesystem "Unicode composition sensitive"? +# +# That is, does the OS or the filesystem allow files to exist with +# both the NFC and NFD spellings? Or, does the OS/FS lie to us and +# tell us that the NFC and NFD forms are equivalent. +# +# This is or may be independent of what type of filesystem we have, +# since it might be handled by the OS at a layer above the FS. +# Testing shows on MacOS using APFS, HFS+, and FAT32 reports a +# collision, for example. +# +# This does not tell us how the Unicode pathname will be spelled +# on disk, but rather only that the two spelling "collide". We +# will examine the actual on disk spelling in a later prereq. +# +test_lazy_prereq UNICODE_COMPOSITION_SENSITIVE ' + mkdir trial_${utf8_nfc} && + mkdir trial_${utf8_nfd} +' + +# Is the spelling of an NFC pathname preserved on disk? +# +# On MacOS with HFS+ and FAT32, NFC paths are converted into NFD +# and on APFS, NFC paths are preserved. As we have established +# above, this is independent of "composition sensitivity". +# +test_lazy_prereq UNICODE_NFC_PRESERVED ' + mkdir c_${utf8_nfc} && + ls | test-tool hexdump | grep "63 5f c3 a9" +' + +# Is the spelling of an NFD pathname preserved on disk? +# +test_lazy_prereq UNICODE_NFD_PRESERVED ' + mkdir d_${utf8_nfd} && + ls | test-tool hexdump | grep "64 5f 65 cc 81" +' + +# The following _DOUBLE_ forms are more for my curiosity, +# but there may be quirks lurking when there are multiple +# combining characters in non-canonical order. + +# Unicode also allows multiple combining characters +# that can be decomposed in pieces. +# +# NFC: U+1f67 GREEK SMALL LETTER OMEGA WITH DASIA AND PERISPOMENI +# UTF8(NFC): \xe1 \xbd \xa7 +# +# NFD1: U+1f61 GREEK SMALL LETTER OMEGA WITH DASIA +# U+0342 COMBINING GREEK PERISPOMENI +# UTF8(NFD1): \xe1 \xbd \xa1 + \xcd \x82 +# +# But U+1f61 decomposes into +# NFD2: U+03c9 GREEK SMALL LETTER OMEGA +# U+0314 COMBINING REVERSED COMMA ABOVE +# UTF8(NFD2): \xcf \x89 + \xcc \x94 +# +# Yielding: \xcf \x89 + \xcc \x94 + \xcd \x82 +# +# Note that I've used the canonical ordering of the +# combinining characters. It is also possible to +# swap them. My testing shows that that non-standard +# ordering also causes a collision in mkdir. However, +# the resulting names don't draw correctly on the +# terminal (implying that the on-disk format also has +# them out of order). +# +greek_nfc=$(printf "\xe1\xbd\xa7") +greek_nfd1=$(printf "\xe1\xbd\xa1\xcd\x82") +greek_nfd2=$(printf "\xcf\x89\xcc\x94\xcd\x82") + +# See if a double decomposition also collides. +# +test_lazy_prereq UNICODE_DOUBLE_COMPOSITION_SENSITIVE ' + mkdir trial_${greek_nfc} && + mkdir trial_${greek_nfd2} +' + +# See if the NFC spelling appears on the disk. +# +test_lazy_prereq UNICODE_DOUBLE_NFC_PRESERVED ' + mkdir c_${greek_nfc} && + ls | test-tool hexdump | grep "63 5f e1 bd a7" +' + +# See if the NFD spelling appears on the disk. +# +test_lazy_prereq UNICODE_DOUBLE_NFD_PRESERVED ' + mkdir d_${greek_nfd2} && + ls | test-tool hexdump | grep "64 5f cf 89 cc 94 cd 82" +' + +# The following is for debugging. I found it useful when +# trying to understand the various (OS, FS) quirks WRT +# Unicode and how composition/decomposition is handled. +# For example, when trying to understand how (macOS, APFS) +# and (macOS, HFS) and (macOS, FAT32) compare. +# +# It is rather noisy, so it is disabled by default. +# +if test "$unicode_debug" = "true" +then + if test_have_prereq UNICODE_COMPOSITION_SENSITIVE + then + echo NFC and NFD are distinct on this OS/filesystem. + else + echo NFC and NFD are aliases on this OS/filesystem. + fi + + if test_have_prereq UNICODE_NFC_PRESERVED + then + echo NFC maintains original spelling. + else + echo NFC is modified. + fi + + if test_have_prereq UNICODE_NFD_PRESERVED + then + echo NFD maintains original spelling. + else + echo NFD is modified. + fi + + if test_have_prereq UNICODE_DOUBLE_COMPOSITION_SENSITIVE + then + echo DOUBLE NFC and NFD are distinct on this OS/filesystem. + else + echo DOUBLE NFC and NFD are aliases on this OS/filesystem. + fi + + if test_have_prereq UNICODE_DOUBLE_NFC_PRESERVED + then + echo Double NFC maintains original spelling. + else + echo Double NFC is modified. + fi + + if test_have_prereq UNICODE_DOUBLE_NFD_PRESERVED + then + echo Double NFD maintains original spelling. + else + echo Double NFD is modified. + fi +fi
Signature · 1 line
gitgitgadget
Re: [PATCH v7 27/30] t/lib-unicode-nfc-nfd: helper prereqs for testing unicode nfc/nfd
t/lib-unicode-nfc-nfd.sh line 43
Quoted 1 line · Jeff Hostetler via GitGitGadget
t/lib-unicode-nfc-nfd.sh new file +1
-0+ ls | test-tool hexdump | grep "63 5f c3 a9"
A few comments:
* Not folding output lines at arbitrary place like "od", "hd", etc. does, is a good design decision made by "hexdump" here. Depending on where in the pathname the 4-byte sequence appears, tools from other people may split the sequence across output lines, making grep ineffective. But our hexdump would work fine here.
* For the narrow purpose of the tests in this script, output that is a single long line produced by hexdump might be sufficient, but I wonder if it makes the tool more useful if we at least placed the hexified output for each line on separate output lines.
* Purist in us may find it a bit disturbing that exit status from test-tool is hidden by the pipe. I do not care too deeply about it, as it is very unlikely that we care about segfault after hexdump successfully shows the substring the downstream grep is looking for, but it does make us feel dirty.
A devil's advocate suggestion is to go in the completely opposite side of the spectrum. Perhaps if we are willing to limit the tool's utility to the tests done in this script file, it might be a good idea to combine the latter two elements in the pipeline, i.e.
ls | test-tool hexgrep 63 5f c3 a9
that exits with 0 when the output from "ls" has the 4-byte sequence, exits with 1 when it does not, and exits with 139 when it segfauls ;-)
Re: [PATCH v7 27/30] t/lib-unicode-nfc-nfd: helper prereqs for testing unicode nfc/nfd
Hi Junio,
Quoted 11 lines · Junio C Hamano
"Jeff Hostetler via GitGitGadget" <[email protected]> writes:
A devil's advocate suggestion is to go in the completely opposite side of the spectrum. Perhaps if we are willing to limit the tool's utility to the tests done in this script file, it might be a good idea to combine the latter two elements in the pipeline, i.e.
ls | test-tool hexgrep 63 5f c3 a9
that exits with 0 when the output from "ls" has the 4-byte sequence, exits with 1 when it does not, and exits with 139 when it segfauls ;-)
I like the idea, but from what I recall of the Knuth-Pratt algorithm [*1*], the implementation might get a bit more involved than the current test-hexdump.c. With non-repetitive patterns like you wrote above, you can simply re-set the needle's offset to 0 if a mismatch was seen. It's partially-repetitive patterns such as 01 02 01 02 01 ff that make things
tricker: After encountering a 01 02 01 02 01, if the next character is a 02, we must not reset the needle's offset completely, as the next two characters might be 01 ff, i.e. a match.
Since the purpose of this already-long, already well-iterated patch series is not necessarily to improve the test suite in such an involved manner, it should be left as an excercise for another patch series whose purpose _is_ to improve Git's test framework.
Don't get me wrong, I am very much in favor of that hexgrep idea. Just in its own, dedicated patch series.
Ciao,
Dscho
Footnote *1*: I actually had to look at Wikipedia page at
https://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm
to realize that I did unjustice to James Morris by forgetting that they had discovered the algorithm independently.
Re: [PATCH v7 27/30] t/lib-unicode-nfc-nfd: helper prereqs for testing unicode nfc/nfd
In reply to Junio C HamanoQuoted 13 lines · Junio C Hamano
t/lib-unicode-nfc-nfd.sh line 43
Quoted 1 line · Jeff Hostetler via GitGitGadget
t/lib-unicode-nfc-nfd.sh new file +1
-0+ ls | test-tool hexdump | grep "63 5f c3 a9"A few comments:
* Not folding output lines at arbitrary place like "od", "hd", etc. does, is a good design decision made by "hexdump" here. Depending on where in the pathname the 4-byte sequence appears, tools from other people may split the sequence across output lines, making grep ineffective. But our hexdump would work fine here.
Quoted 5 lines
* For the narrow purpose of the tests in this script, output that is a single long line produced by hexdump might be sufficient, but I wonder if it makes the tool more useful if we at least placed the hexified output for each line on separate output lines.
Yeah, having tools arbitrarily wrap every 16 or whatever bytes (and including offset line prefixes) makes it difficult to use when looking for specific patterns that might span a boundary.
I could see having a command line option to emit a '\n' (in addition to or in place of) each LF in the input. I suppose it depends on the type of data we are dumping. (That also gets into issues about CRLFs, however.)
I'm using hexdump for unicode text here, soit could make sense. But if I were using it to dump .git/index it wouldn't.
So having the default be one very long line is a good start. We can teach it more later.
Quoted 6 lines
* Purist in us may find it a bit disturbing that exit status from test-tool is hidden by the pipe. I do not care too deeply about it, as it is very unlikely that we care about segfault after hexdump successfully shows the substring the downstream grep is looking for, but it does make us feel dirty.
Given the simplicity of the current version of the helper, I'm not really worried about such problems. I suppose that we could do the usual trick of writing the hex dump to a file and grepping it, but I'm not sure it's worth the bother right now.
Quoted 11 lines
A devil's advocate suggestion is to go in the completely opposite side of the spectrum. Perhaps if we are willing to limit the tool's utility to the tests done in this script file, it might be a good idea to combine the latter two elements in the pipeline, i.e.
ls | test-tool hexgrep 63 5f c3 a9
that exits with 0 when the output from "ls" has the 4-byte sequence, exits with 1 when it does not, and exits with 139 when it segfauls ;-)
I was a little afraid to suggest a hex version of grep. That would be interesting project to work on, but has lots of hard problems in it and is too much to tack on to this series. Johannes raises some interesting questions in a later response in this thread that suggest that this could be a seriously non-trivial task. So again, I'd like to not attempt this.
Thanks
Jeff
PATCH v7 28/30 t7527: test Unicode NFC/NFD handling on MacOS
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Confirm that the daemon reports events using the on-disk spelling for Unicode NFC/NFD characters. On APFS we still have Unicode aliasing, so we cannot create two files that only differ by NFC/NFD, but the on-disk format preserves the spelling used to create the file. On HFS+ we also have aliasing, but the path is always stored on disk in NFD.
--- t/t7527-builtin-fsmonitor.sh | 55 ++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+)
t/t7527-builtin-fsmonitor.sh +55 -0
@@ -868,4 +868,59 @@ test_expect_success CASE_INSENSITIVE_FS 'case insensitive+preserving' ' egrep "^event: abc/def/xyz$" ./insensitive.trace ' +# The variable "unicode_debug" is defined in the following library +# script to dump information about how the (OS, FS) handles Unicode +# composition. Uncomment the following line if you want to enable it. +# +# unicode_debug=true + +. "$TEST_DIRECTORY/lib-unicode-nfc-nfd.sh" + +# See if the OS or filesystem does NFC/NFD aliasing/munging. +# +# The daemon should err on the side of caution and send BOTH the +# NFC and NFD forms. It does not know the original spelling of +# the pathname (how the user thinks it should be spelled), so +# emit both and let the client decide (when necessary). This is +# similar to "core.precomposeUnicode". +# +test_expect_success !UNICODE_COMPOSITION_SENSITIVE 'Unicode nfc/nfd' ' + test_when_finished "stop_daemon_delete_repo test_unicode" && + + git init test_unicode && + + start_daemon -C test_unicode --tf "$PWD/unicode.trace" && + + # Create a directory using an NFC spelling. + # + mkdir test_unicode/nfc && + mkdir test_unicode/nfc/c_${utf8_nfc} && + + # Create a directory using an NFD spelling. + # + mkdir test_unicode/nfd && + mkdir test_unicode/nfd/d_${utf8_nfd} && + + git -C test_unicode fsmonitor--daemon stop && + + if test_have_prereq UNICODE_NFC_PRESERVED + then + # We should have seen NFC event from OS. + # We should not have synthesized an NFD event. + egrep "^event: nfc/c_${utf8_nfc}/?$" ./unicode.trace && + egrep -v "^event: nfc/c_${utf8_nfd}/?$" ./unicode.trace + else + # We should have seen NFD event from OS. + # We should have synthesized an NFC event. + egrep "^event: nfc/c_${utf8_nfd}/?$" ./unicode.trace && + egrep "^event: nfc/c_${utf8_nfc}/?$" ./unicode.trace + fi && + + # We assume UNICODE_NFD_PRESERVED. + # We should have seen explicit NFD from OS. + # We should have synthesized an NFC event. + egrep "^event: nfd/d_${utf8_nfd}/?$" ./unicode.trace && + egrep "^event: nfd/d_${utf8_nfc}/?$" ./unicode.trace +' + test_done
Signature · 1 line
gitgitgadget
PATCH v7 29/30 fsmonitor--daemon: allow --super-prefix argument
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create a test in t7527 to verify that we get a stray warning from git fsmonitor--daemon start when indirectly called from git submodule absorbgitdirs.
Update git fsmonitor--daemon to take (and ignore) the --super-prefix argument to suppress the warning.
When we have:
1. a submodule with a sub/.git/ directory (rather than a sub/.git file).
2. core.fsmonitor is turned on in the submodule, but the daemon is not yet started in the submodule.
3. and someone does a git submodule absorbgitdirs in the super.
Git will recursively invoke git submodule--helper absorb-git-dirs in the submodule. This will read the index and may attempt to start the fsmonitor--daemon with the --super-prefix argument.
git fsmonitor--daemon start does not accept the --super-prefix argument and causes a warning to be issued.
This does not cause a problem because the refresh_index() code assumes a trivial response if the daemon does not start.
The net-net is a harmelss, but stray warning. Lets eliminate the warning.
--- git.c | 2 +- t/t7527-builtin-fsmonitor.sh | 50 ++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 1 deletion(-)
git.c +1 -1
@@ -537,7 +537,7 @@ static struct cmd_struct commands[] = { { "format-patch", cmd_format_patch, RUN_SETUP }, { "fsck", cmd_fsck, RUN_SETUP }, { "fsck-objects", cmd_fsck, RUN_SETUP }, - { "fsmonitor--daemon", cmd_fsmonitor__daemon, RUN_SETUP }, + { "fsmonitor--daemon", cmd_fsmonitor__daemon, SUPPORT_SUPER_PREFIX | RUN_SETUP }, { "gc", cmd_gc, RUN_SETUP }, { "get-tar-commit-id", cmd_get_tar_commit_id, NO_PARSEOPT }, { "grep", cmd_grep, RUN_SETUP_GENTLY },
t/t7527-builtin-fsmonitor.sh +50 -0
56 lines not on this page · raw message
Signature · 1 line
gitgitgadget
PATCH v7 30/30 t7527: improve implicit shutdown testing in fsmonitor--daemon
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Refactor the tests that exercise implicit shutdown cases to make them more robust and less racy.
The fsmonitor--daemon will implicitly shutdown in a variety of situations, such as when the ".git" directory is deleted or renamed.
The existing tests would delete or rename the directory, sleep for one second, and then check the status of the daemon. This is racy, since the client/status command has no way to sync with the daemon. This was noticed occasionally on very slow CI build machines where it would cause a random test to fail.
Replace the simple sleep with a sleep-and-retry loop.
--- t/t7527-builtin-fsmonitor.sh | 54 ++++++++++++++++++++++++++---------- 1 file changed, 40 insertions(+), 14 deletions(-)
t/t7527-builtin-fsmonitor.sh +40 -14
@@ -124,6 +124,36 @@ test_expect_success 'implicit daemon start' ' test_must_fail git -C test_implicit fsmonitor--daemon status ' +# Verify that the daemon has shutdown. Spin a few seconds to +# make the test a little more robust during CI testing. +# +# We're looking for an implicit shutdown, such as when we delete or +# rename the ".git" directory. Our delete/rename will cause a file +# system event that the daemon will see and the daemon will +# auto-shutdown as soon as it sees it. But this is racy with our `git +# fsmonitor--daemon status` commands (and we cannot use a cookie file +# here to help us). So spin a little and give the daemon a chance to +# see the event. (This is primarily for underpowered CI build/test +# machines (where it might take a moment to wake and reschedule the +# daemon process) to avoid false alarms during test runs.) +# +IMPLICIT_TIMEOUT=5 + +verify_implicit_shutdown () { + r=$1 && + + k=0 && + while test "$k" -lt $IMPLICIT_TIMEOUT + do + git -C $r fsmonitor--daemon status || return 0 + + sleep 1 + k=$(( $k + 1 )) + done && + + return 1 +} + test_expect_success 'implicit daemon stop (delete .git)' ' test_when_finished "stop_daemon_delete_repo test_implicit_1" &&
@@ -142,10 +172,9 @@ test_expect_success 'implicit daemon stop (delete .git)' ' # This would make the test result dependent upon whether we # were using fsmonitor on our development worktree. # - sleep 1 && mkdir test_implicit_1/.git && - test_must_fail git -C test_implicit_1 fsmonitor--daemon status + verify_implicit_shutdown test_implicit_1 ' test_expect_success 'implicit daemon stop (rename .git)' '
@@ -160,10 +189,9 @@ test_expect_success 'implicit daemon stop (rename .git)' ' # See [1] above. # - sleep 1 && mkdir test_implicit_2/.git && - test_must_fail git -C test_implicit_2 fsmonitor--daemon status + verify_implicit_shutdown test_implicit_2 ' # File systems on Windows may or may not have shortnames.
@@ -194,13 +222,11 @@ test_expect_success MINGW,SHORTNAMES 'implicit daemon stop (rename GIT~1)' ' # mv test_implicit_1s/GIT~1 test_implicit_1s/.gitxyz && - sleep 1 && - # put it back so that our status will not crawl out to our - # parent directory. + # See [1] above. # this moves {.gitxyz, GITXYZ~1} to {.git, GIT~1}. mv test_implicit_1s/.gitxyz test_implicit_1s/.git && - test_must_fail git -C test_implicit_1s fsmonitor--daemon status + verify_implicit_shutdown test_implicit_1s ' # Here we first create a file with LONGNAME of "GIT~1" before
@@ -223,12 +249,10 @@ test_expect_success MINGW,SHORTNAMES 'implicit daemon stop (rename GIT~2)' ' # mv test_implicit_1s2/GIT~2 test_implicit_1s2/.gitxyz && - sleep 1 && - # put it back so that our status will not crawl out to our - # parent directory. + # See [1] above. mv test_implicit_1s2/.gitxyz test_implicit_1s2/.git && - test_must_fail git -C test_implicit_1s2 fsmonitor--daemon status + verify_implicit_shutdown test_implicit_1s2 ' test_expect_success 'cannot start multiple daemons' '
@@ -905,9 +929,11 @@ test_expect_success CASE_INSENSITIVE_FS 'case insensitive+preserving' ' # Rename .git using an alternate spelling to verify that that # daemon detects it and automatically shuts down. mv test_insensitive/.GIT test_insensitive/.FOO && - sleep 1 && + + # See [1] above. mv test_insensitive/.FOO test_insensitive/.git && - test_must_fail git -C test_insensitive fsmonitor--daemon status && + + verify_implicit_shutdown test_insensitive && # Verify that events were reported using on-disk spellings of the # directories and files that we touched. We may or may not get a
Signature · 1 line
gitgitgadget
Hi Jeff,
Quoted 11 lines · Jeff Hostetler via GitGitGadget
1:
8b7c5f4e23! 1:26144c5865fsm-listen-win32: handle shortnames[...] @@ compat/fsmonitor/fsm-listen-win32.c: static int normalize_path_in_utf8(FILE_NOTI +{ + wchar_t buf_in[MAX_PATH + 1]; + wchar_t buf_out[MAX_PATH + 1]; -+ wchar_t *last_slash = NULL; -+ wchar_t *last_bslash = NULL; + wchar_t *last; ++ wchar_t *p; +
Quoted 8 lines
+ /* build L"<wt-root-path>/.git" */
-+ wcscpy(buf_in, watch->wpath_longname);
-+ wcscpy(buf_in + watch->wpath_longname_len, L".git");
++ swprintf(buf_in, ARRAY_SIZE(buf_in) - 1, L"%s.git",
++ watch->wpath_longname);
+
-+ if (!GetShortPathNameW(buf_in, buf_out, MAX_PATH))
++ if (!GetShortPathNameW(buf_in, buf_out, ARRAY_SIZE(buf_out)))Nice touch using ARRAY_SIZE() here!
The changes look good to me, from my side this is good to go.
Thank you so much!
Dscho
Quoted 24 lines · Johannes Schindelin
Hi Jeff,
Quoted 11 lines · Jeff Hostetler via GitGitGadget
1:
8b7c5f4e23! 1:26144c5865fsm-listen-win32: handle shortnames[...] @@ compat/fsmonitor/fsm-listen-win32.c: static int normalize_path_in_utf8(FILE_NOTI +{ + wchar_t buf_in[MAX_PATH + 1]; + wchar_t buf_out[MAX_PATH + 1]; -+ wchar_t *last_slash = NULL; -+ wchar_t *last_bslash = NULL; + wchar_t *last; ++ wchar_t *p; +Quoted 8 lines
+ /* build L"<wt-root-path>/.git" */ -+ wcscpy(buf_in, watch->wpath_longname); -+ wcscpy(buf_in + watch->wpath_longname_len, L".git"); ++ swprintf(buf_in, ARRAY_SIZE(buf_in) - 1, L"%s.git", ++ watch->wpath_longname); + -+ if (!GetShortPathNameW(buf_in, buf_out, MAX_PATH)) ++ if (!GetShortPathNameW(buf_in, buf_out, ARRAY_SIZE(buf_out)))
Quoted 1 line
Nice touch using
ARRAY_SIZE()here!
Thanks. And hopefully it will make the GFW downstream MAX_LONG_PATH fixups easier too.
Quoted 6 lines
The changes look good to me, from my side this is good to go.
Thank you so much!
Dscho
Thanks for all your reviews.
Jeff
Hi Jeff,
Quoted 25 lines · Jeff Hostetler
Quoted 24 lines · Johannes Schindelin
Quoted 20 lines · Jeff Hostetler via GitGitGadget
1:
8b7c5f4e23! 1:26144c5865fsm-listen-win32: handle shortnames[...] @@ compat/fsmonitor/fsm-listen-win32.c: static int normalize_path_in_utf8(FILE_NOTI +{ + wchar_t buf_in[MAX_PATH + 1]; + wchar_t buf_out[MAX_PATH + 1]; -+ wchar_t *last_slash = NULL; -+ wchar_t *last_bslash = NULL; + wchar_t *last; ++ wchar_t *p; + + /* build L"<wt-root-path>/.git" */ -+ wcscpy(buf_in, watch->wpath_longname); -+ wcscpy(buf_in + watch->wpath_longname_len, L".git"); ++ swprintf(buf_in, ARRAY_SIZE(buf_in) - 1, L"%s.git", ++ watch->wpath_longname); + -+ if (!GetShortPathNameW(buf_in, buf_out, MAX_PATH)) ++ if (!GetShortPathNameW(buf_in, buf_out, ARRAY_SIZE(buf_out)))
Quoted 4 lines
Quoted 1 line
Nice touch using
ARRAY_SIZE()here!Thanks. And hopefully it will make the GFW downstream
MAX_LONG_PATHfixups easier too.
As a matter of fact, it already did!
Thank you,
Dscho
v8 cover Builtin FSMonitor Part 3
In reply to Jeff Hostetler via GitGitGadgetHere is version 8 of part 3 of FSMonitor.
This version addresses the new t/helper/test-hexdump utility to emit an LF at the end. I also updated the test scripts to write hexdump output to a file and then grep that file to avoid having the tool being in the middle of a pipeline.
Jeff Hostetler (30):
fsm-listen-win32: handle shortnames
t7527: test FSMonitor on repos with Unicode root paths t/helper/fsmonitor-client: create stress test
fsmonitor-settings: bare repos are incompatible with FSMonitor
fsmonitor-settings: stub in Win32-specific incompatibility checking
fsmonitor-settings: VFS for Git virtual repos are incompatible
fsmonitor-settings: stub in macOS-specific incompatibility checking
fsmonitor-settings: remote repos on macOS are incompatible
fsmonitor-settings: remote repos on Windows are incompatible
fsmonitor-settings: NTFS and FAT32 on MacOS are incompatible
unpack-trees: initialize fsmonitor_has_run_once in o->result
fsm-listen-darwin: ignore FSEvents caused by xattr changes on macOS
fsmonitor--daemon: cd out of worktree root
fsmonitor--daemon: prepare for adding health thread
fsmonitor--daemon: rename listener thread related variables
fsmonitor--daemon: stub in health thread
fsm-health-win32: add polling framework to monitor daemon health
fsm-health-win32: force shutdown daemon if worktree root moves
fsm-listen-darwin: shutdown daemon if worktree root is moved/renamed
fsmonitor: optimize processing of directory events
t7527: FSMonitor tests for directory moves
t/perf/p7527: add perf test for builtin FSMonitor
fsmonitor: never set CE_FSMONITOR_VALID on submodules
t7527: test FSMonitor on case insensitive+preserving file system
fsmonitor: on macOS also emit NFC spelling for NFD pathname t/helper/hexdump: add helper to print hexdump of stdin t/lib-unicode-nfc-nfd: helper prereqs for testing unicode nfc/nfd
t7527: test Unicode NFC/NFD handling on MacOS
fsmonitor--daemon: allow --super-prefix argument
t7527: improve implicit shutdown testing in fsmonitor--daemon
Makefile | 20 +- builtin/fsmonitor--daemon.c | 116 ++++++- builtin/update-index.c | 16 + compat/fsmonitor/fsm-health-darwin.c | 24 ++ compat/fsmonitor/fsm-health-win32.c | 278 ++++++++++++++++ compat/fsmonitor/fsm-health.h | 47 +++ compat/fsmonitor/fsm-listen-darwin.c | 122 ++++++- compat/fsmonitor/fsm-listen-win32.c | 436 ++++++++++++++++++++----- compat/fsmonitor/fsm-listen.h | 2 +- compat/fsmonitor/fsm-settings-darwin.c | 89 +++++ compat/fsmonitor/fsm-settings-win32.c | 137 ++++++++ config.mak.uname | 5 + contrib/buildsystems/CMakeLists.txt | 8 + fsmonitor--daemon.h | 11 +- fsmonitor-settings.c | 167 ++++++++-- fsmonitor-settings.h | 33 ++ fsmonitor.c | 73 ++++- fsmonitor.h | 11 + git.c | 2 +- t/helper/test-fsmonitor-client.c | 106 ++++++ t/helper/test-hexdump.c | 30 ++ t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + t/lib-unicode-nfc-nfd.sh | 162 +++++++++ t/perf/p7527-builtin-fsmonitor.sh | 257 +++++++++++++++ t/t7519-status-fsmonitor.sh | 32 ++ t/t7527-builtin-fsmonitor.sh | 401 ++++++++++++++++++++++- unpack-trees.c | 1 + 28 files changed, 2439 insertions(+), 149 deletions(-) create mode 100644 compat/fsmonitor/fsm-health-darwin.c create mode 100644 compat/fsmonitor/fsm-health-win32.c create mode 100644 compat/fsmonitor/fsm-health.h create mode 100644 compat/fsmonitor/fsm-settings-darwin.c create mode 100644 compat/fsmonitor/fsm-settings-win32.c create mode 100644 t/helper/test-hexdump.c create mode 100755 t/lib-unicode-nfc-nfd.sh create mode 100755 t/perf/p7527-builtin-fsmonitor.sh
base-commit: 5eb696daba2fe108d4d9ba2ccf4b357447ef9946
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1143%2Fjeffhostetler%2Fbuiltin-fsmonitor-part3-v8
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1143/jeffhostetler/builtin-fsmonitor-part3-v8
Pull-Request: https://github.com/gitgitgadget/git/pull/1143
Range-diff vs v7:
1: 26144c58659 = 1: 26144c58659 fsm-listen-win32: handle shortnames
2: 1bf2e36b6ad = 2: 1bf2e36b6ad t7527: test FSMonitor on repos with Unicode root paths
3: 4bca494bb22 = 3: 4bca494bb22 t/helper/fsmonitor-client: create stress test
4: 663deabc3f6 = 4: 663deabc3f6 fsmonitor-settings: bare repos are incompatible with FSMonitor
5: 7cb0180a1ed = 5: 7cb0180a1ed fsmonitor-settings: stub in Win32-specific incompatibility checking
6: 9774faddc45 = 6: 9774faddc45 fsmonitor-settings: VFS for Git virtual repos are incompatible
7: f7ef7dcffc8 = 7: f7ef7dcffc8 fsmonitor-settings: stub in macOS-specific incompatibility checking
8: dc2dfd67931 = 8: dc2dfd67931 fsmonitor-settings: remote repos on macOS are incompatible
9: 5627038aaa3 = 9: 5627038aaa3 fsmonitor-settings: remote repos on Windows are incompatible
10: 9a12cc78b5d = 10: 9a12cc78b5d fsmonitor-settings: NTFS and FAT32 on MacOS are incompatible
11: aaff000cecb = 11: aaff000cecb unpack-trees: initialize fsmonitor_has_run_once in o->result
12: 4f2b15d3d1f = 12: 4f2b15d3d1f fsm-listen-darwin: ignore FSEvents caused by xattr changes on macOS
13: 427dec412a5 = 13: 427dec412a5 fsmonitor--daemon: cd out of worktree root
14: 51b266b06e1 = 14: 51b266b06e1 fsmonitor--daemon: prepare for adding health thread
15: 594e0ae243d = 15: 594e0ae243d fsmonitor--daemon: rename listener thread related variables
16: c2b5c02ed38 = 16: c2b5c02ed38 fsmonitor--daemon: stub in health thread
17: 46a5ae2a635 = 17: 46a5ae2a635 fsm-health-win32: add polling framework to monitor daemon health
18: 7cf1be5f8e2 = 18: 7cf1be5f8e2 fsm-health-win32: force shutdown daemon if worktree root moves
19: 95cf1299d44 = 19: 95cf1299d44 fsm-listen-darwin: shutdown daemon if worktree root is moved/renamed
20: b020bfb4568 = 20: b020bfb4568 fsmonitor: optimize processing of directory events
21: d058d7e0c08 = 21: d058d7e0c08 t7527: FSMonitor tests for directory moves
22: f5dac286812 = 22: f5dac286812 t/perf/p7527: add perf test for builtin FSMonitor
23: 92f5c0d2c8b = 23: 92f5c0d2c8b fsmonitor: never set CE_FSMONITOR_VALID on submodules
24: 40b80adbb31 = 24: 40b80adbb31 t7527: test FSMonitor on case insensitive+preserving file system
25: b93f0642699 = 25: ea19a06e8cb fsmonitor: on macOS also emit NFC spelling for NFD pathname
26: 6f2e935f148 ! 26: 66a01db4739 t/helper/hexdump: add helper to print hexdump of stdin
@@
## Metadata ##
-Author: Jeff Hostetler <[email protected]>
+Author: Jeff Hostetler <[email protected]>
## Commit message ##
t/helper/hexdump: add helper to print hexdump of stdin
Co-authored-by: Johannes Schindelin <[email protected]>
Signed-off-by: Johannes Schindelin <[email protected]>
- Signed-off-by: Jeff Hostetler <[email protected]>
+ Signed-off-by: Jeff Hostetler <[email protected]>
## Makefile ##
@@ Makefile: TEST_BUILTINS_OBJS += test-getcwd.o
@@ t/helper/test-hexdump.c (new)
+{
+ char buf[1024];
+ ssize_t i, len;
++ int have_data = 0;
+
+ for (;;) {
+ len = xread(0, buf, sizeof(buf));
@@ t/helper/test-hexdump.c (new)
+ if (!len)
+ break;
+
++ have_data = 1;
++
+ for (i = 0; i < len; i++)
+ printf("%02x ", (unsigned char)buf[i]);
+ }
+
++ if (have_data)
++ putchar('\n');
++
+ return 0;
+} 27: 6a830869954 ! 27: 25c6066eddc t/lib-unicode-nfc-nfd: helper prereqs for testing unicode nfc/nfd
@@ t/lib-unicode-nfc-nfd.sh (new)
+#
+test_lazy_prereq UNICODE_NFC_PRESERVED '
+ mkdir c_${utf8_nfc} &&
-+ ls | test-tool hexdump | grep "63 5f c3 a9"
++ ls | test-tool hexdump >dump &&
++ grep "63 5f c3 a9" dump
+'
+
+# Is the spelling of an NFD pathname preserved on disk?
+#
+test_lazy_prereq UNICODE_NFD_PRESERVED '
+ mkdir d_${utf8_nfd} &&
-+ ls | test-tool hexdump | grep "64 5f 65 cc 81"
++ ls | test-tool hexdump >dump &&
++ grep "64 5f 65 cc 81" dump
+'
+
+# The following _DOUBLE_ forms are more for my curiosity,
@@ t/lib-unicode-nfc-nfd.sh (new)
+#
+test_lazy_prereq UNICODE_DOUBLE_NFC_PRESERVED '
+ mkdir c_${greek_nfc} &&
-+ ls | test-tool hexdump | grep "63 5f e1 bd a7"
++ ls | test-tool hexdump >dump &&
++ grep "63 5f e1 bd a7" dump
+'
+
+# See if the NFD spelling appears on the disk.
+#
+test_lazy_prereq UNICODE_DOUBLE_NFD_PRESERVED '
+ mkdir d_${greek_nfd2} &&
-+ ls | test-tool hexdump | grep "64 5f cf 89 cc 94 cd 82"
++ ls | test-tool hexdump >dump &&
++ grep "64 5f cf 89 cc 94 cd 82" dump
+'
+
+# The following is for debugging. I found it useful when
28: f9a7869d202 = 28: fc3a0e7847f t7527: test Unicode NFC/NFD handling on MacOS
29: 9fc7c970929 = 29: 25676ca4ec2 fsmonitor--daemon: allow --super-prefix argument
30: ca833ecc7a1 ! 30: d70d2545a5a t7527: improve implicit shutdown testing in fsmonitor--daemon @@
## Metadata ##
-Author: Jeff Hostetler <[email protected]> +Author: Jeff Hostetler <[email protected]>
## Commit message ##
t7527: improve implicit shutdown testing in fsmonitor--daemon
@@ Commit message
Replace the simple sleep with a sleep-and-retry loop.
- Signed-off-by: Jeff Hostetler <[email protected]>
+ Signed-off-by: Jeff Hostetler <[email protected]>
## t/t7527-builtin-fsmonitor.sh ##
@@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'implicit daemon start' '
Signature · 1 line
gitgitgadget
PATCH v8 1/30 fsm-listen-win32: handle shortnames
From: Jeff Hostetler <[email protected]>
Teach FSMonitor daemon on Windows to recognize shortname paths as aliases of normal longname paths. FSMonitor clients, such as git status, should receive the longname spelling of changed files (when possible).
Sometimes we receive FS events using the shortname, such as when a CMD shell runs "RENAME GIT~1 FOO" or "RMDIR GIT~1". The FS notification arrives using whatever combination of long and shortnames were used by the other process. (Shortnames do seem to be case normalized, however.)
Use Windows GetLongPathNameW() to try to map the pathname spelling in the notification event into the normalized longname spelling. (This can fail if the file/directory is deleted, moved, or renamed, because we are asking the FS for the mapping in response to the event and after it has already happened, but we try.)
Special case the shortname spelling of ".git" to avoid under-reporting these events.
--- compat/fsmonitor/fsm-listen-win32.c | 386 ++++++++++++++++++++++++---- t/t7527-builtin-fsmonitor.sh | 65 +++++ 2 files changed, 397 insertions(+), 54 deletions(-)
compat/fsmonitor/fsm-listen-win32.c +332 -54
@@ -25,6 +25,9 @@ struct one_watchDWORD count;struct strbuf path; +wchar_t wpath_longname[MAX_PATH +1 ]; +DWORD wpath_longname_len; +HANDLE hDir;HANDLE hEvent;OVERLAPPED overlapped;
@@ -34,6 +37,21 @@ struct one_watch* need to later call GetOverlappedResult() and possibly CancelIoEx(). */ BOOL is_active; + +/* +* Are shortnames enabled on the containing drive? This is +* always true for "C:/" drives and usually never true for +* other drives. +* +* We only set this for the worktree because we only need to +* convert shortname paths to longname paths for items we send +* to clients. (We don't care about shortname expansion for +* paths inside a GITDIR because we never send them to +* clients.) +*/ +BOOL has_shortnames; +BOOL has_tilde; +wchar_t dotgit_shortname[16 ];/* for 8.3 name */ };struct fsmonitor_daemon_backend_data
@@ -51,17 +69,18 @@ struct fsmonitor_daemon_backend_data };/* -* Convert the WCHAR path from the notification into UTF8 and -* then normalize it. +* Convert the WCHAR path from the event into UTF8 and normalize it. +* +* `wpath_len` is in WCHARS not bytes. */ -static int normalize_path_in_utf8 (FILE_NOTIFY_INFORMATION *info, +static int normalize_path_in_utf8 (wchar_t *wpath,DWORD wpath_len,struct strbuf *normalized_path) {int reserve;int len =0 ;strbuf_reset (normalized_path); -if (!info->FileNameLength) +if (!wpath_len) goto normalize;/*
@@ -70,12 +89,12 @@ static int normalize_path_in_utf8(FILE_NOTIFY_INFORMATION *info,* sequence of 2 UTF8 characters. That should let us * avoid ERROR_INSUFFICIENT_BUFFER 99.9+% of the time. */ - reserve = info->FileNameLength +1 ; + reserve =2 * wpath_len +1 ;strbuf_grow (normalized_path, reserve);for (;;) { - len =WideCharToMultiByte (CP_UTF8 ,0 , info->FileName, - info->FileNameLength /sizeof (WCHAR ), + len =WideCharToMultiByte (CP_UTF8 ,0 , + wpath, wpath_len, normalized_path->buf,strbuf_avail (normalized_path) -1 ,NULL ,NULL );
@@ -83,9 +102,7 @@ static int normalize_path_in_utf8(FILE_NOTIFY_INFORMATION *info, goto normalize;if (GetLastError () !=ERROR_INSUFFICIENT_BUFFER ) {error (_ ("[GLE %ld] could not convert path to UTF-8: '%.*ls'" ), -GetLastError (), - (int )(info->FileNameLength /sizeof (WCHAR )), - info->FileName); +GetLastError (), (int )wpath_len, wpath);return -1 ; }
@@ -98,6 +115,173 @@ normalize:return strbuf_normalize_path (normalized_path); } +/* +* See if the worktree root directory has shortnames enabled. +* This will help us decide if we need to do an expensive shortname +* to longname conversion on every notification event. +* +* We do not want to create a file to test this, so we assume that the +* root directory contains a ".git" file or directory. (Our caller +* only calls us for the worktree root, so this should be fine.) +* +* Remember the spelling of the shortname for ".git" if it exists. +*/ +static void check_for_shortnames (struct one_watch *watch) +{ +wchar_t buf_in[MAX_PATH +1 ]; +wchar_t buf_out[MAX_PATH +1 ]; +wchar_t *last; +wchar_t *p; + +/* build L"<wt-root-path>/.git" */ +swprintf (buf_in,ARRAY_SIZE (buf_in) -1 ,L"%s.git" , + watch->wpath_longname); + +if (!GetShortPathNameW (buf_in, buf_out,ARRAY_SIZE (buf_out))) +return ; + +/* +* Get the final filename component of the shortpath. +* We know that the path does not have a final slash. +*/ +for (last = p = buf_out; *p; p++) +if (*p ==L'/' || *p =='\\' ) + last = p +1 ; + +if (!wcscmp (last,L".git" )) +return ; + + watch->has_shortnames =1 ; +wcsncpy (watch->dotgit_shortname, last, +ARRAY_SIZE (watch->dotgit_shortname)); + +/* +* The shortname for ".git" is usually of the form "GIT~1", so +* we should be able to avoid shortname to longname mapping on +* every notification event if the source string does not +* contain a "~". +* +* However, the documentation for GetLongPathNameW() says +* that there are filesystems that don't follow that pattern +* and warns against this optimization. +* +* Lets test this. +*/ +if (wcschr (watch->dotgit_shortname,L'~' )) + watch->has_tilde =1 ; +} + +enum get_relative_result { +GRR_NO_CONVERSION_NEEDED , +GRR_HAVE_CONVERSION , +GRR_SHUTDOWN , +}; + +/* +* Info notification paths are relative to the root of the watch. +* If our CWD is still at the root, then we can use relative paths +* to convert from shortnames to longnames. If our process has a +* different CWD, then we need to construct an absolute path, do +* the conversion, and then return the root-relative portion. +* +* We use the longname form of the root as our basis and assume that +* it already has a trailing slash. +* +* `wpath_len` is in WCHARS not bytes. +*/ +static enum get_relative_result get_relative_longname ( +struct one_watch *watch, +const wchar_t *wpath,DWORD wpath_len, +wchar_t *wpath_longname,size_t bufsize_wpath_longname) +{ +wchar_t buf_in[2 *MAX_PATH +1 ]; +wchar_t buf_out[MAX_PATH +1 ]; +DWORD root_len; +DWORD out_len; + +/* +* Build L"<wt-root-path>/<event-rel-path>" +* Note that the <event-rel-path> might not be null terminated +* so we avoid swprintf() constructions. +*/ + root_len = watch->wpath_longname_len; +if (root_len + wpath_len >=ARRAY_SIZE (buf_in)) { +/* +* This should not happen. We cannot append the observed +* relative path onto the end of the worktree root path +* without overflowing the buffer. Just give up. +*/ +return GRR_SHUTDOWN ; + } +wcsncpy (buf_in, watch->wpath_longname, root_len); +wcsncpy (buf_in + root_len, wpath, wpath_len); + buf_in[root_len + wpath_len] =0 ; + +/* +* We don't actually know if the source pathname is a +* shortname or a longname. This Windows routine allows +* either to be given as input. +*/ + out_len =GetLongPathNameW (buf_in, buf_out,ARRAY_SIZE (buf_out)); +if (!out_len) { +/* +* The shortname to longname conversion can fail for +* various reasons, for example if the file has been +* deleted. (That is, if we just received a +* delete-file notification event and the file is +* already gone, we can't ask the file system to +* lookup the longname for it. Likewise, for moves +* and renames where we are given the old name.) +* +* Since deleting or moving a file or directory by its +* shortname is rather obscure, I'm going ignore the +* failure and ask the caller to report the original +* relative path. This seems kinder than failing here +* and forcing a resync. Besides, forcing a resync on +* every file/directory delete would effectively +* cripple monitoring. +* +* We might revisit this in the future. +*/ +return GRR_NO_CONVERSION_NEEDED ; + } + +if (!wcscmp (buf_in, buf_out)) { +/* +* The path does not have a shortname alias. +*/ +return GRR_NO_CONVERSION_NEEDED ; + } + +if (wcsncmp (buf_in, buf_out, root_len)) { +/* +* The spelling of the root directory portion of the computed +* longname has changed. This should not happen. Basically, +* it means that we don't know where (without recomputing the +* longname of just the root directory) to split out the +* relative path. Since this should not happen, I'm just +* going to let this fail and force a shutdown (because all +* subsequent events are probably going to see the same +* mismatch). +*/ +return GRR_SHUTDOWN ; + } + +if (out_len - root_len >= bufsize_wpath_longname) { +/* +* This should not happen. We cannot copy the root-relative +* portion of the path into the provided buffer without an +* overrun. Just give up. +*/ +return GRR_SHUTDOWN ; + } + +/* Return the worktree root-relative portion of the longname. */ + +wcscpy (wpath_longname, buf_out + root_len); +return GRR_HAVE_CONVERSION ; +} +void fsm_listen__stop_async (struct fsmonitor_daemon_state *state) {SetEvent (state->backend_data->hListener[LISTENER_SHUTDOWN ]);
@@ -111,7 +295,9 @@ static struct one_watch *create_watch(struct fsmonitor_daemon_state *state,DWORD share_mode =FILE_SHARE_WRITE |FILE_SHARE_READ |FILE_SHARE_DELETE ;HANDLE hDir; -wchar_t wpath[MAX_PATH ]; +DWORD len_longname; +wchar_t wpath[MAX_PATH +1 ]; +wchar_t wpath_longname[MAX_PATH +1 ];if (xutftowcs_path (wpath, path) <0 ) {error (_ ("could not convert to wide characters: '%s'" ), path);
@@ -128,6 +314,21 @@ static struct one_watch *create_watch(struct fsmonitor_daemon_state *state,return NULL ; } + len_longname =GetLongPathNameW (wpath, wpath_longname, +ARRAY_SIZE (wpath_longname)); +if (!len_longname) { +error (_ ("[GLE %ld] could not get longname of '%s'" ), +GetLastError (), path); +CloseHandle (hDir); +return NULL ; + } + +if (wpath_longname[len_longname -1 ] !=L'/' && + wpath_longname[len_longname -1 ] !=L'\\' ) { + wpath_longname[len_longname++] =L'/' ; + wpath_longname[len_longname] =0 ; + } +CALLOC_ARRAY (watch,1 ); watch->buf_len =sizeof (watch->buffer);/* assume full MAX_RDCW_BUF */
@@ -135,6 +336,9 @@ static struct one_watch *create_watch(struct fsmonitor_daemon_state *state,strbuf_init (&watch->path,0 );strbuf_addstr (&watch->path, path); +wcscpy (watch->wpath_longname, wpath_longname); + watch->wpath_longname_len = len_longname; + watch->hDir = hDir; watch->hEvent =CreateEvent (NULL , TRUE, FALSE,NULL );
@@ -258,6 +462,62 @@ static void cancel_rdcw_watch(struct one_watch *watch) watch->is_active = FALSE; } +/* +* Process a single relative pathname event. +* Return 1 if we should shutdown. +*/ +static int process_1_worktree_event ( +struct string_list *cookie_list, +struct fsmonitor_batch **batch, +const struct strbuf *path, +enum fsmonitor_path_type t, +DWORD info_action) +{ +const char *slash; + +switch (t) { +case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX : +/* special case cookie files within .git */ + +/* Use just the filename of the cookie file. */ + slash =find_last_dir_sep (path->buf); +string_list_append (cookie_list, + slash ? slash +1 : path->buf); +break ; + +case IS_INSIDE_DOT_GIT : +/* ignore everything inside of "<worktree>/.git/" */ +break ; + +case IS_DOT_GIT : +/* "<worktree>/.git" was deleted (or renamed away) */ +if ((info_action ==FILE_ACTION_REMOVED ) || + (info_action ==FILE_ACTION_RENAMED_OLD_NAME )) { +trace2_data_string ("fsmonitor" ,NULL , +"fsm-listen/dotgit" , +"removed" ); +return 1 ; + } +break ; + +case IS_WORKDIR_PATH : +/* queue normal pathname */ +if (!*batch) + *batch =fsmonitor_batch__new (); +fsmonitor_batch__add_path (*batch, path->buf); +break ; + +case IS_GITDIR : +case IS_INSIDE_GITDIR : +case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX : +default : +BUG ("unexpected path classification '%d' for '%s'" , + t, path->buf); + } + +return 0 ; +} +/* * Process filesystem events that happen anywhere (recursively) under the * <worktree> root directory. For a normal working directory, this includes
@@ -274,6 +534,7 @@ static int process_worktree_events(struct fsmonitor_daemon_state *state)struct string_list cookie_list =STRING_LIST_INIT_DUP ;struct fsmonitor_batch *batch =NULL ;const char *p = watch->buffer; +wchar_t wpath_longname[MAX_PATH +1 ];/* * If the kernel gets more events than will fit in the kernel
@@ -306,54 +567,64 @@ static int process_worktree_events(struct fsmonitor_daemon_state *state)*/ for (;;) {FILE_NOTIFY_INFORMATION *info = (void *)p; -const char *slash; +wchar_t *wpath = info->FileName; +DWORD wpath_len = info->FileNameLength /sizeof (WCHAR );enum fsmonitor_path_type t; +enum get_relative_result grr; + +if (watch->has_shortnames) { +if (!wcscmp (wpath, watch->dotgit_shortname)) { +/* +* This event exactly matches the +* spelling of the shortname of +* ".git", so we can skip some steps. +* +* (This case is odd because the user +* can "rm -rf GIT~1" and we cannot +* use the filesystem to map it back +* to ".git".) +*/ +strbuf_reset (&path); +strbuf_addstr (&path,".git" ); + t =IS_DOT_GIT ; + goto process_it; + } -strbuf_reset (&path); -if (normalize_path_in_utf8 (info, &path) ==-1 ) - goto skip_this_path; - - t =fsmonitor_classify_path_workdir_relative (path.buf); - -switch (t) { -case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX : -/* special case cookie files within .git */ - -/* Use just the filename of the cookie file. */ - slash =find_last_dir_sep (path.buf); -string_list_append (&cookie_list, - slash ? slash +1 : path.buf); -break ; - -case IS_INSIDE_DOT_GIT : -/* ignore everything inside of "<worktree>/.git/" */ -break ; +if (watch->has_tilde && !wcschr (wpath,L'~' )) { +/* +* Shortnames on this filesystem have tildes +* and the notification path does not have +* one, so we assume that it is a longname. +*/ + goto normalize_it; + } -case IS_DOT_GIT : -/* "<worktree>/.git" was deleted (or renamed away) */ -if ((info->Action ==FILE_ACTION_REMOVED ) || - (info->Action ==FILE_ACTION_RENAMED_OLD_NAME )) { -trace2_data_string ("fsmonitor" ,NULL , -"fsm-listen/dotgit" , -"removed" ); + grr =get_relative_longname (watch, wpath, wpath_len, + wpath_longname, +ARRAY_SIZE (wpath_longname)); +switch (grr) { +case GRR_NO_CONVERSION_NEEDED :/* use info buffer as is */ +break ; +case GRR_HAVE_CONVERSION : + wpath = wpath_longname; + wpath_len =wcslen (wpath); +break ; +default : +case GRR_SHUTDOWN : goto force_shutdown; } -break ; + } -case IS_WORKDIR_PATH : -/* queue normal pathname */ -if (!batch) - batch =fsmonitor_batch__new (); -fsmonitor_batch__add_path (batch, path.buf); -break ; +normalize_it: +if (normalize_path_in_utf8 (wpath, wpath_len, &path) ==-1 ) + goto skip_this_path; -case IS_GITDIR : -case IS_INSIDE_GITDIR : -case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX : -default : -BUG ("unexpected path classification '%d' for '%s'" , - t, path.buf); - } + t =fsmonitor_classify_path_workdir_relative (path.buf); + +process_it: +if (process_1_worktree_event (&cookie_list, &batch, &path, t, + info->Action)) + goto force_shutdown; skip_this_path:if (!info->NextEntryOffset)
@@ -382,6 +653,9 @@ force_shutdown:* Note that we DO NOT get filesystem events on the external <gitdir> * itself (it is not inside something that we are watching). In particular, * we do not get an event if the external <gitdir> is deleted. +* +* Also, we do not care about shortnames within the external <gitdir>, since +* we never send these paths to clients. */ static int process_gitdir_events (struct fsmonitor_daemon_state *state) {
@@ -403,8 +677,10 @@ static int process_gitdir_events(struct fsmonitor_daemon_state *state)const char *slash;enum fsmonitor_path_type t; -strbuf_reset (&path); -if (normalize_path_in_utf8 (info, &path) ==-1 ) +if (normalize_path_in_utf8 ( + info->FileName, + info->FileNameLength /sizeof (WCHAR ), + &path) ==-1 ) goto skip_this_path; t =fsmonitor_classify_path_gitdir_relative (path.buf);
@@ -538,6 +814,8 @@ int fsm_listen__ctor(struct fsmonitor_daemon_state *state)if (!data->watch_worktree) goto failed; +check_for_shortnames (data->watch_worktree); +if (state->nr_paths_watching >1 ) { data->watch_gitdir =create_watch (state, state->path_gitdir_watch.buf);
t/t7527-builtin-fsmonitor.sh +65 -0
71 lines not on this page · raw message
Signature · 1 line
gitgitgadget
PATCH v8 2/30 t7527: test FSMonitor on repos with Unicode root paths
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create some test repos with UTF8 characters in the pathname of the root directory and verify that the builtin FSMonitor can watch them.
This test is mainly for Windows where we need to avoid *A() routines.
--- t/t7527-builtin-fsmonitor.sh | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+)
t/t7527-builtin-fsmonitor.sh +23 -0
@@ -671,4 +671,27 @@ do done done +# Test Unicode UTF-8 characters in the pathname of the working +# directory root. Use of "*A()" routines rather than "*W()" routines +# on Windows can sometimes lead to odd failures. +# +u1=$(printf "u_c3_a6__\xC3\xA6") +u2=$(printf "u_e2_99_ab__\xE2\x99\xAB") +u_values="$u1 $u2" +for u in $u_values +do + test_expect_success "unicode in repo root path: $u" ' + test_when_finished "stop_daemon_delete_repo $u" && + + git init "$u" && + echo 1 >"$u"/file1 && + git -C "$u" add file1 && + git -C "$u" config core.fsmonitor true && + + start_daemon -C "$u" && + git -C "$u" status >actual && + grep "new file: file1" actual + ' +done + test_done
Signature · 1 line
gitgitgadget
PATCH v8 3/30 t/helper/fsmonitor-client: create stress test
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create a stress test to hammer on the fsmonitor daemon. Create a client-side thread pool of n threads and have each of them make m requests as fast as they can.
We do not currently inspect the contents of the response. We're only interested in placing a heavy request load on the daemon.
This test is useful for interactive testing and various experimentation. For example, to place additional load on the daemon while another test is running. We currently do not have a test script that actually uses this helper. We might add such a test in the future.
--- t/helper/test-fsmonitor-client.c | 106 +++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+)
t/helper/test-fsmonitor-client.c +106 -0
@@ -7,6 +7,8 @@#include "cache.h" #include "parse-options.h" #include "fsmonitor-ipc.h" +#include "thread-utils.h" +#include "trace2.h" #ifndef HAVE_FSMONITOR_DAEMON_BACKEND int cmd__fsmonitor_client (int argc,const char **argv)
@@ -79,20 +81,121 @@ static int do_send_flush(void)return 0 ; } +struct hammer_thread_data +{ +pthread_t pthread_id; +int thread_nr; + +int nr_requests; +const char *token; + +int sum_successful; +int sum_errors; +}; + +static void *hammer_thread_proc (void *_hammer_thread_data) +{ +struct hammer_thread_data *data = _hammer_thread_data; +struct strbuf answer =STRBUF_INIT ; +int k; +int ret; + +trace2_thread_start ("hammer" ); + +for (k =0 ; k < data->nr_requests; k++) { +strbuf_reset (&answer); + + ret =fsmonitor_ipc__send_query (data->token, &answer); +if (ret <0 ) + data->sum_errors++; +else + data->sum_successful++; + } + +strbuf_release (&answer); +trace2_thread_exit (); +return NULL ; +} + +/* +* Start a pool of client threads that will each send a series of +* commands to the daemon. +* +* The goal is to overload the daemon with a sustained series of +* concurrent requests. +*/ +static int do_hammer (const char *token,int nr_threads,int nr_requests) +{ +struct hammer_thread_data *data =NULL ; +int k; +int sum_join_errors =0 ; +int sum_commands =0 ; +int sum_errors =0 ; + +if (!token || !*token) + token =get_token_from_index (); +if (nr_threads <1 ) + nr_threads =1 ; +if (nr_requests <1 ) + nr_requests =1 ; + +CALLOC_ARRAY (data, nr_threads); + +for (k =0 ; k < nr_threads; k++) { +struct hammer_thread_data *p = &data[k]; + p->thread_nr = k; + p->nr_requests = nr_requests; + p->token = token; + +if (pthread_create (&p->pthread_id,NULL , hammer_thread_proc, p)) { +warning ("failed to create thread[%d] skipping remainder" , k); + nr_threads = k; +break ; + } + } + +for (k =0 ; k < nr_threads; k++) { +struct hammer_thread_data *p = &data[k]; + +if (pthread_join (p->pthread_id,NULL )) + sum_join_errors++; + sum_commands += p->sum_successful; + sum_errors += p->sum_errors; + } + +fprintf (stderr,"HAMMER: [threads %d][requests %d] [ok %d][err %d][join %d]\n" , + nr_threads, nr_requests, sum_commands, sum_errors, sum_join_errors); + +free (data); + +/* +* Return an error if any of the _send_query requests failed. +* We don't care about thread create/join errors. +*/ +return sum_errors >0 ; +} +int cmd__fsmonitor_client (int argc,const char **argv) {const char *subcmd;const char *token =NULL ; +int nr_threads =1 ; +int nr_requests =1 ;const char *const fsmonitor_client_usage[] = {"test-tool fsmonitor-client query [<token>]" ,"test-tool fsmonitor-client flush" , +"test-tool fsmonitor-client hammer [<token>] [<threads>] [<requests>]" ,NULL , };struct option options[] = {OPT_STRING (0 ,"token" , &token,"token" ,"command token to send to the server" ), + +OPT_INTEGER (0 ,"threads" , &nr_threads,"number of client threads" ), +OPT_INTEGER (0 ,"requests" , &nr_requests,"number of requests per thread" ), +OPT_END () };
@@ -111,6 +214,9 @@ int cmd__fsmonitor_client(int argc, const char **argv)if (!strcmp (subcmd,"flush" ))return !!do_send_flush (); +if (!strcmp (subcmd,"hammer" )) +return !!do_hammer (token, nr_threads, nr_requests); + die("Unhandled subcommand: '%s'", subcmd); }#endif
Signature · 1 line
gitgitgadget
PATCH v8 4/30 fsmonitor-settings: bare repos are incompatible with FSMonitor
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Bare repos do not have a worktree, so there is nothing for the daemon watch.
--- builtin/fsmonitor--daemon.c | 18 +++++ builtin/update-index.c | 16 +++++ fsmonitor-settings.c | 133 ++++++++++++++++++++++++++++++------ fsmonitor-settings.h | 16 +++++ t/t7519-status-fsmonitor.sh | 23 +++++++ 5 files changed, 186 insertions(+), 20 deletions(-)
builtin/fsmonitor--daemon.c +18 -0
@@ -1423,6 +1423,7 @@ static int try_to_start_background_daemon(void)int cmd_fsmonitor__daemon (int argc,const char **argv,const char *prefix) {const char *subcmd; +enum fsmonitor_reason reason;int detach_console =0 ;struct option options[] = {
@@ -1449,6 +1450,23 @@ int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix)die (_ ("invalid 'ipc-threads' value (%d)" ), fsmonitor__ipc_threads); +prepare_repo_settings (the_repository); +/* +* If the repo is fsmonitor-compatible, explicitly set IPC-mode +* (without bothering to load the `core.fsmonitor` config settings). +* +* If the repo is not compatible, the repo-settings will be set to +* incompatible rather than IPC, so we can use one of the __get +* routines to detect the discrepancy. +*/ +fsm_settings__set_ipc (the_repository); + + reason =fsm_settings__get_reason (the_repository); +if (reason >FSMONITOR_REASON_OK ) +die ("%s" , +fsm_settings__get_incompatible_msg (the_repository, + reason)); +if (!strcmp (subcmd,"start" ))return !!try_to_start_background_daemon ();
builtin/update-index.c +16 -0
@@ -1237,6 +1237,22 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)if (fsmonitor >0 ) {enum fsmonitor_mode fsm_mode =fsm_settings__get_mode (r); +enum fsmonitor_reason reason =fsm_settings__get_reason (r); + +/* +* The user wants to turn on FSMonitor using the command +* line argument. (We don't know (or care) whether that +* is the IPC or HOOK version.) +* +* Use one of the __get routines to force load the FSMonitor +* config settings into the repo-settings. That will detect +* whether the file system is compatible so that we can stop +* here with a nice error message. +*/ +if (reason >FSMONITOR_REASON_OK ) +die ("%s" , +fsm_settings__get_incompatible_msg (r, reason)); +if (fsm_mode ==FSMONITOR_MODE_DISABLED ) { warning(_("core.fsmonitor is unset; " "set it if you really want to "
fsmonitor-settings.c +113 -20
200 lines not on this page · raw message
fsmonitor-settings.h +16 -0
@@ -4,18 +4,34 @@struct repository ;enum fsmonitor_mode { +FSMONITOR_MODE_INCOMPATIBLE =-1 ,/* see _reason */ FSMONITOR_MODE_DISABLED =0 ,FSMONITOR_MODE_HOOK =1 ,/* core.fsmonitor=<hook_path> */ FSMONITOR_MODE_IPC =2 ,/* core.fsmonitor=<true> */ }; +/* +* Incompatibility reasons. +*/ +enum fsmonitor_reason { +FSMONITOR_REASON_UNTESTED =0 , +FSMONITOR_REASON_OK ,/* no incompatibility or when disabled */ +FSMONITOR_REASON_BARE , +}; +void fsm_settings__set_ipc (struct repository *r);void fsm_settings__set_hook (struct repository *r,const char *path);void fsm_settings__set_disabled (struct repository *r); +void fsm_settings__set_incompatible (struct repository *r, +enum fsmonitor_reason reason);enum fsmonitor_mode fsm_settings__get_mode (struct repository *r);const char *fsm_settings__get_hook_path (struct repository *r); +enum fsmonitor_reason fsm_settings__get_reason (struct repository *r); +char *fsm_settings__get_incompatible_msg (const struct repository *r, +enum fsmonitor_reason reason); +struct fsmonitor_settings ;#endif /* FSMONITOR_SETTINGS_H */
t/t7519-status-fsmonitor.sh +23 -0
@@ -55,6 +55,29 @@ test_lazy_prereq UNTRACKED_CACHE ' test $ret -ne 1 ' +# Test that we detect and disallow repos that are incompatible with FSMonitor. +test_expect_success 'incompatible bare repo' ' + test_when_finished "rm -rf ./bare-clone actual expect" && + git init --bare bare-clone && + + test_must_fail \ + git -C ./bare-clone -c core.fsmonitor=foo \ + update-index --fsmonitor 2>actual && + grep "bare repository .* is incompatible with fsmonitor" actual && + + test_must_fail \ + git -C ./bare-clone -c core.fsmonitor=true \ + update-index --fsmonitor 2>actual && + grep "bare repository .* is incompatible with fsmonitor" actual +' + +test_expect_success FSMONITOR_DAEMON 'run fsmonitor-daemon in bare repo' ' + test_when_finished "rm -rf ./bare-clone actual" && + git init --bare bare-clone && + test_must_fail git -C ./bare-clone fsmonitor--daemon run 2>actual && + grep "bare repository .* is incompatible with fsmonitor" actual +' + test_expect_success 'setup' ' mkdir -p .git/hooks && : >tracked &&
Signature · 1 line
gitgitgadget
PATCH v8 5/30 fsmonitor-settings: stub in Win32-specific incompatibility checking
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Extend generic incompatibility checkout with platform-specific mechanism. Stub in Win32 version.
In the existing fsmonitor-settings code we have a way to mark types of repos as incompatible with fsmonitor (whether via the hook and IPC APIs). For example, we do this for bare repos, since there are no files to watch.
Extend this exclusion mechanism for platform-specific reasons. This commit just creates the framework and adds a stub for Win32.
--- Makefile | 13 +++++++++++++ compat/fsmonitor/fsm-settings-win32.c | 9 +++++++++ config.mak.uname | 4 ++++ contrib/buildsystems/CMakeLists.txt | 3 +++ fsmonitor-settings.c | 10 ++++++++++ fsmonitor-settings.h | 13 +++++++++++++ 6 files changed, 52 insertions(+) create mode 100644 compat/fsmonitor/fsm-settings-win32.c
Makefile +13 -0
@@ -475,6 +475,11 @@ all:: # `compat/fsmonitor/fsm-listen-<name>.c` that implements the # `fsm_listen__*()` routines. # +# If your platform has OS-specific ways to tell if a repo is incompatible with +# fsmonitor (whether the hook or IPC daemon version), set FSMONITOR_OS_SETTINGS +# to the "<name>" of the corresponding `compat/fsmonitor/fsm-settings-<name>.c` +# that implements the `fsm_os_settings__*()` routines. +# # Define DEVELOPER to enable more compiler warnings. Compiler version # and family are auto detected, but could be overridden by defining # COMPILER_FEATURES (see config.mak.dev). You can still set
@@ -1979,6 +1984,11 @@ ifdef FSMONITOR_DAEMON_BACKEND COMPAT_OBJS += compat/fsmonitor/fsm-listen-$(FSMONITOR_DAEMON_BACKEND).o endif +ifdef FSMONITOR_OS_SETTINGS + COMPAT_CFLAGS += -DHAVE_FSMONITOR_OS_SETTINGS + COMPAT_OBJS += compat/fsmonitor/fsm-settings-$(FSMONITOR_OS_SETTINGS).o +endif + ifeq ($(TCLTK_PATH),) NO_TCLTK = NoThanks endif
@@ -2901,6 +2911,9 @@ GIT-BUILD-OPTIONS: FORCE ifdef FSMONITOR_DAEMON_BACKEND @echo FSMONITOR_DAEMON_BACKEND=\''$(subst ','\'',$(subst ','\'',$(FSMONITOR_DAEMON_BACKEND)))'\' >>$@+ endif +ifdef FSMONITOR_OS_SETTINGS + @echo FSMONITOR_OS_SETTINGS=\''$(subst ','\'',$(subst ','\'',$(FSMONITOR_OS_SETTINGS)))'\' >>$@+ +endif ifdef TEST_OUTPUT_DIRECTORY @echo TEST_OUTPUT_DIRECTORY=\''$(subst ','\'',$(subst ','\'',$(TEST_OUTPUT_DIRECTORY)))'\' >>$@+ endif
compat/fsmonitor/fsm-settings-win32.c new file +9 -0
@@ -0,0 +1,9 @@ +#include "cache.h" +#include "config.h" +#include "repository.h" +#include "fsmonitor-settings.h" + +enum fsmonitor_reason fsm_os__incompatible (struct repository *r) +{ +return FSMONITOR_REASON_OK ; +}
config.mak.uname +4 -0
@@ -450,6 +450,8 @@ ifeq ($(uname_S),Windows) # These are always available, so we do not have to conditionally # support it. FSMONITOR_DAEMON_BACKEND = win32 + FSMONITOR_OS_SETTINGS = win32 + NO_SVN_TESTS = YesPlease RUNTIME_PREFIX = YesPlease HAVE_WPGMPTR = YesWeDo
@@ -639,6 +641,8 @@ ifeq ($(uname_S),MINGW) # These are always available, so we do not have to conditionally # support it. FSMONITOR_DAEMON_BACKEND = win32 + FSMONITOR_OS_SETTINGS = win32 + RUNTIME_PREFIX = YesPlease HAVE_WPGMPTR = YesWeDo NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
contrib/buildsystems/CMakeLists.txt +3 -0
@@ -289,6 +289,9 @@ if(SUPPORTS_SIMPLE_IPC) if(CMAKE_SYSTEM_NAME STREQUAL "Windows") add_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND) list(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-win32.c) + + add_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS) + list(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-win32.c) elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") add_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND) list(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-darwin.c)
fsmonitor-settings.c +10 -0
@@ -23,6 +23,16 @@ static enum fsmonitor_reason check_for_incompatible(struct repository *r)return FSMONITOR_REASON_BARE ; } +#ifdef HAVE_FSMONITOR_OS_SETTINGS + { +enum fsmonitor_reason reason; + + reason =fsm_os__incompatible (r); +if (reason !=FSMONITOR_REASON_OK ) +return reason; + } +#endif +return FSMONITOR_REASON_OK ; }
fsmonitor-settings.h +13 -0
@@ -34,4 +34,17 @@ char *fsm_settings__get_incompatible_msg(const struct repository *r,struct fsmonitor_settings ; +#ifdef HAVE_FSMONITOR_OS_SETTINGS +/* +* Ask platform-specific code whether the repository is incompatible +* with fsmonitor (both hook and ipc modes). For example, if the working +* directory is on a remote volume and mounted via a technology that does +* not support notification events, then we should not pretend to watch it. +* +* fsm_os__* routines should considered private to fsm_settings__ +* routines. +*/ +enum fsmonitor_reason fsm_os__incompatible (struct repository *r); +#endif /* HAVE_FSMONITOR_OS_SETTINGS */ +#endif /* FSMONITOR_SETTINGS_H */
Signature · 1 line
gitgitgadget
PATCH v8 6/30 fsmonitor-settings: VFS for Git virtual repos are incompatible
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
VFS for Git virtual repositories are incompatible with FSMonitor.
VFS for Git is a downstream fork of Git. It contains its own custom file system watcher that is aware of the virtualization. If a working directory is being managed by VFS for Git, we should not try to watch it because we may get incomplete results.
We do not know anything about how VFS for Git works, but we do know that VFS for Git working directories contain a well-defined config setting. If it is set, mark the working directory as incompatible.
--- compat/fsmonitor/fsm-settings-win32.c | 26 ++++++++++++++++++++++++++ fsmonitor-settings.c | 6 ++++++ fsmonitor-settings.h | 1 + t/t7519-status-fsmonitor.sh | 9 +++++++++ 4 files changed, 42 insertions(+)
compat/fsmonitor/fsm-settings-win32.c +26 -0
@@ -3,7 +3,33 @@#include "repository.h" #include "fsmonitor-settings.h" +/* +* VFS for Git is incompatible with FSMonitor. +* +* Granted, core Git does not know anything about VFS for Git and we +* shouldn't make assumptions about a downstream feature, but users +* can install both versions. And this can lead to incorrect results +* from core Git commands. So, without bringing in any of the VFS for +* Git code, do a simple config test for a published config setting. +* (We do not look at the various *_TEST_* environment variables.) +*/ +static enum fsmonitor_reason check_vfs4git (struct repository *r) +{ +const char *const_str; + +if (!repo_config_get_value (r,"core.virtualfilesystem" , &const_str)) +return FSMONITOR_REASON_VFS4GIT ; + +return FSMONITOR_REASON_OK ; +} +enum fsmonitor_reason fsm_os__incompatible (struct repository *r) { +enum fsmonitor_reason reason; + + reason =check_vfs4git (r); +if (reason !=FSMONITOR_REASON_OK ) +return reason; +return FSMONITOR_REASON_OK ; }
fsmonitor-settings.c +6 -0
@@ -207,6 +207,12 @@ char *fsm_settings__get_incompatible_msg(const struct repository *r,_ ("bare repository '%s' is incompatible with fsmonitor" ), xgetcwd()); goto done; + +case FSMONITOR_REASON_VFS4GIT : +strbuf_addf (&msg, +_ ("virtual repository '%s' is incompatible with fsmonitor" ), + r->worktree); + goto done; } BUG("Unhandled case in fsm_settings__get_incompatible_msg: '%d'",
fsmonitor-settings.h +1 -0
@@ -17,6 +17,7 @@ enum fsmonitor_reason {FSMONITOR_REASON_UNTESTED =0 ,FSMONITOR_REASON_OK ,/* no incompatibility or when disabled */ FSMONITOR_REASON_BARE , + FSMONITOR_REASON_VFS4GIT, /* VFS for Git virtualization */ };void fsm_settings__set_ipc (struct repository *r);
t/t7519-status-fsmonitor.sh +9 -0
@@ -78,6 +78,15 @@ test_expect_success FSMONITOR_DAEMON 'run fsmonitor-daemon in bare repo' ' grep "bare repository .* is incompatible with fsmonitor" actual ' +test_expect_success MINGW,FSMONITOR_DAEMON 'run fsmonitor-daemon in virtual repo' ' + test_when_finished "rm -rf ./fake-virtual-clone actual" && + git init fake-virtual-clone && + test_must_fail git -C ./fake-virtual-clone \ + -c core.virtualfilesystem=true \ + fsmonitor--daemon run 2>actual && + grep "virtual repository .* is incompatible with fsmonitor" actual +' + test_expect_success 'setup' ' mkdir -p .git/hooks && : >tracked &&
Signature · 1 line
gitgitgadget
PATCH v8 7/30 fsmonitor-settings: stub in macOS-specific incompatibility checking
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
--- compat/fsmonitor/fsm-settings-darwin.c | 9 +++++++++ config.mak.uname | 1 + contrib/buildsystems/CMakeLists.txt | 3 +++ 3 files changed, 13 insertions(+) create mode 100644 compat/fsmonitor/fsm-settings-darwin.c
compat/fsmonitor/fsm-settings-darwin.c new file +9 -0
@@ -0,0 +1,9 @@ +#include "cache.h" +#include "config.h" +#include "repository.h" +#include "fsmonitor-settings.h" + +enum fsmonitor_reason fsm_os__incompatible (struct repository *r) +{ +return FSMONITOR_REASON_OK ; +}
config.mak.uname +1 -0
@@ -163,6 +163,7 @@ ifeq ($(uname_S),Darwin) ifndef NO_PTHREADS ifndef NO_UNIX_SOCKETS FSMONITOR_DAEMON_BACKEND = darwin + FSMONITOR_OS_SETTINGS = darwin endif endif
contrib/buildsystems/CMakeLists.txt +3 -0
@@ -295,6 +295,9 @@ if(SUPPORTS_SIMPLE_IPC) elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") add_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND) list(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-darwin.c) + + add_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS) + list(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-darwin.c) endif() endif()
Signature · 1 line
gitgitgadget
PATCH v8 8/30 fsmonitor-settings: remote repos on macOS are incompatible
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Teach Git to detect remote working directories on macOS and mark them as incompatible with FSMonitor.
With this, git fsmonitor--daemon run will error out with a message like it does for bare repos.
Client commands, like git status, will not attempt to start the daemon.
--- compat/fsmonitor/fsm-settings-darwin.c | 66 ++++++++++++++++++++++++++ fsmonitor-settings.c | 12 +++++ fsmonitor-settings.h | 2 + 3 files changed, 80 insertions(+)
compat/fsmonitor/fsm-settings-darwin.c +66 -0
@@ -2,8 +2,74 @@#include "config.h" #include "repository.h" #include "fsmonitor-settings.h" +#include "fsmonitor.h" +#include <sys/param.h> +#include <sys/mount.h> + +/* +* Remote working directories are problematic for FSMonitor. +* +* The underlying file system on the server machine and/or the remote +* mount type (NFS, SAMBA, etc.) dictates whether notification events +* are available at all to remote client machines. +* +* Kernel differences between the server and client machines also +* dictate the how (buffering, frequency, de-dup) the events are +* delivered to client machine processes. +* +* A client machine (such as a laptop) may choose to suspend/resume +* and it is unclear (without lots of testing) whether the watcher can +* resync after a resume. We might be able to treat this as a normal +* "events were dropped by the kernel" event and do our normal "flush +* and resync" --or-- we might need to close the existing (zombie?) +* notification fd and create a new one. +* +* In theory, the above issues need to be addressed whether we are +* using the Hook or IPC API. +* +* For the builtin FSMonitor, we create the Unix domain socket for the +* IPC in the .git directory. If the working directory is remote, +* then the socket will be created on the remote file system. This +* can fail if the remote file system does not support UDS file types +* (e.g. smbfs to a Windows server) or if the remote kernel does not +* allow a non-local process to bind() the socket. (These problems +* could be fixed by moving the UDS out of the .git directory and to a +* well-known local directory on the client machine, but care should +* be taken to ensure that $HOME is actually local and not a managed +* file share.) +* +* So (for now at least), mark remote working directories as +* incompatible. +*/ +static enum fsmonitor_reason check_remote (struct repository *r) +{ +struct statfs fs; + +if (statfs (r->worktree, &fs) ==-1 ) { +int saved_errno = errno; +trace_printf_key (&trace_fsmonitor,"statfs('%s') failed: %s" , + r->worktree,strerror (saved_errno)); + errno = saved_errno; +return FSMONITOR_REASON_ERROR ; + } + +trace_printf_key (&trace_fsmonitor, +"statfs('%s') [type 0x%08x][flags 0x%08x] '%s'" , + r->worktree, fs.f_type, fs.f_flags, fs.f_fstypename); + +if (!(fs.f_flags &MNT_LOCAL )) +return FSMONITOR_REASON_REMOTE ; + +return FSMONITOR_REASON_OK ; +}enum fsmonitor_reason fsm_os__incompatible (struct repository *r) { +enum fsmonitor_reason reason; + + reason =check_remote (r); +if (reason !=FSMONITOR_REASON_OK ) +return reason; +return FSMONITOR_REASON_OK ; }
fsmonitor-settings.c +12 -0
@@ -208,6 +208,18 @@ char *fsm_settings__get_incompatible_msg(const struct repository *r, xgetcwd()); goto done; +case FSMONITOR_REASON_ERROR : +strbuf_addf (&msg, +_ ("repository '%s' is incompatible with fsmonitor due to errors" ), + r->worktree); + goto done; + +case FSMONITOR_REASON_REMOTE : +strbuf_addf (&msg, +_ ("remote repository '%s' is incompatible with fsmonitor" ), + r->worktree); + goto done; +case FSMONITOR_REASON_VFS4GIT : strbuf_addf(&msg, _("virtual repository '%s' is incompatible with fsmonitor"),
fsmonitor-settings.h +2 -0
@@ -17,6 +17,8 @@ enum fsmonitor_reason {FSMONITOR_REASON_UNTESTED =0 ,FSMONITOR_REASON_OK ,/* no incompatibility or when disabled */ FSMONITOR_REASON_BARE , +FSMONITOR_REASON_ERROR ,/* FS error probing for compatibility */ +FSMONITOR_REASON_REMOTE , FSMONITOR_REASON_VFS4GIT, /* VFS for Git virtualization */ };
Signature · 1 line
gitgitgadget
PATCH v8 9/30 fsmonitor-settings: remote repos on Windows are incompatible
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Teach Git to detect remote working directories on Windows and mark them as incompatible with FSMonitor.
With this git fsmonitor--daemon run will error out with a message like it does for bare repos.
Client commands, such as git status, will not attempt to start the daemon.
--- compat/fsmonitor/fsm-settings-win32.c | 102 ++++++++++++++++++++++++++ 1 file changed, 102 insertions(+)
compat/fsmonitor/fsm-settings-win32.c +102 -0
@@ -2,6 +2,7 @@#include "config.h" #include "repository.h" #include "fsmonitor-settings.h" +#include "fsmonitor.h" /* * VFS for Git is incompatible with FSMonitor.
@@ -23,6 +24,103 @@ static enum fsmonitor_reason check_vfs4git(struct repository *r)return FSMONITOR_REASON_OK ; } +/* +* Remote working directories are problematic for FSMonitor. +* +* The underlying file system on the server machine and/or the remote +* mount type dictates whether notification events are available at +* all to remote client machines. +* +* Kernel differences between the server and client machines also +* dictate the how (buffering, frequency, de-dup) the events are +* delivered to client machine processes. +* +* A client machine (such as a laptop) may choose to suspend/resume +* and it is unclear (without lots of testing) whether the watcher can +* resync after a resume. We might be able to treat this as a normal +* "events were dropped by the kernel" event and do our normal "flush +* and resync" --or-- we might need to close the existing (zombie?) +* notification fd and create a new one. +* +* In theory, the above issues need to be addressed whether we are +* using the Hook or IPC API. +* +* So (for now at least), mark remote working directories as +* incompatible. +* +* Notes for testing: +* +* (a) Windows allows a network share to be mapped to a drive letter. +* (This is the normal method to access it.) +* +* $ NET USE Z: \\server\share +* $ git -C Z:/repo status +* +* (b) Windows allows a network share to be referenced WITHOUT mapping +* it to drive letter. +* +* $ NET USE \\server\share\dir +* $ git -C //server/share/repo status +* +* (c) Windows allows "SUBST" to create a fake drive mapping to an +* arbitrary path (which may be remote) +* +* $ SUBST Q: Z:\repo +* $ git -C Q:/ status +* +* (d) Windows allows a directory symlink to be created on a local +* file system that points to a remote repo. +* +* $ mklink /d ./link //server/share/repo +* $ git -C ./link status +*/ +static enum fsmonitor_reason check_remote (struct repository *r) +{ +wchar_t wpath[MAX_PATH ]; +wchar_t wfullpath[MAX_PATH ]; +size_t wlen; +UINT driveType; + +/* +* Do everything in wide chars because the drive letter might be +* a multi-byte sequence. See win32_has_dos_drive_prefix(). +*/ +if (xutftowcs_path (wpath, r->worktree) <0 ) +return FSMONITOR_REASON_ERROR ; + +/* +* GetDriveTypeW() requires a final slash. We assume that the +* worktree pathname points to an actual directory. +*/ + wlen =wcslen (wpath); +if (wpath[wlen -1 ] !=L'\\' && wpath[wlen -1 ] !=L'/' ) { + wpath[wlen++] =L'\\' ; + wpath[wlen] =0 ; + } + +/* +* Normalize the path. If nothing else, this converts forward +* slashes to backslashes. This is essential to get GetDriveTypeW() +* correctly handle some UNC "\\server\share\..." paths. +*/ +if (!GetFullPathNameW (wpath,MAX_PATH , wfullpath,NULL )) +return FSMONITOR_REASON_ERROR ; + + driveType =GetDriveTypeW (wfullpath); +trace_printf_key (&trace_fsmonitor, +"DriveType '%s' L'%ls' (%u)" , + r->worktree, wfullpath, driveType); + +if (driveType ==DRIVE_REMOTE ) { +trace_printf_key (&trace_fsmonitor, +"check_remote('%s') true" , + r->worktree); +return FSMONITOR_REASON_REMOTE ; + } + +return FSMONITOR_REASON_OK ; +} +enum fsmonitor_reason fsm_os__incompatible (struct repository *r) {enum fsmonitor_reason reason;
@@ -31,5 +129,9 @@ enum fsmonitor_reason fsm_os__incompatible(struct repository *r)if (reason !=FSMONITOR_REASON_OK )return reason; + reason =check_remote (r); +if (reason !=FSMONITOR_REASON_OK ) +return reason; +return FSMONITOR_REASON_OK ; }
Signature · 1 line
gitgitgadget
PATCH v8 10/30 fsmonitor-settings: NTFS and FAT32 on MacOS are incompatible
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
On MacOS mark repos on NTFS or FAT32 volumes as incompatible.
The builtin FSMonitor used Unix domain sockets on MacOS for IPC with clients. These sockets are kept in the .git directory. Unix sockets are not supported by NTFS and FAT32, so the daemon cannot start up.
Test for this during our compatibility checking so that client commands do not keep trying to start the daemon.
--- compat/fsmonitor/fsm-settings-darwin.c | 20 +++++++++++++++++--- fsmonitor-settings.c | 6 ++++++ fsmonitor-settings.h | 1 + 3 files changed, 24 insertions(+), 3 deletions(-)
compat/fsmonitor/fsm-settings-darwin.c +17 -3
@@ -7,7 +7,7 @@#include <sys/mount.h> /* -* Remote working directories are problematic for FSMonitor. +* [1] Remote working directories are problematic for FSMonitor. * * The underlying file system on the server machine and/or the remote * mount type (NFS, SAMBA, etc.) dictates whether notification events
@@ -40,8 +40,16 @@* * So (for now at least), mark remote working directories as * incompatible. +* +* +* [2] FAT32 and NTFS working directories are problematic too. +* +* The builtin FSMonitor uses a Unix domain socket in the .git +* directory for IPC. These Windows drive formats do not support +* Unix domain sockets, so mark them as incompatible for the daemon. +* */ -static enum fsmonitor_reason check_remote (struct repository *r) +static enum fsmonitor_reason check_volume (struct repository *r) {struct statfs fs;
@@ -60,6 +68,12 @@ static enum fsmonitor_reason check_remote(struct repository *r)if (!(fs.f_flags &MNT_LOCAL ))return FSMONITOR_REASON_REMOTE ; +if (!strcmp (fs.f_fstypename,"msdos" ))/* aka FAT32 */ +return FSMONITOR_REASON_NOSOCKETS ; + +if (!strcmp (fs.f_fstypename,"ntfs" )) +return FSMONITOR_REASON_NOSOCKETS ; +return FSMONITOR_REASON_OK ; }
@@ -67,7 +81,7 @@ enum fsmonitor_reason fsm_os__incompatible(struct repository *r) {enum fsmonitor_reason reason; - reason =check_remote (r); + reason =check_volume (r);if (reason !=FSMONITOR_REASON_OK )return reason;
fsmonitor-settings.c +6 -0
@@ -225,6 +225,12 @@ char *fsm_settings__get_incompatible_msg(const struct repository *r,_ ("virtual repository '%s' is incompatible with fsmonitor" ), r->worktree); goto done; + +case FSMONITOR_REASON_NOSOCKETS : +strbuf_addf (&msg, +_ ("repository '%s' is incompatible with fsmonitor due to lack of Unix sockets" ), + r->worktree); + goto done; } BUG("Unhandled case in fsm_settings__get_incompatible_msg: '%d'",
fsmonitor-settings.h +1 -0
@@ -20,6 +20,7 @@ enum fsmonitor_reason {FSMONITOR_REASON_ERROR ,/* FS error probing for compatibility */ FSMONITOR_REASON_REMOTE ,FSMONITOR_REASON_VFS4GIT ,/* VFS for Git virtualization */ + FSMONITOR_REASON_NOSOCKETS, /* NTFS,FAT32 do not support Unix sockets */ };void fsm_settings__set_ipc (struct repository *r);
Signature · 1 line
gitgitgadget
PATCH v8 11/30 unpack-trees: initialize fsmonitor_has_run_once in o->result
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Initialize o->result.fsmonitor_has_run_once based upon value in o->src_index->fsmonitor_has_run_once to prevent a second fsmonitor query during the tree traversal and possibly getting a skewed view of the working directory.
The checkout code has already talked to the fsmonitor and the traversal is updating the index as it traverses, so there is no need to query the fsmonitor.
--- unpack-trees.c | 1 + 1 file changed, 1 insertion(+)
unpack-trees.c +1 -0
@@ -1772,6 +1772,7 @@ int unpack_trees(unsigned len, struct tree_desc *t, struct unpack_trees_options o->result.fsmonitor_last_update =xstrdup_or_null (o->src_index->fsmonitor_last_update); + o->result.fsmonitor_has_run_once = o->src_index->fsmonitor_has_run_once;/* * Sparse checkout loop #1: set NEW_SKIP_WORKTREE on existing entries
Signature · 1 line
gitgitgadget
PATCH v8 12/30 fsm-listen-darwin: ignore FSEvents caused by xattr changes on macOS
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Ignore FSEvents resulting from xattr changes. Git does not care about xattr's or changes to xattr's, so don't waste time collecting these events in the daemon nor transmitting them to clients.
Various security tools add xattrs to files and/or directories, such as to mark them as having been downloaded. We should ignore these events since it doesn't affect the content of the file/directory or the normal meta-data that Git cares about.
--- compat/fsmonitor/fsm-listen-darwin.c | 34 +++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-)
compat/fsmonitor/fsm-listen-darwin.c +33 -1
@@ -100,7 +100,7 @@ static void log_flags_set(const char *path, const FSEventStreamEventFlags flag)if (flag & kFSEventStreamEventFlagItemCloned)strbuf_addstr (&msg,"ItemCloned|" ); -trace_printf_key (&trace_fsmonitor,"fsevent: '%s', flags=%u %s" , +trace_printf_key (&trace_fsmonitor,"fsevent: '%s', flags=0x%x %s" , path, flag, msg.buf);strbuf_release (&msg);
@@ -125,6 +125,31 @@ static int ef_is_dropped(const FSEventStreamEventFlags ef) ef & kFSEventStreamEventFlagUserDropped); } +/* +* If an `xattr` change is the only reason we received this event, +* then silently ignore it. Git doesn't care about xattr's. We +* have to be careful here because the kernel can combine multiple +* events for a single path. And because events always have certain +* bits set, such as `ItemIsFile` or `ItemIsDir`. +* +* Return 1 if we should ignore it. +*/ +static int ef_ignore_xattr (const FSEventStreamEventFlags ef) +{ +static const FSEventStreamEventFlags mask = + kFSEventStreamEventFlagItemChangeOwner | + kFSEventStreamEventFlagItemCreated | + kFSEventStreamEventFlagItemFinderInfoMod | + kFSEventStreamEventFlagItemInodeMetaMod | + kFSEventStreamEventFlagItemModified | + kFSEventStreamEventFlagItemRemoved | + kFSEventStreamEventFlagItemRenamed | + kFSEventStreamEventFlagItemXattrMod | + kFSEventStreamEventFlagItemCloned; + +return ((ef & mask) == kFSEventStreamEventFlagItemXattrMod); +} + static void fsevent_callback(ConstFSEventStreamRef streamRef, void *ctx, size_t num_of_events,
@@ -190,6 +215,13 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef,continue ; } +if (ef_ignore_xattr (event_flags[k])) { +trace_printf_key (&trace_fsmonitor, +"ignore-xattr: '%s', flags=0x%x" , + path_k, event_flags[k]); +continue ; + } +switch (fsmonitor_classify_path_absolute (state, path_k)) {case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX :
Signature · 1 line
gitgitgadget
PATCH v8 13/30 fsmonitor--daemon: cd out of worktree root
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Teach the fsmonitor--daemon to CD outside of the worktree before starting up.
The common Git startup mechanism causes the CWD of the daemon process to be in the root of the worktree. On Windows, this causes the daemon process to hold a locked handle on the CWD and prevents other processes from moving or deleting the worktree while the daemon is running.
CD to HOME before entering main event loops.
--- builtin/fsmonitor--daemon.c | 32 +++++++++++++++++++++++++++-- compat/fsmonitor/fsm-listen-win32.c | 22 ++++++++++++++------ fsmonitor--daemon.h | 1 + 3 files changed, 47 insertions(+), 8 deletions(-)
builtin/fsmonitor--daemon.c +30 -2
@@ -1181,11 +1181,11 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)* before we need it. */ if (ipc_server_run_async (&state->ipc_server_data, -fsmonitor_ipc__get_path (), &ipc_opts, + state->path_ipc.buf, &ipc_opts, handle_client, state))return error_errno (_ ("could not start IPC thread pool on '%s'" ), -fsmonitor_ipc__get_path ()); + state->path_ipc.buf);/* * Start the fsmonitor listener thread to collect filesystem
@@ -1220,6 +1220,7 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)static int fsmonitor_run_daemon (void ) {struct fsmonitor_daemon_state state; +const char *home;int err;memset (&state,0 ,sizeof (state));
@@ -1289,6 +1290,15 @@ static int fsmonitor_run_daemon(void)strbuf_addch (&state.path_cookie_prefix,'/' ); +/* +* We create a named-pipe or unix domain socket inside of the +* ".git" directory. (Well, on Windows, we base our named +* pipe in the NPFS on the absolute path of the git +* directory.) +*/ +strbuf_init (&state.path_ipc,0 ); +strbuf_addstr (&state.path_ipc,absolute_path (fsmonitor_ipc__get_path ())); +/* * Confirm that we can create platform-specific resources for the * filesystem listener before we bother starting all the threads.
@@ -1298,6 +1308,23 @@ static int fsmonitor_run_daemon(void) goto done; } +/* +* CD out of the worktree root directory. +* +* The common Git startup mechanism causes our CWD to be the +* root of the worktree. On Windows, this causes our process +* to hold a locked handle on the CWD. This prevents the +* worktree from being moved or deleted while the daemon is +* running. +* +* We assume that our FS and IPC listener threads have either +* opened all of the handles that they need or will do +* everything using absolute paths. +*/ + home =getenv ("HOME" ); +if (home && *home &&chdir (home)) +die_errno (_ ("could not cd home '%s'" ), home); + err =fsmonitor_run_daemon_1 (&state); done:
@@ -1310,6 +1337,7 @@ done:strbuf_release (&state.path_worktree_watch);strbuf_release (&state.path_gitdir_watch);strbuf_release (&state.path_cookie_prefix); +strbuf_release (&state.path_ipc);return err; }
compat/fsmonitor/fsm-listen-win32.c +16 -6
@@ -424,12 +424,22 @@ static int recv_rdcw_watch(struct one_watch *watch) }/* -* NEEDSWORK: If an external <gitdir> is deleted, the above -* returns an error. I'm not sure that there's anything that -* we can do here other than failing -- the <worktree>/.git -* link file would be broken anyway. We might try to check -* for that and return a better error message, but I'm not -* sure it is worth it. +* GetOverlappedResult() fails if the watched directory is +* deleted while we were waiting for an overlapped IO to +* complete. The documentation did not list specific errors, +* but I observed ERROR_ACCESS_DENIED (0x05) errors during +* testing. +* +* Note that we only get notificaiton events for events +* *within* the directory, not *on* the directory itself. +* (These might be properies of the parent directory, for +* example). +* +* NEEDSWORK: We might try to check for the deleted directory +* case and return a better error message, but I'm not sure it +* is worth it. +* +* Shutdown if we get any error. */ error(_("GetOverlappedResult failed on '%s' [GLE %ld]"),
fsmonitor--daemon.h +1 -0
@@ -54,6 +54,7 @@ struct fsmonitor_daemon_state {struct fsmonitor_daemon_backend_data *backend_data;struct ipc_server_data *ipc_server_data; +struct strbuf path_ipc; };/*
Signature · 1 line
gitgitgadget
PATCH v8 14/30 fsmonitor--daemon: prepare for adding health thread
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Refactor daemon thread startup to make it easier to start a third thread class to monitor the health of the daemon.
--- builtin/fsmonitor--daemon.c | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-)
builtin/fsmonitor--daemon.c +20 -7
@@ -1174,6 +1174,8 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)*/ .uds_disallow_chdir = 0 }; +int listener_started =0 ; +int err =0 ;/* * Start the IPC thread pool before the we've started the file
@@ -1194,15 +1196,20 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)if (pthread_create (&state->listener_thread,NULL , fsm_listen__thread_proc, state) <0 ) {ipc_server_stop_async (state->ipc_server_data); -ipc_server_await (state->ipc_server_data); - -return error (_ ("could not start fsmonitor listener thread" )); + err =error (_ ("could not start fsmonitor listener thread" )); + goto cleanup; } + listener_started =1 ;/* * The daemon is now fully functional in background threads. +* Our primary thread should now just wait while the threads +* do all the work. +*/ +cleanup: +/* * Wait for the IPC thread pool to shutdown (whether by client -* request or from filesystem activity). +* request, from filesystem activity, or an error). */ ipc_server_await (state->ipc_server_data);
@@ -1211,10 +1218,16 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)* event from the IPC thread pool, but it doesn't hurt to tell * it again. And wait for it to shutdown. */ -fsm_listen__stop_async (state); -pthread_join (state->listener_thread,NULL ); +if (listener_started) { +fsm_listen__stop_async (state); +pthread_join (state->listener_thread,NULL ); + } -return state->error_code; +if (err) +return err; +if (state->error_code) +return state->error_code; +return 0 ; }static int fsmonitor_run_daemon (void )
Signature · 1 line
gitgitgadget
PATCH v8 15/30 fsmonitor--daemon: rename listener thread related variables
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Rename platform-specific listener thread related variables and data types as we prepare to add another backend thread type.
[] struct fsmonitor_daemon_backend_data becomes struct fsm_listen_data [] state->backend_data becomes state->listen_data
[] state->error_code becomes state->listen_error_code
--- builtin/fsmonitor--daemon.c | 6 +++--- compat/fsmonitor/fsm-listen-darwin.c | 30 ++++++++++++++-------------- compat/fsmonitor/fsm-listen-win32.c | 28 +++++++++++++------------- compat/fsmonitor/fsm-listen.h | 2 +- fsmonitor--daemon.h | 6 +++--- 5 files changed, 36 insertions(+), 36 deletions(-)
builtin/fsmonitor--daemon.c +3 -3
@@ -1225,8 +1225,8 @@ cleanup:if (err)return err; -if (state->error_code) -return state->error_code; +if (state->listen_error_code) +return state->listen_error_code;return 0 ; }
@@ -1241,7 +1241,7 @@ static int fsmonitor_run_daemon(void)hashmap_init (&state.cookies, cookies_cmp,NULL ,0 );pthread_mutex_init (&state.main_lock,NULL );pthread_cond_init (&state.cookies_cond,NULL ); - state.error_code =0 ; + state.listen_error_code =0 ; state.current_token_data =fsmonitor_new_token_data ();/* Prepare to (recursively) watch the <worktree-root> directory. */
compat/fsmonitor/fsm-listen-darwin.c +15 -15
@@ -27,7 +27,7 @@#include "fsm-listen.h" #include "fsmonitor--daemon.h" -struct fsmonitor_daemon_backend_data +struct fsm_listen_data {CFStringRef cfsr_worktree_path;CFStringRef cfsr_gitdir_path;
@@ -158,7 +158,7 @@ static void fsevent_callback(ConstFSEventStreamRef streamRef, const FSEventStreamEventId event_ids[]) {struct fsmonitor_daemon_state *state = ctx; -struct fsmonitor_daemon_backend_data *data = state->backend_data; +struct fsm_listen_data *data = state->listen_data;char **paths = (char **)event_paths;struct fsmonitor_batch *batch =NULL ;struct string_list cookie_list =STRING_LIST_INIT_DUP ;
@@ -350,11 +350,11 @@ int fsm_listen__ctor(struct fsmonitor_daemon_state *state)NULL , NULL }; -struct fsmonitor_daemon_backend_data *data; +struct fsm_listen_data *data;const void *dir_array[2 ];CALLOC_ARRAY (data,1 ); - state->backend_data = data; + state->listen_data = data; data->cfsr_worktree_path =CFStringCreateWithCString (NULL , state->path_worktree_watch.buf, kCFStringEncodingUTF8);
@@ -386,18 +386,18 @@ int fsm_listen__ctor(struct fsmonitor_daemon_state *state) failed:error (_ ("Unable to create FSEventStream." )); -FREE_AND_NULL (state->backend_data); +FREE_AND_NULL (state->listen_data);return -1 ; }void fsm_listen__dtor (struct fsmonitor_daemon_state *state) { -struct fsmonitor_daemon_backend_data *data; +struct fsm_listen_data *data; -if (!state || !state->backend_data) +if (!state || !state->listen_data)return ; - data = state->backend_data; + data = state->listen_data;if (data->stream) {if (data->stream_started)
@@ -407,14 +407,14 @@ void fsm_listen__dtor(struct fsmonitor_daemon_state *state)FSEventStreamRelease (data->stream); } -FREE_AND_NULL (state->backend_data); +FREE_AND_NULL (state->listen_data); }void fsm_listen__stop_async (struct fsmonitor_daemon_state *state) { -struct fsmonitor_daemon_backend_data *data; +struct fsm_listen_data *data; - data = state->backend_data; + data = state->listen_data; data->shutdown_style =SHUTDOWN_EVENT ;CFRunLoopStop (data->rl);
@@ -422,9 +422,9 @@ void fsm_listen__stop_async(struct fsmonitor_daemon_state *state)void fsm_listen__loop (struct fsmonitor_daemon_state *state) { -struct fsmonitor_daemon_backend_data *data; +struct fsm_listen_data *data; - data = state->backend_data; + data = state->listen_data; data->rl =CFRunLoopGetCurrent ();
@@ -441,7 +441,7 @@ void fsm_listen__loop(struct fsmonitor_daemon_state *state)switch (data->shutdown_style) {case FORCE_ERROR_STOP : - state->error_code =-1 ; + state->listen_error_code =-1 ;/* fall thru */ case FORCE_SHUTDOWN :ipc_server_stop_async (state->ipc_server_data);
@@ -453,7 +453,7 @@ void fsm_listen__loop(struct fsmonitor_daemon_state *state)return ; force_error_stop_without_loop: - state->error_code =-1 ; + state->listen_error_code =-1 ;ipc_server_stop_async (state->ipc_server_data);return ; }
compat/fsmonitor/fsm-listen-win32.c +14 -14
@@ -54,7 +54,7 @@ struct one_watchwchar_t dotgit_shortname[16 ];/* for 8.3 name */ }; -struct fsmonitor_daemon_backend_data +struct fsm_listen_data {struct one_watch *watch_worktree;struct one_watch *watch_gitdir;
@@ -284,7 +284,7 @@ static enum get_relative_result get_relative_longname(void fsm_listen__stop_async (struct fsmonitor_daemon_state *state) { -SetEvent (state->backend_data->hListener[LISTENER_SHUTDOWN ]); +SetEvent (state->listen_data->hListener[LISTENER_SHUTDOWN ]); } static struct one_watch *create_watch(struct fsmonitor_daemon_state *state,
@@ -359,7 +359,7 @@ static void destroy_watch(struct one_watch *watch)free (watch); } -static int start_rdcw_watch (struct fsmonitor_daemon_backend_data *data, +static int start_rdcw_watch (struct fsm_listen_data *data,struct one_watch *watch) { DWORD dwNotifyFilter =
@@ -538,7 +538,7 @@ static int process_1_worktree_event(*/ static int process_worktree_events (struct fsmonitor_daemon_state *state) { -struct fsmonitor_daemon_backend_data *data = state->backend_data; +struct fsm_listen_data *data = state->listen_data;struct one_watch *watch = data->watch_worktree;struct strbuf path =STRBUF_INIT ;struct string_list cookie_list =STRING_LIST_INIT_DUP ;
@@ -669,7 +669,7 @@ force_shutdown:*/ static int process_gitdir_events (struct fsmonitor_daemon_state *state) { -struct fsmonitor_daemon_backend_data *data = state->backend_data; +struct fsm_listen_data *data = state->listen_data;struct one_watch *watch = data->watch_gitdir;struct strbuf path =STRBUF_INIT ;struct string_list cookie_list =STRING_LIST_INIT_DUP ;
@@ -727,11 +727,11 @@ skip_this_path:void fsm_listen__loop (struct fsmonitor_daemon_state *state) { -struct fsmonitor_daemon_backend_data *data = state->backend_data; +struct fsm_listen_data *data = state->listen_data;DWORD dwWait;int result; - state->error_code =0 ; + state->listen_error_code =0 ;if (start_rdcw_watch (data, data->watch_worktree) ==-1 ) goto force_error_stop;
@@ -796,7 +796,7 @@ void fsm_listen__loop(struct fsmonitor_daemon_state *state) } force_error_stop: - state->error_code =-1 ; + state->listen_error_code =-1 ; force_shutdown:/*
@@ -813,7 +813,7 @@ clean_shutdown:int fsm_listen__ctor (struct fsmonitor_daemon_state *state) { -struct fsmonitor_daemon_backend_data *data; +struct fsm_listen_data *data;CALLOC_ARRAY (data,1 );
@@ -846,7 +846,7 @@ int fsm_listen__ctor(struct fsmonitor_daemon_state *state) data->nr_listener_handles++; } - state->backend_data = data; + state->listen_data = data;return 0 ; failed:
@@ -859,16 +859,16 @@ failed:void fsm_listen__dtor (struct fsmonitor_daemon_state *state) { -struct fsmonitor_daemon_backend_data *data; +struct fsm_listen_data *data; -if (!state || !state->backend_data) +if (!state || !state->listen_data)return ; - data = state->backend_data; + data = state->listen_data;CloseHandle (data->hEventShutdown);destroy_watch (data->watch_worktree);destroy_watch (data->watch_gitdir); -FREE_AND_NULL (state->backend_data); +FREE_AND_NULL (state->listen_data); }
compat/fsmonitor/fsm-listen.h +1 -1
@@ -33,7 +33,7 @@ void fsm_listen__dtor(struct fsmonitor_daemon_state *state);* do so if the listener thread receives a normal shutdown signal from * the IPC layer.) * -* It should set `state->error_code` to -1 if the daemon should exit +* It should set `state->listen_error_code` to -1 if the daemon should exit * with an error. */ void fsm_listen__loop (struct fsmonitor_daemon_state *state);
fsmonitor--daemon.h +3 -3
@@ -33,7 +33,7 @@ void fsmonitor_batch__free_list(struct fsmonitor_batch *batch);*/ void fsmonitor_batch__add_path (struct fsmonitor_batch *batch,const char *path); -struct fsmonitor_daemon_backend_data ;/* opaque platform-specific data */ +struct fsm_listen_data ;/* opaque platform-specific data for listener thread */ struct fsmonitor_daemon_state {pthread_t listener_thread;
@@ -50,8 +50,8 @@ struct fsmonitor_daemon_state {int cookie_seq;struct hashmap cookies; -int error_code; -struct fsmonitor_daemon_backend_data *backend_data; +int listen_error_code; +struct fsm_listen_data *listen_data;struct ipc_server_data *ipc_server_data;struct strbuf path_ipc;
Signature · 1 line
gitgitgadget
PATCH v8 16/30 fsmonitor--daemon: stub in health thread
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create another thread to watch over the daemon process and automatically shut it down if necessary.
This commit creates the basic framework for a "health" thread to monitor the daemon and/or the file system. Later commits will add platform-specific code to do the actual work.
The "health" thread is intended to monitor conditions that would be difficult to track inside the IPC thread pool and/or the file system listener threads. For example, when there are file system events outside of the watched worktree root or if we want to have an idle-timeout auto-shutdown feature.
This commit creates the health thread itself, defines the thread-proc and sets up the thread's event loop. It integrates this new thread into the existing IPC and Listener thread models.
This commit defines the API to the platform-specific code where all of the monitoring will actually happen.
The platform-specific code for MacOS is just stubs. Meaning that the health thread will immediately exit on MacOS, but that is OK and expected. Future work can define MacOS-specific monitoring.
The platform-specific code for Windows sets up enough of the WaitForMultipleObjects() machinery to watch for system and/or custom events. Currently, the set of wait handles only includes our custom shutdown event (sent from our other theads). Later commits in this series will extend the set of wait handles to monitor other conditions.
--- Makefile | 6 ++- builtin/fsmonitor--daemon.c | 39 +++++++++++++++ compat/fsmonitor/fsm-health-darwin.c | 24 ++++++++++ compat/fsmonitor/fsm-health-win32.c | 72 ++++++++++++++++++++++++++++ compat/fsmonitor/fsm-health.h | 47 ++++++++++++++++++ contrib/buildsystems/CMakeLists.txt | 2 + fsmonitor--daemon.h | 4 ++ 7 files changed, 192 insertions(+), 2 deletions(-) create mode 100644 compat/fsmonitor/fsm-health-darwin.c create mode 100644 compat/fsmonitor/fsm-health-win32.c create mode 100644 compat/fsmonitor/fsm-health.h
Makefile +4 -2
@@ -472,8 +472,9 @@ all:: # # If your platform supports a built-in fsmonitor backend, set # FSMONITOR_DAEMON_BACKEND to the "<name>" of the corresponding -# `compat/fsmonitor/fsm-listen-<name>.c` that implements the -# `fsm_listen__*()` routines. +# `compat/fsmonitor/fsm-listen-<name>.c` and +# `compat/fsmonitor/fsm-health-<name>.c` files +# that implement the `fsm_listen__*()` and `fsm_health__*()` routines. # # If your platform has OS-specific ways to tell if a repo is incompatible with # fsmonitor (whether the hook or IPC daemon version), set FSMONITOR_OS_SETTINGS
@@ -1982,6 +1983,7 @@ endif ifdef FSMONITOR_DAEMON_BACKEND COMPAT_CFLAGS += -DHAVE_FSMONITOR_DAEMON_BACKEND COMPAT_OBJS += compat/fsmonitor/fsm-listen-$(FSMONITOR_DAEMON_BACKEND).o + COMPAT_OBJS += compat/fsmonitor/fsm-health-$(FSMONITOR_DAEMON_BACKEND).o endif ifdef FSMONITOR_OS_SETTINGS
builtin/fsmonitor--daemon.c +39 -0
@@ -3,6 +3,7 @@#include "parse-options.h" #include "fsmonitor.h" #include "fsmonitor-ipc.h" +#include "compat/fsmonitor/fsm-health.h" #include "compat/fsmonitor/fsm-listen.h" #include "fsmonitor--daemon.h" #include "simple-ipc.h"
@@ -1136,6 +1137,18 @@ void fsmonitor_publish(struct fsmonitor_daemon_state *state,pthread_mutex_unlock (&state->main_lock); } +static void *fsm_health__thread_proc (void *_state) +{ +struct fsmonitor_daemon_state *state = _state; + +trace2_thread_start ("fsm-health" ); + +fsm_health__loop (state); + +trace2_thread_exit (); +return NULL ; +} +static void *fsm_listen__thread_proc (void *_state) {struct fsmonitor_daemon_state *state = _state;
@@ -1174,6 +1187,7 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state)*/ .uds_disallow_chdir = 0 }; +int health_started =0 ;int listener_started =0 ;int err =0 ;
@@ -1201,6 +1215,17 @@ static int fsmonitor_run_daemon_1(struct fsmonitor_daemon_state *state) } listener_started =1 ; +/* +* Start the health thread to watch over our process. +*/ +if (pthread_create (&state->health_thread,NULL , + fsm_health__thread_proc, state) <0 ) { +ipc_server_stop_async (state->ipc_server_data); + err =error (_ ("could not start fsmonitor health thread" )); + goto cleanup; + } + health_started =1 ; +/* * The daemon is now fully functional in background threads. * Our primary thread should now just wait while the threads
@@ -1223,10 +1248,17 @@ cleanup:pthread_join (state->listener_thread,NULL ); } +if (health_started) { +fsm_health__stop_async (state); +pthread_join (state->health_thread,NULL ); + } +if (err)return err;if (state->listen_error_code)return state->listen_error_code; +if (state->health_error_code) +return state->health_error_code;return 0 ; }
@@ -1242,6 +1274,7 @@ static int fsmonitor_run_daemon(void)pthread_mutex_init (&state.main_lock,NULL );pthread_cond_init (&state.cookies_cond,NULL ); state.listen_error_code =0 ; + state.health_error_code =0 ; state.current_token_data =fsmonitor_new_token_data ();/* Prepare to (recursively) watch the <worktree-root> directory. */
@@ -1321,6 +1354,11 @@ static int fsmonitor_run_daemon(void) goto done; } +if (fsm_health__ctor (&state)) { + err =error (_ ("could not initialize health thread" )); + goto done; + } +/* * CD out of the worktree root directory. *
@@ -1344,6 +1382,7 @@ done:pthread_cond_destroy (&state.cookies_cond);pthread_mutex_destroy (&state.main_lock);fsm_listen__dtor (&state); +fsm_health__dtor (&state);ipc_server_free (state.ipc_server_data);
compat/fsmonitor/fsm-health-darwin.c new file +24 -0
@@ -0,0 +1,24 @@ +#include "cache.h" +#include "config.h" +#include "fsmonitor.h" +#include "fsm-health.h" +#include "fsmonitor--daemon.h" + +int fsm_health__ctor (struct fsmonitor_daemon_state *state) +{ +return 0 ; +} + +void fsm_health__dtor (struct fsmonitor_daemon_state *state) +{ +return ; +} + +void fsm_health__loop (struct fsmonitor_daemon_state *state) +{ +return ; +} + +void fsm_health__stop_async (struct fsmonitor_daemon_state *state) +{ +}
compat/fsmonitor/fsm-health-win32.c new file +72 -0
72 lines not on this page · raw message
compat/fsmonitor/fsm-health.h new file +47 -0
47 lines not on this page · raw message
contrib/buildsystems/CMakeLists.txt +2 -0
@@ -289,12 +289,14 @@ if(SUPPORTS_SIMPLE_IPC) if(CMAKE_SYSTEM_NAME STREQUAL "Windows") add_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND) list(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-win32.c) + list(APPEND compat_SOURCES compat/fsmonitor/fsm-health-win32.c) add_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS) list(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-win32.c) elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") add_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND) list(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-darwin.c) + list(APPEND compat_SOURCES compat/fsmonitor/fsm-health-darwin.c) add_compile_definitions(HAVE_FSMONITOR_OS_SETTINGS) list(APPEND compat_SOURCES compat/fsmonitor/fsm-settings-darwin.c)
fsmonitor--daemon.h +4 -0
@@ -34,9 +34,11 @@ void fsmonitor_batch__free_list(struct fsmonitor_batch *batch);void fsmonitor_batch__add_path (struct fsmonitor_batch *batch,const char *path);struct fsm_listen_data ;/* opaque platform-specific data for listener thread */ +struct fsm_health_data ;/* opaque platform-specific data for health thread */ struct fsmonitor_daemon_state {pthread_t listener_thread; +pthread_t health_thread;pthread_mutex_t main_lock;struct strbuf path_worktree_watch;
@@ -51,7 +53,9 @@ struct fsmonitor_daemon_state {struct hashmap cookies;int listen_error_code; +int health_error_code;struct fsm_listen_data *listen_data; +struct fsm_health_data *health_data;struct ipc_server_data *ipc_server_data;struct strbuf path_ipc;
Signature · 1 line
gitgitgadget
PATCH v8 17/30 fsm-health-win32: add polling framework to monitor daemon health
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Extend the Windows version of the "health" thread to periodically inspect the system and shutdown if warranted.
This commit updates the thread's wait loop to use a timeout and defines a (currently empty) table of functions to poll the system.
A later commit will add functions to the table to actually inspect the system.
--- compat/fsmonitor/fsm-health-win32.c | 65 ++++++++++++++++++++++++++++- 1 file changed, 64 insertions(+), 1 deletion(-)
compat/fsmonitor/fsm-health-win32.c +64 -1
@@ -4,6 +4,24 @@#include "fsm-health.h" #include "fsmonitor--daemon.h" +/* +* Every minute wake up and test our health. +*/ +#define WAIT_FREQ_MS (60 * 1000) + +/* +* State machine states for each of the interval functions +* used for polling our health. +*/ +enum interval_fn_ctx { +CTX_INIT =0 , +CTX_TERM , +CTX_TIMER +}; + +typedef int (interval_fn )(struct fsmonitor_daemon_state *state, +enum interval_fn_ctx ctx); +struct fsm_health_data {HANDLE hEventShutdown;
@@ -42,18 +60,61 @@ void fsm_health__dtor(struct fsmonitor_daemon_state *state)FREE_AND_NULL (state->health_data); } +/* +* A table of the polling functions. +*/ +static interval_fn *table[] = { +NULL ,/* must be last */ +}; + +/* +* Call all of the polling functions in the table. +* Shortcut and return first error. +* +* Return 0 if all succeeded. +*/ +static int call_all (struct fsmonitor_daemon_state *state, +enum interval_fn_ctx ctx) +{ +int k; + +for (k =0 ; table[k]; k++) { +int r = table[k](state, ctx); +if (r) +return r; + } + +return 0 ; +} +void fsm_health__loop (struct fsmonitor_daemon_state *state) {struct fsm_health_data *data = state->health_data; +int r; + + r =call_all (state,CTX_INIT ); +if (r <0 ) + goto force_error_stop; +if (r >0 ) + goto force_shutdown;for (;;) {DWORD dwWait =WaitForMultipleObjects (data->nr_handles, data->hHandles, - FALSE,INFINITE ); + FALSE,WAIT_FREQ_MS );if (dwWait ==WAIT_OBJECT_0 +HEALTH_SHUTDOWN ) goto clean_shutdown; +if (dwWait ==WAIT_TIMEOUT ) { + r =call_all (state,CTX_TIMER ); +if (r <0 ) + goto force_error_stop; +if (r >0 ) + goto force_shutdown; +continue ; + } +error (_ ("health thread wait failed [GLE %ld]" ),GetLastError ()); goto force_error_stop;
@@ -61,8 +122,10 @@ void fsm_health__loop(struct fsmonitor_daemon_state *state) force_error_stop: state->health_error_code =-1 ; +force_shutdown:ipc_server_stop_async (state->ipc_server_data); clean_shutdown: +call_all (state,CTX_TERM );return ; }
Signature · 1 line
gitgitgadget