Here is V7 of Part 2 of my builtin FSmonitor series. This version squashes in the fixups from Part 2.5 and any other last minute suggestions. I'll send an updated version of Part 3 that builds upon this version. Here is a range-diff from V6 to V7 relative to 715d08a9e5 (The eighth batch, 2022-02-25). Changes since V6 can be summarized as: (1) Adding/removing "_()" on die() and error() messages. (2) Splitting GCC-specific declarations for MacOS FSEvents into a separate header file. (3) Enhancing the "start_daemon()" function in t7527 to take keyword args and reduce some code duplication in unit tests. (4) Fixing "&&" chains in the tests. (5) Other minor style cleanups. $ git range-diff eight..builtin-fsmonitor-part2-v6 eight..builtin-fsmonitor-part2 1: 10a34082fc = 1: e98373f997 fsmonitor: enhance existing comments, clarify trivial response handling 2: e1c946af2c ! 2: ab68b94417 fsmonitor-ipc: create client routines for git-fsmonitor--daemon @@ fsmonitor-ipc.c (new) + state = ipc_client_try_connect(fsmonitor_ipc__get_path(), &options, + &connection); + if (state != IPC_STATE__LISTENING) { -+ die("fsmonitor--daemon is not running"); ++ die(_("fsmonitor--daemon is not running")); + return -1; + } + @@ fsmonitor-ipc.c (new) + ipc_client_close_connection(connection); + + if (ret == -1) { -+ die("could not send '%s' command to fsmonitor--daemon", c); ++ die(_("could not send '%s' command to fsmonitor--daemon"), c); + return -1; + } + 3: ae622a517c ! 3: e04c7301f2 fsmonitor: config settings are repository-specific @@ fsmonitor-settings.c (new) + return; + + CALLOC_ARRAY(s, 1); ++ s->mode = FSMONITOR_MODE_DISABLED; + + r->settings.fsmonitor = s; + -+ fsm_settings__set_disabled(r); -+ + /* + * Overload the existing "core.fsmonitor" config setting (which + * has historically been either unset or a hook pathname) to 4: 55974867da = 4: ea02ba25d8 fsmonitor: use IPC to query the builtin FSMonitor daemon 5: ce42d5bbaf = 5: 6ab7db9cb7 fsmonitor: document builtin fsmonitor 6: 9ce938c69b = 6: 0ce8ae3f2c fsmonitor--daemon: add a built-in fsmonitor daemon 7: 3bd7e6a051 = 7: 4624ce2fa4 fsmonitor--daemon: implement 'stop' and 'status' commands 8: 92d3e54da7 = 8: a29fe7266a compat/fsmonitor/fsm-listen-win32: stub in backend for Windows 9: f121922127 = 9: 2f8a42fdb9 compat/fsmonitor/fsm-listen-darwin: stub in backend for Darwin 10: a3f94627cf ! 10: f07800690e fsmonitor--daemon: implement 'run' command @@ builtin/fsmonitor--daemon.c: static int do_as_client__status(void) + * common error case. + */ + if (fsmonitor_ipc__get_state() == IPC_STATE__LISTENING) -+ die("fsmonitor--daemon is already running '%s'", ++ die(_("fsmonitor--daemon is already running '%s'"), + the_repository->worktree); + + if (fsmonitor__announce_startup) { 11: 8de40b0fe8 ! 11: a6a39a3306 fsmonitor--daemon: implement 'start' command @@ builtin/fsmonitor--daemon.c: static int try_to_run_foreground_daemon(void) + * immediately exited). + */ + if (fsmonitor_ipc__get_state() == IPC_STATE__LISTENING) -+ die("fsmonitor--daemon is already running '%s'", ++ die(_("fsmonitor--daemon is already running '%s'"), + the_repository->worktree); + + if (fsmonitor__announce_startup) { @@ builtin/fsmonitor--daemon.c: static int try_to_run_foreground_daemon(void) + default: + case SBGR_ERROR: + case SBGR_CB_ERROR: -+ return error("daemon failed to start"); ++ return error(_("daemon failed to start")); + + case SBGR_TIMEOUT: -+ return error("daemon not online yet"); ++ return error(_("daemon not online yet")); + + case SBGR_DIED: -+ return error("daemon terminated"); ++ return error(_("daemon terminated")); + } +} + 12: e5d419b1ea = 12: d62e338d00 fsmonitor--daemon: add pathname classification 13: c2f3668a86 = 13: 53e06b4ae5 fsmonitor--daemon: define token-ids 14: bd492d13fb = 14: 39f43fabe0 fsmonitor--daemon: create token-based changed path cache 15: 6b5b3d0822 ! 15: 239558e34f compat/fsmonitor/fsm-listen-win32: implement FSMonitor backend on Windows @@ compat/fsmonitor/fsm-listen-win32.c + if (len > 0) + goto normalize; + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { -+ error("[GLE %ld] could not convert path to UTF-8: '%.*ls'", ++ error(_("[GLE %ld] could not convert path to UTF-8: '%.*ls'"), + GetLastError(), + (int)(info->FileNameLength / sizeof(WCHAR)), + info->FileName); @@ compat/fsmonitor/fsm-listen-win32.c + if (watch->is_active) + return 0; + -+ error("ReadDirectoryChangedW failed on '%s' [GLE %ld]", ++ error(_("ReadDirectoryChangedW failed on '%s' [GLE %ld]"), + watch->path.buf, GetLastError()); + return -1; +} @@ compat/fsmonitor/fsm-listen-win32.c + * sure it is worth it. + */ + -+ error("GetOverlappedResult failed on '%s' [GLE %ld]", ++ error(_("GetOverlappedResult failed on '%s' [GLE %ld]"), + watch->path.buf, gle); + return -1; +} 16: cdef9730b3 ! 16: 14b775e9d8 compat/fsmonitor/fsm-listen-darwin: add MacOS header files for FSEvent @@ Commit message Signed-off-by: Jeff Hostetler <jeffhost@xxxxxxxxxxxxx> Signed-off-by: Junio C Hamano <gitster@xxxxxxxxx> - ## compat/fsmonitor/fsm-listen-darwin.c ## + ## compat/fsmonitor/fsm-darwin-gcc.h (new) ## @@ ++#ifndef FSM_DARWIN_GCC_H ++#define FSM_DARWIN_GCC_H ++ +#ifndef __clang__ +/* + * It is possible to #include CoreFoundation/CoreFoundation.h when compiling @@ compat/fsmonitor/fsm-listen-darwin.c +void FSEventStreamStop(FSEventStreamRef stream); +void FSEventStreamInvalidate(FSEventStreamRef stream); +void FSEventStreamRelease(FSEventStreamRef stream); ++ ++#endif /* !clang */ ++#endif /* FSM_DARWIN_GCC_H */ + + ## compat/fsmonitor/fsm-listen-darwin.c ## +@@ ++#ifndef __clang__ ++#include "fsm-darwin-gcc.h" +#else -+/* -+ * Let Apple's headers declare `isalnum()` first, before -+ * Git's headers override it via a constant -+ */ -+#include <string.h> +#include <CoreFoundation/CoreFoundation.h> +#include <CoreServices/CoreServices.h> ++ ++#ifndef AVAILABLE_MAC_OS_X_VERSION_10_13_AND_LATER ++/* ++ * This enum value was added in 10.13 to: ++ * ++ * /Applications/Xcode.app/Contents/Developer/Platforms/ \ ++ * MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/ \ ++ * Library/Frameworks/CoreServices.framework/Frameworks/ \ ++ * FSEvents.framework/Versions/Current/Headers/FSEvents.h ++ * ++ * If we're compiling against an older SDK, this symbol won't be ++ * present. Silently define it here so that we don't have to ifdef ++ * the logging or masking below. This should be harmless since older ++ * versions of macOS won't ever emit this FS event anyway. ++ */ ++#define kFSEventStreamEventFlagItemCloned 0x00400000 ++#endif +#endif + #include "cache.h" 17: aabfebd3a4 ! 17: 55bd7aee06 compat/fsmonitor/fsm-listen-darwin: implement FSEvent listener on MacOS @@ Commit message Signed-off-by: Junio C Hamano <gitster@xxxxxxxxx> ## compat/fsmonitor/fsm-listen-darwin.c ## -@@ compat/fsmonitor/fsm-listen-darwin.c: void FSEventStreamRelease(FSEventStreamRef stream); +@@ #include "cache.h" #include "fsmonitor.h" #include "fsm-listen.h" @@ compat/fsmonitor/fsm-listen-darwin.c: void FSEventStreamRelease(FSEventStreamRef + return 0; + +failed: -+ error("Unable to create FSEventStream."); ++ error(_("Unable to create FSEventStream.")); + + FREE_AND_NULL(state->backend_data); return -1; @@ compat/fsmonitor/fsm-listen-darwin.c: void FSEventStreamRelease(FSEventStreamRef + data->stream_scheduled = 1; + + if (!FSEventStreamStart(data->stream)) { -+ error("Failed to start the FSEventStream"); ++ error(_("Failed to start the FSEventStream")); + goto force_error_stop_without_loop; + } + data->stream_started = 1; 18: 0896e72e2b = 18: 1f4b5209bf fsmonitor--daemon: implement handle_client callback 19: bc240a9e66 ! 19: 8cf62c9fc6 help: include fsmonitor--daemon feature flag in version info @@ t/test-lib.sh: test_lazy_prereq SHA1 ' +# Does this platform support `git fsmonitor--daemon` +# +test_lazy_prereq FSMONITOR_DAEMON ' -+ git version --build-options | grep "feature:" | grep "fsmonitor--daemon" ++ git version --build-options >output && ++ grep "feature: fsmonitor--daemon" output +' 20: 2b563c240e ! 20: 1bd74a8159 t/helper/fsmonitor-client: create IPC client to talk to FSMonitor Daemon @@ t/helper/test-fsmonitor-client.c (new) + + ret = fsmonitor_ipc__send_query(token, &answer); + if (ret < 0) -+ die(_("could not query fsmonitor--daemon")); ++ die("could not query fsmonitor--daemon"); + + write_in_full(1, answer.buf, answer.len); + strbuf_release(&answer); @@ t/helper/test-fsmonitor-client.c (new) + const char *token = NULL; + + const char * const fsmonitor_client_usage[] = { -+ N_("test-helper fsmonitor-client query [<token>]"), -+ N_("test-helper fsmonitor-client flush"), ++ "test-tool fsmonitor-client query [<token>]", ++ "test-tool fsmonitor-client flush", + NULL, + }; + + struct option options[] = { -+ OPT_STRING(0, "token", &token, N_("token"), -+ N_("command token to send to the server")), ++ OPT_STRING(0, "token", &token, "token", ++ "command token to send to the server"), + OPT_END() + }; + -+ if (argc < 2) -+ usage_with_options(fsmonitor_client_usage, options); ++ argc = parse_options(argc, argv, NULL, options, fsmonitor_client_usage, 0); + -+ if (argc == 2 && !strcmp(argv[1], "-h")) ++ if (argc != 1) + usage_with_options(fsmonitor_client_usage, options); + -+ subcmd = argv[1]; -+ argv--; -+ argc++; -+ -+ argc = parse_options(argc, argv, NULL, options, fsmonitor_client_usage, 0); ++ subcmd = argv[0]; + + setup_git_directory(); + 21: db8809da08 ! 21: 4a920d0b54 t7527: create test for fsmonitor--daemon @@ t/t7527-builtin-fsmonitor.sh (new) +fi + +stop_daemon_delete_repo () { -+ r=$1 -+ git -C $r fsmonitor--daemon stop >/dev/null 2>/dev/null ++ r=$1 && ++ test_might_fail git -C $r fsmonitor--daemon stop && + rm -rf $1 -+ return 0 +} + +start_daemon () { -+ case "$#" in -+ 1) r="-C $1";; -+ *) r=""; -+ esac ++ r= && ++ tf= && ++ t2= && ++ tk= && ++ ++ while test "$#" -ne 0 ++ do ++ case "$1" in ++ -C) ++ shift; ++ test "$#" -ne 0 || BUG "error: -C requires arg" ++ r="-C $1" ++ shift ++ ;; ++ -tf) ++ shift; ++ test "$#" -ne 0 || BUG "error: -tf requires arg" ++ tf="$1" ++ shift ++ ;; ++ -t2) ++ shift; ++ test "$#" -ne 0 || BUG "error: -t2 requires arg" ++ t2="$1" ++ shift ++ ;; ++ -tk) ++ shift; ++ test "$#" -ne 0 || BUG "error: -tk requires arg" ++ tk="$1" ++ shift ++ ;; ++ *) ++ BUG "error: unknown option: '$1'" ++ ;; ++ esac ++ done && + -+ git $r fsmonitor--daemon start || return $? -+ git $r fsmonitor--daemon status || return $? -+ -+ return 0 ++ ( ++ if test -n "$tf" ++ then ++ GIT_TRACE_FSMONITOR="$tf" ++ export GIT_TRACE_FSMONITOR ++ fi && ++ ++ if test -n "$t2" ++ then ++ GIT_TRACE2_PERF="$t2" ++ export GIT_TRACE2_PERF ++ fi && ++ ++ if test -n "$tk" ++ then ++ GIT_TEST_FSMONITOR_TOKEN="$tk" ++ export GIT_TEST_FSMONITOR_TOKEN ++ fi && ++ ++ git $r fsmonitor--daemon start && ++ git $r fsmonitor--daemon status ++ ) +} + +# Is a Trace2 data event present with the given catetory and key? +# We do not care what the value is. +# +have_t2_data_event () { -+ c=$1 -+ k=$2 ++ c=$1 && ++ k=$2 && + + grep -e '"event":"data".*"category":"'"$c"'".*"key":"'"$k"'"' +} @@ t/t7527-builtin-fsmonitor.sh (new) + test_when_finished "stop_daemon_delete_repo test_explicit" && + + git init test_explicit && -+ start_daemon test_explicit && ++ start_daemon -C test_explicit && + + git -C test_explicit fsmonitor--daemon stop && + test_must_fail git -C test_explicit fsmonitor--daemon status @@ t/t7527-builtin-fsmonitor.sh (new) + # but this test case is only concerned with whether the daemon was + # implicitly started.) + -+ GIT_TRACE2_EVENT="$(pwd)/.git/trace" \ ++ GIT_TRACE2_EVENT="$PWD/.git/trace" \ + test-tool -C test_implicit fsmonitor-client query --token 0 >actual && + nul_to_q <actual >actual.filtered && + grep "builtin:" actual.filtered && @@ t/t7527-builtin-fsmonitor.sh (new) + + git init test_implicit_1 && + -+ start_daemon test_implicit_1 && ++ start_daemon -C test_implicit_1 && + + # deleting the .git directory will implicitly stop the daemon. + rm -rf test_implicit_1/.git && @@ t/t7527-builtin-fsmonitor.sh (new) + + git init test_implicit_2 && + -+ start_daemon test_implicit_2 && ++ start_daemon -C test_implicit_2 && + + # renaming the .git directory will implicitly stop the daemon. + mv test_implicit_2/.git test_implicit_2/.xxx && @@ t/t7527-builtin-fsmonitor.sh (new) + + git init test_multiple && + -+ start_daemon test_multiple && ++ start_daemon -C test_multiple && + + test_must_fail git -C test_multiple fsmonitor--daemon start 2>actual && + grep "fsmonitor--daemon is already running" actual && @@ t/t7527-builtin-fsmonitor.sh (new) +# This is here in case something else fails first. +# +redundant_stop_daemon () { -+ git fsmonitor--daemon stop -+ return 0 ++ test_might_fail git fsmonitor--daemon stop +} + +test_expect_success 'update-index implicitly starts daemon' ' @@ t/t7527-builtin-fsmonitor.sh (new) + + test_must_fail git fsmonitor--daemon status && + -+ GIT_TRACE2_EVENT="$(pwd)/.git/trace_implicit_1" \ ++ GIT_TRACE2_EVENT="$PWD/.git/trace_implicit_1" \ + git update-index --fsmonitor && + + git fsmonitor--daemon status && @@ t/t7527-builtin-fsmonitor.sh (new) + + test_must_fail git fsmonitor--daemon status && + -+ GIT_TRACE2_EVENT="$(pwd)/.git/trace_implicit_2" \ ++ GIT_TRACE2_EVENT="$PWD/.git/trace_implicit_2" \ + git status >actual && + + git fsmonitor--daemon status && @@ t/t7527-builtin-fsmonitor.sh (new) +' + +edit_files () { -+ echo 1 >modified -+ echo 2 >dir1/modified -+ echo 3 >dir2/modified ++ echo 1 >modified && ++ echo 2 >dir1/modified && ++ echo 3 >dir2/modified && + >dir1/untracked +} + +delete_files () { -+ rm -f delete -+ rm -f dir1/delete ++ rm -f delete && ++ rm -f dir1/delete && + rm -f dir2/delete +} + +create_files () { -+ echo 1 >new -+ echo 2 >dir1/new ++ echo 1 >new && ++ echo 2 >dir1/new && + echo 3 >dir2/new +} + +rename_files () { -+ mv rename renamed -+ mv dir1/rename dir1/renamed ++ mv rename renamed && ++ mv dir1/rename dir1/renamed && + mv dir2/rename dir2/renamed +} + +file_to_directory () { -+ rm -f delete -+ mkdir delete ++ rm -f delete && ++ mkdir delete && + echo 1 >delete/new +} + +directory_to_file () { -+ rm -rf dir1 ++ rm -rf dir1 && + echo 1 >dir1 +} + -+verify_status () { -+ git status >actual && -+ GIT_INDEX_FILE=.git/fresh-index git read-tree master && -+ GIT_INDEX_FILE=.git/fresh-index git -c core.fsmonitor=false status >expect && -+ test_cmp expect actual && -+ echo HELLO AFTER && -+ cat .git/trace && -+ echo HELLO AFTER -+} -+ +# 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 @@ t/t7527-builtin-fsmonitor.sh (new) +# daemon) because these commands might implicitly restart the daemon. + +clean_up_repo_and_stop_daemon () { -+ git reset --hard HEAD -+ git clean -fd -+ git fsmonitor--daemon stop ++ git reset --hard HEAD && ++ git clean -fd && ++ test_might_fail git fsmonitor--daemon stop && + rm -f .git/trace +} + +test_expect_success 'edit some files' ' + test_when_finished clean_up_repo_and_stop_daemon && + -+ ( -+ GIT_TRACE_FSMONITOR="$(pwd)/.git/trace" && -+ export GIT_TRACE_FSMONITOR && -+ -+ start_daemon -+ ) && ++ start_daemon -tf "$PWD/.git/trace" && + + edit_files && + -+ test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 && ++ test-tool fsmonitor-client query --token 0 && + + grep "^event: dir1/modified$" .git/trace && + grep "^event: dir2/modified$" .git/trace && @@ t/t7527-builtin-fsmonitor.sh (new) +test_expect_success 'create some files' ' + test_when_finished clean_up_repo_and_stop_daemon && + -+ ( -+ GIT_TRACE_FSMONITOR="$(pwd)/.git/trace" && -+ export GIT_TRACE_FSMONITOR && -+ -+ start_daemon -+ ) && ++ start_daemon -tf "$PWD/.git/trace" && + + create_files && + -+ test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 && ++ test-tool fsmonitor-client query --token 0 && + + grep "^event: dir1/new$" .git/trace && + grep "^event: dir2/new$" .git/trace && @@ t/t7527-builtin-fsmonitor.sh (new) +test_expect_success 'delete some files' ' + test_when_finished clean_up_repo_and_stop_daemon && + -+ ( -+ GIT_TRACE_FSMONITOR="$(pwd)/.git/trace" && -+ export GIT_TRACE_FSMONITOR && -+ -+ start_daemon -+ ) && ++ start_daemon -tf "$PWD/.git/trace" && + + delete_files && + -+ test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 && ++ test-tool fsmonitor-client query --token 0 && + + grep "^event: dir1/delete$" .git/trace && + grep "^event: dir2/delete$" .git/trace && @@ t/t7527-builtin-fsmonitor.sh (new) +test_expect_success 'rename some files' ' + test_when_finished clean_up_repo_and_stop_daemon && + -+ ( -+ GIT_TRACE_FSMONITOR="$(pwd)/.git/trace" && -+ export GIT_TRACE_FSMONITOR && -+ -+ start_daemon -+ ) && ++ start_daemon -tf "$PWD/.git/trace" && + + rename_files && + -+ test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 && ++ test-tool fsmonitor-client query --token 0 && + + grep "^event: dir1/rename$" .git/trace && + grep "^event: dir2/rename$" .git/trace && @@ t/t7527-builtin-fsmonitor.sh (new) +test_expect_success 'rename directory' ' + test_when_finished clean_up_repo_and_stop_daemon && + -+ ( -+ GIT_TRACE_FSMONITOR="$(pwd)/.git/trace" && -+ export GIT_TRACE_FSMONITOR && -+ -+ start_daemon -+ ) && ++ start_daemon -tf "$PWD/.git/trace" && + + mv dirtorename dirrenamed && + -+ test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 && ++ test-tool fsmonitor-client query --token 0 && + + grep "^event: dirtorename/*$" .git/trace && + grep "^event: dirrenamed/*$" .git/trace @@ t/t7527-builtin-fsmonitor.sh (new) +test_expect_success 'file changes to directory' ' + test_when_finished clean_up_repo_and_stop_daemon && + -+ ( -+ GIT_TRACE_FSMONITOR="$(pwd)/.git/trace" && -+ export GIT_TRACE_FSMONITOR && -+ -+ start_daemon -+ ) && ++ start_daemon -tf "$PWD/.git/trace" && + + file_to_directory && + -+ test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 && ++ test-tool fsmonitor-client query --token 0 && + + grep "^event: delete$" .git/trace && + grep "^event: delete/new$" .git/trace @@ t/t7527-builtin-fsmonitor.sh (new) +test_expect_success 'directory changes to a file' ' + test_when_finished clean_up_repo_and_stop_daemon && + -+ ( -+ GIT_TRACE_FSMONITOR="$(pwd)/.git/trace" && -+ export GIT_TRACE_FSMONITOR && -+ -+ start_daemon -+ ) && ++ start_daemon -tf "$PWD/.git/trace" && + + directory_to_file && + -+ test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 && ++ test-tool fsmonitor-client query --token 0 && + + grep "^event: dir1$" .git/trace +' @@ t/t7527-builtin-fsmonitor.sh (new) + + git init test_flush && + -+ ( -+ GIT_TEST_FSMONITOR_TOKEN=true && -+ export GIT_TEST_FSMONITOR_TOKEN && -+ -+ GIT_TRACE_FSMONITOR="$(pwd)/.git/trace_daemon" && -+ export GIT_TRACE_FSMONITOR && -+ -+ start_daemon test_flush -+ ) && ++ start_daemon -C test_flush -tf "$PWD/.git/trace_daemon" -tk true && + + # The daemon should have an initial token with no events in _0 and + # then a few (probably platform-specific number of) events in _1. @@ t/t7527-builtin-fsmonitor.sh (new) + test-tool -C test_flush fsmonitor-client query --token "builtin:test_00000001:0" >actual_0 && + nul_to_q <actual_0 >actual_q0 && + -+ touch test_flush/file_1 && -+ touch test_flush/file_2 && ++ >test_flush/file_1 && ++ >test_flush/file_2 && + + test-tool -C test_flush fsmonitor-client query --token "builtin:test_00000001:0" >actual_1 && + nul_to_q <actual_1 >actual_q1 && @@ t/t7527-builtin-fsmonitor.sh (new) + + grep "^builtin:test_00000002:0Q$" actual_q2 && + -+ touch test_flush/file_3 && ++ >test_flush/file_3 && + + test-tool -C test_flush fsmonitor-client query --token "builtin:test_00000002:0" >actual_3 && + nul_to_q <actual_3 >actual_q3 && @@ t/t7527-builtin-fsmonitor.sh (new) +test_expect_success 'worktree with .git file' ' + git -C wt-base worktree add ../wt-secondary && + -+ ( -+ GIT_TRACE2_PERF="$(pwd)/trace2_wt_secondary" && -+ export GIT_TRACE2_PERF && -+ -+ GIT_TRACE_FSMONITOR="$(pwd)/trace_wt_secondary" && -+ export GIT_TRACE_FSMONITOR && -+ -+ start_daemon wt-secondary -+ ) && ++ start_daemon -C wt-secondary \ ++ -tf "$PWD/trace_wt_secondary" \ ++ -t2 "$PWD/trace2_wt_secondary" && + + git -C wt-secondary fsmonitor--daemon stop && + test_must_fail git -C wt-secondary fsmonitor--daemon status 22: 15bd5aaff3 = 22: c925a9a745 t/perf: avoid copying builtin fsmonitor files into test repo 23: 4cb97918d2 = 23: 5b3381c223 t/helper/test-chmtime: skip directories on Windows 24: a70748b464 ! 24: 803a540cc0 t/perf/p7519: speed up test on Windows @@ t/perf/p7519-fsmonitor.sh: test_fsmonitor_suite() { head -100000 | \ grep -v \" | \ - sed '\''s/\(.\)/\\\1/g'\'' | \ -+ egrep -v " ." | \ ++ grep -v " ." | \ xargs test-tool chmtime -300 && git status ' 25: b0c9c9c7a4 ! 25: d5ca2df31c t/perf/p7519: add fsmonitor--daemon test cases @@ Commit message Signed-off-by: Junio C Hamano <gitster@xxxxxxxxx> ## t/perf/p7519-fsmonitor.sh ## +@@ t/perf/p7519-fsmonitor.sh: test_expect_success "one time repo setup" ' + fi + ' + +-setup_for_fsmonitor () { ++setup_for_fsmonitor_hook () { + # set INTEGRATION_SCRIPT depending on the environment + if test -n "$INTEGRATION_PATH" + then @@ t/perf/p7519-fsmonitor.sh: test_perf_w_drop_caches () { } @@ t/perf/p7519-fsmonitor.sh: test_perf_w_drop_caches () { + if test -n "$USE_FSMONITOR_DAEMON" + then + DESC="builtin fsmonitor--daemon" -+ elif test -n "$INTEGRATION_SCRIPT"; then ++ elif test -n "$INTEGRATION_SCRIPT" ++ then DESC="fsmonitor=$(basename $INTEGRATION_SCRIPT)" else DESC="fsmonitor=disabled" +@@ t/perf/p7519-fsmonitor.sh: test_fsmonitor_suite () { + trace_start fsmonitor-watchman + if test -n "$GIT_PERF_7519_FSMONITOR"; then + for INTEGRATION_PATH in $GIT_PERF_7519_FSMONITOR; do +- test_expect_success "setup for fsmonitor $INTEGRATION_PATH" 'setup_for_fsmonitor' ++ test_expect_success "setup for fsmonitor $INTEGRATION_PATH" 'setup_for_fsmonitor_hook' + test_fsmonitor_suite + done + else +- test_expect_success "setup for fsmonitor" 'setup_for_fsmonitor' ++ test_expect_success "setup for fsmonitor hook" 'setup_for_fsmonitor_hook' + test_fsmonitor_suite + fi + @@ t/perf/p7519-fsmonitor.sh: test_expect_success "setup without fsmonitor" ' test_fsmonitor_suite trace_stop @@ t/perf/p7519-fsmonitor.sh: test_expect_success "setup without fsmonitor" ' +then + USE_FSMONITOR_DAEMON=t + -+ trace_start fsmonitor--daemon--server -+ git fsmonitor--daemon start ++ test_expect_success "setup for builtin fsmonitor" ' ++ trace_start fsmonitor--daemon--server && ++ git fsmonitor--daemon start && + -+ trace_start fsmonitor--daemon--client ++ trace_start fsmonitor--daemon--client && + -+ git config core.fsmonitor true -+ git update-index --fsmonitor ++ git config core.fsmonitor true && ++ git update-index --fsmonitor ++ ' + + test_fsmonitor_suite + 26: 64a5b74167 = 26: 42631259e8 fsmonitor--daemon: periodically truncate list of modified files 27: 5b06eb5d0e ! 27: f256c3cbe8 fsmonitor--daemon: use a cookie file to sync with file system @@ builtin/fsmonitor--daemon.c: static int do_as_client__status(void) +enum fsmonitor_cookie_item_result { + FCIR_ERROR = -1, /* could not create cookie file ? */ -+ FCIR_INIT = 0, ++ FCIR_INIT, + FCIR_SEEN, + FCIR_ABORT, +}; + +struct fsmonitor_cookie_item { + struct hashmap_entry entry; -+ const char *name; ++ char *name; + enum fsmonitor_cookie_item_result result; +}; + @@ builtin/fsmonitor--daemon.c: static int do_as_client__status(void) + * that the listener thread has seen it. + */ + fd = open(cookie_pathname.buf, O_WRONLY | O_CREAT | O_EXCL, 0600); -+ if (fd >= 0) { -+ close(fd); -+ unlink(cookie_pathname.buf); -+ -+ /* -+ * Technically, this is an infinite wait (well, unless another -+ * thread sends us an abort). I'd like to change this to -+ * use `pthread_cond_timedwait()` and return an error/timeout -+ * and let the caller do the trivial response thing, but we -+ * don't have that routine in our thread-utils. -+ * -+ * After extensive beta testing I'm not really worried about -+ * this. Also note that the above open() and unlink() calls -+ * will cause at least two FS events on that path, so the odds -+ * of getting stuck are pretty slim. -+ */ -+ while (cookie->result == FCIR_INIT) -+ pthread_cond_wait(&state->cookies_cond, -+ &state->main_lock); -+ } else { ++ if (fd < 0) { + error_errno(_("could not create fsmonitor cookie '%s'"), + cookie->name); + + cookie->result = FCIR_ERROR; ++ goto done; + } + ++ /* ++ * Technically, close() and unlink() can fail, but we don't ++ * care here. We only created the file to trigger a watch ++ * event from the FS to know that when we're up to date. ++ */ ++ close(fd); ++ unlink(cookie_pathname.buf); ++ ++ /* ++ * Technically, this is an infinite wait (well, unless another ++ * thread sends us an abort). I'd like to change this to ++ * use `pthread_cond_timedwait()` and return an error/timeout ++ * and let the caller do the trivial response thing, but we ++ * don't have that routine in our thread-utils. ++ * ++ * After extensive beta testing I'm not really worried about ++ * this. Also note that the above open() and unlink() calls ++ * will cause at least two FS events on that path, so the odds ++ * of getting stuck are pretty slim. ++ */ ++ while (cookie->result == FCIR_INIT) ++ pthread_cond_wait(&state->cookies_cond, ++ &state->main_lock); ++ ++done: + hashmap_remove(&state->cookies, &cookie->entry, NULL); + + result = cookie->result; + -+ free((char*)cookie->name); ++ free(cookie->name); + free(cookie); + strbuf_release(&cookie_pathname); + 28: 1fd5439de0 = 28: 08af8296f9 fsmonitor: force update index after large responses 29: 6fc1430285 ! 29: e6cf84dc8e t7527: test status with untracked-cache and fsmonitor--daemon @@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'cleanup worktrees' ' +} + +matrix_try () { -+ uc=$1 -+ fsm=$2 -+ fn=$3 ++ uc=$1 && ++ fsm=$2 && ++ fn=$3 && + + test_expect_success "Matrix[uc:$uc][fsm:$fsm] $fn" ' + matrix_clean_up_repo && 30: b915b95cc2 < -: ---------- update-index: convert fsmonitor warnings to advise Jeff Hostetler (29): fsmonitor: enhance existing comments, clarify trivial response handling fsmonitor-ipc: create client routines for git-fsmonitor--daemon fsmonitor: config settings are repository-specific fsmonitor: use IPC to query the builtin FSMonitor daemon fsmonitor: document builtin fsmonitor fsmonitor--daemon: add a built-in fsmonitor daemon fsmonitor--daemon: implement 'stop' and 'status' commands compat/fsmonitor/fsm-listen-win32: stub in backend for Windows compat/fsmonitor/fsm-listen-darwin: stub in backend for Darwin fsmonitor--daemon: implement 'run' command fsmonitor--daemon: implement 'start' command fsmonitor--daemon: add pathname classification fsmonitor--daemon: define token-ids fsmonitor--daemon: create token-based changed path cache compat/fsmonitor/fsm-listen-win32: implement FSMonitor backend on Windows compat/fsmonitor/fsm-listen-darwin: add MacOS header files for FSEvent compat/fsmonitor/fsm-listen-darwin: implement FSEvent listener on MacOS fsmonitor--daemon: implement handle_client callback help: include fsmonitor--daemon feature flag in version info t/helper/fsmonitor-client: create IPC client to talk to FSMonitor Daemon t7527: create test for fsmonitor--daemon t/perf: avoid copying builtin fsmonitor files into test repo t/helper/test-chmtime: skip directories on Windows t/perf/p7519: speed up test on Windows t/perf/p7519: add fsmonitor--daemon test cases fsmonitor--daemon: periodically truncate list of modified files fsmonitor--daemon: use a cookie file to sync with file system fsmonitor: force update index after large responses t7527: test status with untracked-cache and fsmonitor--daemon .gitignore | 1 + Documentation/config/core.txt | 60 +- Documentation/git-fsmonitor--daemon.txt | 75 ++ Documentation/git-update-index.txt | 8 +- Makefile | 17 + builtin.h | 1 + builtin/fsmonitor--daemon.c | 1480 +++++++++++++++++++++++ builtin/update-index.c | 7 +- cache.h | 1 - compat/fsmonitor/fsm-darwin-gcc.h | 92 ++ compat/fsmonitor/fsm-listen-darwin.c | 427 +++++++ compat/fsmonitor/fsm-listen-win32.c | 586 +++++++++ compat/fsmonitor/fsm-listen.h | 49 + config.c | 14 - config.h | 1 - config.mak.uname | 20 + contrib/buildsystems/CMakeLists.txt | 10 + environment.c | 1 - fsmonitor--daemon.h | 166 +++ fsmonitor-ipc.c | 171 +++ fsmonitor-ipc.h | 48 + fsmonitor-settings.c | 114 ++ fsmonitor-settings.h | 21 + fsmonitor.c | 216 +++- fsmonitor.h | 15 +- git.c | 1 + help.c | 4 + repo-settings.c | 1 + repository.h | 3 + t/README | 4 +- t/helper/test-chmtime.c | 15 + t/helper/test-fsmonitor-client.c | 116 ++ t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + t/perf/p7519-fsmonitor.sh | 68 +- t/perf/perf-lib.sh | 2 +- t/t7527-builtin-fsmonitor.sh | 594 +++++++++ t/test-lib.sh | 7 + 38 files changed, 4312 insertions(+), 106 deletions(-) create mode 100644 Documentation/git-fsmonitor--daemon.txt create mode 100644 builtin/fsmonitor--daemon.c create mode 100644 compat/fsmonitor/fsm-darwin-gcc.h create mode 100644 compat/fsmonitor/fsm-listen-darwin.c create mode 100644 compat/fsmonitor/fsm-listen-win32.c create mode 100644 compat/fsmonitor/fsm-listen.h create mode 100644 fsmonitor--daemon.h create mode 100644 fsmonitor-ipc.c create mode 100644 fsmonitor-ipc.h create mode 100644 fsmonitor-settings.c create mode 100644 fsmonitor-settings.h create mode 100644 t/helper/test-fsmonitor-client.c create mode 100755 t/t7527-builtin-fsmonitor.sh base-commit: 715d08a9e51251ad8290b181b6ac3b9e1f9719d7 Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-1041%2Fjeffhostetler%2Fbuiltin-fsmonitor-part2-v7 Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1041/jeffhostetler/builtin-fsmonitor-part2-v7 Pull-Request: https://github.com/gitgitgadget/git/pull/1041 Range-diff vs v6: 1: 10a34082fcc = 1: e98373f997f fsmonitor: enhance existing comments, clarify trivial response handling 2: e1c946af2c5 ! 2: ab68b944173 fsmonitor-ipc: create client routines for git-fsmonitor--daemon @@ fsmonitor-ipc.c (new) + state = ipc_client_try_connect(fsmonitor_ipc__get_path(), &options, + &connection); + if (state != IPC_STATE__LISTENING) { -+ die("fsmonitor--daemon is not running"); ++ die(_("fsmonitor--daemon is not running")); + return -1; + } + @@ fsmonitor-ipc.c (new) + ipc_client_close_connection(connection); + + if (ret == -1) { -+ die("could not send '%s' command to fsmonitor--daemon", c); ++ die(_("could not send '%s' command to fsmonitor--daemon"), c); + return -1; + } + 3: ae622a517cf ! 3: e04c7301f24 fsmonitor: config settings are repository-specific @@ fsmonitor-settings.c (new) + return; + + CALLOC_ARRAY(s, 1); ++ s->mode = FSMONITOR_MODE_DISABLED; + + r->settings.fsmonitor = s; + -+ fsm_settings__set_disabled(r); -+ + /* + * Overload the existing "core.fsmonitor" config setting (which + * has historically been either unset or a hook pathname) to 4: 55974867da5 = 4: ea02ba25d8f fsmonitor: use IPC to query the builtin FSMonitor daemon 5: ce42d5bbaf6 = 5: 6ab7db9cb76 fsmonitor: document builtin fsmonitor 6: 9ce938c69b5 = 6: 0ce8ae3f2cf fsmonitor--daemon: add a built-in fsmonitor daemon 7: 3bd7e6a051e = 7: 4624ce2fa47 fsmonitor--daemon: implement 'stop' and 'status' commands 8: 92d3e54da7f = 8: a29fe7266a4 compat/fsmonitor/fsm-listen-win32: stub in backend for Windows 9: f1219221270 = 9: 2f8a42fdb93 compat/fsmonitor/fsm-listen-darwin: stub in backend for Darwin 10: a3f94627cf6 ! 10: f07800690ee fsmonitor--daemon: implement 'run' command @@ builtin/fsmonitor--daemon.c: static int do_as_client__status(void) + * common error case. + */ + if (fsmonitor_ipc__get_state() == IPC_STATE__LISTENING) -+ die("fsmonitor--daemon is already running '%s'", ++ die(_("fsmonitor--daemon is already running '%s'"), + the_repository->worktree); + + if (fsmonitor__announce_startup) { 11: 8de40b0fe8b ! 11: a6a39a3306d fsmonitor--daemon: implement 'start' command @@ builtin/fsmonitor--daemon.c: static int try_to_run_foreground_daemon(void) + * immediately exited). + */ + if (fsmonitor_ipc__get_state() == IPC_STATE__LISTENING) -+ die("fsmonitor--daemon is already running '%s'", ++ die(_("fsmonitor--daemon is already running '%s'"), + the_repository->worktree); + + if (fsmonitor__announce_startup) { @@ builtin/fsmonitor--daemon.c: static int try_to_run_foreground_daemon(void) + default: + case SBGR_ERROR: + case SBGR_CB_ERROR: -+ return error("daemon failed to start"); ++ return error(_("daemon failed to start")); + + case SBGR_TIMEOUT: -+ return error("daemon not online yet"); ++ return error(_("daemon not online yet")); + + case SBGR_DIED: -+ return error("daemon terminated"); ++ return error(_("daemon terminated")); + } +} + 12: e5d419b1ea0 = 12: d62e338d008 fsmonitor--daemon: add pathname classification 13: c2f3668a866 = 13: 53e06b4ae5d fsmonitor--daemon: define token-ids 14: bd492d13fb1 = 14: 39f43fabe02 fsmonitor--daemon: create token-based changed path cache 15: 6b5b3d08227 ! 15: 239558e34ff compat/fsmonitor/fsm-listen-win32: implement FSMonitor backend on Windows @@ compat/fsmonitor/fsm-listen-win32.c + if (len > 0) + goto normalize; + if (GetLastError() != ERROR_INSUFFICIENT_BUFFER) { -+ error("[GLE %ld] could not convert path to UTF-8: '%.*ls'", ++ error(_("[GLE %ld] could not convert path to UTF-8: '%.*ls'"), + GetLastError(), + (int)(info->FileNameLength / sizeof(WCHAR)), + info->FileName); @@ compat/fsmonitor/fsm-listen-win32.c + if (watch->is_active) + return 0; + -+ error("ReadDirectoryChangedW failed on '%s' [GLE %ld]", ++ error(_("ReadDirectoryChangedW failed on '%s' [GLE %ld]"), + watch->path.buf, GetLastError()); + return -1; +} @@ compat/fsmonitor/fsm-listen-win32.c + * sure it is worth it. + */ + -+ error("GetOverlappedResult failed on '%s' [GLE %ld]", ++ error(_("GetOverlappedResult failed on '%s' [GLE %ld]"), + watch->path.buf, gle); + return -1; +} 16: cdef9730b3f ! 16: 14b775e9d8b compat/fsmonitor/fsm-listen-darwin: add MacOS header files for FSEvent @@ Commit message Signed-off-by: Jeff Hostetler <jeffhost@xxxxxxxxxxxxx> Signed-off-by: Junio C Hamano <gitster@xxxxxxxxx> - ## compat/fsmonitor/fsm-listen-darwin.c ## + ## compat/fsmonitor/fsm-darwin-gcc.h (new) ## @@ ++#ifndef FSM_DARWIN_GCC_H ++#define FSM_DARWIN_GCC_H ++ +#ifndef __clang__ +/* + * It is possible to #include CoreFoundation/CoreFoundation.h when compiling @@ compat/fsmonitor/fsm-listen-darwin.c +void FSEventStreamStop(FSEventStreamRef stream); +void FSEventStreamInvalidate(FSEventStreamRef stream); +void FSEventStreamRelease(FSEventStreamRef stream); ++ ++#endif /* !clang */ ++#endif /* FSM_DARWIN_GCC_H */ + + ## compat/fsmonitor/fsm-listen-darwin.c ## +@@ ++#ifndef __clang__ ++#include "fsm-darwin-gcc.h" +#else -+/* -+ * Let Apple's headers declare `isalnum()` first, before -+ * Git's headers override it via a constant -+ */ -+#include <string.h> +#include <CoreFoundation/CoreFoundation.h> +#include <CoreServices/CoreServices.h> ++ ++#ifndef AVAILABLE_MAC_OS_X_VERSION_10_13_AND_LATER ++/* ++ * This enum value was added in 10.13 to: ++ * ++ * /Applications/Xcode.app/Contents/Developer/Platforms/ \ ++ * MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/ \ ++ * Library/Frameworks/CoreServices.framework/Frameworks/ \ ++ * FSEvents.framework/Versions/Current/Headers/FSEvents.h ++ * ++ * If we're compiling against an older SDK, this symbol won't be ++ * present. Silently define it here so that we don't have to ifdef ++ * the logging or masking below. This should be harmless since older ++ * versions of macOS won't ever emit this FS event anyway. ++ */ ++#define kFSEventStreamEventFlagItemCloned 0x00400000 ++#endif +#endif + #include "cache.h" 17: aabfebd3a41 ! 17: 55bd7aee06c compat/fsmonitor/fsm-listen-darwin: implement FSEvent listener on MacOS @@ Commit message Signed-off-by: Junio C Hamano <gitster@xxxxxxxxx> ## compat/fsmonitor/fsm-listen-darwin.c ## -@@ compat/fsmonitor/fsm-listen-darwin.c: void FSEventStreamRelease(FSEventStreamRef stream); +@@ #include "cache.h" #include "fsmonitor.h" #include "fsm-listen.h" @@ compat/fsmonitor/fsm-listen-darwin.c: void FSEventStreamRelease(FSEventStreamRef + return 0; + +failed: -+ error("Unable to create FSEventStream."); ++ error(_("Unable to create FSEventStream.")); + + FREE_AND_NULL(state->backend_data); return -1; @@ compat/fsmonitor/fsm-listen-darwin.c: void FSEventStreamRelease(FSEventStreamRef + data->stream_scheduled = 1; + + if (!FSEventStreamStart(data->stream)) { -+ error("Failed to start the FSEventStream"); ++ error(_("Failed to start the FSEventStream")); + goto force_error_stop_without_loop; + } + data->stream_started = 1; 18: 0896e72e2b8 = 18: 1f4b5209bf6 fsmonitor--daemon: implement handle_client callback 19: bc240a9e665 ! 19: 8cf62c9fc6f help: include fsmonitor--daemon feature flag in version info @@ t/test-lib.sh: test_lazy_prereq SHA1 ' +# Does this platform support `git fsmonitor--daemon` +# +test_lazy_prereq FSMONITOR_DAEMON ' -+ git version --build-options | grep "feature:" | grep "fsmonitor--daemon" ++ git version --build-options >output && ++ grep "feature: fsmonitor--daemon" output +' 20: 2b563c240eb ! 20: 1bd74a81593 t/helper/fsmonitor-client: create IPC client to talk to FSMonitor Daemon @@ t/helper/test-fsmonitor-client.c (new) + + ret = fsmonitor_ipc__send_query(token, &answer); + if (ret < 0) -+ die(_("could not query fsmonitor--daemon")); ++ die("could not query fsmonitor--daemon"); + + write_in_full(1, answer.buf, answer.len); + strbuf_release(&answer); @@ t/helper/test-fsmonitor-client.c (new) + const char *token = NULL; + + const char * const fsmonitor_client_usage[] = { -+ N_("test-helper fsmonitor-client query [<token>]"), -+ N_("test-helper fsmonitor-client flush"), ++ "test-tool fsmonitor-client query [<token>]", ++ "test-tool fsmonitor-client flush", + NULL, + }; + + struct option options[] = { -+ OPT_STRING(0, "token", &token, N_("token"), -+ N_("command token to send to the server")), ++ OPT_STRING(0, "token", &token, "token", ++ "command token to send to the server"), + OPT_END() + }; + -+ if (argc < 2) -+ usage_with_options(fsmonitor_client_usage, options); ++ argc = parse_options(argc, argv, NULL, options, fsmonitor_client_usage, 0); + -+ if (argc == 2 && !strcmp(argv[1], "-h")) ++ if (argc != 1) + usage_with_options(fsmonitor_client_usage, options); + -+ subcmd = argv[1]; -+ argv--; -+ argc++; -+ -+ argc = parse_options(argc, argv, NULL, options, fsmonitor_client_usage, 0); ++ subcmd = argv[0]; + + setup_git_directory(); + 21: db8809da089 ! 21: 4a920d0b54a t7527: create test for fsmonitor--daemon @@ t/t7527-builtin-fsmonitor.sh (new) +fi + +stop_daemon_delete_repo () { -+ r=$1 -+ git -C $r fsmonitor--daemon stop >/dev/null 2>/dev/null ++ r=$1 && ++ test_might_fail git -C $r fsmonitor--daemon stop && + rm -rf $1 -+ return 0 +} + +start_daemon () { -+ case "$#" in -+ 1) r="-C $1";; -+ *) r=""; -+ esac ++ r= && ++ tf= && ++ t2= && ++ tk= && ++ ++ while test "$#" -ne 0 ++ do ++ case "$1" in ++ -C) ++ shift; ++ test "$#" -ne 0 || BUG "error: -C requires arg" ++ r="-C $1" ++ shift ++ ;; ++ -tf) ++ shift; ++ test "$#" -ne 0 || BUG "error: -tf requires arg" ++ tf="$1" ++ shift ++ ;; ++ -t2) ++ shift; ++ test "$#" -ne 0 || BUG "error: -t2 requires arg" ++ t2="$1" ++ shift ++ ;; ++ -tk) ++ shift; ++ test "$#" -ne 0 || BUG "error: -tk requires arg" ++ tk="$1" ++ shift ++ ;; ++ *) ++ BUG "error: unknown option: '$1'" ++ ;; ++ esac ++ done && + -+ git $r fsmonitor--daemon start || return $? -+ git $r fsmonitor--daemon status || return $? -+ -+ return 0 ++ ( ++ if test -n "$tf" ++ then ++ GIT_TRACE_FSMONITOR="$tf" ++ export GIT_TRACE_FSMONITOR ++ fi && ++ ++ if test -n "$t2" ++ then ++ GIT_TRACE2_PERF="$t2" ++ export GIT_TRACE2_PERF ++ fi && ++ ++ if test -n "$tk" ++ then ++ GIT_TEST_FSMONITOR_TOKEN="$tk" ++ export GIT_TEST_FSMONITOR_TOKEN ++ fi && ++ ++ git $r fsmonitor--daemon start && ++ git $r fsmonitor--daemon status ++ ) +} + +# Is a Trace2 data event present with the given catetory and key? +# We do not care what the value is. +# +have_t2_data_event () { -+ c=$1 -+ k=$2 ++ c=$1 && ++ k=$2 && + + grep -e '"event":"data".*"category":"'"$c"'".*"key":"'"$k"'"' +} @@ t/t7527-builtin-fsmonitor.sh (new) + test_when_finished "stop_daemon_delete_repo test_explicit" && + + git init test_explicit && -+ start_daemon test_explicit && ++ start_daemon -C test_explicit && + + git -C test_explicit fsmonitor--daemon stop && + test_must_fail git -C test_explicit fsmonitor--daemon status @@ t/t7527-builtin-fsmonitor.sh (new) + # but this test case is only concerned with whether the daemon was + # implicitly started.) + -+ GIT_TRACE2_EVENT="$(pwd)/.git/trace" \ ++ GIT_TRACE2_EVENT="$PWD/.git/trace" \ + test-tool -C test_implicit fsmonitor-client query --token 0 >actual && + nul_to_q <actual >actual.filtered && + grep "builtin:" actual.filtered && @@ t/t7527-builtin-fsmonitor.sh (new) + + git init test_implicit_1 && + -+ start_daemon test_implicit_1 && ++ start_daemon -C test_implicit_1 && + + # deleting the .git directory will implicitly stop the daemon. + rm -rf test_implicit_1/.git && @@ t/t7527-builtin-fsmonitor.sh (new) + + git init test_implicit_2 && + -+ start_daemon test_implicit_2 && ++ start_daemon -C test_implicit_2 && + + # renaming the .git directory will implicitly stop the daemon. + mv test_implicit_2/.git test_implicit_2/.xxx && @@ t/t7527-builtin-fsmonitor.sh (new) + + git init test_multiple && + -+ start_daemon test_multiple && ++ start_daemon -C test_multiple && + + test_must_fail git -C test_multiple fsmonitor--daemon start 2>actual && + grep "fsmonitor--daemon is already running" actual && @@ t/t7527-builtin-fsmonitor.sh (new) +# This is here in case something else fails first. +# +redundant_stop_daemon () { -+ git fsmonitor--daemon stop -+ return 0 ++ test_might_fail git fsmonitor--daemon stop +} + +test_expect_success 'update-index implicitly starts daemon' ' @@ t/t7527-builtin-fsmonitor.sh (new) + + test_must_fail git fsmonitor--daemon status && + -+ GIT_TRACE2_EVENT="$(pwd)/.git/trace_implicit_1" \ ++ GIT_TRACE2_EVENT="$PWD/.git/trace_implicit_1" \ + git update-index --fsmonitor && + + git fsmonitor--daemon status && @@ t/t7527-builtin-fsmonitor.sh (new) + + test_must_fail git fsmonitor--daemon status && + -+ GIT_TRACE2_EVENT="$(pwd)/.git/trace_implicit_2" \ ++ GIT_TRACE2_EVENT="$PWD/.git/trace_implicit_2" \ + git status >actual && + + git fsmonitor--daemon status && @@ t/t7527-builtin-fsmonitor.sh (new) +' + +edit_files () { -+ echo 1 >modified -+ echo 2 >dir1/modified -+ echo 3 >dir2/modified ++ echo 1 >modified && ++ echo 2 >dir1/modified && ++ echo 3 >dir2/modified && + >dir1/untracked +} + +delete_files () { -+ rm -f delete -+ rm -f dir1/delete ++ rm -f delete && ++ rm -f dir1/delete && + rm -f dir2/delete +} + +create_files () { -+ echo 1 >new -+ echo 2 >dir1/new ++ echo 1 >new && ++ echo 2 >dir1/new && + echo 3 >dir2/new +} + +rename_files () { -+ mv rename renamed -+ mv dir1/rename dir1/renamed ++ mv rename renamed && ++ mv dir1/rename dir1/renamed && + mv dir2/rename dir2/renamed +} + +file_to_directory () { -+ rm -f delete -+ mkdir delete ++ rm -f delete && ++ mkdir delete && + echo 1 >delete/new +} + +directory_to_file () { -+ rm -rf dir1 ++ rm -rf dir1 && + echo 1 >dir1 +} + -+verify_status () { -+ git status >actual && -+ GIT_INDEX_FILE=.git/fresh-index git read-tree master && -+ GIT_INDEX_FILE=.git/fresh-index git -c core.fsmonitor=false status >expect && -+ test_cmp expect actual && -+ echo HELLO AFTER && -+ cat .git/trace && -+ echo HELLO AFTER -+} -+ +# 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 @@ t/t7527-builtin-fsmonitor.sh (new) +# daemon) because these commands might implicitly restart the daemon. + +clean_up_repo_and_stop_daemon () { -+ git reset --hard HEAD -+ git clean -fd -+ git fsmonitor--daemon stop ++ git reset --hard HEAD && ++ git clean -fd && ++ test_might_fail git fsmonitor--daemon stop && + rm -f .git/trace +} + +test_expect_success 'edit some files' ' + test_when_finished clean_up_repo_and_stop_daemon && + -+ ( -+ GIT_TRACE_FSMONITOR="$(pwd)/.git/trace" && -+ export GIT_TRACE_FSMONITOR && -+ -+ start_daemon -+ ) && ++ start_daemon -tf "$PWD/.git/trace" && + + edit_files && + -+ test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 && ++ test-tool fsmonitor-client query --token 0 && + + grep "^event: dir1/modified$" .git/trace && + grep "^event: dir2/modified$" .git/trace && @@ t/t7527-builtin-fsmonitor.sh (new) +test_expect_success 'create some files' ' + test_when_finished clean_up_repo_and_stop_daemon && + -+ ( -+ GIT_TRACE_FSMONITOR="$(pwd)/.git/trace" && -+ export GIT_TRACE_FSMONITOR && -+ -+ start_daemon -+ ) && ++ start_daemon -tf "$PWD/.git/trace" && + + create_files && + -+ test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 && ++ test-tool fsmonitor-client query --token 0 && + + grep "^event: dir1/new$" .git/trace && + grep "^event: dir2/new$" .git/trace && @@ t/t7527-builtin-fsmonitor.sh (new) +test_expect_success 'delete some files' ' + test_when_finished clean_up_repo_and_stop_daemon && + -+ ( -+ GIT_TRACE_FSMONITOR="$(pwd)/.git/trace" && -+ export GIT_TRACE_FSMONITOR && -+ -+ start_daemon -+ ) && ++ start_daemon -tf "$PWD/.git/trace" && + + delete_files && + -+ test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 && ++ test-tool fsmonitor-client query --token 0 && + + grep "^event: dir1/delete$" .git/trace && + grep "^event: dir2/delete$" .git/trace && @@ t/t7527-builtin-fsmonitor.sh (new) +test_expect_success 'rename some files' ' + test_when_finished clean_up_repo_and_stop_daemon && + -+ ( -+ GIT_TRACE_FSMONITOR="$(pwd)/.git/trace" && -+ export GIT_TRACE_FSMONITOR && -+ -+ start_daemon -+ ) && ++ start_daemon -tf "$PWD/.git/trace" && + + rename_files && + -+ test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 && ++ test-tool fsmonitor-client query --token 0 && + + grep "^event: dir1/rename$" .git/trace && + grep "^event: dir2/rename$" .git/trace && @@ t/t7527-builtin-fsmonitor.sh (new) +test_expect_success 'rename directory' ' + test_when_finished clean_up_repo_and_stop_daemon && + -+ ( -+ GIT_TRACE_FSMONITOR="$(pwd)/.git/trace" && -+ export GIT_TRACE_FSMONITOR && -+ -+ start_daemon -+ ) && ++ start_daemon -tf "$PWD/.git/trace" && + + mv dirtorename dirrenamed && + -+ test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 && ++ test-tool fsmonitor-client query --token 0 && + + grep "^event: dirtorename/*$" .git/trace && + grep "^event: dirrenamed/*$" .git/trace @@ t/t7527-builtin-fsmonitor.sh (new) +test_expect_success 'file changes to directory' ' + test_when_finished clean_up_repo_and_stop_daemon && + -+ ( -+ GIT_TRACE_FSMONITOR="$(pwd)/.git/trace" && -+ export GIT_TRACE_FSMONITOR && -+ -+ start_daemon -+ ) && ++ start_daemon -tf "$PWD/.git/trace" && + + file_to_directory && + -+ test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 && ++ test-tool fsmonitor-client query --token 0 && + + grep "^event: delete$" .git/trace && + grep "^event: delete/new$" .git/trace @@ t/t7527-builtin-fsmonitor.sh (new) +test_expect_success 'directory changes to a file' ' + test_when_finished clean_up_repo_and_stop_daemon && + -+ ( -+ GIT_TRACE_FSMONITOR="$(pwd)/.git/trace" && -+ export GIT_TRACE_FSMONITOR && -+ -+ start_daemon -+ ) && ++ start_daemon -tf "$PWD/.git/trace" && + + directory_to_file && + -+ test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 && ++ test-tool fsmonitor-client query --token 0 && + + grep "^event: dir1$" .git/trace +' @@ t/t7527-builtin-fsmonitor.sh (new) + + git init test_flush && + -+ ( -+ GIT_TEST_FSMONITOR_TOKEN=true && -+ export GIT_TEST_FSMONITOR_TOKEN && -+ -+ GIT_TRACE_FSMONITOR="$(pwd)/.git/trace_daemon" && -+ export GIT_TRACE_FSMONITOR && -+ -+ start_daemon test_flush -+ ) && ++ start_daemon -C test_flush -tf "$PWD/.git/trace_daemon" -tk true && + + # The daemon should have an initial token with no events in _0 and + # then a few (probably platform-specific number of) events in _1. @@ t/t7527-builtin-fsmonitor.sh (new) + test-tool -C test_flush fsmonitor-client query --token "builtin:test_00000001:0" >actual_0 && + nul_to_q <actual_0 >actual_q0 && + -+ touch test_flush/file_1 && -+ touch test_flush/file_2 && ++ >test_flush/file_1 && ++ >test_flush/file_2 && + + test-tool -C test_flush fsmonitor-client query --token "builtin:test_00000001:0" >actual_1 && + nul_to_q <actual_1 >actual_q1 && @@ t/t7527-builtin-fsmonitor.sh (new) + + grep "^builtin:test_00000002:0Q$" actual_q2 && + -+ touch test_flush/file_3 && ++ >test_flush/file_3 && + + test-tool -C test_flush fsmonitor-client query --token "builtin:test_00000002:0" >actual_3 && + nul_to_q <actual_3 >actual_q3 && @@ t/t7527-builtin-fsmonitor.sh (new) +test_expect_success 'worktree with .git file' ' + git -C wt-base worktree add ../wt-secondary && + -+ ( -+ GIT_TRACE2_PERF="$(pwd)/trace2_wt_secondary" && -+ export GIT_TRACE2_PERF && -+ -+ GIT_TRACE_FSMONITOR="$(pwd)/trace_wt_secondary" && -+ export GIT_TRACE_FSMONITOR && -+ -+ start_daemon wt-secondary -+ ) && ++ start_daemon -C wt-secondary \ ++ -tf "$PWD/trace_wt_secondary" \ ++ -t2 "$PWD/trace2_wt_secondary" && + + git -C wt-secondary fsmonitor--daemon stop && + test_must_fail git -C wt-secondary fsmonitor--daemon status 22: 15bd5aaff36 = 22: c925a9a7459 t/perf: avoid copying builtin fsmonitor files into test repo 23: 4cb97918d20 = 23: 5b3381c223e t/helper/test-chmtime: skip directories on Windows 24: a70748b4640 ! 24: 803a540cc00 t/perf/p7519: speed up test on Windows @@ t/perf/p7519-fsmonitor.sh: test_fsmonitor_suite() { head -100000 | \ grep -v \" | \ - sed '\''s/\(.\)/\\\1/g'\'' | \ -+ egrep -v " ." | \ ++ grep -v " ." | \ xargs test-tool chmtime -300 && git status ' 25: b0c9c9c7a40 ! 25: d5ca2df31c8 t/perf/p7519: add fsmonitor--daemon test cases @@ Commit message Signed-off-by: Junio C Hamano <gitster@xxxxxxxxx> ## t/perf/p7519-fsmonitor.sh ## +@@ t/perf/p7519-fsmonitor.sh: test_expect_success "one time repo setup" ' + fi + ' + +-setup_for_fsmonitor () { ++setup_for_fsmonitor_hook () { + # set INTEGRATION_SCRIPT depending on the environment + if test -n "$INTEGRATION_PATH" + then @@ t/perf/p7519-fsmonitor.sh: test_perf_w_drop_caches () { } @@ t/perf/p7519-fsmonitor.sh: test_perf_w_drop_caches () { + if test -n "$USE_FSMONITOR_DAEMON" + then + DESC="builtin fsmonitor--daemon" -+ elif test -n "$INTEGRATION_SCRIPT"; then ++ elif test -n "$INTEGRATION_SCRIPT" ++ then DESC="fsmonitor=$(basename $INTEGRATION_SCRIPT)" else DESC="fsmonitor=disabled" +@@ t/perf/p7519-fsmonitor.sh: test_fsmonitor_suite () { + trace_start fsmonitor-watchman + if test -n "$GIT_PERF_7519_FSMONITOR"; then + for INTEGRATION_PATH in $GIT_PERF_7519_FSMONITOR; do +- test_expect_success "setup for fsmonitor $INTEGRATION_PATH" 'setup_for_fsmonitor' ++ test_expect_success "setup for fsmonitor $INTEGRATION_PATH" 'setup_for_fsmonitor_hook' + test_fsmonitor_suite + done + else +- test_expect_success "setup for fsmonitor" 'setup_for_fsmonitor' ++ test_expect_success "setup for fsmonitor hook" 'setup_for_fsmonitor_hook' + test_fsmonitor_suite + fi + @@ t/perf/p7519-fsmonitor.sh: test_expect_success "setup without fsmonitor" ' test_fsmonitor_suite trace_stop @@ t/perf/p7519-fsmonitor.sh: test_expect_success "setup without fsmonitor" ' +then + USE_FSMONITOR_DAEMON=t + -+ trace_start fsmonitor--daemon--server -+ git fsmonitor--daemon start ++ test_expect_success "setup for builtin fsmonitor" ' ++ trace_start fsmonitor--daemon--server && ++ git fsmonitor--daemon start && + -+ trace_start fsmonitor--daemon--client ++ trace_start fsmonitor--daemon--client && + -+ git config core.fsmonitor true -+ git update-index --fsmonitor ++ git config core.fsmonitor true && ++ git update-index --fsmonitor ++ ' + + test_fsmonitor_suite + 26: 64a5b741670 = 26: 42631259e89 fsmonitor--daemon: periodically truncate list of modified files 27: 5b06eb5d0e6 ! 27: f256c3cbe8b fsmonitor--daemon: use a cookie file to sync with file system @@ builtin/fsmonitor--daemon.c: static int do_as_client__status(void) +enum fsmonitor_cookie_item_result { + FCIR_ERROR = -1, /* could not create cookie file ? */ -+ FCIR_INIT = 0, ++ FCIR_INIT, + FCIR_SEEN, + FCIR_ABORT, +}; + +struct fsmonitor_cookie_item { + struct hashmap_entry entry; -+ const char *name; ++ char *name; + enum fsmonitor_cookie_item_result result; +}; + @@ builtin/fsmonitor--daemon.c: static int do_as_client__status(void) + * that the listener thread has seen it. + */ + fd = open(cookie_pathname.buf, O_WRONLY | O_CREAT | O_EXCL, 0600); -+ if (fd >= 0) { -+ close(fd); -+ unlink(cookie_pathname.buf); -+ -+ /* -+ * Technically, this is an infinite wait (well, unless another -+ * thread sends us an abort). I'd like to change this to -+ * use `pthread_cond_timedwait()` and return an error/timeout -+ * and let the caller do the trivial response thing, but we -+ * don't have that routine in our thread-utils. -+ * -+ * After extensive beta testing I'm not really worried about -+ * this. Also note that the above open() and unlink() calls -+ * will cause at least two FS events on that path, so the odds -+ * of getting stuck are pretty slim. -+ */ -+ while (cookie->result == FCIR_INIT) -+ pthread_cond_wait(&state->cookies_cond, -+ &state->main_lock); -+ } else { ++ if (fd < 0) { + error_errno(_("could not create fsmonitor cookie '%s'"), + cookie->name); + + cookie->result = FCIR_ERROR; ++ goto done; + } + ++ /* ++ * Technically, close() and unlink() can fail, but we don't ++ * care here. We only created the file to trigger a watch ++ * event from the FS to know that when we're up to date. ++ */ ++ close(fd); ++ unlink(cookie_pathname.buf); ++ ++ /* ++ * Technically, this is an infinite wait (well, unless another ++ * thread sends us an abort). I'd like to change this to ++ * use `pthread_cond_timedwait()` and return an error/timeout ++ * and let the caller do the trivial response thing, but we ++ * don't have that routine in our thread-utils. ++ * ++ * After extensive beta testing I'm not really worried about ++ * this. Also note that the above open() and unlink() calls ++ * will cause at least two FS events on that path, so the odds ++ * of getting stuck are pretty slim. ++ */ ++ while (cookie->result == FCIR_INIT) ++ pthread_cond_wait(&state->cookies_cond, ++ &state->main_lock); ++ ++done: + hashmap_remove(&state->cookies, &cookie->entry, NULL); + + result = cookie->result; + -+ free((char*)cookie->name); ++ free(cookie->name); + free(cookie); + strbuf_release(&cookie_pathname); + 28: 1fd5439de03 = 28: 08af8296f96 fsmonitor: force update index after large responses 29: 6fc1430285f ! 29: e6cf84dc8eb t7527: test status with untracked-cache and fsmonitor--daemon @@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'cleanup worktrees' ' +} + +matrix_try () { -+ uc=$1 -+ fsm=$2 -+ fn=$3 ++ uc=$1 && ++ fsm=$2 && ++ fn=$3 && + + test_expect_success "Matrix[uc:$uc][fsm:$fsm] $fn" ' + matrix_clean_up_repo && 30: b915b95cc2f < -: ----------- update-index: convert fsmonitor warnings to advise -- gitgitgadget