Builtin FSMonitor Part 2
Subsystems (2)
PATCH v5 15/30 compat/fsmonitor/fsm-listen-win32: implement FSMonitor backend on Windows
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Teach the win32 backend to register a watch on the working tree root directory (recursively). Also watch the <gitdir> if it is not inside the working tree. And to collect path change notifications into batches and publish.
--- compat/fsmonitor/fsm-listen-win32.c | 565 ++++++++++++++++++++++++++++ 1 file changed, 565 insertions(+)
compat/fsmonitor/fsm-listen-win32.c +565 -0
@@ -2,20 +2,585 @@#include "config.h" #include "fsmonitor.h" #include "fsm-listen.h" +#include "fsmonitor--daemon.h" + +/* +* The documentation of ReadDirectoryChangesW() states that the maximum +* buffer size is 64K when the monitored directory is remote. +* +* Larger buffers may be used when the monitored directory is local and +* will help us receive events faster from the kernel and avoid dropped +* events. +* +* So we try to use a very large buffer and silently fallback to 64K if +* we get an error. +*/ +#define MAX_RDCW_BUF_FALLBACK (65536) +#define MAX_RDCW_BUF (65536 * 8) + +struct one_watch +{ +char buffer[MAX_RDCW_BUF ]; +DWORD buf_len; +DWORD count; + +struct strbuf path; +HANDLE hDir; +HANDLE hEvent; +OVERLAPPED overlapped; + +/* +* Is there an active ReadDirectoryChangesW() call pending. If so, we +* need to later call GetOverlappedResult() and possibly CancelIoEx(). +*/ +BOOL is_active; +}; + +struct fsmonitor_daemon_backend_data +{ +struct one_watch *watch_worktree; +struct one_watch *watch_gitdir; + +HANDLE hEventShutdown; + +HANDLE hListener[3 ];/* we don't own these handles */ +#define LISTENER_SHUTDOWN 0 +#define LISTENER_HAVE_DATA_WORKTREE 1 +#define LISTENER_HAVE_DATA_GITDIR 2 +int nr_listener_handles; +}; + +/* +* Convert the WCHAR path from the notification into UTF8 and +* then normalize it. +*/ +static int normalize_path_in_utf8 (FILE_NOTIFY_INFORMATION *info, +struct strbuf *normalized_path) +{ +int reserve; +int len =0 ; + +strbuf_reset (normalized_path); +if (!info->FileNameLength) + goto normalize; + +/* +* Pre-reserve enough space in the UTF8 buffer for +* each Unicode WCHAR character to be mapped into a +* sequence of 2 UTF8 characters. That should let us +* avoid ERROR_INSUFFICIENT_BUFFER 99.9+% of the time. +*/ + reserve = info->FileNameLength +1 ; +strbuf_grow (normalized_path, reserve); + +for (;;) { + len =WideCharToMultiByte (CP_UTF8 ,0 , info->FileName, + info->FileNameLength /sizeof (WCHAR ), + normalized_path->buf, +strbuf_avail (normalized_path) -1 , +NULL ,NULL ); +if (len >0 ) + 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); +return -1 ; + } + +strbuf_grow (normalized_path, +strbuf_avail (normalized_path) + reserve); + } + +normalize: +strbuf_setlen (normalized_path, len); +return strbuf_normalize_path (normalized_path); +}void fsm_listen__stop_async (struct fsmonitor_daemon_state *state) { +SetEvent (state->backend_data->hListener[LISTENER_SHUTDOWN ]); +} + +static struct one_watch *create_watch (struct fsmonitor_daemon_state *state, +const char *path) +{ +struct one_watch *watch =NULL ; +DWORD desired_access =FILE_LIST_DIRECTORY ; +DWORD share_mode = +FILE_SHARE_WRITE |FILE_SHARE_READ |FILE_SHARE_DELETE ; +HANDLE hDir; +wchar_t wpath[MAX_PATH ]; + +if (xutftowcs_path (wpath, path) <0 ) { +error (_ ("could not convert to wide characters: '%s'" ), path); +return NULL ; + } + + hDir =CreateFileW (wpath, + desired_access, share_mode,NULL ,OPEN_EXISTING , +FILE_FLAG_BACKUP_SEMANTICS |FILE_FLAG_OVERLAPPED , +NULL ); +if (hDir ==INVALID_HANDLE_VALUE ) { +error (_ ("[GLE %ld] could not watch '%s'" ), +GetLastError (), path); +return NULL ; + } + +CALLOC_ARRAY (watch,1 ); + + watch->buf_len =sizeof (watch->buffer);/* assume full MAX_RDCW_BUF */ + +strbuf_init (&watch->path,0 ); +strbuf_addstr (&watch->path, path); + + watch->hDir = hDir; + watch->hEvent =CreateEvent (NULL , TRUE, FALSE,NULL ); + +return watch; +} + +static void destroy_watch (struct one_watch *watch) +{ +if (!watch) +return ; + +strbuf_release (&watch->path); +if (watch->hDir !=INVALID_HANDLE_VALUE ) +CloseHandle (watch->hDir); +if (watch->hEvent !=INVALID_HANDLE_VALUE ) +CloseHandle (watch->hEvent); + +free (watch); +} + +static int start_rdcw_watch (struct fsmonitor_daemon_backend_data *data, +struct one_watch *watch) +{ +DWORD dwNotifyFilter = +FILE_NOTIFY_CHANGE_FILE_NAME | +FILE_NOTIFY_CHANGE_DIR_NAME | +FILE_NOTIFY_CHANGE_ATTRIBUTES | +FILE_NOTIFY_CHANGE_SIZE | +FILE_NOTIFY_CHANGE_LAST_WRITE | +FILE_NOTIFY_CHANGE_CREATION ; + +ResetEvent (watch->hEvent); + +memset (&watch->overlapped,0 ,sizeof (watch->overlapped)); + watch->overlapped.hEvent = watch->hEvent; + +/* +* Queue an async call using Overlapped IO. This returns immediately. +* Our event handle will be signalled when the real result is available. +* +* The return value here just means that we successfully queued it. +* We won't know if the Read...() actually produces data until later. +*/ + watch->is_active =ReadDirectoryChangesW ( + watch->hDir, watch->buffer, watch->buf_len, TRUE, + dwNotifyFilter, &watch->count, &watch->overlapped,NULL ); + +if (watch->is_active) +return 0 ; + +error ("ReadDirectoryChangedW failed on '%s' [GLE %ld]" , + watch->path.buf,GetLastError ()); +return -1 ; +} + +static int recv_rdcw_watch (struct one_watch *watch) +{ +DWORD gle; + + watch->is_active = FALSE; + +/* +* The overlapped result is ready. If the Read...() was successful +* we finally receive the actual result into our buffer. +*/ +if (GetOverlappedResult (watch->hDir, &watch->overlapped, &watch->count, + TRUE)) +return 0 ; + + gle =GetLastError (); +if (gle ==ERROR_INVALID_PARAMETER && +/* +* The kernel throws an invalid parameter error when our +* buffer is too big and we are pointed at a remote +* directory (and possibly for other reasons). Quietly +* set it down and try again. +* +* See note about MAX_RDCW_BUF at the top. +*/ + watch->buf_len >MAX_RDCW_BUF_FALLBACK ) { + watch->buf_len =MAX_RDCW_BUF_FALLBACK ; +return -2 ; + } + +/* +* 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. +*/ + +error ("GetOverlappedResult failed on '%s' [GLE %ld]" , + watch->path.buf, gle); +return -1 ; +} + +static void cancel_rdcw_watch (struct one_watch *watch) +{ +DWORD count; + +if (!watch || !watch->is_active) +return ; + +/* +* The calls to ReadDirectoryChangesW() and GetOverlappedResult() +* form a "pair" (my term) where we queue an IO and promise to +* hang around and wait for the kernel to give us the result. +* +* If for some reason after we queue the IO, we have to quit +* or otherwise not stick around for the second half, we must +* tell the kernel to abort the IO. This prevents the kernel +* from writing to our buffer and/or signalling our event +* after we free them. +* +* (Ask me how much fun it was to track that one down). +*/ +CancelIoEx (watch->hDir, &watch->overlapped); +GetOverlappedResult (watch->hDir, &watch->overlapped, &count, TRUE); + watch->is_active = FALSE; +} + +/* +* Process filesystem events that happen anywhere (recursively) under the +* <worktree> root directory. For a normal working directory, this includes +* both version controlled files and the contents of the .git/ directory. +* +* If <worktree>/.git is a file, then we only see events for the file +* itself. +*/ +static int process_worktree_events (struct fsmonitor_daemon_state *state) +{ +struct fsmonitor_daemon_backend_data *data = state->backend_data; +struct one_watch *watch = data->watch_worktree; +struct strbuf path =STRBUF_INIT ; +struct string_list cookie_list =STRING_LIST_INIT_DUP ; +struct fsmonitor_batch *batch =NULL ; +const char *p = watch->buffer; + +/* +* If the kernel gets more events than will fit in the kernel +* buffer associated with our RDCW handle, it drops them and +* returns a count of zero. +* +* Yes, the call returns WITHOUT error and with length zero. +* This is the documented behavior. (My testing has confirmed +* that it also sets the last error to ERROR_NOTIFY_ENUM_DIR, +* but we do not rely on that since the function did not +* return an error and it is not documented.) +* +* (The "overflow" case is not ambiguous with the "no data" case +* because we did an INFINITE wait.) +* +* This means we have a gap in coverage. Tell the daemon layer +* to resync. +*/ +if (!watch->count) { +trace2_data_string ("fsmonitor" ,NULL ,"fsm-listen/kernel" , +"overflow" ); +fsmonitor_force_resync (state); +return LISTENER_HAVE_DATA_WORKTREE ; + } + +/* +* On Windows, `info` contains an "array" of paths that are +* relative to the root of whichever directory handle received +* the event. +*/ +for (;;) { +FILE_NOTIFY_INFORMATION *info = (void *)p; +const char *slash; +enum fsmonitor_path_type t; + +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 ; + +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" ); + 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 ; + +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); + } + +skip_this_path: +if (!info->NextEntryOffset) +break ; + p += info->NextEntryOffset; + } + +fsmonitor_publish (state, batch, &cookie_list); + batch =NULL ; +string_list_clear (&cookie_list,0 ); +strbuf_release (&path); +return LISTENER_HAVE_DATA_WORKTREE ; + +force_shutdown: +fsmonitor_batch__free_list (batch); +string_list_clear (&cookie_list,0 ); +strbuf_release (&path); +return LISTENER_SHUTDOWN ; +} + +/* +* Process filesystem events that happened anywhere (recursively) under the +* external <gitdir> (such as non-primary worktrees or submodules). +* We only care about cookie files that our client threads created here. +* +* 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. +*/ +static int process_gitdir_events (struct fsmonitor_daemon_state *state) +{ +struct fsmonitor_daemon_backend_data *data = state->backend_data; +struct one_watch *watch = data->watch_gitdir; +struct strbuf path =STRBUF_INIT ; +struct string_list cookie_list =STRING_LIST_INIT_DUP ; +const char *p = watch->buffer; + +if (!watch->count) { +trace2_data_string ("fsmonitor" ,NULL ,"fsm-listen/kernel" , +"overflow" ); +fsmonitor_force_resync (state); +return LISTENER_HAVE_DATA_GITDIR ; + } + +for (;;) { +FILE_NOTIFY_INFORMATION *info = (void *)p; +const char *slash; +enum fsmonitor_path_type t; + +strbuf_reset (&path); +if (normalize_path_in_utf8 (info, &path) ==-1 ) + goto skip_this_path; + + t =fsmonitor_classify_path_gitdir_relative (path.buf); + +switch (t) { +case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX : +/* special case cookie files within gitdir */ + +/* 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_GITDIR : + goto skip_this_path; + +default : +BUG ("unexpected path classification '%d' for '%s'" , + t, path.buf); + } + +skip_this_path: +if (!info->NextEntryOffset) +break ; + p += info->NextEntryOffset; + } + +fsmonitor_publish (state,NULL , &cookie_list); +string_list_clear (&cookie_list,0 ); +strbuf_release (&path); +return LISTENER_HAVE_DATA_GITDIR ; }void fsm_listen__loop (struct fsmonitor_daemon_state *state) { +struct fsmonitor_daemon_backend_data *data = state->backend_data; +DWORD dwWait; +int result; + + state->error_code =0 ; + +if (start_rdcw_watch (data, data->watch_worktree) ==-1 ) + goto force_error_stop; + +if (data->watch_gitdir && +start_rdcw_watch (data, data->watch_gitdir) ==-1 ) + goto force_error_stop; + +for (;;) { + dwWait =WaitForMultipleObjects (data->nr_listener_handles, + data->hListener, + FALSE,INFINITE ); + +if (dwWait ==WAIT_OBJECT_0 +LISTENER_HAVE_DATA_WORKTREE ) { + result =recv_rdcw_watch (data->watch_worktree); +if (result ==-1 ) { +/* hard error */ + goto force_error_stop; + } +if (result ==-2 ) { +/* retryable error */ +if (start_rdcw_watch (data, data->watch_worktree) ==-1 ) + goto force_error_stop; +continue ; + } + +/* have data */ +if (process_worktree_events (state) ==LISTENER_SHUTDOWN ) + goto force_shutdown; +if (start_rdcw_watch (data, data->watch_worktree) ==-1 ) + goto force_error_stop; +continue ; + } + +if (dwWait ==WAIT_OBJECT_0 +LISTENER_HAVE_DATA_GITDIR ) { + result =recv_rdcw_watch (data->watch_gitdir); +if (result ==-1 ) { +/* hard error */ + goto force_error_stop; + } +if (result ==-2 ) { +/* retryable error */ +if (start_rdcw_watch (data, data->watch_gitdir) ==-1 ) + goto force_error_stop; +continue ; + } + +/* have data */ +if (process_gitdir_events (state) ==LISTENER_SHUTDOWN ) + goto force_shutdown; +if (start_rdcw_watch (data, data->watch_gitdir) ==-1 ) + goto force_error_stop; +continue ; + } + +if (dwWait ==WAIT_OBJECT_0 +LISTENER_SHUTDOWN ) + goto clean_shutdown; + +error (_ ("could not read directory changes [GLE %ld]" ), +GetLastError ()); + goto force_error_stop; + } + +force_error_stop: + state->error_code =-1 ; + +force_shutdown: +/* +* Tell the IPC thead pool to stop (which completes the await +* in the main thread (which will also signal this thread (if +* we are still alive))). +*/ +ipc_server_stop_async (state->ipc_server_data); + +clean_shutdown: +cancel_rdcw_watch (data->watch_worktree); +cancel_rdcw_watch (data->watch_gitdir); }int fsm_listen__ctor (struct fsmonitor_daemon_state *state) { +struct fsmonitor_daemon_backend_data *data; + +CALLOC_ARRAY (data,1 ); + + data->hEventShutdown =CreateEvent (NULL , TRUE, FALSE,NULL ); + + data->watch_worktree =create_watch (state, + state->path_worktree_watch.buf); +if (!data->watch_worktree) + goto failed; + +if (state->nr_paths_watching >1 ) { + data->watch_gitdir =create_watch (state, + state->path_gitdir_watch.buf); +if (!data->watch_gitdir) + goto failed; + } + + data->hListener[LISTENER_SHUTDOWN ] = data->hEventShutdown; + data->nr_listener_handles++; + + data->hListener[LISTENER_HAVE_DATA_WORKTREE ] = + data->watch_worktree->hEvent; + data->nr_listener_handles++; + +if (data->watch_gitdir) { + data->hListener[LISTENER_HAVE_DATA_GITDIR ] = + data->watch_gitdir->hEvent; + data->nr_listener_handles++; + } + + state->backend_data = data; +return 0 ; + +failed: +CloseHandle (data->hEventShutdown); +destroy_watch (data->watch_worktree); +destroy_watch (data->watch_gitdir); +return -1 ; }void fsm_listen__dtor (struct fsmonitor_daemon_state *state) { +struct fsmonitor_daemon_backend_data *data; + +if (!state || !state->backend_data) +return ; + + data = state->backend_data; + +CloseHandle (data->hEventShutdown); +destroy_watch (data->watch_worktree); +destroy_watch (data->watch_gitdir); + +FREE_AND_NULL (state->backend_data); }
Signature · 1 line
gitgitgadget
Re: [PATCH v5 15/30] compat/fsmonitor/fsm-listen-win32: implement FSMonitor backend on Windows
Hi Jeff,
merely a comment:
Quoted 37 lines · Jeff Hostetler via GitGitGadget
compat/fsmonitor/fsm-listen-win32.c +37
-0+/* +* Convert the WCHAR path from the notification into UTF8 and +* then normalize it. +*/ +static int normalize_path_in_utf8 (FILE_NOTIFY_INFORMATION *info, +struct strbuf *normalized_path) +{ +int reserve; +int len =0 ; + +strbuf_reset (normalized_path); +if (!info->FileNameLength) + goto normalize; + +/* +* Pre-reserve enough space in the UTF8 buffer for +* each Unicode WCHAR character to be mapped into a +* sequence of 2 UTF8 characters. That should let us +* avoid ERROR_INSUFFICIENT_BUFFER 99.9+% of the time. +*/ + reserve = info->FileNameLength +1 ; +strbuf_grow (normalized_path, reserve); + +for (;;) { + len =WideCharToMultiByte (CP_UTF8 ,0 , info->FileName, + info->FileNameLength /sizeof (WCHAR ), + normalized_path->buf, +strbuf_avail (normalized_path) -1 , +NULL ,NULL ); +if (len >0 ) + 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); +return -1 ;
compat/fsmonitor/fsm-listen-win32.c line 99
Quoted 10 lines
compat/fsmonitor/fsm-listen-win32.c +10
-0+ } + +strbuf_grow (normalized_path, +strbuf_avail (normalized_path) + reserve); + } + +normalize: +strbuf_setlen (normalized_path, len); +return strbuf_normalize_path (normalized_path); +}
There are Unicode pages that require quite a few more bytes per wide character (IIRC it can blow up to six bytes), but it should be good enough for now, and we can always revisit this easily enough at a later stage.
I really like this patch, as it makes the complexities of handling two watches and the complexities of handling overlapped (and asynchronous) results look easy.
Thank you!
Dscho
PATCH v5 16/30 compat/fsmonitor/fsm-listen-darwin: add macos header files for FSEvent
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Include MacOS system declarations to allow us to use FSEvent and CoreFoundation APIs. We need GCC and clang versions because of compiler and header file conflicts.
While it is quite possible to #include Apple's CoreServices.h when compiling C source code with clang, trying to build it with GCC currently fails with this error:
In file included
from /Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk/System/... ...Library/Frameworks/Security.framework/Headers/AuthSession.h:32, from /Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk/System/... ...Library/Frameworks/Security.framework/Headers/Security.h:42, from /Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk/System/... ...Library/Frameworks/CoreServices.framework/Frameworks/... ...OSServices.framework/Headers/CSIdentity.h:43,
from /Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk/System/... ...Library/Frameworks/CoreServices.framework/Frameworks/... ...OSServices.framework/Headers/OSServices.h:29,
from /Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk/System/... ...Library/Frameworks/CoreServices.framework/Frameworks/... ...LaunchServices.framework/Headers/IconsCore.h:23,
from /Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk/System/... ...Library/Frameworks/CoreServices.framework/Frameworks/... ...LaunchServices.framework/Headers/LaunchServices.h:23, from /Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk/System/... ...Library/Frameworks/CoreServices.framework/Headers/CoreServices.h:45,
/Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk/System/...
...Library/Frameworks/Security.framework/Headers/Authorization.h:193:7:
error: variably modified 'bytes' at file scope
193 | char bytes[kAuthorizationExternalFormLength];
| ^~~~~The underlying reason is that GCC (rightfully) objects that an enum value such as kAuthorizationExternalFormLength is not a constant (because it is not, the preprocessor has no knowledge of it, only the actual C compiler does) and can therefore not be used to define the size of a C array.
This is a known problem and tracked in GCC's bug tracker:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93082
In the meantime, let's not block things and go the slightly ugly route of declaring/defining the FSEvents constants, data structures and functions that we need, so that we can avoid above-mentioned issue.
Let's do this _only_ for GCC, though, so that the CI/PR builds (which build both with clang and with GCC) can guarantee that we _are_ using the correct data types.
--- compat/fsmonitor/fsm-listen-darwin.c | 96 ++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+)
compat/fsmonitor/fsm-listen-darwin.c +96 -0
@@ -1,3 +1,99 @@ +#if defined(__GNUC__) +/* +* It is possible to #include CoreFoundation/CoreFoundation.h when compiling +* with clang, but not with GCC as of time of writing. +* +* See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93082 for details. +*/ +typedef unsigned int FSEventStreamCreateFlags ; +#define kFSEventStreamEventFlagNone 0x00000000 +#define kFSEventStreamEventFlagMustScanSubDirs 0x00000001 +#define kFSEventStreamEventFlagUserDropped 0x00000002 +#define kFSEventStreamEventFlagKernelDropped 0x00000004 +#define kFSEventStreamEventFlagEventIdsWrapped 0x00000008 +#define kFSEventStreamEventFlagHistoryDone 0x00000010 +#define kFSEventStreamEventFlagRootChanged 0x00000020 +#define kFSEventStreamEventFlagMount 0x00000040 +#define kFSEventStreamEventFlagUnmount 0x00000080 +#define kFSEventStreamEventFlagItemCreated 0x00000100 +#define kFSEventStreamEventFlagItemRemoved 0x00000200 +#define kFSEventStreamEventFlagItemInodeMetaMod 0x00000400 +#define kFSEventStreamEventFlagItemRenamed 0x00000800 +#define kFSEventStreamEventFlagItemModified 0x00001000 +#define kFSEventStreamEventFlagItemFinderInfoMod 0x00002000 +#define kFSEventStreamEventFlagItemChangeOwner 0x00004000 +#define kFSEventStreamEventFlagItemXattrMod 0x00008000 +#define kFSEventStreamEventFlagItemIsFile 0x00010000 +#define kFSEventStreamEventFlagItemIsDir 0x00020000 +#define kFSEventStreamEventFlagItemIsSymlink 0x00040000 +#define kFSEventStreamEventFlagOwnEvent 0x00080000 +#define kFSEventStreamEventFlagItemIsHardlink 0x00100000 +#define kFSEventStreamEventFlagItemIsLastHardlink 0x00200000 +#define kFSEventStreamEventFlagItemCloned 0x00400000 + +typedef struct __FSEventStream *FSEventStreamRef ; +typedef const FSEventStreamRef ConstFSEventStreamRef ; + +typedef unsigned int CFStringEncoding ; +#define kCFStringEncodingUTF8 0x08000100 + +typedef const struct __CFString *CFStringRef ; +typedef const struct __CFArray *CFArrayRef ; +typedef const struct __CFRunLoop *CFRunLoopRef ; + +struct FSEventStreamContext { +long long version; +void *cb_data, *retain, *release, *copy_description; +}; + +typedef struct FSEventStreamContext FSEventStreamContext ; +typedef unsigned int FSEventStreamEventFlags ; +#define kFSEventStreamCreateFlagNoDefer 0x02 +#define kFSEventStreamCreateFlagWatchRoot 0x04 +#define kFSEventStreamCreateFlagFileEvents 0x10 + +typedef unsigned long long FSEventStreamEventId ; +#define kFSEventStreamEventIdSinceNow 0xFFFFFFFFFFFFFFFFULL + +typedef void (*FSEventStreamCallback )(ConstFSEventStreamRef streamRef, +void *context, +__SIZE_TYPE__ num_of_events, +void *event_paths, +const FSEventStreamEventFlags event_flags[], +const FSEventStreamEventId event_ids[]); +typedef double CFTimeInterval ; +FSEventStreamRef FSEventStreamCreate (void *allocator, +FSEventStreamCallback callback, +FSEventStreamContext *context, +CFArrayRef paths_to_watch, +FSEventStreamEventId since_when, +CFTimeInterval latency, +FSEventStreamCreateFlags flags); +CFStringRef CFStringCreateWithCString (void *allocator,const char *string, +CFStringEncoding encoding); +CFArrayRef CFArrayCreate (void *allocator,const void **items,long long count, +void *callbacks); +void CFRunLoopRun (void ); +void CFRunLoopStop (CFRunLoopRef run_loop); +CFRunLoopRef CFRunLoopGetCurrent (void ); +extern CFStringRef kCFRunLoopDefaultMode; +void FSEventStreamScheduleWithRunLoop (FSEventStreamRef stream, +CFRunLoopRef run_loop, +CFStringRef run_loop_mode); +unsigned char FSEventStreamStart (FSEventStreamRef stream); +void FSEventStreamStop (FSEventStreamRef stream); +void FSEventStreamInvalidate (FSEventStreamRef stream); +void FSEventStreamRelease (FSEventStreamRef stream); +#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> +#endif +#include "cache.h" #include "fsmonitor.h" #include "fsm-listen.h"
Signature · 1 line
gitgitgadget
Re: [PATCH v5 16/30] compat/fsmonitor/fsm-listen-darwin: add macos header files for FSEvent
Hi Jeff,
just a quick note:
Quoted 9 lines · Jeff Hostetler via GitGitGadget
[...]
The underlying reason is that GCC (rightfully) objects that anenumvalue such askAuthorizationExternalFormLengthis not a constant (because it is not, the preprocessor has no knowledge of it, only the actual C compiler does) and can therefore not be used to define the size of a C array.This is a known problem and tracked in GCC's bug tracker:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93082
I just checked and the bug is still marked as "NEW". Therefore we still need this patch.
Ciao,
Dscho
PATCH v5 17/30 compat/fsmonitor/fsm-listen-darwin: implement FSEvent listener on MacOS
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Implement file system event listener on MacOS using FSEvent, CoreFoundation, and CoreServices.
--- compat/fsmonitor/fsm-listen-darwin.c | 382 ++++++++++++++++++++++++++- 1 file changed, 381 insertions(+), 1 deletion(-)
compat/fsmonitor/fsm-listen-darwin.c +381 -1
@@ -1,4 +1,4 @@ -#if defined(__GNUC__) +#ifndef __clang__/* * It is possible to #include CoreFoundation/CoreFoundation.h when compiling * with clang, but not with GCC as of time of writing.
@@ -97,20 +97,400 @@ void FSEventStreamRelease(FSEventStreamRef stream);#include "cache.h" #include "fsmonitor.h" #include "fsm-listen.h" +#include "fsmonitor--daemon.h" + +struct fsmonitor_daemon_backend_data +{ +CFStringRef cfsr_worktree_path; +CFStringRef cfsr_gitdir_path; + +CFArrayRef cfar_paths_to_watch; +int nr_paths_watching; + +FSEventStreamRef stream; + +CFRunLoopRef rl; + +enum shutdown_style { +SHUTDOWN_EVENT =0 , +FORCE_SHUTDOWN , +FORCE_ERROR_STOP , + } shutdown_style; + +unsigned int stream_scheduled:1 ; +unsigned int stream_started:1 ; +}; + +static void log_flags_set (const char *path,const FSEventStreamEventFlags flag) +{ +struct strbuf msg =STRBUF_INIT ; + +if (flag & kFSEventStreamEventFlagMustScanSubDirs) +strbuf_addstr (&msg,"MustScanSubDirs|" ); +if (flag & kFSEventStreamEventFlagUserDropped) +strbuf_addstr (&msg,"UserDropped|" ); +if (flag & kFSEventStreamEventFlagKernelDropped) +strbuf_addstr (&msg,"KernelDropped|" ); +if (flag & kFSEventStreamEventFlagEventIdsWrapped) +strbuf_addstr (&msg,"EventIdsWrapped|" ); +if (flag & kFSEventStreamEventFlagHistoryDone) +strbuf_addstr (&msg,"HistoryDone|" ); +if (flag & kFSEventStreamEventFlagRootChanged) +strbuf_addstr (&msg,"RootChanged|" ); +if (flag & kFSEventStreamEventFlagMount) +strbuf_addstr (&msg,"Mount|" ); +if (flag & kFSEventStreamEventFlagUnmount) +strbuf_addstr (&msg,"Unmount|" ); +if (flag & kFSEventStreamEventFlagItemChangeOwner) +strbuf_addstr (&msg,"ItemChangeOwner|" ); +if (flag & kFSEventStreamEventFlagItemCreated) +strbuf_addstr (&msg,"ItemCreated|" ); +if (flag & kFSEventStreamEventFlagItemFinderInfoMod) +strbuf_addstr (&msg,"ItemFinderInfoMod|" ); +if (flag & kFSEventStreamEventFlagItemInodeMetaMod) +strbuf_addstr (&msg,"ItemInodeMetaMod|" ); +if (flag & kFSEventStreamEventFlagItemIsDir) +strbuf_addstr (&msg,"ItemIsDir|" ); +if (flag & kFSEventStreamEventFlagItemIsFile) +strbuf_addstr (&msg,"ItemIsFile|" ); +if (flag & kFSEventStreamEventFlagItemIsHardlink) +strbuf_addstr (&msg,"ItemIsHardlink|" ); +if (flag & kFSEventStreamEventFlagItemIsLastHardlink) +strbuf_addstr (&msg,"ItemIsLastHardlink|" ); +if (flag & kFSEventStreamEventFlagItemIsSymlink) +strbuf_addstr (&msg,"ItemIsSymlink|" ); +if (flag & kFSEventStreamEventFlagItemModified) +strbuf_addstr (&msg,"ItemModified|" ); +if (flag & kFSEventStreamEventFlagItemRemoved) +strbuf_addstr (&msg,"ItemRemoved|" ); +if (flag & kFSEventStreamEventFlagItemRenamed) +strbuf_addstr (&msg,"ItemRenamed|" ); +if (flag & kFSEventStreamEventFlagItemXattrMod) +strbuf_addstr (&msg,"ItemXattrMod|" ); +if (flag & kFSEventStreamEventFlagOwnEvent) +strbuf_addstr (&msg,"OwnEvent|" ); +if (flag & kFSEventStreamEventFlagItemCloned) +strbuf_addstr (&msg,"ItemCloned|" ); + +trace_printf_key (&trace_fsmonitor,"fsevent: '%s', flags=%u %s" , + path, flag, msg.buf); + +strbuf_release (&msg); +} + +static int ef_is_root_delete (const FSEventStreamEventFlags ef) +{ +return (ef & kFSEventStreamEventFlagItemIsDir && + ef & kFSEventStreamEventFlagItemRemoved); +} + +static int ef_is_root_renamed (const FSEventStreamEventFlags ef) +{ +return (ef & kFSEventStreamEventFlagItemIsDir && + ef & kFSEventStreamEventFlagItemRenamed); +} + +static int ef_is_dropped (const FSEventStreamEventFlags ef) +{ +return (ef & kFSEventStreamEventFlagMustScanSubDirs || + ef & kFSEventStreamEventFlagKernelDropped || + ef & kFSEventStreamEventFlagUserDropped); +} + +static void fsevent_callback (ConstFSEventStreamRef streamRef, +void *ctx, +size_t num_of_events, +void *event_paths, +const FSEventStreamEventFlags event_flags[], +const FSEventStreamEventId event_ids[]) +{ +struct fsmonitor_daemon_state *state = ctx; +struct fsmonitor_daemon_backend_data *data = state->backend_data; +char **paths = (char **)event_paths; +struct fsmonitor_batch *batch =NULL ; +struct string_list cookie_list =STRING_LIST_INIT_DUP ; +const char *path_k; +const char *slash; +int k; +struct strbuf tmp =STRBUF_INIT ; + +/* +* Build a list of all filesystem changes into a private/local +* list and without holding any locks. +*/ +for (k =0 ; k < num_of_events; k++) { +/* +* On Mac, we receive an array of absolute paths. +*/ + path_k = paths[k]; + +/* +* If you want to debug FSEvents, log them to GIT_TRACE_FSMONITOR. +* Please don't log them to Trace2. +* +* trace_printf_key(&trace_fsmonitor, "Path: '%s'", path_k); +*/ + +/* +* If event[k] is marked as dropped, we assume that we have +* lost sync with the filesystem and should flush our cached +* data. We need to: +* +* [1] Abort/wake any client threads waiting for a cookie and +* flush the cached state data (the current token), and +* create a new token. +* +* [2] Discard the batch that we were locally building (since +* they are conceptually relative to the just flushed +* token). +*/ +if (ef_is_dropped (event_flags[k])) { +if (trace_pass_fl (&trace_fsmonitor)) +log_flags_set (path_k, event_flags[k]); + +fsmonitor_force_resync (state); +fsmonitor_batch__free_list (batch); +string_list_clear (&cookie_list,0 ); + +/* +* We assume that any events that we received +* in this callback after this dropped event +* may still be valid, so we continue rather +* than break. (And just in case there is a +* delete of ".git" hiding in there.) +*/ +continue ; + } + +switch (fsmonitor_classify_path_absolute (state, path_k)) { + +case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX : +case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX : +/* special case cookie files within .git or gitdir */ + +/* Use just the filename of the cookie file. */ + slash =find_last_dir_sep (path_k); +string_list_append (&cookie_list, + slash ? slash +1 : path_k); +break ; + +case IS_INSIDE_DOT_GIT : +case IS_INSIDE_GITDIR : +/* ignore all other paths inside of .git or gitdir */ +break ; + +case IS_DOT_GIT : +case IS_GITDIR : +/* +* If .git directory is deleted or renamed away, +* we have to quit. +*/ +if (ef_is_root_delete (event_flags[k])) { +trace_printf_key (&trace_fsmonitor, +"event: gitdir removed" ); + goto force_shutdown; + } +if (ef_is_root_renamed (event_flags[k])) { +trace_printf_key (&trace_fsmonitor, +"event: gitdir renamed" ); + goto force_shutdown; + } +break ; + +case IS_WORKDIR_PATH : +/* try to queue normal pathnames */ + +if (trace_pass_fl (&trace_fsmonitor)) +log_flags_set (path_k, event_flags[k]); + +/* +* Because of the implicit "binning" (the +* kernel calls us at a given frequency) and +* de-duping (the kernel is free to combine +* multiple events for a given pathname), an +* individual fsevent could be marked as both +* a file and directory. Add it to the queue +* with both spellings so that the client will +* know how much to invalidate/refresh. +*/ + +if (event_flags[k] & kFSEventStreamEventFlagItemIsFile) { +const char *rel = path_k + + state->path_worktree_watch.len +1 ; + +if (!batch) + batch =fsmonitor_batch__new (); +fsmonitor_batch__add_path (batch, rel); + } + +if (event_flags[k] & kFSEventStreamEventFlagItemIsDir) { +const char *rel = path_k + + state->path_worktree_watch.len +1 ; + +strbuf_reset (&tmp); +strbuf_addstr (&tmp, rel); +strbuf_addch (&tmp,'/' ); + +if (!batch) + batch =fsmonitor_batch__new (); +fsmonitor_batch__add_path (batch, tmp.buf); + } + +break ; + +case IS_OUTSIDE_CONE : +default : +trace_printf_key (&trace_fsmonitor, +"ignoring '%s'" , path_k); +break ; + } + } + +fsmonitor_publish (state, batch, &cookie_list); +string_list_clear (&cookie_list,0 ); +strbuf_release (&tmp); +return ; + +force_shutdown: +fsmonitor_batch__free_list (batch); +string_list_clear (&cookie_list,0 ); + + data->shutdown_style =FORCE_SHUTDOWN ; +CFRunLoopStop (data->rl); +strbuf_release (&tmp); +return ; +} + +/* +* NEEDSWORK: Investigate the proper value for the `latency` argument +* in the call to `FSEventStreamCreate()`. I'm not sure that this +* needs to be a config setting or just something that we tune after +* some testing. +* +* With a latency of 0.1, I was seeing lots of dropped events during +* the "touch 100000" files test within t/perf/p7519, but with a +* latency of 0.001 I did not see any dropped events. So the +* "correct" value may be somewhere in between. +* +* https://developer.apple.com/documentation/coreservices/1443980-fseventstreamcreate +*/ int fsm_listen__ctor (struct fsmonitor_daemon_state *state) { +FSEventStreamCreateFlags flags = kFSEventStreamCreateFlagNoDefer | + kFSEventStreamCreateFlagWatchRoot | + kFSEventStreamCreateFlagFileEvents; +FSEventStreamContext ctx = { +0 , + state, +NULL , +NULL , +NULL + }; +struct fsmonitor_daemon_backend_data *data; +const void *dir_array[2 ]; + +CALLOC_ARRAY (data,1 ); + state->backend_data = data; + + data->cfsr_worktree_path =CFStringCreateWithCString ( +NULL , state->path_worktree_watch.buf, kCFStringEncodingUTF8); + dir_array[data->nr_paths_watching++] = data->cfsr_worktree_path; + +if (state->nr_paths_watching >1 ) { + data->cfsr_gitdir_path =CFStringCreateWithCString ( +NULL , state->path_gitdir_watch.buf, + kCFStringEncodingUTF8); + dir_array[data->nr_paths_watching++] = data->cfsr_gitdir_path; + } + + data->cfar_paths_to_watch =CFArrayCreate (NULL , dir_array, + data->nr_paths_watching, +NULL ); + data->stream =FSEventStreamCreate (NULL , fsevent_callback, &ctx, + data->cfar_paths_to_watch, + kFSEventStreamEventIdSinceNow, +0.001 , flags); +if (data->stream ==NULL ) + goto failed; + +/* +* `data->rl` needs to be set inside the listener thread. +*/ + +return 0 ; + +failed: +error ("Unable to create FSEventStream." ); + +FREE_AND_NULL (state->backend_data);return -1 ; }void fsm_listen__dtor (struct fsmonitor_daemon_state *state) { +struct fsmonitor_daemon_backend_data *data; + +if (!state || !state->backend_data) +return ; + + data = state->backend_data; + +if (data->stream) { +if (data->stream_started) +FSEventStreamStop (data->stream); +if (data->stream_scheduled) +FSEventStreamInvalidate (data->stream); +FSEventStreamRelease (data->stream); + } + +FREE_AND_NULL (state->backend_data); }void fsm_listen__stop_async (struct fsmonitor_daemon_state *state) { +struct fsmonitor_daemon_backend_data *data; + + data = state->backend_data; + data->shutdown_style =SHUTDOWN_EVENT ; + +CFRunLoopStop (data->rl); }void fsm_listen__loop (struct fsmonitor_daemon_state *state) { +struct fsmonitor_daemon_backend_data *data; + + data = state->backend_data; + + data->rl =CFRunLoopGetCurrent (); + +FSEventStreamScheduleWithRunLoop (data->stream, data->rl, kCFRunLoopDefaultMode); + data->stream_scheduled =1 ; + +if (!FSEventStreamStart (data->stream)) { +error ("Failed to start the FSEventStream" ); + goto force_error_stop_without_loop; + } + data->stream_started =1 ; + +CFRunLoopRun (); + +switch (data->shutdown_style) { +case FORCE_ERROR_STOP : + state->error_code =-1 ; +/* fall thru */ +case FORCE_SHUTDOWN : +ipc_server_stop_async (state->ipc_server_data); +/* fall thru */ +case SHUTDOWN_EVENT : +default : +break ; + } +return ; + +force_error_stop_without_loop: + state->error_code =-1 ; +ipc_server_stop_async (state->ipc_server_data); +return ; }
Signature · 1 line
gitgitgadget
Re: [PATCH v5 17/30] compat/fsmonitor/fsm-listen-darwin: implement FSEvent listener on MacOS
Hi Jeff,
compat/fsmonitor/fsm-listen-darwin.c line 1
Quoted 7 lines · Jeff Hostetler via GitGitGadget
compat/fsmonitor/fsm-listen-darwin.c +1
-1@@ -1,4 +1,4 @@ -#if defined(__GNUC__) +#ifndef __clang__
This probably wants to be squashed into the previous patch, and while at it, the corresponding commit message should probably be adjusted:
We need GCC and clang versions because of compiler and header file conflicts.
This should probably say something along the lines "This is only needed for GCC, clang is fine" instead.
Quoted 40 lines
compat/fsmonitor/fsm-listen-darwin.c +40
-0+static void log_flags_set (const char *path,const FSEventStreamEventFlags flag) +{ +struct strbuf msg =STRBUF_INIT ; + +if (flag & kFSEventStreamEventFlagMustScanSubDirs) +strbuf_addstr (&msg,"MustScanSubDirs|" ); +if (flag & kFSEventStreamEventFlagUserDropped) +strbuf_addstr (&msg,"UserDropped|" ); +if (flag & kFSEventStreamEventFlagKernelDropped) +strbuf_addstr (&msg,"KernelDropped|" ); +if (flag & kFSEventStreamEventFlagEventIdsWrapped) +strbuf_addstr (&msg,"EventIdsWrapped|" ); +if (flag & kFSEventStreamEventFlagHistoryDone) +strbuf_addstr (&msg,"HistoryDone|" ); +if (flag & kFSEventStreamEventFlagRootChanged) +strbuf_addstr (&msg,"RootChanged|" ); +if (flag & kFSEventStreamEventFlagMount) +strbuf_addstr (&msg,"Mount|" ); +if (flag & kFSEventStreamEventFlagUnmount) +strbuf_addstr (&msg,"Unmount|" ); +if (flag & kFSEventStreamEventFlagItemChangeOwner) +strbuf_addstr (&msg,"ItemChangeOwner|" ); +if (flag & kFSEventStreamEventFlagItemCreated) +strbuf_addstr (&msg,"ItemCreated|" ); +if (flag & kFSEventStreamEventFlagItemFinderInfoMod) +strbuf_addstr (&msg,"ItemFinderInfoMod|" ); +if (flag & kFSEventStreamEventFlagItemInodeMetaMod) +strbuf_addstr (&msg,"ItemInodeMetaMod|" ); +if (flag & kFSEventStreamEventFlagItemIsDir) +strbuf_addstr (&msg,"ItemIsDir|" ); +if (flag & kFSEventStreamEventFlagItemIsFile) +strbuf_addstr (&msg,"ItemIsFile|" ); +if (flag & kFSEventStreamEventFlagItemIsHardlink) +strbuf_addstr (&msg,"ItemIsHardlink|" ); +if (flag & kFSEventStreamEventFlagItemIsLastHardlink) +strbuf_addstr (&msg,"ItemIsLastHardlink|" ); +if (flag & kFSEventStreamEventFlagItemIsSymlink) +strbuf_addstr (&msg,"ItemIsSymlink|" ); +if (flag & kFSEventStreamEventFlagItemModified) +strbuf_addstr (&msg,"ItemModified|" );
compat/fsmonitor/fsm-listen-darwin.c void FSEventStreamRelease(FSEventStreamRef stream); · line 173
Quoted 10 lines
compat/fsmonitor/fsm-listen-darwin.c +10
-0+if (flag & kFSEventStreamEventFlagItemRemoved) +strbuf_addstr (&msg,"ItemRemoved|" ); +if (flag & kFSEventStreamEventFlagItemRenamed) +strbuf_addstr (&msg,"ItemRenamed|" ); +if (flag & kFSEventStreamEventFlagItemXattrMod) +strbuf_addstr (&msg,"ItemXattrMod|" ); +if (flag & kFSEventStreamEventFlagOwnEvent) +strbuf_addstr (&msg,"OwnEvent|" ); +if (flag & kFSEventStreamEventFlagItemCloned) +strbuf_addstr (&msg,"ItemCloned|" );
I cannot think of any more elegant way to do this, either, but I wish there was a way...
Quoted 5 lines
+/*
+ * NEEDSWORK: Investigate the proper value for thelatencyargument
+ * in the call toFSEventStreamCreate(). I'm not sure that this
+ * needs to be a config setting or just something that we tune after
+ * some testing.
Since this was written, we had ample time to investigate the proper value, and I think you actually did. Therefore this comment is probably no longer needed.
Quoted 8 lines
+ *
+ * With a latency of 0.1, I was seeing lots of dropped events during
+ * the "touch 100000" files test within t/perf/p7519, but with a
+ * latency of 0.001 I did not see any dropped events. So the
+ * "correct" value may be somewhere in between.
+ *
+ * https://developer.apple.com/documentation/coreservices/1443980-fseventstreamcreate
+ */
The rest of the patch looks good to me, and since we saw this code working nicely in practice, I have high confidence in it.
Ciao,
Dscho
PATCH v5 18/30 fsmonitor--daemon: implement handle_client callback
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Teach fsmonitor--daemon to respond to IPC requests from client Git processes and respond with a list of modified pathnames relative to the provided token.
--- builtin/fsmonitor--daemon.c | 312 +++++++++++++++++++++++++++++++++++- 1 file changed, 310 insertions(+), 2 deletions(-)
builtin/fsmonitor--daemon.c +310 -2
@@ -7,6 +7,7 @@#include "fsmonitor--daemon.h" #include "simple-ipc.h" #include "khash.h" +#include "pkt-line.h" static const char *const builtin_fsmonitor__daemon_usage[] = {N_ ("git fsmonitor--daemon start [<options>]" ),
@@ -351,6 +352,311 @@ void fsmonitor_force_resync(struct fsmonitor_daemon_state *state)pthread_mutex_unlock (&state->main_lock); } +/* +* Format an opaque token string to send to the client. +*/ +static void with_lock__format_response_token ( +struct strbuf *response_token, +const struct strbuf *response_token_id, +const struct fsmonitor_batch *batch) +{ +/* assert current thread holding state->main_lock */ + +strbuf_reset (response_token); +strbuf_addf (response_token,"builtin:%s:%" PRIu64, + response_token_id->buf, batch->batch_seq_nr); +} + +/* +* Parse an opaque token from the client. +* Returns -1 on error. +*/ +static int fsmonitor_parse_client_token (const char *buf_token, +struct strbuf *requested_token_id, +uint64_t *seq_nr) +{ +const char *p; +char *p_end; + +strbuf_reset (requested_token_id); + *seq_nr =0 ; + +if (!skip_prefix (buf_token,"builtin:" , &p)) +return -1 ; + +while (*p && *p !=':' ) +strbuf_addch (requested_token_id, *p++); +if (!*p++) +return -1 ; + + *seq_nr = (uint64_t )strtoumax (p, &p_end,10 ); +if (*p_end) +return -1 ; + +return 0 ; +} + +KHASH_INIT(str, const char *, int, 0, kh_str_hash_func, kh_str_hash_equal) + +static int do_handle_client (struct fsmonitor_daemon_state *state, +const char *command, +ipc_server_reply_cb *reply, +struct ipc_server_reply_data *reply_data) +{ +struct fsmonitor_token_data *token_data =NULL ; +struct strbuf response_token =STRBUF_INIT ; +struct strbuf requested_token_id =STRBUF_INIT ; +struct strbuf payload =STRBUF_INIT ; +uint64_t requested_oldest_seq_nr =0 ; +uint64_t total_response_len =0 ; +const char *p; +const struct fsmonitor_batch *batch_head; +const struct fsmonitor_batch *batch; +intmax_t count =0 , duplicates =0 ; +kh_str_t *shown; +int hash_ret; +int do_trivial =0 ; +int do_flush =0 ; + +/* +* We expect `command` to be of the form: +* +* <command> := quit NUL +* | flush NUL +* | <V1-time-since-epoch-ns> NUL +* | <V2-opaque-fsmonitor-token> NUL +*/ + +if (!strcmp (command,"quit" )) { +/* +* A client has requested over the socket/pipe that the +* daemon shutdown. +* +* Tell the IPC thread pool to shutdown (which completes +* the await in the main thread (which can stop the +* fsmonitor listener thread)). +* +* There is no reply to the client. +*/ +return SIMPLE_IPC_QUIT ; + + }else if (!strcmp (command,"flush" )) { +/* +* Flush all of our cached data and generate a new token +* just like if we lost sync with the filesystem. +* +* Then send a trivial response using the new token. +*/ + do_flush =1 ; + do_trivial =1 ; + + }else if (!skip_prefix (command,"builtin:" , &p)) { +/* assume V1 timestamp or garbage */ + +char *p_end; + +strtoumax (command, &p_end,10 ); +trace_printf_key (&trace_fsmonitor, + ((*p_end) ? +"fsmonitor: invalid command line '%s'" : +"fsmonitor: unsupported V1 protocol '%s'" ), + command); + do_trivial =1 ; + + }else { +/* We have "builtin:*" */ +if (fsmonitor_parse_client_token (command, &requested_token_id, + &requested_oldest_seq_nr)) { +trace_printf_key (&trace_fsmonitor, +"fsmonitor: invalid V2 protocol token '%s'" , + command); + do_trivial =1 ; + + }else { +/* +* We have a V2 valid token: +* "builtin:<token_id>:<seq_nr>" +*/ + } + } + +pthread_mutex_lock (&state->main_lock); + +if (!state->current_token_data) +BUG ("fsmonitor state does not have a current token" ); + +if (do_flush) +with_lock__do_force_resync (state); + +/* +* We mark the current head of the batch list as "pinned" so +* that the listener thread will treat this item as read-only +* (and prevent any more paths from being added to it) from +* now on. +*/ + token_data = state->current_token_data; + batch_head = token_data->batch_head; + ((struct fsmonitor_batch *)batch_head)->pinned_time =time (NULL ); + +/* +* FSMonitor Protocol V2 requires that we send a response header +* with a "new current token" and then all of the paths that changed +* since the "requested token". We send the seq_nr of the just-pinned +* head batch so that future requests from a client will be relative +* to it. +*/ +with_lock__format_response_token (&response_token, + &token_data->token_id, batch_head); + +reply (reply_data, response_token.buf, response_token.len +1 ); + total_response_len += response_token.len +1 ; + +trace2_data_string ("fsmonitor" , the_repository,"response/token" , + response_token.buf); +trace_printf_key (&trace_fsmonitor,"response token: %s" , + response_token.buf); + +if (!do_trivial) { +if (strcmp (requested_token_id.buf, token_data->token_id.buf)) { +/* +* The client last spoke to a different daemon +* instance -OR- the daemon had to resync with +* the filesystem (and lost events), so reject. +*/ +trace2_data_string ("fsmonitor" , the_repository, +"response/token" ,"different" ); + do_trivial =1 ; + + }else if (requested_oldest_seq_nr < + token_data->batch_tail->batch_seq_nr) { +/* +* The client wants older events than we have for +* this token_id. This means that the end of our +* batch list was truncated and we cannot give the +* client a complete snapshot relative to their +* request. +*/ +trace_printf_key (&trace_fsmonitor, +"client requested truncated data" ); + do_trivial =1 ; + } + } + +if (do_trivial) { +pthread_mutex_unlock (&state->main_lock); + +reply (reply_data,"/" ,2 ); + +trace2_data_intmax ("fsmonitor" , the_repository, +"response/trivial" ,1 ); + +strbuf_release (&response_token); +strbuf_release (&requested_token_id); +return 0 ; + } + +/* +* We're going to hold onto a pointer to the current +* token-data while we walk the list of batches of files. +* During this time, we will NOT be under the lock. +* So we ref-count it. +* +* This allows the listener thread to continue prepending +* new batches of items to the token-data (which we'll ignore). +* +* AND it allows the listener thread to do a token-reset +* (and install a new `current_token_data`). +*/ + token_data->client_ref_count++; + +pthread_mutex_unlock (&state->main_lock); + +/* +* The client request is relative to the token that they sent, +* so walk the batch list backwards from the current head back +* to the batch (sequence number) they named. +* +* We use khash to de-dup the list of pathnames. +* +* NEEDSWORK: each batch contains a list of interned strings, +* so we only need to do pointer comparisons here to build the +* hash table. Currently, we're still comparing the string +* values. +*/ + shown =kh_init_str (); +for (batch = batch_head; + batch && batch->batch_seq_nr > requested_oldest_seq_nr; + batch = batch->next) { +size_t k; + +for (k =0 ; k < batch->nr; k++) { +const char *s = batch->interned_paths[k]; +size_t s_len; + +if (kh_get_str (shown, s) !=kh_end (shown)) + duplicates++; +else { +kh_put_str (shown, s, &hash_ret); + +trace_printf_key (&trace_fsmonitor, +"send[%" PRIuMAX"]: %s" , + count, s); + +/* Each path gets written with a trailing NUL */ + s_len =strlen (s) +1 ; + +if (payload.len + s_len >= +LARGE_PACKET_DATA_MAX ) { +reply (reply_data, payload.buf, + payload.len); + total_response_len += payload.len; +strbuf_reset (&payload); + } + +strbuf_add (&payload, s, s_len); + count++; + } + } + } + +if (payload.len) { +reply (reply_data, payload.buf, payload.len); + total_response_len += payload.len; + } + +kh_release_str (shown); + +pthread_mutex_lock (&state->main_lock); + +if (token_data->client_ref_count >0 ) + token_data->client_ref_count--; + +if (token_data->client_ref_count ==0 ) { +if (token_data != state->current_token_data) { +/* +* The listener thread did a token-reset while we were +* walking the batch list. Therefore, this token is +* stale and can be discarded completely. If we are +* the last reader thread using this token, we own +* that work. +*/ +fsmonitor_free_token_data (token_data); + } + } + +pthread_mutex_unlock (&state->main_lock); + +trace2_data_intmax ("fsmonitor" , the_repository,"response/length" , total_response_len); +trace2_data_intmax ("fsmonitor" , the_repository,"response/count/files" , count); +trace2_data_intmax ("fsmonitor" , the_repository,"response/count/duplicates" , duplicates); + +strbuf_release (&response_token); +strbuf_release (&requested_token_id); +strbuf_release (&payload); + +return 0 ; +} +static ipc_server_application_cb handle_client; static int handle_client(void *data,
@@ -358,7 +664,7 @@ static int handle_client(void *data,ipc_server_reply_cb *reply, struct ipc_server_reply_data *reply_data) { - /* struct fsmonitor_daemon_state *state = data; */ + struct fsmonitor_daemon_state *state = data;int result;/*
@@ -369,10 +675,12 @@ static int handle_client(void *data,if (command_len !=strlen (command))BUG ("FSMonitor assumes text messages" ); +trace_printf_key (&trace_fsmonitor,"requested token: %s" , command); +trace2_region_enter ("fsmonitor" ,"handle_client" , the_repository);trace2_data_string ("fsmonitor" , the_repository,"request" , command); - result =0 ;/* TODO Do something here. */ + result =do_handle_client (state, command, reply, reply_data);trace2_region_leave ("fsmonitor" ,"handle_client" , the_repository);
Signature · 1 line
gitgitgadget
PATCH v5 19/30 help: include fsmonitor--daemon feature flag in version info
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Add the "feature: fsmonitor--daemon" message to the output of git version --build-options.
The builtin FSMonitor is only available on certain platforms and even then only when certain Makefile flags are enabled, so print a message in the verbose version output when it is available.
This can be used by test scripts for prereq testing. Granted, tests could just try git fsmonitor--daemon status and look for a 128 exit code or grep for a "not supported" message on stderr, but this is rather obscure.
The main advantage is that the feature message will automatically appear in bug reports and other support requests.
--- help.c | 4 ++++ t/test-lib.sh | 6 ++++++ 2 files changed, 10 insertions(+)
help.c +4 -0
@@ -12,6 +12,7 @@#include "refs.h" #include "parse-options.h" #include "prompt.h" +#include "fsmonitor-ipc.h" struct category_description {uint32_t category;
@@ -695,6 +696,9 @@ void get_version_info(struct strbuf *buf, int show_build_options)strbuf_addf (buf,"sizeof-size_t: %d\n" , (int )sizeof (size_t ));strbuf_addf (buf,"shell-path: %s\n" ,SHELL_PATH );/* NEEDSWORK: also save and output GIT-BUILD_OPTIONS? */ + +if (fsmonitor_ipc__is_supported ()) +strbuf_addstr (buf,"feature: fsmonitor--daemon\n" ); } }
t/test-lib.sh +6 -0
@@ -1799,3 +1799,9 @@ test_lazy_prereq SHA1 ' # Tests that verify the scheduler integration must set this locally # to avoid errors. GIT_TEST_MAINT_SCHEDULER="none:exit 1" + +# Does this platform support `git fsmonitor--daemon` +# +test_lazy_prereq FSMONITOR_DAEMON ' + git version --build-options | grep "feature:" | grep "fsmonitor--daemon" +'
Signature · 1 line
gitgitgadget
Re: [PATCH v5 19/30] help: include fsmonitor--daemon feature flag in version info
Hi Jeff,
Quoted 9 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
Add the "feature: fsmonitor--daemon" message to the output of
git version --build-options.The builtin FSMonitor is only available on certain platforms and even then only when certain
Makefileflags are enabled, so print a message in the verbose version output when it is available.
Quoted 7 lines
This can be used by test scripts for prereq testing. Granted, tests could just try
git fsmonitor--daemon statusand look for a 128 exit code or grep for a "not supported" message on stderr, but this is rather obscure.The main advantage is that the feature message will automatically appear in bug reports and other support requests.
It was also used by Scalar before we started the journey to integrate it into core Git ;-)
Maybe include that, for history buffs like me?
Ciao,
Dscho
PATCH v5 20/30 t/helper/fsmonitor-client: create IPC client to talk to FSMonitor Daemon
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create an IPC client to send query and flush commands to the daemon.
--- Makefile | 1 + t/helper/test-fsmonitor-client.c | 121 +++++++++++++++++++++++++++++++ t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + 4 files changed, 124 insertions(+) create mode 100644 t/helper/test-fsmonitor-client.c
Makefile +1 -0
@@ -709,6 +709,7 @@ TEST_BUILTINS_OBJS += test-dump-split-index.o TEST_BUILTINS_OBJS += test-dump-untracked-cache.o TEST_BUILTINS_OBJS += test-example-decorate.o TEST_BUILTINS_OBJS += test-fast-rebase.o +TEST_BUILTINS_OBJS += test-fsmonitor-client.o TEST_BUILTINS_OBJS += test-genrandom.o TEST_BUILTINS_OBJS += test-genzeros.o TEST_BUILTINS_OBJS += test-getcwd.o
t/helper/test-fsmonitor-client.c new file +121 -0
121 lines not on this page · raw message
t/helper/test-tool.c +1 -0
@@ -31,6 +31,7 @@ static struct test_cmd cmds[] = { { "dump-untracked-cache", cmd__dump_untracked_cache }, { "example-decorate", cmd__example_decorate }, { "fast-rebase", cmd__fast_rebase }, + { "fsmonitor-client", cmd__fsmonitor_client }, { "genrandom", cmd__genrandom }, { "genzeros", cmd__genzeros }, { "getcwd", cmd__getcwd },
t/helper/test-tool.h +1 -0
@@ -22,6 +22,7 @@ int cmd__dump_untracked_cache(int argc, const char **argv);int cmd__dump_reftable (int argc,const char **argv);int cmd__example_decorate (int argc,const char **argv);int cmd__fast_rebase (int argc,const char **argv); +int cmd__fsmonitor_client (int argc,const char **argv);int cmd__genrandom (int argc,const char **argv);int cmd__genzeros (int argc,const char **argv);int cmd__getcwd (int argc,const char **argv);
Signature · 1 line
gitgitgadget
PATCH v5 21/30 t7527: create test for fsmonitor--daemon
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
--- t/t7527-builtin-fsmonitor.sh | 511 +++++++++++++++++++++++++++++++++++ 1 file changed, 511 insertions(+) create mode 100755 t/t7527-builtin-fsmonitor.sh
t/t7527-builtin-fsmonitor.sh new file +511 -0
@@ -0,0 +1,511 @@ +#!/bin/sh + +test_description='built-in file system watcher' + +. ./test-lib.sh + +if ! test_have_prereq FSMONITOR_DAEMON +then + skip_all="fsmonitor--daemon is not supported on this platform" + test_done +fi + +stop_daemon_delete_repo () { + r=$1 + git -C $r fsmonitor--daemon stop >/dev/null 2>/dev/null + rm -rf $1 + return 0 +} + +start_daemon () { + case "$#" in + 1) r="-C $1";; + *) r=""; + esac + + git $r fsmonitor--daemon start || return $? + git $r fsmonitor--daemon status || return $? + + return 0 +} + +# 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 + + grep -e '"event":"data".*"category":"'"$c"'".*"key":"'"$k"'"' +} + +test_expect_success 'explicit daemon start and stop' ' + test_when_finished "stop_daemon_delete_repo test_explicit" && + + git init test_explicit && + start_daemon test_explicit && + + git -C test_explicit fsmonitor--daemon stop && + test_must_fail git -C test_explicit fsmonitor--daemon status +' + +test_expect_success 'implicit daemon start' ' + test_when_finished "stop_daemon_delete_repo test_implicit" && + + git init test_implicit && + test_must_fail git -C test_implicit fsmonitor--daemon status && + + # query will implicitly start the daemon. + # + # for test-script simplicity, we send a V1 timestamp rather than + # a V2 token. either way, the daemon response to any query contains + # a new V2 token. (the daemon may complain that we sent a V1 request, + # but this test case is only concerned with whether the daemon was + # implicitly started.) + + 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 && + + # confirm that a daemon was started in the background. + # + # since the mechanism for starting the background daemon is platform + # dependent, just confirm that the foreground command received a + # response from the daemon. + + have_t2_data_event fsm_client query/response-length <.git/trace && + + git -C test_implicit fsmonitor--daemon status && + git -C test_implicit fsmonitor--daemon stop && + test_must_fail git -C test_implicit fsmonitor--daemon status +' + +test_expect_success 'implicit daemon stop (delete .git)' ' + test_when_finished "stop_daemon_delete_repo test_implicit_1" && + + git init test_implicit_1 && + + start_daemon test_implicit_1 && + + # deleting the .git directory will implicitly stop the daemon. + rm -rf test_implicit_1/.git && + + # [1] Create an empty .git directory so that the following Git + # command will stay relative to the `-C` directory. + # + # Without this, the Git command will override the requested + # -C argument and crawl out to the containing Git source tree. + # 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 +' + +test_expect_success 'implicit daemon stop (rename .git)' ' + test_when_finished "stop_daemon_delete_repo test_implicit_2" && + + git init test_implicit_2 && + + start_daemon test_implicit_2 && + + # renaming the .git directory will implicitly stop the daemon. + mv test_implicit_2/.git test_implicit_2/.xxx && + + # See [1] above. + # + sleep 1 && + mkdir test_implicit_2/.git && + + test_must_fail git -C test_implicit_2 fsmonitor--daemon status +' + +test_expect_success 'cannot start multiple daemons' ' + test_when_finished "stop_daemon_delete_repo test_multiple" && + + git init test_multiple && + + start_daemon test_multiple && + + test_must_fail git -C test_multiple fsmonitor--daemon start 2>actual && + grep "fsmonitor--daemon is already running" actual && + + git -C test_multiple fsmonitor--daemon stop && + test_must_fail git -C test_multiple fsmonitor--daemon status +' + +# These tests use the main repo in the trash directory + +test_expect_success 'setup' ' + >tracked && + >modified && + >delete && + >rename && + mkdir dir1 && + >dir1/tracked && + >dir1/modified && + >dir1/delete && + >dir1/rename && + mkdir dir2 && + >dir2/tracked && + >dir2/modified && + >dir2/delete && + >dir2/rename && + mkdir dirtorename && + >dirtorename/a && + >dirtorename/b && + + cat >.gitignore <<-\EOF && + .gitignore + expect* + actual* + EOF + + git -c core.fsmonitor=false add . && + test_tick && + git -c core.fsmonitor=false commit -m initial && + + git config core.fsmonitor true +' + +# The test already explicitly stopped (or tried to stop) the daemon. +# This is here in case something else fails first. +# +redundant_stop_daemon () { + git fsmonitor--daemon stop + return 0 +} + +test_expect_success 'update-index implicitly starts daemon' ' + test_when_finished redundant_stop_daemon && + + test_must_fail git fsmonitor--daemon status && + + GIT_TRACE2_EVENT="$(pwd)/.git/trace_implicit_1" \ + git update-index --fsmonitor && + + git fsmonitor--daemon status && + test_might_fail git fsmonitor--daemon stop && + + # Confirm that the trace2 log contains a record of the + # daemon starting. + test_subcommand git fsmonitor--daemon start <.git/trace_implicit_1 +' + +test_expect_success 'status implicitly starts daemon' ' + test_when_finished redundant_stop_daemon && + + test_must_fail git fsmonitor--daemon status && + + GIT_TRACE2_EVENT="$(pwd)/.git/trace_implicit_2" \ + git status >actual && + + git fsmonitor--daemon status && + test_might_fail git fsmonitor--daemon stop && + + # Confirm that the trace2 log contains a record of the + # daemon starting. + test_subcommand git fsmonitor--daemon start <.git/trace_implicit_2 +' + +edit_files () { + echo 1 >modified + echo 2 >dir1/modified + echo 3 >dir2/modified + >dir1/untracked +} + +delete_files () { + rm -f delete + rm -f dir1/delete + rm -f dir2/delete +} + +create_files () { + echo 1 >new + echo 2 >dir1/new + echo 3 >dir2/new +} + +rename_files () { + mv rename renamed + mv dir1/rename dir1/renamed + mv dir2/rename dir2/renamed +} + +file_to_directory () { + rm -f delete + mkdir delete + echo 1 >delete/new +} + +directory_to_file () { + 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 +# is reported by `git status`. +# +# We run a simple query after modifying the filesystem just to introduce +# a bit of a delay so that the trace logging from the daemon has time to +# get flushed to disk. +# +# We `reset` and `clean` at the bottom of each test (and before stopping the +# 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 + 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 + ) && + + edit_files && + + test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 && + + grep "^event: dir1/modified$" .git/trace && + grep "^event: dir2/modified$" .git/trace && + grep "^event: modified$" .git/trace && + grep "^event: dir1/untracked$" .git/trace +' + +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 + ) && + + create_files && + + test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 && + + grep "^event: dir1/new$" .git/trace && + grep "^event: dir2/new$" .git/trace && + grep "^event: new$" .git/trace +' + +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 + ) && + + delete_files && + + test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 && + + grep "^event: dir1/delete$" .git/trace && + grep "^event: dir2/delete$" .git/trace && + grep "^event: delete$" .git/trace +' + +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 + ) && + + rename_files && + + test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 && + + grep "^event: dir1/rename$" .git/trace && + grep "^event: dir2/rename$" .git/trace && + grep "^event: rename$" .git/trace && + grep "^event: dir1/renamed$" .git/trace && + grep "^event: dir2/renamed$" .git/trace && + grep "^event: renamed$" .git/trace +' + +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 + ) && + + mv dirtorename dirrenamed && + + test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 && + + grep "^event: dirtorename/*$" .git/trace && + grep "^event: dirrenamed/*$" .git/trace +' + +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 + ) && + + file_to_directory && + + test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 && + + grep "^event: delete$" .git/trace && + grep "^event: delete/new$" .git/trace +' + +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 + ) && + + directory_to_file && + + test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 && + + grep "^event: dir1$" .git/trace +' + +# The next few test cases exercise the token-resync code. When filesystem +# drops events (because of filesystem velocity or because the daemon isn't +# polling fast enough), we need to discard the cached data (relative to the +# current token) and start collecting events under a new token. +# +# the 'test-tool fsmonitor-client flush' command can be used to send a +# "flush" message to a running daemon and ask it to do a flush/resync. + +test_expect_success 'flush cached data' ' + test_when_finished "stop_daemon_delete_repo test_flush" && + + 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 + ) && + + # The daemon should have an initial token with no events in _0 and + # then a few (probably platform-specific number of) events in _1. + # These should both have the same <token_id>. + + 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-tool -C test_flush fsmonitor-client query --token "builtin:test_00000001:0" >actual_1 && + nul_to_q <actual_1 >actual_q1 && + + grep "file_1" actual_q1 && + + # Force a flush. This will change the <token_id>, reset the <seq_nr>, and + # flush the file data. Then create some events and ensure that the file + # again appears in the cache. It should have the new <token_id>. + + test-tool -C test_flush fsmonitor-client flush >flush_0 && + nul_to_q <flush_0 >flush_q0 && + grep "^builtin:test_00000002:0Q/Q$" flush_q0 && + + test-tool -C test_flush fsmonitor-client query --token "builtin:test_00000002:0" >actual_2 && + nul_to_q <actual_2 >actual_q2 && + + grep "^builtin:test_00000002:0Q$" actual_q2 && + + touch 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 && + + grep "file_3" actual_q3 +' + +# The next few test cases create repos where the .git directory is NOT +# inside the one of the working directory. That is, where .git is a file +# that points to a directory elsewhere. This happens for submodules and +# non-primary worktrees. + +test_expect_success 'setup worktree base' ' + git init wt-base && + echo 1 >wt-base/file1 && + git -C wt-base add file1 && + git -C wt-base commit -m "c1" +' + +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 + ) && + + git -C wt-secondary fsmonitor--daemon stop && + test_must_fail git -C wt-secondary fsmonitor--daemon status +' + +# NEEDSWORK: Repeat one of the "edit" tests on wt-secondary and +# confirm that we get the same events and behavior -- that is, that +# fsmonitor--daemon correctly watches BOTH the working directory and +# the external GITDIR directory and behaves the same as when ".git" +# is a directory inside the working directory. + +test_expect_success 'cleanup worktrees' ' + stop_daemon_delete_repo wt-secondary && + stop_daemon_delete_repo wt-base +' + +test_done
Signature · 1 line
gitgitgadget
PATCH v5 22/30 t/perf: avoid copying builtin fsmonitor files into test repo
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Do not copy any of the various fsmonitor--daemon files from the .git directory of the (GIT_PREF_REPO or GIT_PERF_LARGE_REPO) source repo into the test's trash directory.
When perf tests start, they copy the contents of the source repo into the test's trash directory. If fsmonitor is running in the source repo, there may be control files, such as the IPC socket and/or fsmonitor cookie files. These should not be copied into the test repo.
Unix domain sockets cannot be copied in the manner used by the test setup, so if present, the test setup fails.
Cookie files are harmless, but we should avoid them.
The builtin fsmonitor keeps all such control files/sockets in .git/fsmonitor--daemon*, so it is simple to exclude them.
--- t/perf/perf-lib.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
t/perf/perf-lib.sh +1 -1
@@ -78,7 +78,7 @@ test_perf_copy_repo_contents () { for stuff in "$1"/* do case "$stuff" in - */objects|*/hooks|*/config|*/commondir|*/gitdir|*/worktrees) + */objects|*/hooks|*/config|*/commondir|*/gitdir|*/worktrees|*/fsmonitor--daemon*) ;; *) cp -R "$stuff" "$repo/.git/" || exit 1
Signature · 1 line
gitgitgadget
PATCH v5 23/30 t/helper/test-chmtime: skip directories on Windows
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Teach test-tool.exe chmtime to ignore errors when setting the mtime on a directory on Windows.
NEEDSWORK: The Windows version of utime() (aka mingw_utime()) does not properly handle directories because it uses _wopen(). It should be converted to using CreateFileW() and backup semantics at a minimum. Since I'm already in the middle of a large patch series, I did not want to destabilize other callers of utime() right now. The problem has only been observed in the t/perf/p7519 test when the test repo contains an empty directory on disk.
--- t/helper/test-chmtime.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+)
t/helper/test-chmtime.c +15 -0
@@ -134,6 +134,21 @@ int cmd__chmtime(int argc, const char **argv) }if (utb.modtime != sb.st_mtime &&utime (argv[i], &utb) <0 ) { +#ifdef GIT_WINDOWS_NATIVE +if (S_ISDIR (sb.st_mode)) { +/* +* NEEDSWORK: The Windows version of `utime()` +* (aka `mingw_utime()`) does not correctly +* handle directory arguments, since it uses +* `_wopen()`. Ignore it for now since this +* is just a test. +*/ +fprintf (stderr, + ("Failed to modify time on directory %s. " +"Skipping\n" ), argv[i]); +continue ; + } +#endif fprintf (stderr,"Failed to modify time on %s: %s\n" , argv[i],strerror (errno));return 1 ;
Signature · 1 line
gitgitgadget
Re: [PATCH v5 23/30] t/helper/test-chmtime: skip directories on Windows
Random follow-up on this: in message/patch 76b6216281e3463821e650495f3090c677905f73.1646041236.git.gitgitgadget@gmail.com I propose a fix to this issue. If accepted, that fix (and related changes to rely
more heavily on chmtime in t7063) will cause *this* change to cause test failures.
If that patch is accepted, this commit will simply need to be dropped as far as I understand.
Quoted 48 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
Teach
test-tool.exe chmtimeto ignore errors when setting the mtime on a directory on Windows.NEEDSWORK: The Windows version of
utime()(akamingw_utime()) does not properly handle directories because it uses_wopen(). It should be converted to usingCreateFileW()and backup semantics at a minimum. Since I'm already in the middle of a large patch series, I did not want to destabilize other callers ofutime()right now. The problem has only been observed in the t/perf/p7519 test when the test repo contains an empty directory on disk.Signed-off-by Jeff Hostetler <[email protected]>--- t/helper/test-chmtime.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+)t/helper/test-chmtime.c +15
-1@@ -134,6 +134,21 @@ int cmd__chmtime(int argc, const char **argv) }if (utb.modtime != sb.st_mtime &&utime (argv[i], &utb) <0 ) { +#ifdef GIT_WINDOWS_NATIVE +if (S_ISDIR (sb.st_mode)) { +/* +* NEEDSWORK: The Windows version of `utime()` +* (aka `mingw_utime()`) does not correctly +* handle directory arguments, since it uses +* `_wopen()`. Ignore it for now since this +* is just a test. +*/ +fprintf (stderr, + ("Failed to modify time on directory %s. " +"Skipping\n" ), argv[i]); +continue ; + } +#endif fprintf (stderr,"Failed to modify time on %s: %s\n" , argv[i],strerror (errno));return 1 ; --gitgitgadget
Re: [PATCH v5 23/30] t/helper/test-chmtime: skip directories on Windows
Quoted 10 lines · Tao Klerks
Random follow-up on this: in message/patch 76b6216281e3463821e650495f3090c677905f73.1646041236.git.gitgitgadget@gmail.com I propose a fix to this issue. If accepted, that fix (and related changes to rely
more heavily on chmtime in t7063) will cause *this* change to cause test failures.If that patch is accepted, this commit will simply need to be dropped as far as I understand.
Thanks for the note. Yes, with your change we should be able to drop or revert this commit.
Thanks
Jeff
Re: [PATCH v5 23/30] t/helper/test-chmtime: skip directories on Windows
Quoted 12 lines · Jeff Hostetler
Quoted 10 lines · Tao Klerks
Random follow-up on this: in message/patch 76b6216281e3463821e650495f3090c677905f73.1646041236.git.gitgitgadget@gmail.com I propose a fix to this issue. If accepted, that fix (and related changes to rely
more heavily on chmtime in t7063) will cause *this* change to cause test failures.
If that patch is accepted, this commit will simply need to be dropped
as far as I understand.
Quoted 2 lines
Thanks for the note. Yes, with your change we should be able to drop or revert this commit.
That sounds nice. The fewer differences among platforms we have to maintain, the easier.
PATCH v5 24/30 t/perf/p7519: speed up test on Windows
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Change p7519 to use test_seq and xargs rather than a for loop to touch thousands of files. This takes minutes off of test runs on Windows because of process creation overhead.
--- t/perf/p7519-fsmonitor.sh | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-)
t/perf/p7519-fsmonitor.sh +20 -12
@@ -72,7 +72,7 @@ then fi fi -trace_start() { +trace_start () { if test -n "$GIT_PERF_7519_TRACE" then name="$1"
@@ -91,13 +91,20 @@ trace_start() { fi } -trace_stop() { +trace_stop () { if test -n "$GIT_PERF_7519_TRACE" then unset GIT_TRACE2_PERF fi } +touch_files () { + n=$1 + d="$n"_files + + (cd $d ; test_seq 1 $n | xargs touch ) +} + test_expect_success "one time repo setup" ' # set untrackedCache depending on the environment if test -n "$GIT_PERF_7519_UNTRACKED_CACHE"
@@ -119,10 +126,11 @@ test_expect_success "one time repo setup" ' fi && mkdir 1_file 10_files 100_files 1000_files 10000_files && - for i in $(test_seq 1 10); do touch 10_files/$i || return 1; done && - for i in $(test_seq 1 100); do touch 100_files/$i || return 1; done && - for i in $(test_seq 1 1000); do touch 1000_files/$i || return 1; done && - for i in $(test_seq 1 10000); do touch 10000_files/$i || return 1; done && + touch_files 1 && + touch_files 10 && + touch_files 100 && + touch_files 1000 && + touch_files 10000 && git add 1_file 10_files 100_files 1000_files 10000_files && git commit -qm "Add files" &&
@@ -133,7 +141,7 @@ test_expect_success "one time repo setup" ' fi ' -setup_for_fsmonitor() { +setup_for_fsmonitor () { # set INTEGRATION_SCRIPT depending on the environment if test -n "$INTEGRATION_PATH" then
@@ -173,7 +181,7 @@ test_perf_w_drop_caches () { test_perf "$@" } -test_fsmonitor_suite() { +test_fsmonitor_suite () { if test -n "$INTEGRATION_SCRIPT"; then DESC="fsmonitor=$(basename $INTEGRATION_SCRIPT)" else
@@ -199,15 +207,15 @@ test_fsmonitor_suite() { # Update the mtimes on upto 100k files to make status think # that they are dirty. For simplicity, omit any files with - # LFs (i.e. anything that ls-files thinks it needs to dquote). - # Then fully backslash-quote the paths to capture any - # whitespace so that they pass thru xargs properly. + # LFs (i.e. anything that ls-files thinks it needs to dquote) + # and any files with whitespace so that they pass thru xargs + # properly. # test_perf_w_drop_caches "status (dirty) ($DESC)" ' git ls-files | \ head -100000 | \ grep -v \" | \ - sed '\''s/\(.\)/\\\1/g'\'' | \ + egrep -v " ." | \ xargs test-tool chmtime -300 && git status '
Signature · 1 line
gitgitgadget
Re: [PATCH v5 24/30] t/perf/p7519: speed up test on Windows
Quoted 11 lines · Jeff Hostetler via GitGitGadget
(quoted diff) +7
-0+touch_files () { + n=$1 + d="$n"_files + + (cd $d ; test_seq 1 $n | xargs touch ) +} + test_expect_success "one time repo setup" ' # set untrackedCache depending on the environment if test -n "$GIT_PERF_7519_UNTRACKED_CACHE"
Review test_expect_success "one time repo setup" ' · line 129
Quoted 8 lines
(quoted diff) +1
-4@@ -119,10 +126,11 @@ test_expect_success "one time repo setup" ' fi && mkdir 1_file 10_files 100_files 1000_files 10000_files && - for i in $(test_seq 1 10); do touch 10_files/$i || return 1; done && - for i in $(test_seq 1 100); do touch 100_files/$i || return 1; done && - for i in $(test_seq 1 1000); do touch 1000_files/$i || return 1; done && - for i in $(test_seq 1 10000); do touch 10000_files/$i || return 1; done && + touch_files 1 &&
This causes touch_files to chdir to 1_files and run "touch 1" in there, but because there is no such directory (we have 1_file/ directory, but not 1_files/ directory), it would fail.
Review test_expect_success "one time repo setup" ' · line 133
Quoted 4 lines
(quoted diff) +4
-0+ touch_files 10 && + touch_files 100 && + touch_files 1000 && + touch_files 10000 &&
Apparently nobody has run this perf script recently since part #2 was posted.
Review test_expect_success "one time repo setup" ' · line 134
Quoted 1 line
(quoted diff) +0
-0git add 1_file 10_files 100_files 1000_files 10000_files &&
The original introduced at bb7cc7e7 (t/perf/fsmonitor: separate one time repo initialization, 2020-10-26) created an empty directory 1_file and without creating anything in it, ran "git add" on it.
If we are not doing anything to 1_file directory anyway, perhaps we can get rid of it to avoid the breakage in "make perf"?
If we have a chance to reroll this series, we can squash in something like this, perhaps (it does not deserve to be a separate step).
--- >8 ---
Subject: [PATCH] p7519: leave 1_file directory empty
The step "t/perf/p7519: speed up test on Windows" in the topic builtin-fsmonitor-part-2 (not in 'next' yet) attempts to create one file in 1_files directory, but the original introduced at bb7cc7e7 (t/perf/fsmonitor: separate one time repo initialization, 2020-10-26):
(1) created 1_file directory,
(2) left the directory empty, and
(3) a later test expected (and still expects) that there is nothing in the directory.
Revert the behaviour back to what the original wanted to do.
--- t/perf/p7519-fsmonitor.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
t/perf/p7519-fsmonitor.sh +1 -1
@@ -126,7 +126,7 @@ test_expect_success "one time repo setup" ' fi && mkdir 1_file 10_files 100_files 1000_files 10000_files && - touch_files 1 && + : 1_file directory should be left empty && touch_files 10 && touch_files 100 && touch_files 1000 &&
Signature · 1 line
2.35.1-193-g45fe28c951
Re: [PATCH v5 24/30] t/perf/p7519: speed up test on Windows
Quoted 40 lines · Junio C Hamano
Quoted 11 lines · Jeff Hostetler via GitGitGadget
(quoted diff) +7
-0+touch_files () { + n=$1 + d="$n"_files + + (cd $d ; test_seq 1 $n | xargs touch ) +} + test_expect_success "one time repo setup" ' # set untrackedCache depending on the environment if test -n "$GIT_PERF_7519_UNTRACKED_CACHE"Review test_expect_success "one time repo setup" ' · line 129
Quoted 8 lines
(quoted diff) +1
-4@@ -119,10 +126,11 @@ test_expect_success "one time repo setup" ' fi && mkdir 1_file 10_files 100_files 1000_files 10000_files && - for i in $(test_seq 1 10); do touch 10_files/$i || return 1; done && - for i in $(test_seq 1 100); do touch 100_files/$i || return 1; done && - for i in $(test_seq 1 1000); do touch 1000_files/$i || return 1; done && - for i in $(test_seq 1 10000); do touch 10000_files/$i || return 1; done && + touch_files 1 &&This causes
touch_filesto chdir to 1_files and run "touch 1" in there, but because there is no such directory (we have 1_file/ directory, but not 1_files/ directory), it would fail.Review test_expect_success "one time repo setup" ' · line 133
Quoted 4 lines
(quoted diff) +4
-0+ touch_files 10 && + touch_files 100 && + touch_files 1000 && + touch_files 10000 &&Apparently nobody has run this perf script recently since part #2 was posted.
Review test_expect_success "one time repo setup" ' · line 134
Quoted 1 line
(quoted diff) +0
-0git add 1_file 10_files 100_files 1000_files 10000_files &&The original introduced at
bb7cc7e7(t/perf/fsmonitor: separate one time repo initialization, 2020-10-26) created an empty directory 1_file and without creating anything in it, ran "git add" on it.
Quoted 6 lines
If we are not doing anything to 1_file directory anyway, perhaps we can get rid of it to avoid the breakage in "make perf"?
If we have a chance to reroll this series, we can squash in something like this, perhaps (it does not deserve to be a separate step).
Good catch!
It looks to me like there was an oversight/typo in the original 89afd5f5ad (t/perf: add fsmonitor perf test for git diff, 2020-10-20). They created the "1_file" directory and didn't put anything in it. Then later when they test it, they say "0_files" in the test name and "1_file" in the "git diff" command.
I don't think it's worth keeping an empty directory here, since there won't be anything in the index after the add and since the directory is empty the untracked cache won't have anything to scan.
My version (without the typo) would have created 1 file in the directory but I don't think that's worth keeping either, since we create thousands of files in steps right after it.
I'll make a note to remove it.
Jeff
Quoted 38 lines
--- >8 ---
Subject: [PATCH] p7519: leave 1_file directory emptyThe step "t/perf/p7519: speed up test on Windows" in the topic builtin-fsmonitor-part-2 (not in 'next' yet) attempts to create one file in 1_files directory, but the original introduced at
bb7cc7e7(t/perf/fsmonitor: separate one time repo initialization, 2020-10-26):(1) created 1_file directory,
(2) left the directory empty, and
(3) a later test expected (and still expects) that there is nothing in the directory.
Revert the behaviour back to what the original wanted to do.
Signed-off-by Junio C Hamano <[email protected]>--- t/perf/p7519-fsmonitor.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)t/perf/p7519-fsmonitor.sh +1
-1@@ -126,7 +126,7 @@ test_expect_success "one time repo setup" ' fi && mkdir 1_file 10_files 100_files 1000_files 10000_files && - touch_files 1 && + : 1_file directory should be left empty && touch_files 10 && touch_files 100 && touch_files 1000 &&
PATCH v5 25/30 t/perf/p7519: add fsmonitor--daemon test cases
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Repeat all of the fsmonitor perf tests using git fsmonitor--daemon and the "Simple IPC" interface.
--- t/perf/p7519-fsmonitor.sh | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-)
t/perf/p7519-fsmonitor.sh +28 -1
@@ -182,7 +182,10 @@ test_perf_w_drop_caches () { } test_fsmonitor_suite () { - if test -n "$INTEGRATION_SCRIPT"; then + if test -n "$USE_FSMONITOR_DAEMON" + then + DESC="builtin fsmonitor--daemon" + elif test -n "$INTEGRATION_SCRIPT"; then DESC="fsmonitor=$(basename $INTEGRATION_SCRIPT)" else DESC="fsmonitor=disabled"
@@ -293,4 +296,28 @@ test_expect_success "setup without fsmonitor" ' test_fsmonitor_suite trace_stop +# +# Run a full set of perf tests using the built-in fsmonitor--daemon. +# It does not use the Hook API, so it has a different setup. +# Explicitly start the daemon here and before we start client commands +# so that we can later add custom tracing. +# +if test_have_prereq FSMONITOR_DAEMON +then + USE_FSMONITOR_DAEMON=t + + trace_start fsmonitor--daemon--server + git fsmonitor--daemon start + + trace_start fsmonitor--daemon--client + + git config core.fsmonitor true + git update-index --fsmonitor + + test_fsmonitor_suite + + git fsmonitor--daemon stop + trace_stop +fi + test_done
Signature · 1 line
gitgitgadget
PATCH v5 26/30 fsmonitor--daemon: periodically truncate list of modified files
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Teach fsmonitor--daemon to periodically truncate the list of modified files to save some memory.
Clients will ask for the set of changes relative to a token that they found in the FSMN index extension in the index. (This token is like a point in time, but different). Clients will then update the index to contain the response token (so that subsequent commands will be relative to this new token).
Therefore, the daemon can gradually truncate the in-memory list of changed paths as they become obsolete (older than the previous token). Since we may have multiple clients making concurrent requests with a skew of tokens and clients may be racing to the talk to the daemon, we lazily truncate the list.
We introduce a 5 minute delay and truncate batches 5 minutes after they are considered obsolete.
--- builtin/fsmonitor--daemon.c | 88 +++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+)
builtin/fsmonitor--daemon.c +88 -0
@@ -299,6 +299,75 @@ static void fsmonitor_batch__combine(struct fsmonitor_batch *batch_dest, batch_src->interned_paths[k]; } +/* +* To keep the batch list from growing unbounded in response to filesystem +* activity, we try to truncate old batches from the end of the list as +* they become irrelevant. +* +* We assume that the .git/index will be updated with the most recent token +* any time the index is updated. And future commands will only ask for +* recent changes *since* that new token. So as tokens advance into the +* future, older batch items will never be requested/needed. So we can +* truncate them without loss of functionality. +* +* However, multiple commands may be talking to the daemon concurrently +* or perform a slow command, so a little "token skew" is possible. +* Therefore, we want this to be a little bit lazy and have a generous +* delay. +* +* The current reader thread walked backwards in time from `token->batch_head` +* back to `batch_marker` somewhere in the middle of the batch list. +* +* Let's walk backwards in time from that marker an arbitrary delay +* and truncate the list there. Note that these timestamps are completely +* artificial (based on when we pinned the batch item) and not on any +* filesystem activity. +* +* Return the obsolete portion of the list after we have removed it from +* the official list so that the caller can free it after leaving the lock. +*/ +#define MY_TIME_DELAY_SECONDS (5 * 60)/* seconds */ + +static struct fsmonitor_batch *with_lock__truncate_old_batches ( +struct fsmonitor_daemon_state *state, +const struct fsmonitor_batch *batch_marker) +{ +/* assert current thread holding state->main_lock */ + +const struct fsmonitor_batch *batch; +struct fsmonitor_batch *remainder; + +if (!batch_marker) +return NULL ; + +trace_printf_key (&trace_fsmonitor,"Truncate: mark (%" PRIu64",%" PRIu64")" , + batch_marker->batch_seq_nr, + (uint64_t )batch_marker->pinned_time); + +for (batch = batch_marker; batch; batch = batch->next) { +time_t t; + +if (!batch->pinned_time)/* an overflow batch */ +continue ; + + t = batch->pinned_time +MY_TIME_DELAY_SECONDS ; +if (t > batch_marker->pinned_time)/* too close to marker */ +continue ; + + goto truncate_past_here; + } + +return NULL ; + +truncate_past_here: + state->current_token_data->batch_tail = (struct fsmonitor_batch *)batch; + + remainder = ((struct fsmonitor_batch *)batch)->next; + ((struct fsmonitor_batch *)batch)->next =NULL ; + +return remainder; +} +static void fsmonitor_free_token_data (struct fsmonitor_token_data *token) {if (!token)
@@ -412,6 +481,7 @@ static int do_handle_client(struct fsmonitor_daemon_state *state,const char *p;const struct fsmonitor_batch *batch_head;const struct fsmonitor_batch *batch; +struct fsmonitor_batch *remainder =NULL ;intmax_t count =0 , duplicates =0 ;kh_str_t *shown;int hash_ret;
@@ -641,11 +711,29 @@ static int do_handle_client(struct fsmonitor_daemon_state *state,* that work. */ fsmonitor_free_token_data (token_data); + }else if (batch) { +/* +* We are holding the lock and are the only +* reader of the ref-counted portion of the +* list, so we get the honor of seeing if the +* list can be truncated to save memory. +* +* The main loop did not walk to the end of the +* list, so this batch is the first item in the +* batch-list that is older than the requested +* end-point sequence number. See if the tail +* end of the list is obsolete. +*/ + remainder =with_lock__truncate_old_batches (state, + batch); } }pthread_mutex_unlock (&state->main_lock); +if (remainder) +fsmonitor_batch__free_list (remainder); +trace2_data_intmax ("fsmonitor" , the_repository,"response/length" , total_response_len);trace2_data_intmax ("fsmonitor" , the_repository,"response/count/files" , count);trace2_data_intmax ("fsmonitor" , the_repository,"response/count/duplicates" , duplicates);
Signature · 1 line
gitgitgadget
Re: [PATCH v5 26/30] fsmonitor--daemon: periodically truncate list of modified files
Hi Jeff,
Quoted 11 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
Teach fsmonitor--daemon to periodically truncate the list of modified files to save some memory.
Clients will ask for the set of changes relative to a token that they found in the FSMN index extension in the index. (This token is like a point in time, but different). Clients will then update the index to contain the response token (so that subsequent commands will be relative to this new token).
Quoted 8 lines
Therefore, the daemon can gradually truncate the in-memory list of changed paths as they become obsolete (older than the previous token). Since we may have multiple clients making concurrent requests with a skew of tokens and clients may be racing to the talk to the daemon, we lazily truncate the list.
We introduce a 5 minute delay and truncate batches 5 minutes after they are considered obsolete.
I tried to poke holes into this strategy for a while, but failed whichever way I looked. Meaning: I am now convinced that this strategy is sound.
Thanks,
Dscho
PATCH v5 27/30 fsmonitor--daemon: use a cookie file to sync with file system
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Teach fsmonitor--daemon client threads to create a cookie file inside the .git directory and then wait until FS events for the cookie are observed by the FS listener thread.
This helps address the racy nature of file system events by blocking the client response until the kernel has drained any event backlog.
This is especially important on MacOS where kernel events are only issued with a limited frequency. See the latency argument of FSeventStreamCreate(). The kernel only signals every latency seconds, but does not guarantee that the kernel queue is completely drained, so we may have to wait more than one interval. If we increase the frequency, the system is more likely to drop events. We avoid these issues by having each client thread create a unique cookie file and then wait until it is seen in the event stream.
--- builtin/fsmonitor--daemon.c | 228 +++++++++++++++++++++++++++++++++++- fsmonitor--daemon.h | 5 + 2 files changed, 232 insertions(+), 1 deletion(-)
builtin/fsmonitor--daemon.c +227 -1
@@ -94,6 +94,149 @@ static int do_as_client__status(void) } } +enum fsmonitor_cookie_item_result { +FCIR_ERROR =-1 ,/* could not create cookie file ? */ +FCIR_INIT =0 , +FCIR_SEEN , +FCIR_ABORT , +}; + +struct fsmonitor_cookie_item { +struct hashmap_entry entry; +const char *name; +enum fsmonitor_cookie_item_result result; +}; + +static int cookies_cmp (const void *data,const struct hashmap_entry *he1, +const struct hashmap_entry *he2,const void *keydata) +{ +const struct fsmonitor_cookie_item *a = + container_of(he1, const struct fsmonitor_cookie_item, entry); +const struct fsmonitor_cookie_item *b = + container_of(he2, const struct fsmonitor_cookie_item, entry); + +return strcmp (a->name, keydata ? keydata : b->name); +} + +static enum fsmonitor_cookie_item_result with_lock__wait_for_cookie ( +struct fsmonitor_daemon_state *state) +{ +/* assert current thread holding state->main_lock */ + +int fd; +struct fsmonitor_cookie_item *cookie; +struct strbuf cookie_pathname =STRBUF_INIT ; +struct strbuf cookie_filename =STRBUF_INIT ; +enum fsmonitor_cookie_item_result result; +int my_cookie_seq; + +CALLOC_ARRAY (cookie,1 ); + + my_cookie_seq = state->cookie_seq++; + +strbuf_addf (&cookie_filename,"%i-%i" ,getpid (), my_cookie_seq); + +strbuf_addbuf (&cookie_pathname, &state->path_cookie_prefix); +strbuf_addbuf (&cookie_pathname, &cookie_filename); + + cookie->name =strbuf_detach (&cookie_filename,NULL ); + cookie->result =FCIR_INIT ; +hashmap_entry_init (&cookie->entry,strhash (cookie->name)); + +hashmap_add (&state->cookies, &cookie->entry); + +trace_printf_key (&trace_fsmonitor,"cookie-wait: '%s' '%s'" , + cookie->name, cookie_pathname.buf); + +/* +* Create the cookie file on disk and then wait for a notification +* 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); + +/* +* NEEDSWORK: 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. +*/ +while (cookie->result ==FCIR_INIT ) +pthread_cond_wait (&state->cookies_cond, + &state->main_lock); + }else { +error_errno (_ ("could not create fsmonitor cookie '%s'" ), + cookie->name); + + cookie->result =FCIR_ERROR ; + } + +hashmap_remove (&state->cookies, &cookie->entry,NULL ); + + result = cookie->result; + +free ((char *)cookie->name); +free (cookie); +strbuf_release (&cookie_pathname); + +return result; +} + +/* +* Mark these cookies as _SEEN and wake up the corresponding client threads. +*/ +static void with_lock__mark_cookies_seen (struct fsmonitor_daemon_state *state, +const struct string_list *cookie_names) +{ +/* assert current thread holding state->main_lock */ + +int k; +int nr_seen =0 ; + +for (k =0 ; k < cookie_names->nr; k++) { +struct fsmonitor_cookie_item key; +struct fsmonitor_cookie_item *cookie; + + key.name = cookie_names->items[k].string; +hashmap_entry_init (&key.entry,strhash (key.name)); + + cookie =hashmap_get_entry (&state->cookies, &key, entry,NULL ); +if (cookie) { +trace_printf_key (&trace_fsmonitor,"cookie-seen: '%s'" , + cookie->name); + cookie->result =FCIR_SEEN ; + nr_seen++; + } + } + +if (nr_seen) +pthread_cond_broadcast (&state->cookies_cond); +} + +/* +* Set _ABORT on all pending cookies and wake up all client threads. +*/ +static void with_lock__abort_all_cookies (struct fsmonitor_daemon_state *state) +{ +/* assert current thread holding state->main_lock */ + +struct hashmap_iter iter; +struct fsmonitor_cookie_item *cookie; +int nr_aborted =0 ; + +hashmap_for_each_entry (&state->cookies, &iter, cookie, entry) { +trace_printf_key (&trace_fsmonitor,"cookie-abort: '%s'" , + cookie->name); + cookie->result =FCIR_ABORT ; + nr_aborted++; + } + +if (nr_aborted) +pthread_cond_broadcast (&state->cookies_cond); +} +/* * Requests to and from a FSMonitor Protocol V2 provider use an opaque * "token" as a virtual timestamp. Clients can request a summary of all
@@ -391,6 +534,9 @@ static void fsmonitor_free_token_data(struct fsmonitor_token_data *token)* We should create a new token and start fresh (as if we just * booted up). * +* [2] Some of those lost events may have been for cookie files. We +* should assume the worst and abort them rather letting them starve. +* * If there are no concurrent threads readering the current token data * series, we can free it now. Otherwise, let the last reader free * it.
@@ -412,6 +558,8 @@ static void with_lock__do_force_resync(struct fsmonitor_daemon_state *state) state->current_token_data = new_one;fsmonitor_free_token_data (free_me); + +with_lock__abort_all_cookies (state); }void fsmonitor_force_resync (struct fsmonitor_daemon_state *state)
@@ -487,6 +635,8 @@ static int do_handle_client(struct fsmonitor_daemon_state *state,int hash_ret;int do_trivial =0 ;int do_flush =0 ; +int do_cookie =0 ; +enum fsmonitor_cookie_item_result cookie_result;/* * We expect `command` to be of the form:
@@ -547,6 +697,7 @@ static int do_handle_client(struct fsmonitor_daemon_state *state,* We have a V2 valid token: * "builtin:<token_id>:<seq_nr>" */ + do_cookie =1 ; } }
@@ -555,6 +706,30 @@ static int do_handle_client(struct fsmonitor_daemon_state *state,if (!state->current_token_data)BUG ("fsmonitor state does not have a current token" ); +/* +* Write a cookie file inside the directory being watched in +* an effort to flush out existing filesystem events that we +* actually care about. Suspend this client thread until we +* see the filesystem events for this cookie file. +* +* Creating the cookie lets us guarantee that our FS listener +* thread has drained the kernel queue and we are caught up +* with the kernel. +* +* If we cannot create the cookie (or otherwise guarantee that +* we are caught up), we send a trivial response. We have to +* assume that there might be some very, very recent activity +* on the FS still in flight. +*/ +if (do_cookie) { + cookie_result =with_lock__wait_for_cookie (state); +if (cookie_result !=FCIR_SEEN ) { +error (_ ("fsmonitor: cookie_result '%d' != SEEN" ), + cookie_result); + do_trivial =1 ; + } + } +if (do_flush)with_lock__do_force_resync (state);
@@ -775,7 +950,9 @@ static int handle_client(void *data,return result; } -#define FSMONITOR_COOKIE_PREFIX ".fsmonitor-daemon-" +#define FSMONITOR_DIR "fsmonitor--daemon" +#define FSMONITOR_COOKIE_DIR "cookies" +#define FSMONITOR_COOKIE_PREFIX (FSMONITOR_DIR "/" FSMONITOR_COOKIE_DIR "/")enum fsmonitor_path_type fsmonitor_classify_path_workdir_relative (const char *rel)
@@ -928,6 +1105,9 @@ void fsmonitor_publish(struct fsmonitor_daemon_state *state, } } +if (cookie_names->nr) +with_lock__mark_cookies_seen (state, cookie_names); +pthread_mutex_unlock (&state->main_lock); }
@@ -1019,7 +1199,9 @@ static int fsmonitor_run_daemon(void)memset (&state,0 ,sizeof (state)); +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.current_token_data =fsmonitor_new_token_data ();
@@ -1044,6 +1226,44 @@ static int fsmonitor_run_daemon(void) state.nr_paths_watching =2 ; } +/* +* We will write filesystem syncing cookie files into +* <gitdir>/<fsmonitor-dir>/<cookie-dir>/<pid>-<seq>. +* +* The extra layers of subdirectories here keep us from +* changing the mtime on ".git/" or ".git/foo/" when we create +* or delete cookie files. +* +* There have been problems with some IDEs that do a +* non-recursive watch of the ".git/" directory and run a +* series of commands any time something happens. +* +* For example, if we place our cookie files directly in +* ".git/" or ".git/foo/" then a `git status` (or similar +* command) from the IDE will cause a cookie file to be +* created in one of those dirs. This causes the mtime of +* those dirs to change. This triggers the IDE's watch +* notification. This triggers the IDE to run those commands +* again. And the process repeats and the machine never goes +* idle. +* +* Adding the extra layers of subdirectories prevents the +* mtime of ".git/" and ".git/foo" from changing when a +* cookie file is created. +*/ +strbuf_init (&state.path_cookie_prefix,0 ); +strbuf_addbuf (&state.path_cookie_prefix, &state.path_gitdir_watch); + +strbuf_addch (&state.path_cookie_prefix,'/' ); +strbuf_addstr (&state.path_cookie_prefix,FSMONITOR_DIR ); +mkdir (state.path_cookie_prefix.buf,0777 ); + +strbuf_addch (&state.path_cookie_prefix,'/' ); +strbuf_addstr (&state.path_cookie_prefix,FSMONITOR_COOKIE_DIR ); +mkdir (state.path_cookie_prefix.buf,0777 ); + +strbuf_addch (&state.path_cookie_prefix,'/' ); +/* * Confirm that we can create platform-specific resources for the * filesystem listener before we bother starting all the threads.
@@ -1056,6 +1276,7 @@ static int fsmonitor_run_daemon(void) err =fsmonitor_run_daemon_1 (&state); done: +pthread_cond_destroy (&state.cookies_cond);pthread_mutex_destroy (&state.main_lock);fsm_listen__dtor (&state);
@@ -1063,6 +1284,11 @@ done:strbuf_release (&state.path_worktree_watch);strbuf_release (&state.path_gitdir_watch); +strbuf_release (&state.path_cookie_prefix); + +/* +* NEEDSWORK: Consider "rm -rf <gitdir>/<fsmonitor-dir>" +*/ return err; }
fsmonitor--daemon.h +5 -0
@@ -45,6 +45,11 @@ struct fsmonitor_daemon_state {struct fsmonitor_token_data *current_token_data; +struct strbuf path_cookie_prefix; +pthread_cond_t cookies_cond; +int cookie_seq; +struct hashmap cookies; +int error_code;struct fsmonitor_daemon_backend_data *backend_data;
Signature · 1 line
gitgitgadget
Re: [PATCH v5 27/30] fsmonitor--daemon: use a cookie file to sync with file system
Hi Jeff,
Quoted 10 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
Teach fsmonitor--daemon client threads to create a cookie file inside the .git directory and then wait until FS events for the cookie are observed by the FS listener thread.
This helps address the racy nature of file system events by blocking the client response until the kernel has drained any event backlog.
Quoted 8 lines
This is especially important on MacOS where kernel events are only issued with a limited frequency. See the
latencyargument ofFSeventStreamCreate(). The kernel only signals everylatencyseconds, but does not guarantee that the kernel queue is completely drained, so we may have to wait more than one interval. If we increase the frequency, the system is more likely to drop events. We avoid these issues by having each client thread create a unique cookie file and then wait until it is seen in the event stream.
It took a couple iterations of this cookie file business to become robust... ;-)
About these NEEDSWORKs:
Quoted 8 lines
builtin/fsmonitor--daemon.c +8
-0+/* +* Create the cookie file on disk and then wait for a notification +* 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);
builtin/fsmonitor--daemon.c static int do_as_client__status(void) · line 168
Quoted 10 lines
builtin/fsmonitor--daemon.c +10
-0+ +/* +* NEEDSWORK: 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. +*/ +while (cookie->result ==FCIR_INIT ) +pthread_cond_wait (&state->cookies_cond, + &state->main_lock);
It would probably make sense to do this at some stage, but since we have code that has seen quite a bit of real-world testing, I am in favor of postponing this change to a later date.
builtin/fsmonitor--daemon.c done: · line 1294
Quoted 12 lines
builtin/fsmonitor--daemon.c +5
-0@@ -1063,6 +1284,11 @@ done:strbuf_release (&state.path_worktree_watch);strbuf_release (&state.path_gitdir_watch); +strbuf_release (&state.path_cookie_prefix); + +/* +* NEEDSWORK: Consider "rm -rf <gitdir>/<fsmonitor-dir>" +*/ return err; }
In this instance, I think we can just drop the NEEDSWORK: it makes sense to keep around these directories rather than destroying and re-creating them over and over again.
Ciao,
Dscho
PATCH v5 28/30 fsmonitor: force update index after large responses
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Measure the time taken to apply the FSMonitor query result to the index and the untracked-cache.
Set the FSMONITOR_CHANGED bit on istate->cache_changed when FSMonitor returns a very large repsonse to ensure that the index is written to disk.
Normally, when the FSMonitor response includes a tracked file, the index is always updated. Similarly, the index might be updated when the response alters the untracked-cache (when enabled). However, in cases where neither of those cause the index to be considered changed, the FSMonitor response is wasted. Subsequent Git commands will make requests with the same token and receive the same response.
If that response is very large, performance may suffer. It would be more efficient to force update the index now (and the token in the index extension) in order to reduce the size of the response received by future commands.
This was observed on Windows after a large checkout. On Windows, the kernel emits events for the files that are changed as they are changed. However, it might delay events for the containing directories until the system is more idle (or someone scans the directory (so it seems)). The first status following a checkout would get the list of files. The subsequent status commands would get the list of directories as the events trickled out. But they would never catch up because the token was not advanced because the index wasn't updated.
This list of directories caused wt_status_collect_untracked() to unnecessarily spend time actually scanning them during each command.
--- fsmonitor.c | 57 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 56 insertions(+), 1 deletion(-)
fsmonitor.c +56 -1
@@ -219,6 +219,45 @@ static void fsmonitor_refresh_callback(struct index_state *istate, char *name)untracked_cache_invalidate_path (istate, name,0 ); } +/* +* The number of pathnames that we need to receive from FSMonitor +* before we force the index to be updated. +* +* Note that any pathname within the set of received paths MAY cause +* cache-entry or istate flag bits to be updated and thus cause the +* index to be updated on disk. +* +* However, the response may contain many paths (such as ignored +* paths) that will not update any flag bits. And thus not force the +* index to be updated. (This is fine and normal.) It also means +* that the token will not be updated in the FSMonitor index +* extension. So the next Git command will find the same token in the +* index, make the same token-relative request, and receive the same +* response (plus any newly changed paths). If this response is large +* (and continues to grow), performance could be impacted. +* +* For example, if the user runs a build and it writes 100K object +* files but doesn't modify any source files, the index would not need +* to be updated. The FSMonitor response (after the build and +* relative to a pre-build token) might be 5MB. Each subsequent Git +* command will receive that same 100K/5MB response until something +* causes the index to be updated. And `refresh_fsmonitor()` will +* have to iterate over those 100K paths each time. +* +* Performance could be improved if we optionally force update the +* index after a very large response and get an updated token into +* the FSMonitor index extension. This should allow subsequent +* commands to get smaller and more current responses. +* +* The value chosen here does not need to be precise. The index +* will be updated automatically the first time the user touches +* a tracked file and causes a command like `git status` to +* update an mtime to be updated and/or set a flag bit. +* +* NEEDSWORK: Does this need to be a config value? +*/ +static int fsmonitor_force_update_threshold =100 ; +void refresh_fsmonitor (struct index_state *istate) {struct strbuf query_result =STRBUF_INIT ;
@@ -362,25 +401,39 @@ apply_results:* information and that we should consider everything * invalid. We call this a trivial response. */ +trace2_region_enter ("fsmonitor" ,"apply_results" , istate->repo); +if (query_success && !is_trivial) {/* * Mark all pathnames returned by the monitor as dirty. * * This updates both the cache-entries and the untracked-cache. */ +int count =0 ; + buf = query_result.buf;for (i = bol; i < query_result.len; i++) {if (buf[i] !='\0' )continue ;fsmonitor_refresh_callback (istate, buf + bol); bol = i +1 ; + count++; } -if (bol < query_result.len) +if (bol < query_result.len) {fsmonitor_refresh_callback (istate, buf + bol); + count++; + }/* Now mark the untracked cache for fsmonitor usage */ if (istate->untracked) istate->untracked->use_fsmonitor =1 ; + +if (count > fsmonitor_force_update_threshold) + istate->cache_changed |=FSMONITOR_CHANGED ; + +trace2_data_intmax ("fsmonitor" , istate->repo,"apply_count" , + count); + }else {/* * We failed to get a response or received a trivial response,
@@ -409,6 +462,8 @@ apply_results:if (istate->untracked) istate->untracked->use_fsmonitor =0 ; } +trace2_region_leave ("fsmonitor" ,"apply_results" , istate->repo); +strbuf_release (&query_result);/* Now that we've updated istate, save the last_update_token */
Signature · 1 line
gitgitgadget
Re: [PATCH v5 28/30] fsmonitor: force update index after large responses
Hi Jeff,
fsmonitor.c static void fsmonitor_refresh_callback(struct index_state *istate, char *name) · line 263
Quoted 9 lines · Jeff Hostetler via GitGitGadget
[...]
+ *
+ * NEEDSWORK: Does this need to be a config value?
+ */fsmonitor.c +2
-0+static int fsmonitor_force_update_threshold =100 ; +void refresh_fsmonitor (struct index_state *istate) {struct strbuf query_result =STRBUF_INIT ;
We gave this ample opportunity to rise to a need, and no need arose, to introduce a config option.
Therefore I am in favor of simply dropping the comment.
Ciao,
Dscho
PATCH v5 29/30 t7527: test status with untracked-cache and fsmonitor--daemon
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create 2x2 test matrix with the untracked-cache and fsmonitor--daemon features and a series of edits and verify that status output is identical.
--- t/t7527-builtin-fsmonitor.sh | 93 ++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+)
t/t7527-builtin-fsmonitor.sh +93 -0
@@ -162,6 +162,8 @@ test_expect_success 'setup' ' .gitignore expect* actual* + flush* + trace* EOF git -c core.fsmonitor=false add . &&
@@ -508,4 +510,95 @@ test_expect_success 'cleanup worktrees' ' stop_daemon_delete_repo wt-base ' +# The next few tests perform arbitrary/contrived file operations and +# confirm that status is correct. That is, that the data (or lack of +# data) from fsmonitor doesn't cause incorrect results. And doesn't +# cause incorrect results when the untracked-cache is enabled. + +test_lazy_prereq UNTRACKED_CACHE ' + git update-index --test-untracked-cache +' + +test_expect_success 'Matrix: setup for untracked-cache,fsmonitor matrix' ' + test_unconfig core.fsmonitor && + git update-index --no-fsmonitor && + test_might_fail git fsmonitor--daemon stop +' + +matrix_clean_up_repo () { + git reset --hard HEAD && + git clean -fd +} + +matrix_try () { + uc=$1 + fsm=$2 + fn=$3 + + test_expect_success "Matrix[uc:$uc][fsm:$fsm] $fn" ' + matrix_clean_up_repo && + $fn && + if test $uc = false && test $fsm = false + then + git status --porcelain=v1 >.git/expect.$fn + else + git status --porcelain=v1 >.git/actual.$fn && + test_cmp .git/expect.$fn .git/actual.$fn + fi + ' +} + +uc_values="false" +test_have_prereq UNTRACKED_CACHE && uc_values="false true" +for uc_val in $uc_values +do + if test $uc_val = false + then + test_expect_success "Matrix[uc:$uc_val] disable untracked cache" ' + git config core.untrackedcache false && + git update-index --no-untracked-cache + ' + else + test_expect_success "Matrix[uc:$uc_val] enable untracked cache" ' + git config core.untrackedcache true && + git update-index --untracked-cache + ' + fi + + fsm_values="false true" + for fsm_val in $fsm_values + do + if test $fsm_val = false + then + test_expect_success "Matrix[uc:$uc_val][fsm:$fsm_val] disable fsmonitor" ' + test_unconfig core.fsmonitor && + git update-index --no-fsmonitor && + test_might_fail git fsmonitor--daemon stop + ' + else + test_expect_success "Matrix[uc:$uc_val][fsm:$fsm_val] enable fsmonitor" ' + git config core.fsmonitor true && + git fsmonitor--daemon start && + git update-index --fsmonitor + ' + fi + + matrix_try $uc_val $fsm_val edit_files + matrix_try $uc_val $fsm_val delete_files + matrix_try $uc_val $fsm_val create_files + matrix_try $uc_val $fsm_val rename_files + matrix_try $uc_val $fsm_val file_to_directory + matrix_try $uc_val $fsm_val directory_to_file + + if test $fsm_val = true + then + test_expect_success "Matrix[uc:$uc_val][fsm:$fsm_val] disable fsmonitor at end" ' + test_unconfig core.fsmonitor && + git update-index --no-fsmonitor && + test_might_fail git fsmonitor--daemon stop + ' + fi + done +done + test_done
Signature · 1 line
gitgitgadget
PATCH v5 30/30 update-index: convert fsmonitor warnings to advise
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
--- builtin/update-index.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-)
builtin/update-index.c +6 -6
@@ -1227,18 +1227,18 @@ 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);if (fsm_mode ==FSMONITOR_MODE_DISABLED ) { -warning (_ ("core.fsmonitor is unset; " -"set it if you really want to " -"enable fsmonitor" )); +advise (_ ("core.fsmonitor is unset; " +"set it if you really want to " +"enable fsmonitor" )); }add_fsmonitor (&the_index);report (_ ("fsmonitor enabled" )); }else if (!fsmonitor) {enum fsmonitor_mode fsm_mode =fsm_settings__get_mode (r);if (fsm_mode >FSMONITOR_MODE_DISABLED ) -warning (_ ("core.fsmonitor is set; " -"remove it if you really want to " -"disable fsmonitor" )); +advise (_ ("core.fsmonitor is set; " +"remove it if you really want to " +"disable fsmonitor" ));remove_fsmonitor (&the_index);report (_ ("fsmonitor disabled" )); }
Signature · 1 line
gitgitgadget
Hi Jeff,
Quoted 5 lines · Jeff Hostetler via GitGitGadget
Here is V5 of Part 2 of my Builtin FSMonitor series. I apologize for the delay since V4 that I submitted back in October. (Insert the usual $DayJob excuse...)
I have rebased this branch onto the current "master" branch.
Thank you for your tireless work on this. I do see that it requires a ton of effort on your part and just wanted to let you know that I appreciate it very much!
Quoted 2 lines
In this version I removed the core.useBuiltinFSMonitor config setting and instead extended the existing core.fsmonitor.
I am somewhat surprised that a reviewer suggested this, as it breaks the common paradigm we use to allow using several Git versions on the same worktree.
Imagine, for example, that you run a Git version that understands core.fsmonitor=true to imply the built-in FSMonitor, while you _also_ use an IDE that bundles a slightly older Git version that mistakes the true for meaning the executable true (which is not a FSMonitor at all, but its exit code suggests that everything's fine and dandy). The result would be that the IDE does not see _any_ updates anymore, but nothing would suggest that anything is wrong.
We can probably warn users about this, and we can also work around the fact that Git for Windows already uses core.useBuiltinFSMonitor, but it makes me somewhat uneasy nevertheless.
Thank you,
Dscho
Quoted 6 lines · Johannes Schindelin
Quoted 2 lines
In this version I removed the core.useBuiltinFSMonitor config setting and instead extended the existing core.fsmonitor.
I am somewhat surprised that a reviewer suggested this, as it breaks the common paradigm we use to allow using several Git versions on the same worktree.
I do not think sharing the same repository with different versions of Git was considered as a possible source of problems during the review discussion.
https://lore.kernel.org/git/[email protected]/
I am not saying that we should not consider it; I am just stating the fact that there was nobody who raised as a potential issue during the discussion that lead to the cited message.
Hi Junio,
Quoted 13 lines · Junio C Hamano
Quoted 6 lines · Johannes Schindelin
Quoted 2 lines
In this version I removed the core.useBuiltinFSMonitor config setting and instead extended the existing core.fsmonitor.
I am somewhat surprised that a reviewer suggested this, as it breaks the common paradigm we use to allow using several Git versions on the same worktree.
I do not think sharing the same repository with different versions of Git was considered as a possible source of problems during the review discussion.
Quoted 5 lines
https://lore.kernel.org/git/[email protected]/
I am not saying that we should not consider it; I am just stating the fact that there was nobody who raised as a potential issue during the discussion that lead to the cited message.
Just to make sure: I did not intend to insult anyone (and in hindsight I wish that I had made that clearer).
Ciao,
Dscho
Quoted 2 lines · Johannes Schindelin
Just to make sure: I did not intend to insult anyone (and in hindsight I wish that I had made that clearer).
It is OK that you are wiser in hindsight. We all are, and we try to do better the next time ;-)
Thanks for reminding of the topic.
As a general principle, when introducing a new feature that achieves the same goal in a new and improved way, it is safer to introduce it in such a way that users of older implementations that lack the feature cannot choose it by mistake.
One way to do so is to we reuse the same configuration knob by adding a new settings value that older implementations would choke on, the users will be forced to ensure that the older and proven way is used consistently everywhere, until every tool the user uses are ready.
For this instance, I think it is OK to split and allow two to operate on the same data at the same time, because I believe that both old and new implementation will leave a permanent difference to the on-disk data that cannot later be reused by the other [*].
But it is an exception than a norm when adding a new thing that extends an existing feature (as opposed to inventing totally a new thing that won't overlap with any existing one). As a general principle, it is much safer to make sure it breaks (and have users hold off) when the new setting is given to an old implementation.
* Side note. For example, if we introduce the index-v5 feature by not reusing the index.version but with index.usev5 variable, new implementations that know about the knob would write out v5 data that existing implementations will not work with.
Also, from the point of view of the longer-term maintenance, of course not having to deal with orthogonal looking different configuration variables where newer ones override the older ones will induce more pain over time.
Quoted 29 lines · Johannes Schindelin
Hi Jeff,
Quoted 5 lines · Jeff Hostetler via GitGitGadget
Here is V5 of Part 2 of my Builtin FSMonitor series. I apologize for the delay since V4 that I submitted back in October. (Insert the usual $DayJob excuse...)
I have rebased this branch onto the current "master" branch.
Thank you for your tireless work on this. I do see that it requires a ton of effort on your part and just wanted to let you know that I appreciate it very much!
Quoted 2 lines
In this version I removed the core.useBuiltinFSMonitor config setting and instead extended the existing core.fsmonitor.
I am somewhat surprised that a reviewer suggested this, as it breaks the common paradigm we use to allow using several Git versions on the same worktree.
Imagine, for example, that you run a Git version that understands
core.fsmonitor=trueto imply the built-in FSMonitor, while you _also_ use an IDE that bundles a slightly older Git version that mistakes thetruefor meaning the executabletrue(which is not a FSMonitor at all, but its exit code suggests that everything's fine and dandy). The result would be that the IDE does not see _any_ updates anymore, but nothing would suggest that anything is wrong.
Quoted 7 lines
We can probably warn users about this, and we can also work around the fact that Git for Windows already uses
core.useBuiltinFSMonitor, but it makes me somewhat uneasy nevertheless.Thank you,
Dscho
This is a valid concern and I should have thought to mention it when the suggestion came up on the list. Yes, extending core.fsmonitor to take a boolean or a path could confuse older clients (like ones bundled with an IDE, like VS).
My assumption was that since we shipped core.useBuiltinFSMonitor in GFW with an experimental label, that normal users would not be using it at all and especially not from their IDEs, so it wouldn't matter. And experimental features are just that -- experimental and subject to change.
But your point is valid -- if someone does have the odd hook called "true" or "1", they'll get an unexpected result.
Jeff
Hi Jeff,
Quoted 28 lines · Jeff Hostetler
Quoted 17 lines · Johannes Schindelin
Quoted 2 lines · Jeff Hostetler via GitGitGadget
In this version I removed the core.useBuiltinFSMonitor config setting and instead extended the existing core.fsmonitor.
I am somewhat surprised that a reviewer suggested this, as it breaks the common paradigm we use to allow using several Git versions on the same worktree.
Imagine, for example, that you run a Git version that understands
core.fsmonitor=trueto imply the built-in FSMonitor, while you _also_ use an IDE that bundles a slightly older Git version that mistakes thetruefor meaning the executabletrue(which is not a FSMonitor at all, but its exit code suggests that everything's fine and dandy). The result would be that the IDE does not see _any_ updates anymore, but nothing would suggest that anything is wrong.Quoted 3 lines
We can probably warn users about this, and we can also work around the fact that Git for Windows already uses
core.useBuiltinFSMonitor, but it makes me somewhat uneasy nevertheless.This is a valid concern and I should have thought to mention it when the suggestion came up on the list. Yes, extending
core.fsmonitorto take a boolean or a path could confuse older clients (like ones bundled with an IDE, like VS).
Quoted 8 lines
My assumption was that since we shipped
core.useBuiltinFSMonitorin GFW with an experimental label, that normal users would not be using it at all and especially not from their IDEs, so it wouldn't matter. And experimental features are just that -- experimental and subject to change.But your point is valid -- if someone does have the odd hook called "true" or "1", they'll get an unexpected result.
I wondered about that for a while, and put that to a test last night. I set core.fsmonitor = true and then modified a file and ran git status. Something I did not expect happened: it picked up on the modified file!
It also printed out a warning:
warning: Empty last update token.
This is the reason why it works: by default, current Git versions assume that the FSMonitor hook understands the FSMonitor protocol v2, which starts by the client sending out a token, receiving a new token and then the paths of the files/directories/symlinks to inspect. Since the program true does _not_ write that token, Git warns that it did not receive a token and continues as if no FSMonitor had been configured.
So that's good news!
The less good news is that prior to v2.26.0, Git did not support v2 of the FSMonitor protocol, but only v1. And v1 does not expect such a token. Git versions between v2.16.0 and v2.26.0 will interpret a successful run of the true executable with an empty output to mean that no files have been modified.
And indeed, in my tests, after making sure that the Git index had been refreshed explicitly and then modifying a file and then running git status with v2.16.0, Git did not pick up on the modification.
That's the less good news.
At first I thought that we're pretty safe because nobody should use older Git versions and enable FSMonitor because FSMonitor protocol v1 is known to be subject to racy behavior. But then, Git users sometimes do not completely control which Git versions they use. Take for example Visual Studio users who also use the Git Bash to work on their worktree. While their Git Bash might be reasonably recent, Visual Studio comes with its own embedded Git version. Therefore, a user might want to play with the built-in FSMonitor in Git Bash, find that it dramatically speeds up everything (as it does for me, thank you so much!), and not realize that the Git executable used by Visual Studio totally misinterprets core.fsmonitor to refer to /usr/bin/true.exe and then miss any modifications.
As long as the embedded Git version is at least v2.26.0, Visual Studio will at least work correctly (because it ignores true.exe's output and continue as if no FSMonitor had been configured). But as soon as an older version is used, Git would work incorrectly, without any indication what is going wrong.
I tried to come up with alternatives (because I _really_ dislike being a reviewer who only points out what's wrong without any constructive suggestion how to do it better), and the best alternatives I came up were:
- stick with core.useBuiltinFSMonitor as before, or
- use a special value of core.fsmonitor that simply is not a valid executable name. In 2019, when I worked on the original precursor of the built-in FSMonitor (before I had to drop working on FSMonitor because of all the security work that went into v2.24.1), I had picked :builtin: because colons are illegal on Windows, but of _course_ they are legal everywhere else. But one thing is not possible, even on Linux:
to have a trailing slash in an executable name. So something like /builtin-fsmonitor/ would work.
However, after seeing how nicely your latest iteration cleans up the code by simply interpreting a Boolean value to refer to the built-in FSMonitor, I _really_ would like to make it work.
Maybe we can declare that it is "safe enough" to rely on new enough Git versions to be used by users who use multiple Git versions on the same worktree? They should use _at least_ v2.26.1 anyway, because that one fixed a rather important vulnerability (CVE-2020-5260)? At least for Visual Studio, this is true: it ships with Git version 2.33.0.windows.2.
What do you think? Can we somehow make core.fsmonitor = true work?
Ciao,
Dscho
Quoted 114 lines · Johannes Schindelin
Hi Jeff,
Quoted 28 lines · Jeff Hostetler
Quoted 17 lines · Johannes Schindelin
Quoted 2 lines · Jeff Hostetler via GitGitGadget
In this version I removed the core.useBuiltinFSMonitor config setting and instead extended the existing core.fsmonitor.
I am somewhat surprised that a reviewer suggested this, as it breaks the common paradigm we use to allow using several Git versions on the same worktree.
Imagine, for example, that you run a Git version that understands
core.fsmonitor=trueto imply the built-in FSMonitor, while you _also_ use an IDE that bundles a slightly older Git version that mistakes thetruefor meaning the executabletrue(which is not a FSMonitor at all, but its exit code suggests that everything's fine and dandy). The result would be that the IDE does not see _any_ updates anymore, but nothing would suggest that anything is wrong.Quoted 3 lines
We can probably warn users about this, and we can also work around the fact that Git for Windows already uses
core.useBuiltinFSMonitor, but it makes me somewhat uneasy nevertheless.This is a valid concern and I should have thought to mention it when the suggestion came up on the list. Yes, extending
core.fsmonitorto take a boolean or a path could confuse older clients (like ones bundled with an IDE, like VS).Quoted 8 lines
My assumption was that since we shipped
core.useBuiltinFSMonitorin GFW with an experimental label, that normal users would not be using it at all and especially not from their IDEs, so it wouldn't matter. And experimental features are just that -- experimental and subject to change.But your point is valid -- if someone does have the odd hook called "true" or "1", they'll get an unexpected result.
I wondered about that for a while, and put that to a test last night. I set
core.fsmonitor = trueand then modified a file and rangit status. Something I did not expect happened: it picked up on the modified file!It also printed out a warning:
warning: Empty last update token.
This is the reason why it works: by default, current Git versions assume that the FSMonitor hook understands the FSMonitor protocol v2, which starts by the client sending out a token, receiving a new token and then the paths of the files/directories/symlinks to inspect. Since the program
truedoes _not_ write that token, Git warns that it did not receive a token and continues as if no FSMonitor had been configured.So that's good news!
The less good news is that prior to v2.26.0, Git did not support v2 of the FSMonitor protocol, but only v1. And v1 does not expect such a token. Git versions between v2.16.0 and v2.26.0 will interpret a successful run of the
trueexecutable with an empty output to mean that no files have been modified.And indeed, in my tests, after making sure that the Git index had been refreshed explicitly and then modifying a file and then running
git statuswith v2.16.0, Git did not pick up on the modification.That's the less good news.
At first I thought that we're pretty safe because nobody should use older Git versions and enable FSMonitor because FSMonitor protocol v1 is known to be subject to racy behavior. But then, Git users sometimes do not completely control which Git versions they use. Take for example Visual Studio users who also use the Git Bash to work on their worktree. While their Git Bash might be reasonably recent, Visual Studio comes with its own embedded Git version. Therefore, a user might want to play with the built-in FSMonitor in Git Bash, find that it dramatically speeds up everything (as it does for me, thank you so much!), and not realize that the Git executable used by Visual Studio totally misinterprets
core.fsmonitorto refer to/usr/bin/true.exeand then miss any modifications.As long as the embedded Git version is at least v2.26.0, Visual Studio will at least work correctly (because it ignores
true.exe's output and continue as if no FSMonitor had been configured). But as soon as an older version is used, Git would work incorrectly, without any indication what is going wrong.I tried to come up with alternatives (because I _really_ dislike being a reviewer who only points out what's wrong without any constructive suggestion how to do it better), and the best alternatives I came up were:
- stick with
core.useBuiltinFSMonitoras before, or- use a special value of
core.fsmonitorthat simply is not a valid executable name. In 2019, when I worked on the original precursor of the built-in FSMonitor (before I had to drop working on FSMonitor because of all the security work that went into v2.24.1), I had picked:builtin:because colons are illegal on Windows, but of _course_ they are legal everywhere else. But one thing is not possible, even on Linux:
to have a trailing slash in an executable name. So something like/builtin-fsmonitor/would work.However, after seeing how nicely your latest iteration cleans up the code by simply interpreting a Boolean value to refer to the built-in FSMonitor, I _really_ would like to make it work.
Maybe we can declare that it is "safe enough" to rely on new enough Git versions to be used by users who use multiple Git versions on the same worktree? They should use
_atleast_v2.26.1 anyway, because that one fixed a rather important vulnerability (CVE-2020-5260)? At least for Visual Studio, this is true: it ships with Git version 2.33.0.windows.2.
Quoted 5 lines
What do you think? Can we somehow make
core.fsmonitor = truework?Ciao,
Dscho
Thanks for the testing and background information here!
I agree. I would like to keep the current
"core.fsmonitor = <bool> | <path>"
usage that I have in V5.
It cleaned up things very nicely and it got rid of the somewhat awkward usage of having "core.useBuiltinFSMonitor" override the existing "core.fsmonitor" setting.
It is unfortunate that it might cause a breakage for users who are *also* running a Git version between 2.16 ... 2.26. I have to wonder if it wouldn't be better to spend our energy documenting that users should upgrade, rather than trying to support interop with them.
The 2.16 ... 2.26 versions don't have the security fix referenced above.
The 2.16 ... 2.26 versions are based upon the V1 FSMonitor protocol and don't have fixes for several serious bugs or races in the original implementation:
* 398a3b0899 (fsmonitor: force a refresh after the index was discarded, 2019-05-07)
* 679f2f9fdd (unpack-trees: skip stat on fsmonitor-valid files, 2019-11-20)
* dfaed02862 (fsmonitor: update documentation for hook version and watchman hooks, 2020-01-23)
That last fix added the V2 FSMonitor protocol. This is used by both the hook- and the IPC-based providers.
So anything still using the V1 FSMonitor protocol is going to unreliable
and racy and users should not use it, so I don't think it is worth the effort to complicate our current solution to maintain compatibility. (I hate to say that, but they just shouldn't be using V1 any more.)
On a slight tangent, the current code (before my patch series) does support a "core.fsmonitorhookversion" to allow the client to talk to a V1 or V2 provider explicitly (vs the default of trying V2 and then trying V1). The IPC implementation does not use this config setting, but I could see adding something to emit a warning if it was set to 1 when using the builtin FSMonitor. This might help users who are *also* running a Git version between 2.26 and 2.35 to understand the fallback after the true.exe warning that Johannes described.
On another slight tangent, I'm wondering if we want to officially deprecate the V1 hook code and/or remove support for it from the code.
Jeff
Hi Jeff,
Quoted 40 lines · Jeff Hostetler
Quoted 25 lines · Johannes Schindelin
Quoted 10 lines · Jeff Hostetler
Quoted 8 lines · Johannes Schindelin
Quoted 2 lines · Jeff Hostetler via GitGitGadget
In this version I removed the core.useBuiltinFSMonitor config setting and instead extended the existing core.fsmonitor.
I am somewhat surprised that a reviewer suggested this, as it breaks the common paradigm we use to allow using several Git versions on the same worktree.
[...]
I wondered about that for a while, and put that to a test last night. I set
core.fsmonitor = trueand then modified a file and rangit status. Something I did not expect happened: it picked up on the modified file![... describing how FSMonitor protocol v1 is affected...]
However, after seeing how nicely your latest iteration cleans up the code by simply interpreting a Boolean value to refer to the built-in FSMonitor, I _really_ would like to make it work.
Quoted 8 lines
Maybe we can declare that it is "safe enough" to rely on new enough Git versions to be used by users who use multiple Git versions on the same worktree? They should use
_atleast_v2.26.1 anyway, because that one fixed a rather important vulnerability (CVE-2020-5260)? At least for Visual Studio, this is true: it ships with Git version 2.33.0.windows.2.What do you think? Can we somehow make
core.fsmonitor = truework?[...]
I agree. I would like to keep the current
Quoted 7 lines
"core.fsmonitor = <bool> | <path>"usage that I have in V5.
It cleaned up things very nicely and it got rid of the somewhat awkward usage of having "core.useBuiltinFSMonitor" override the existing "core.fsmonitor" setting.
Yes!
Quoted 4 lines
It is unfortunate that it might cause a breakage for users who are *also* running a Git version between 2.16 ... 2.26. I have to wonder if it wouldn't be better to spend our energy documenting that users should upgrade, rather than trying to support interop with them.
That's a good point: I guess if you added a comment to the documentation of core.fsmonitor = true, that should be good enough.
Quoted 6 lines
[...]
So anything still using the V1 FSMonitor protocol is going to unreliable and racy and users should not use it, so I don't think it is worth the effort to complicate our current solution to maintain compatibility.
(I hate to say that, but they just shouldn't be using V1 any more.)
That's a really good point.
Quoted 8 lines
On a slight tangent, the current code (before my patch series) does support a "core.fsmonitorhookversion" to allow the client to talk to a V1 or V2 provider explicitly (vs the default of trying V2 and then trying V1). The IPC implementation does not use this config setting, but I could see adding something to emit a warning if it was set to 1 when using the builtin FSMonitor. This might help users who are *also* running a Git version between 2.26 and 2.35 to understand the fallback after the true.exe warning that Johannes described.
How about making it an error instead? That should really be helpful: if core.fsmonitor = true and core.fsmonitorHookVersion = 1, just error out. That way, users will more likely fall into the pit of success.
Quoted 2 lines
On another slight tangent, I'm wondering if we want to officially deprecate the V1 hook code and/or remove support for it from the code.
Oooh! That's _also_ a good point.
Maybe we can keep this deprecation out of this here patch series, though. It would be good to get this finished and into next, I think.
I spent some time (in two separate thrusts) to review the entire patch series, and hope that my feedback was useful to you.
In particular with your idea to document the incompatibilities of core.fsmonitor = true with Git v2.16.0..v2.26.0, I am really eager to see (the next iteration of) this patch series advance to the next branch, and then into an official Git version so that more users can benefit from it.
Thank you for all your work on this,
Dscho
v6 cover Builtin FSMonitor Part 2
In reply to Jeff Hostetler via GitGitGadgetHere is V6 of Part 2 of my builtin FSmonitor series.
This version contains mostly cleanup based on feedback from V5. Of note:
* I squashed in the 1_file fix for p7519.
* I squashed in a commit from part 3 to optionally print the "running daemon on..." message on stderr.
* I added a note to the documentation about incompatible changes around core.fsmonitor.
* Removed/rephrased some obsolete NEEDSWORK items.
Tao has an ongoing parallel series to fix test-chmtime on Windows.
https://lore.kernel.org/all/[email protected]/
If that lands first, we should be able to drop my 't/helper/test-chmtime:
skip directories on Windows' commit.
A followup Part 3 will contain additional refinements to the daemon and additional tests. I drew the line here between Part 2 and 3 to make it easier to review.
Jeff Hostetler (30):
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
update-index: convert fsmonitor warnings to advise
.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 | 1473 +++++++++++++++++++++++ builtin/update-index.c | 19 +- cache.h | 1 - compat/fsmonitor/fsm-listen-darwin.c | 499 ++++++++ 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 | 115 ++ 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 | 121 ++ t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + t/perf/p7519-fsmonitor.sh | 61 +- t/perf/perf-lib.sh | 2 +- t/t7527-builtin-fsmonitor.sh | 604 ++++++++++ t/test-lib.sh | 6 + 37 files changed, 4301 insertions(+), 110 deletions(-) create mode 100644 Documentation/git-fsmonitor--daemon.txt create mode 100644 builtin/fsmonitor--daemon.c 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-v6
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1041/jeffhostetler/builtin-fsmonitor-part2-v6
Pull-Request: https://github.com/gitgitgadget/git/pull/1041
Range-diff vs v5:
1: a5ecabb4d02 ! 1: 10a34082fcc fsmonitor: enhance existing comments, clarify trivial response handling
@@ Commit message
fsmonitor: enhance existing comments, clarify trivial response handling
+ Signed-off-by: Junio C Hamano <[email protected]>
## fsmonitor.c ##
@@ fsmonitor.c: static int query_fsmonitor(int version, const char *last_update, struct strbuf *
2: 365964b7664 ! 2: e1c946af2c5 fsmonitor-ipc: create client routines for git-fsmonitor--daemon
@@ Commit message
Stub in empty fsmonitor_ipc__*() functions for unsupported platforms.
Signed-off-by: Jeff Hostetler <[email protected]>
+ Signed-off-by: Junio C Hamano <[email protected]>
## Makefile ##
@@ Makefile: LIB_OBJS += fetch-pack.o
@@ fsmonitor-ipc.c (new)
+#include "strbuf.h"
+#include "trace2.h"
+
-+#ifdef HAVE_FSMONITOR_DAEMON_BACKEND
++#ifndef HAVE_FSMONITOR_DAEMON_BACKEND
++
++/*
++ * A trivial implementation of the fsmonitor_ipc__ API for unsupported
++ * platforms.
++ */
++
++int fsmonitor_ipc__is_supported(void)
++{
++ return 0;
++}
++
++const char *fsmonitor_ipc__get_path(void)
++{
++ return NULL;
++}
++
++enum ipc_active_state fsmonitor_ipc__get_state(void)
++{
++ return IPC_STATE__OTHER_ERROR;
++}
++
++int fsmonitor_ipc__send_query(const char *since_token,
++ struct strbuf *answer)
++{
++ return -1;
++}
++
++int fsmonitor_ipc__send_command(const char *command,
++ struct strbuf *answer)
++{
++ return -1;
++}
++
++#else
+
+int fsmonitor_ipc__is_supported(void)
+{
@@ fsmonitor-ipc.c (new)
+ return 0;
+}
+
-+#else
-+
-+/*
-+ * A trivial implementation of the fsmonitor_ipc__ API for unsupported
-+ * platforms.
-+ */
-+
-+int fsmonitor_ipc__is_supported(void)
-+{
-+ return 0;
-+}
-+
-+const char *fsmonitor_ipc__get_path(void)
-+{
-+ return NULL;
-+}
-+
-+enum ipc_active_state fsmonitor_ipc__get_state(void)
-+{
-+ return IPC_STATE__OTHER_ERROR;
-+}
-+
-+int fsmonitor_ipc__send_query(const char *since_token,
-+ struct strbuf *answer)
-+{
-+ return -1;
-+}
-+
-+int fsmonitor_ipc__send_command(const char *command,
-+ struct strbuf *answer)
-+{
-+ return -1;
-+}
-+
+#endif
## fsmonitor-ipc.h (new) ##
3: 384516ce1a1 ! 3: ae622a517cf fsmonitor: config settings are repository-specific
@@ Commit message
restrictions to override the config values used.
Signed-off-by: Jeff Hostetler <[email protected]>
+ Signed-off-by: Junio C Hamano <[email protected]>
## Makefile ##
@@ Makefile: LIB_OBJS += fmt-merge-msg.o
@@ fsmonitor-settings.c (new)
+
+enum fsmonitor_mode fsm_settings__get_mode(struct repository *r)
+{
++ if (!r)
++ r = the_repository;
++
+ lookup_fsmonitor_settings(r);
+
+ return r->settings.fsmonitor->mode;
@@ fsmonitor-settings.c (new)
+
+const char *fsm_settings__get_hook_path(struct repository *r)
+{
++ if (!r)
++ r = the_repository;
++
+ lookup_fsmonitor_settings(r);
+
+ return r->settings.fsmonitor->hook_path;
@@ fsmonitor-settings.c (new)
+
+void fsm_settings__set_ipc(struct repository *r)
+{
++ if (!r)
++ r = the_repository;
++
+ lookup_fsmonitor_settings(r);
+
+ r->settings.fsmonitor->mode = FSMONITOR_MODE_IPC;
@@ fsmonitor-settings.c (new)
+
+void fsm_settings__set_hook(struct repository *r, const char *path)
+{
++ if (!r)
++ r = the_repository;
++
+ lookup_fsmonitor_settings(r);
+
+ r->settings.fsmonitor->mode = FSMONITOR_MODE_HOOK;
@@ fsmonitor-settings.c (new)
+
+void fsm_settings__set_disabled(struct repository *r)
+{
++ if (!r)
++ r = the_repository;
++
+ lookup_fsmonitor_settings(r);
+
+ r->settings.fsmonitor->mode = FSMONITOR_MODE_DISABLED;
@@ fsmonitor.c: void remove_fsmonitor(struct index_state *istate)
{
unsigned int i;
- int fsmonitor_enabled = git_config_get_fsmonitor();
-+ struct repository *r = istate->repo ? istate->repo : the_repository;
-+ int fsmonitor_enabled = (fsm_settings__get_mode(r) > FSMONITOR_MODE_DISABLED);
++ int fsmonitor_enabled = (fsm_settings__get_mode(istate->repo)
++ > FSMONITOR_MODE_DISABLED);
if (istate->fsmonitor_dirty) {
if (fsmonitor_enabled) {
@@ fsmonitor.h: int fsmonitor_is_trivial_response(const struct strbuf *query_result
static inline int is_fsmonitor_refreshed(const struct index_state *istate)
{
- return !core_fsmonitor || istate->fsmonitor_has_run_once;
-+ struct repository *r = istate->repo ? istate->repo : the_repository;
-+ enum fsmonitor_mode fsm_mode = fsm_settings__get_mode(r);
++ enum fsmonitor_mode fsm_mode = fsm_settings__get_mode(istate->repo);
+
+ return fsm_mode <= FSMONITOR_MODE_DISABLED ||
+ istate->fsmonitor_has_run_once;
@@ fsmonitor.h: static inline int is_fsmonitor_refreshed(const struct index_state *
static inline void mark_fsmonitor_valid(struct index_state *istate, struct cache_entry *ce)
{
- if (core_fsmonitor && !(ce->ce_flags & CE_FSMONITOR_VALID)) {
-+ struct repository *r = istate->repo ? istate->repo : the_repository;
-+ enum fsmonitor_mode fsm_mode = fsm_settings__get_mode(r);
++ enum fsmonitor_mode fsm_mode = fsm_settings__get_mode(istate->repo);
+
+ if (fsm_mode > FSMONITOR_MODE_DISABLED &&
+ !(ce->ce_flags & CE_FSMONITOR_VALID)) {
@@ fsmonitor.h: static inline void mark_fsmonitor_valid(struct index_state *istate,
static inline void mark_fsmonitor_invalid(struct index_state *istate, struct cache_entry *ce)
{
- if (core_fsmonitor) {
-+ struct repository *r = istate->repo ? istate->repo : the_repository;
-+ enum fsmonitor_mode fsm_mode = fsm_settings__get_mode(r);
++ enum fsmonitor_mode fsm_mode = fsm_settings__get_mode(istate->repo);
+
+ if (fsm_mode > FSMONITOR_MODE_DISABLED) {
ce->ce_flags &= ~CE_FSMONITOR_VALID; 4: 8e738a83bc5 ! 4: 55974867da5 fsmonitor: use IPC to query the builtin FSMonitor daemon
@@ Commit message
+ Signed-off-by: Junio C Hamano <[email protected]>
## fsmonitor.c ##
@@ fsmonitor.c: void refresh_fsmonitor(struct index_state *istate)
5: 49e4c146e02 ! 5: ce42d5bbaf6 fsmonitor: document builtin fsmonitor
@@ Commit message
Create git-fsmonitor--daemon manual page and describe its features.
+ Signed-off-by: Junio C Hamano <[email protected]>
## Documentation/config/core.txt ##
@@ Documentation/config/core.txt: core.protectNTFS::
@@ Documentation/config/core.txt: core.protectNTFS::
+git by avoiding unnecessary scanning of files that have not changed. ++
+See the "fsmonitor-watchman" section of linkgit:githooks[5].
+++
++Note that if you concurrently use multiple versions of Git, such ++as one version on the command line and another version in an IDE ++tool, that the definition of core.fsmonitor was extended to ++allow boolean values in addition to hook pathnames. Git versions ++2.35.1 and prior will not understand the boolean values and will ++consider the "true" or "false" values as hook pathnames to be ++invoked. Git versions 2.26 thru 2.35.1 default to hook protocol ++V2 and will fall back to no fsmonitor (full scan). Git versions ++prior to 2.26 default to hook protocol V1 and will silently ++assume there were no changes to report (no scan), so status ++commands may report incomplete results. For this reason, it is ++best to upgrade all of your Git versions before using the built-in ++file system monitor.
core.fsmonitorHookVersion::
- Sets the version of hook that is to be used when calling fsmonitor.
6: bdd7334da31 ! 6: 9ce938c69b5 fsmonitor--daemon: add a built-in fsmonitor daemon
@@ Commit message
+ Signed-off-by: Junio C Hamano <[email protected]>
## .gitignore ##
@@
7: 9f00ada3dd3 ! 7: 3bd7e6a051e fsmonitor--daemon: implement 'stop' and 'status' commands
@@ Commit message
system.
+ Signed-off-by: Junio C Hamano <[email protected]>
## builtin/fsmonitor--daemon.c ##
@@
8: d6819bdad66 ! 8: 92d3e54da7f compat/fsmonitor/fsm-listen-win32: stub in backend for Windows
@@ Commit message
Stub in empty filesystem listener backend for fsmonitor--daemon on Windows.
+ Signed-off-by: Junio C Hamano <[email protected]>
## Makefile ##
@@ Makefile: all::
9: 7de3d01cccc ! 9: f1219221270 compat/fsmonitor/fsm-listen-darwin: stub in backend for Darwin
@@ Commit message
backend for Darwin (aka MacOS).
+ Signed-off-by: Junio C Hamano <[email protected]>
## compat/fsmonitor/fsm-listen-darwin.c (new) ##
@@
10: 6fe5a2bc79e ! 10: a3f94627cf6 fsmonitor--daemon: implement 'run' command
@@ Commit message
backends are still just stubs.
Signed-off-by: Jeff Hostetler <[email protected]>
+ Signed-off-by: Junio C Hamano <[email protected]>
## builtin/fsmonitor--daemon.c ##
@@
@@ builtin/fsmonitor--daemon.c
+#define FSMONITOR__IPC_THREADS "fsmonitor.ipcthreads"
+static int fsmonitor__ipc_threads = 8;
+
++#define FSMONITOR__ANNOUNCE_STARTUP "fsmonitor.announcestartup"
++static int fsmonitor__announce_startup = 0;
++
+static int fsmonitor_config(const char *var, const char *value, void *cb)
+{
+ if (!strcmp(var, FSMONITOR__IPC_THREADS)) {
@@ builtin/fsmonitor--daemon.c
+ return 0;
+ }
+
++ if (!strcmp(var, FSMONITOR__ANNOUNCE_STARTUP)) {
++ int is_bool;
++ int i = git_config_bool_or_int(var, value, &is_bool);
++ if (i < 0)
++ return error(_("value of '%s' not bool or int: %d"),
++ var, i);
++ fsmonitor__announce_startup = i;
++ return 0;
++ }
++
+ return git_default_config(var, value, cb);
+}
+
@@ builtin/fsmonitor--daemon.c: static int do_as_client__status(void)
+ die("fsmonitor--daemon is already running '%s'",
+ the_repository->worktree);
+
-+ printf(_("running fsmonitor-daemon in '%s'\n"),
-+ the_repository->worktree);
-+ fflush(stdout);
++ if (fsmonitor__announce_startup) {
++ fprintf(stderr, _("running fsmonitor-daemon in '%s'\n"),
++ the_repository->worktree);
++ fflush(stderr);
++ }
+
+ return !!fsmonitor_run_daemon();
+}
11: 69fc0998286 ! 11: 8de40b0fe8b fsmonitor--daemon: implement 'start' command
@@ Commit message
because they don't have a Win32 console attached to them.)
Signed-off-by: Jeff Hostetler <[email protected]>
+ Signed-off-by: Junio C Hamano <[email protected]>
## builtin/fsmonitor--daemon.c ##
@@
@@ builtin/fsmonitor--daemon.c: static const char * const builtin_fsmonitor__daemon
+#define FSMONITOR__START_TIMEOUT "fsmonitor.starttimeout"
+static int fsmonitor__start_timeout_sec = 60;
+
- static int fsmonitor_config(const char *var, const char *value, void *cb)
- {
- if (!strcmp(var, FSMONITOR__IPC_THREADS)) {
+ #define FSMONITOR__ANNOUNCE_STARTUP "fsmonitor.announcestartup"
+ static int fsmonitor__announce_startup = 0;
+
@@ builtin/fsmonitor--daemon.c: static int fsmonitor_config(const char *var, const char *value, void *cb)
return 0;
}
@@ builtin/fsmonitor--daemon.c: static int fsmonitor_config(const char *var, const
+ return 0;
+ }
+
- return git_default_config(var, value, cb);
- }
-
+ if (!strcmp(var, FSMONITOR__ANNOUNCE_STARTUP)) {
+ int is_bool;
+ int i = git_config_bool_or_int(var, value, &is_bool);
@@ builtin/fsmonitor--daemon.c: done:
return err;
}
-static int try_to_run_foreground_daemon(void)
-+static int try_to_run_foreground_daemon(int free_console)
++static int try_to_run_foreground_daemon(int detach_console)
{
/*
* Technically, we don't need to probe for an existing daemon
@@ builtin/fsmonitor--daemon.c: static int try_to_run_foreground_daemon(void)
- the_repository->worktree);
- fflush(stdout);
+ fflush(stderr);
+ }
+#ifdef GIT_WINDOWS_NATIVE
-+ if (free_console)
++ if (detach_console)
+ FreeConsole();
+#endif
+
@@ builtin/fsmonitor--daemon.c: static int try_to_run_foreground_daemon(void)
+ die("fsmonitor--daemon is already running '%s'",
+ the_repository->worktree);
+
-+ printf(_("starting fsmonitor-daemon in '%s'\n"),
-+ the_repository->worktree);
-+ fflush(stdout);
++ if (fsmonitor__announce_startup) {
++ fprintf(stderr, _("starting fsmonitor-daemon in '%s'\n"),
++ the_repository->worktree);
++ fflush(stderr);
++ }
+
+ cp.git_cmd = 1;
+
+ strvec_push(&cp.args, "fsmonitor--daemon");
+ strvec_push(&cp.args, "run");
-+ strvec_push(&cp.args, "--free-console");
++ strvec_push(&cp.args, "--detach");
+ strvec_pushf(&cp.args, "--ipc-threads=%d", fsmonitor__ipc_threads);
+
+ cp.no_stdin = 1;
@@ builtin/fsmonitor--daemon.c: static int try_to_run_foreground_daemon(void)
int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix)
{
const char *subcmd;
-+ int free_console = 0;
++ int detach_console = 0;
struct option options[] = {
-+ OPT_BOOL(0, "free-console", &free_console, N_("free console")),
++ OPT_BOOL(0, "detach", &detach_console, N_("detach from console")),
OPT_INTEGER(0, "ipc-threads",
&fsmonitor__ipc_threads,
N_("use <n> ipc worker threads")),
+ OPT_INTEGER(0, "start-timeout",
+ &fsmonitor__start_timeout_sec,
-+ N_("Max seconds to wait for background daemon startup")),
++ N_("max seconds to wait for background daemon startup")),
+
OPT_END()
};
@@ builtin/fsmonitor--daemon.c: int cmd_fsmonitor__daemon(int argc, const char **ar
+
if (!strcmp(subcmd, "run"))
- return !!try_to_run_foreground_daemon();
-+ return !!try_to_run_foreground_daemon(free_console);
++ return !!try_to_run_foreground_daemon(detach_console);
if (!strcmp(subcmd, "stop"))
return !!do_as_client__send_stop(); 12: 21c099c5197 ! 12: e5d419b1ea0 fsmonitor--daemon: add pathname classification
@@ Commit message
When we register for filesystem notifications on a directory, we get events for everything (recursively) in the directory. We want to report to clients changes to tracked and untracked
- paths within the working directory. We do not want to report
- changes within the .git directory, for example.
+ paths within the working directory proper. We do not want to
+ report changes within the .git directory, for example.
This classification will be used in a later commit by the different backends to classify paths as events are received.
+ Signed-off-by: Junio C Hamano <[email protected]>
## builtin/fsmonitor--daemon.c ##
@@ builtin/fsmonitor--daemon.c: static int handle_client(void *data,
@@ fsmonitor--daemon.h: struct fsmonitor_daemon_state {
+ * that to decide whether clients are told about them. (And to watch
+ * for file system synchronization events.)
+ *
++ * The daemon only collects and reports on the set of modified paths ++ * within the working directory (proper).
++ *
+ * The client should only care about paths within the working
+ * directory proper (inside the working directory and not ".git" nor
+ * inside of ".git/"). That is, the client has read the index and is
+ * asking for a list of any paths in the working directory that have
+ * been modified since the last token. The client does not care about
-+ * file system changes within the .git directory (such as new loose ++ * file system changes within the ".git/" directory (such as new loose
+ * objects or packfiles). So the client will only receive paths that
+ * are classified as IS_WORKDIR_PATH.
+ *
++ * Note that ".git" is usually a directory and is therefore inside ++ * the cone of the FS watch that we have on the working directory root, ++ * so we will also get FS events for disk activity on and within ".git/" ++ * that we need to respond to or filter from the client.
++ *
++ * But Git also allows ".git" to be a *file* that points to a GITDIR ++ * outside of the working directory. When this happens, we need to ++ * create FS watches on both the working directory root *and* on the ++ * (external) GITDIR root. (The latter is required because we put ++ * cookie files inside it and use them to sync with the FS event ++ * stream.)
++ *
++ * Note that in the context of this discussion, I'm using "GITDIR" ++ * to only mean an external GITDIR referenced by a ".git" file. ++ *
++ * The platform FS event backends will receive watch-specific ++ * relative paths (except for those OS's that always emit absolute ++ * paths). We use the following enum and routines to classify each ++ * path so that we know how to handle it. There is a slight asymmetry ++ * here because ".git/" is inside the working directory and the ++ * (external) GITDIR is not, and therefore how we handle events may ++ * vary slightly, so I have different enums for "IS...DOT_GIT..." and ++ * "IS...GITDIR...".
++ *
+ * The daemon uses the IS_DOT_GIT and IS_GITDIR internally to mean the
-+ * exact ".git" directory or GITDIR. If the daemon receives a delete -+ * event for either of these directories, it will automatically -+ * shutdown, for example.
++ * exact ".git" file/directory or GITDIR directory. If the daemon ++ * receives a delete event for either of these paths, it will ++ * automatically shutdown, for example.
+ *
+ * Note that the daemon DOES NOT explicitly watch nor special case the
-+ * ".git/index" file. The daemon does not read the index and does not -+ * have any internal index-relative state. The daemon only collects -+ * the set of modified paths within the working directory. ++ * index. The daemon does not read the index nor have any internal ++ * index-relative state, so there are no "IS...INDEX..." enum values.
+ */
+enum fsmonitor_path_type {
+ IS_WORKDIR_PATH = 0,
13: 72979c35ceb ! 13: c2f3668a866 fsmonitor--daemon: define token-ids
@@ Commit message
overall token naming scheme.
+ Signed-off-by: Junio C Hamano <[email protected]>
## builtin/fsmonitor--daemon.c ##
@@ builtin/fsmonitor--daemon.c: static int do_as_client__status(void)
14: 8d1db644409 ! 14: bd492d13fb1 fsmonitor--daemon: create token-based changed path cache
@@ Commit message
paths within these batches for efficient presentation to clients.
+ Signed-off-by: Junio C Hamano <[email protected]>
## builtin/fsmonitor--daemon.c ##
@@ builtin/fsmonitor--daemon.c: struct fsmonitor_token_data {
@@ builtin/fsmonitor--daemon.c: static struct fsmonitor_token_data *fsmonitor_new_t
+ * We should create a new token and start fresh (as if we just
+ * booted up).
+ *
-+ * If there are no concurrent threads readering the current token data
++ * If there are no concurrent threads reading the current token data
+ * series, we can free it now. Otherwise, let the last reader free
+ * it.
+ *
15: 98c5adf8ca0 ! 15: 6b5b3d08227 compat/fsmonitor/fsm-listen-win32: implement FSMonitor backend on Windows
@@ Commit message
into batches and publish.
+ Signed-off-by: Junio C Hamano <[email protected]>
## compat/fsmonitor/fsm-listen-win32.c ##
@@
16: a3b881315fa ! 16: cdef9730b3f compat/fsmonitor/fsm-listen-darwin: add macos header files for FSEvent
@@ Metadata
Author: Jeff Hostetler <[email protected]>
## Commit message ##
- compat/fsmonitor/fsm-listen-darwin: add macos header files for FSEvent
+ compat/fsmonitor/fsm-listen-darwin: add MacOS header files for FSEvent
Include MacOS system declarations to allow us to use FSEvent and
- CoreFoundation APIs. We need GCC and clang versions because of
- compiler and header file conflicts.
+ CoreFoundation APIs. We need different versions of the declarations
+ for GCC vs. clang because of compiler and header file conflicts.
While it is quite possible to #include Apple's CoreServices.h when compiling C source code with clang, trying to build it with GCC
@@ Commit message
+ Signed-off-by: Junio C Hamano <[email protected]>
## compat/fsmonitor/fsm-listen-darwin.c ##
@@
-+#if defined(__GNUC__)
++#ifndef __clang__
+/*
+ * It is possible to #include CoreFoundation/CoreFoundation.h when compiling
+ * with clang, but not with GCC as of time of writing.
17: 162e357db72 ! 17: aabfebd3a41 compat/fsmonitor/fsm-listen-darwin: implement FSEvent listener on MacOS
@@ Commit message
+ Signed-off-by: Junio C Hamano <[email protected]>
## compat/fsmonitor/fsm-listen-darwin.c ##
-@@
--#if defined(__GNUC__)
-+#ifndef __clang__
- /*
- * It is possible to #include CoreFoundation/CoreFoundation.h when compiling
- * with clang, but not with GCC as of time of writing.
@@ compat/fsmonitor/fsm-listen-darwin.c: void FSEventStreamRelease(FSEventStreamRef stream);
#include "cache.h"
#include "fsmonitor.h"
@@ compat/fsmonitor/fsm-listen-darwin.c: void FSEventStreamRelease(FSEventStreamRef
+}
+
+/*
-+ * NEEDSWORK: Investigate the proper value for the latency argument
-+ * in the call to FSEventStreamCreate(). I'm not sure that this
-+ * needs to be a config setting or just something that we tune after
-+ * some testing.
++ * In the call to FSEventStreamCreate() to setup our watch, the
++ * latency argument determines the frequency of calls to our callback
++ * with new FS events. Too slow and events get dropped; too fast and
++ * we burn CPU unnecessarily. Since it is rather obscure, I don't
++ * think this needs to be a config setting. I've done extensive
++ * testing on my systems and chosen the value below. It gives good
++ * results and I've not seen any dropped events.
+ *
+ * With a latency of 0.1, I was seeing lots of dropped events during
+ * the "touch 100000" files test within t/perf/p7519, but with a
-+ * latency of 0.001 I did not see any dropped events. So the
-+ * "correct" value may be somewhere in between.
++ * latency of 0.001 I did not see any dropped events. So I'm going
++ * to assume that this is the "correct" value.
+ *
+ * https://developer.apple.com/documentation/coreservices/1443980-fseventstreamcreate
+ */
18: 3de1c43beaf ! 18: 0896e72e2b8 fsmonitor--daemon: implement handle_client callback
@@ Commit message
relative to the provided token.
+ Signed-off-by: Junio C Hamano <[email protected]>
## builtin/fsmonitor--daemon.c ##
@@
19: 3517c4a3c13 ! 19: bc240a9e665 help: include fsmonitor--daemon feature flag in version info
@@ Commit message
This can be used by test scripts for prereq testing. Granted, tests could just try git fsmonitor--daemon status and look for a 128 exit
- code or grep for a "not supported" message on stderr, but this is
- rather obscure.
+ code or grep for a "not supported" message on stderr, but these
+ methods are rather obscure.
The main advantage is that the feature message will automatically appear in bug reports and other support requests.
+ This concept was also used during the development of Scalar for
+ similar reasons.
+
+ Signed-off-by: Junio C Hamano <[email protected]>
## help.c ##
@@
20: 4ffc2ddf516 ! 20: 2b563c240eb t/helper/fsmonitor-client: create IPC client to talk to FSMonitor Daemon
@@ Commit message
Create an IPC client to send query and flush commands to the daemon.
+ Signed-off-by: Junio C Hamano <[email protected]>
## Makefile ##
@@ Makefile: TEST_BUILTINS_OBJS += test-dump-split-index.o
21: d93310a7c64 ! 21: db8809da089 t7527: create test for fsmonitor--daemon
@@ Commit message
t7527: create test for fsmonitor--daemon
+ Signed-off-by: Junio C Hamano <[email protected]>
## t/t7527-builtin-fsmonitor.sh (new) ##
@@
22: 27e47108908 ! 22: 15bd5aaff36 t/perf: avoid copying builtin fsmonitor files into test repo
@@ Commit message
.git/fsmonitor--daemon*, so it is simple to exclude them.
+ Signed-off-by: Junio C Hamano <[email protected]>
## t/perf/perf-lib.sh ##
@@ t/perf/perf-lib.sh: test_perf_copy_repo_contents () {
23: 6cba1d950b0 ! 23: 4cb97918d20 t/helper/test-chmtime: skip directories on Windows
@@ Commit message
an empty directory on disk.
+ Signed-off-by: Junio C Hamano <[email protected]>
## t/helper/test-chmtime.c ##
@@ t/helper/test-chmtime.c: int cmd__chmtime(int argc, const char **argv)
24: fcf843a0d42 ! 24: a70748b4640 t/perf/p7519: speed up test on Windows
@@ Commit message
on Windows because of process creation overhead.
Signed-off-by: Jeff Hostetler <[email protected]>
+ Signed-off-by: Junio C Hamano <[email protected]>
## t/perf/p7519-fsmonitor.sh ##
@@ t/perf/p7519-fsmonitor.sh: then
@@ t/perf/p7519-fsmonitor.sh: test_expect_success "one time repo setup" '
- for i in $(test_seq 1 100); do touch 100_files/$i || return 1; done &&
- for i in $(test_seq 1 1000); do touch 1000_files/$i || return 1; done &&
- for i in $(test_seq 1 10000); do touch 10000_files/$i || return 1; done &&
-+ touch_files 1 &&
++ : 1_file directory should be left empty &&
+ touch_files 10 &&
+ touch_files 100 &&
+ touch_files 1000 && 25: 198f47bda5a ! 25: b0c9c9c7a40 t/perf/p7519: add fsmonitor--daemon test cases
@@ Commit message
the "Simple IPC" interface.
+ Signed-off-by: Junio C Hamano <[email protected]>
## t/perf/p7519-fsmonitor.sh ##
@@ t/perf/p7519-fsmonitor.sh: test_perf_w_drop_caches () {
26: 19993c130d2 ! 26: 64a5b741670 fsmonitor--daemon: periodically truncate list of modified files
@@ Commit message
they are considered obsolete.
+ Signed-off-by: Junio C Hamano <[email protected]>
## builtin/fsmonitor--daemon.c ##
@@ builtin/fsmonitor--daemon.c: static void fsmonitor_batch__combine(struct fsmonitor_batch *batch_dest,
27: f47a763dc26 ! 27: 5b06eb5d0e6 fsmonitor--daemon: use a cookie file to sync with file system
@@ Commit message
of FSeventStreamCreate(). The kernel only signals every latency
seconds, but does not guarantee that the kernel queue is completely
drained, so we may have to wait more than one interval. If we
- increase the frequency, the system is more likely to drop events.
+ increase the latency, the system is more likely to drop events.
We avoid these issues by having each client thread create a unique
cookie file and then wait until it is seen in the event stream.
+ Signed-off-by: Junio C Hamano <[email protected]>
## builtin/fsmonitor--daemon.c ##
@@ builtin/fsmonitor--daemon.c: static int do_as_client__status(void)
@@ builtin/fsmonitor--daemon.c: static int do_as_client__status(void)
+ unlink(cookie_pathname.buf);
+
+ /*
-+ * NEEDSWORK: This is an infinite wait (well, unless another
++ * 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.
++ * 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,
@@ builtin/fsmonitor--daemon.c: static void fsmonitor_free_token_data(struct fsmoni
+ * [2] Some of those lost events may have been for cookie files. We
+ * should assume the worst and abort them rather letting them starve.
+ *
- * If there are no concurrent threads readering the current token data
+ * If there are no concurrent threads reading the current token data
* series, we can free it now. Otherwise, let the last reader free
* it.
@@ builtin/fsmonitor--daemon.c: static void with_lock__do_force_resync(struct fsmonitor_daemon_state *state)
@@ builtin/fsmonitor--daemon.c: done:
strbuf_release(&state.path_worktree_watch);
strbuf_release(&state.path_gitdir_watch);
+ strbuf_release(&state.path_cookie_prefix);
-+
-+ /*
-+ * NEEDSWORK: Consider "rm -rf <gitdir>/<fsmonitor-dir>"
-+ */
return err;
}
28: aec44a21afd ! 28: 1fd5439de03 fsmonitor: force update index after large responses
@@ Commit message
unnecessarily spend time actually scanning them during each command.
+ Signed-off-by: Junio C Hamano <[email protected]>
## fsmonitor.c ##
@@ fsmonitor.c: static void fsmonitor_refresh_callback(struct index_state *istate, char *name)
@@ fsmonitor.c: static void fsmonitor_refresh_callback(struct index_state *istate,
+ * will be updated automatically the first time the user touches
+ * a tracked file and causes a command like git status to
+ * update an mtime to be updated and/or set a flag bit.
-+ *
-+ * NEEDSWORK: Does this need to be a config value?
+ */
+static int fsmonitor_force_update_threshold = 100;
+
29: d6039987df8 ! 29: 6fc1430285f t7527: test status with untracked-cache and fsmonitor--daemon
@@ Commit message
identical.
+ Signed-off-by: Junio C Hamano <[email protected]>
## t/t7527-builtin-fsmonitor.sh ##
@@ t/t7527-builtin-fsmonitor.sh: test_expect_success 'setup' '
30: 5117fbdfc63 ! 30: b915b95cc2f update-index: convert fsmonitor warnings to advise
@@ Commit message
update-index: convert fsmonitor warnings to advise
+ Signed-off-by: Junio C Hamano <[email protected]>
## builtin/update-index.c ##
@@ builtin/update-index.c: int cmd_update_index(int argc, const char **argv, const char *prefix)
Signature · 1 line
gitgitgadget
PATCH v6 1/30 fsmonitor: enhance existing comments, clarify trivial response handling
From: Jeff Hostetler <[email protected]>
--- fsmonitor.c | 64 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 23 deletions(-)
fsmonitor.c +41 -23
@@ -168,29 +168,15 @@ static int query_fsmonitor(int version, const char *last_update, struct strbuf *if (result)trace2_data_intmax ("fsm_hook" ,NULL ,"query/failed" , result); -else { +else trace2_data_intmax ("fsm_hook" ,NULL ,"query/response-length" , query_result->len); -if (fsmonitor_is_trivial_response (query_result)) -trace2_data_intmax ("fsm_hook" ,NULL , -"query/trivial-response" ,1 ); - } -trace2_region_leave ("fsm_hook" ,"query" ,NULL );return result; } -int fsmonitor_is_trivial_response (const struct strbuf *query_result) -{ -static char trivial_response[3 ] = {'\0' ,'/' ,'\0' }; - -return query_result->len >=3 && - !memcmp (trivial_response, - &query_result->buf[query_result->len -3 ],3 ); -} -static void fsmonitor_refresh_callback (struct index_state *istate,char *name) {int i, len =strlen (name);
@@ -238,6 +224,7 @@ void refresh_fsmonitor(struct index_state *istate)struct strbuf last_update_token =STRBUF_INIT ;char *buf;unsigned int i; +int is_trivial =0 ;if (!core_fsmonitor || istate->fsmonitor_has_run_once)return ;
@@ -283,6 +270,7 @@ void refresh_fsmonitor(struct index_state *istate) query_success =0 ; }else { bol = last_update_token.len +1 ; + is_trivial = query_result.buf[bol] =='/' ; } }else if (hook_version <0 ) { hook_version =HOOK_INTERFACE_VERSION1 ;
@@ -294,16 +282,38 @@ void refresh_fsmonitor(struct index_state *istate)if (hook_version ==HOOK_INTERFACE_VERSION1 ) { query_success = !query_fsmonitor (HOOK_INTERFACE_VERSION1 , istate->fsmonitor_last_update, &query_result); +if (query_success) + is_trivial = query_result.buf[0 ] =='/' ; } +if (is_trivial) +trace2_data_intmax ("fsm_hook" ,NULL , +"query/trivial-response" ,1 ); +trace_performance_since (last_update,"fsmonitor process '%s'" , core_fsmonitor);trace_printf_key (&trace_fsmonitor,"fsmonitor process '%s' returned %s" , core_fsmonitor, query_success ?"success" :"failure" ); } -/* a fsmonitor process can return '/' to indicate all entries are invalid */ -if (query_success && query_result.buf[bol] !='/' ) { -/* Mark all entries returned by the monitor as dirty */ +/* +* The response from FSMonitor (excluding the header token) is +* either: +* +* [a] a (possibly empty) list of NUL delimited relative +* pathnames of changed paths. This list can contain +* files and directories. Directories have a trailing +* slash. +* +* [b] a single '/' to indicate the provider had no +* information and that we should consider everything +* invalid. We call this a trivial response. +*/ +if (query_success && !is_trivial) { +/* +* Mark all pathnames returned by the monitor as dirty. +* +* This updates both the cache-entries and the untracked-cache. +*/ buf = query_result.buf;for (i = bol; i < query_result.len; i++) {if (buf[i] !='\0' )
@@ -318,11 +328,16 @@ void refresh_fsmonitor(struct index_state *istate)if (istate->untracked) istate->untracked->use_fsmonitor =1 ; }else { - -/* We only want to run the post index changed hook if we've actually changed entries, so keep track -* if we actually changed entries or not */ +/* +* We failed to get a response or received a trivial response, +* so invalidate everything. +* +* We only want to run the post index changed hook if +* we've actually changed entries, so keep track if we +* actually changed entries or not. +*/ int is_cache_changed =0 ; -/* Mark all entries invalid */ +for (i =0 ; i < istate->cache_nr; i++) {if (istate->cache[i]->ce_flags &CE_FSMONITOR_VALID ) { is_cache_changed =1 ;
@@ -330,7 +345,10 @@ void refresh_fsmonitor(struct index_state *istate) } } -/* If we're going to check every file, ensure we save the results */ +/* +* If we're going to check every file, ensure we save +* the results. +*/ if (is_cache_changed) istate->cache_changed |=FSMONITOR_CHANGED ;
Signature · 1 line
gitgitgadget
PATCH v6 2/30 fsmonitor-ipc: create client routines for git-fsmonitor--daemon
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create fsmonitor_ipc__*() client routines to spawn the built-in file system monitor daemon and send it an IPC request using the Simple IPC API.
Stub in empty fsmonitor_ipc__*() functions for unsupported platforms.
--- Makefile | 1 + fsmonitor-ipc.c | 171 ++++++++++++++++++++++++++++++++++++++++++++++++ fsmonitor-ipc.h | 48 ++++++++++++++ 3 files changed, 220 insertions(+) create mode 100644 fsmonitor-ipc.c create mode 100644 fsmonitor-ipc.h
Makefile +1 -0
@@ -907,6 +907,7 @@ LIB_OBJS += fetch-pack.o LIB_OBJS += fmt-merge-msg.o LIB_OBJS += fsck.o LIB_OBJS += fsmonitor.o +LIB_OBJS += fsmonitor-ipc.o LIB_OBJS += gettext.o LIB_OBJS += gpg-interface.o LIB_OBJS += graph.o
fsmonitor-ipc.c new file +171 -0
171 lines not on this page · raw message
fsmonitor-ipc.h new file +48 -0
48 lines not on this page · raw message
Signature · 1 line
gitgitgadget
PATCH v6 3/30 fsmonitor: config settings are repository-specific
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Move fsmonitor config settings to a new and opaque struct fsmonitor_settings structure. Add a lazily-loaded pointer to this into struct repo_settings
Create an enum fsmonitor_mode type in struct fsmonitor_settings to represent the state of fsmonitor. This lets us represent which, if any, fsmonitor provider (hook or IPC) is enabled.
Create fsm_settings__get_*() getters to lazily look up fsmonitor- related config settings.
Get rid of the core_fsmonitor global variable. Move the code to lookup the existing core.fsmonitor config value into the fsmonitor settings.
Create a hook pathname variable in struct fsmonitor-settings and only set it when in hook mode.
Extend the definition of core.fsmonitor to be either a boolean or a hook pathname. When true, the builtin FSMonitor is used. When false or unset, no FSMonitor (neither builtin nor hook) is used.
The existing core_fsmonitor global variable was used to store the pathname to the fsmonitor hook *and* it was used as a boolean to see if fsmonitor was enabled. This dual usage and global visibility leads to confusion when we add the IPC-based provider. So lets hide the details in fsmonitor-settings.c and let it decide which provider to use in the case of multiple settings. This avoids cluttering up repo-settings.c with these private details.
A future commit in builtin-fsmonitor series will add the ability to disqualify worktrees for various reasons, such as being mounted from a remote volume, where fsmonitor should not be started. Having the config settings hidden in fsmonitor-settings.c allows such worktree restrictions to override the config values used.
--- Makefile | 1 + builtin/update-index.c | 7 ++- cache.h | 1 - config.c | 14 ----- config.h | 1 - environment.c | 1 - fsmonitor-settings.c | 115 +++++++++++++++++++++++++++++++++++++++++ fsmonitor-settings.h | 21 ++++++++ fsmonitor.c | 63 +++++++++++++--------- fsmonitor.h | 15 ++++-- repository.h | 3 ++ t/README | 4 +- 12 files changed, 197 insertions(+), 49 deletions(-) create mode 100644 fsmonitor-settings.c create mode 100644 fsmonitor-settings.h
Makefile +1 -0
@@ -908,6 +908,7 @@ LIB_OBJS += fmt-merge-msg.o LIB_OBJS += fsck.o LIB_OBJS += fsmonitor.o LIB_OBJS += fsmonitor-ipc.o +LIB_OBJS += fsmonitor-settings.o LIB_OBJS += gettext.o LIB_OBJS += gpg-interface.o LIB_OBJS += graph.o
builtin/update-index.c +5 -2
@@ -1236,14 +1236,17 @@ int cmd_update_index(int argc, const char **argv, const char *prefix) }if (fsmonitor >0 ) { -if (git_config_get_fsmonitor () ==0 ) +enum fsmonitor_mode fsm_mode =fsm_settings__get_mode (r); +if (fsm_mode ==FSMONITOR_MODE_DISABLED ) {warning (_ ("core.fsmonitor is unset; " "set it if you really want to " "enable fsmonitor" )); + }add_fsmonitor (&the_index);report (_ ("fsmonitor enabled" )); }else if (!fsmonitor) { -if (git_config_get_fsmonitor () ==1 ) +enum fsmonitor_mode fsm_mode =fsm_settings__get_mode (r); +if (fsm_mode >FSMONITOR_MODE_DISABLED )warning (_ ("core.fsmonitor is set; " "remove it if you really want to " "disable fsmonitor" ));
cache.h +0 -1
@@ -999,7 +999,6 @@ extern int core_preload_index;extern int precomposed_unicode;extern int protect_hfs;extern int protect_ntfs; -extern const char *core_fsmonitor;extern int core_apply_sparse_checkout;extern int core_sparse_checkout_cone;
config.c +0 -14
@@ -2626,20 +2626,6 @@ int git_config_get_max_percent_split_change(void)return -1 ;/* default value */ } -int git_config_get_fsmonitor (void ) -{ -if (git_config_get_pathname ("core.fsmonitor" , &core_fsmonitor)) - core_fsmonitor =getenv ("GIT_TEST_FSMONITOR" ); - -if (core_fsmonitor && !*core_fsmonitor) - core_fsmonitor =NULL ; - -if (core_fsmonitor) -return 1 ; - -return 0 ; -} -int git_config_get_index_threads (int *dest) {int is_bool, val;
config.h +0 -1
@@ -597,7 +597,6 @@ int git_config_get_pathname(const char *key, const char **dest);int git_config_get_index_threads (int *dest);int git_config_get_split_index (void );int git_config_get_max_percent_split_change (void ); -int git_config_get_fsmonitor (void );/* This dies if the configured or default date is in the future */ int git_config_get_expiry (const char *key,const char **output);
environment.c +0 -1
@@ -84,7 +84,6 @@ int protect_hfs = PROTECT_HFS_DEFAULT;#define PROTECT_NTFS_DEFAULT 1#endif int protect_ntfs =PROTECT_NTFS_DEFAULT ; -const char *core_fsmonitor;/* * The character that begins a commented line in user-editable file
fsmonitor-settings.c new file +115 -0
115 lines not on this page · raw message
fsmonitor-settings.h new file +21 -0
@@ -0,0 +1,21 @@ +#ifndef FSMONITOR_SETTINGS_H +#define FSMONITOR_SETTINGS_H + +struct repository ; + +enum fsmonitor_mode { +FSMONITOR_MODE_DISABLED =0 , +FSMONITOR_MODE_HOOK =1 ,/* core.fsmonitor=<hook_path> */ +FSMONITOR_MODE_IPC =2 ,/* core.fsmonitor=<true> */ +}; + +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); + +enum fsmonitor_mode fsm_settings__get_mode (struct repository *r); +const char *fsm_settings__get_hook_path (struct repository *r); + +struct fsmonitor_settings ; + +#endif /* FSMONITOR_SETTINGS_H */
fsmonitor.c +38 -25
128 lines not on this page · raw message
fsmonitor.h +12 -3
@@ -3,6 +3,7 @@#include "cache.h" #include "dir.h" +#include "fsmonitor-settings.h" extern struct trace_key trace_fsmonitor;
@@ -57,7 +58,10 @@ int fsmonitor_is_trivial_response(const struct strbuf *query_result);*/ static inline int is_fsmonitor_refreshed (const struct index_state *istate) { -return !core_fsmonitor || istate->fsmonitor_has_run_once; +enum fsmonitor_mode fsm_mode =fsm_settings__get_mode (istate->repo); + +return fsm_mode <=FSMONITOR_MODE_DISABLED || + istate->fsmonitor_has_run_once; }/*
@@ -67,7 +71,10 @@ static inline int is_fsmonitor_refreshed(const struct index_state *istate)*/ static inline void mark_fsmonitor_valid (struct index_state *istate,struct cache_entry *ce) { -if (core_fsmonitor && !(ce->ce_flags &CE_FSMONITOR_VALID )) { +enum fsmonitor_mode fsm_mode =fsm_settings__get_mode (istate->repo); + +if (fsm_mode >FSMONITOR_MODE_DISABLED && + !(ce->ce_flags &CE_FSMONITOR_VALID )) { istate->cache_changed =1 ; ce->ce_flags |=CE_FSMONITOR_VALID ;trace_printf_key (&trace_fsmonitor,"mark_fsmonitor_clean '%s'" , ce->name);
@@ -83,7 +90,9 @@ static inline void mark_fsmonitor_valid(struct index_state *istate, struct cache*/ static inline void mark_fsmonitor_invalid (struct index_state *istate,struct cache_entry *ce) { -if (core_fsmonitor) { +enum fsmonitor_mode fsm_mode =fsm_settings__get_mode (istate->repo); + +if (fsm_mode >FSMONITOR_MODE_DISABLED ) { ce->ce_flags &= ~CE_FSMONITOR_VALID ;untracked_cache_invalidate_path (istate, ce->name,1 );trace_printf_key (&trace_fsmonitor,"mark_fsmonitor_invalid '%s'" , ce->name);
repository.h +3 -0
@@ -4,6 +4,7 @@#include "path.h" struct config_set ; +struct fsmonitor_settings ;struct git_hash_algo ;struct index_state ;struct lock_file ;
@@ -35,6 +36,8 @@ struct repo_settings {int command_requires_full_index;int sparse_index; +struct fsmonitor_settings *fsmonitor;/* lazily loaded */ +int index_version;enum untracked_cache_setting core_untracked_cache;
t/README +2 -2
@@ -405,8 +405,8 @@ every 'git commit-graph write', as if the `--changed-paths` option was passed in. GIT_TEST_FSMONITOR=$PWD/t7519/fsmonitor-all exercises the fsmonitor -code path for utilizing a file system monitor to speed up detecting -new or changed files. +code paths for utilizing a (hook based) file system monitor to speed up +detecting new or changed files. GIT_TEST_INDEX_VERSION=<n> exercises the index read/write code path for the index version specified. Can be set to any valid version
Signature · 1 line
gitgitgadget
Re: [PATCH v6 03/30] fsmonitor: config settings are repository-specific
Quoted 1 line · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
I know this is in "next", just looking over this code again...
Quoted 1 line
+static void
lookup_fsmonitor_settings(struct repository *r)
Here we'll start loading the settings...
fsmonitor-settings.c line 22
Quoted 7 lines
fsmonitor-settings.c new file +7
-0+{ +struct fsmonitor_settings *s; +const char *const_str; +int bool_value; + +if (r->settings.fsmonitor) +return ;
MARK
fsmonitor-settings.c line 26
Quoted 3 lines
fsmonitor-settings.c new file +3
-0+CALLOC_ARRAY (s,1 ); + + r->settings.fsmonitor = s;
And right after we alloc the r->settings.fsmonitor we'll ...
fsmonitor-settings.c line 28
Quoted 1 line
fsmonitor-settings.c new file +1
-0+fsm_settings__set_disabled (r);
...call this method...
Quoted 30 lines
fsmonitor-settings.c new file +30
-0+ +/* +* Overload the existing "core.fsmonitor" config setting (which +* has historically been either unset or a hook pathname) to +* now allow a boolean value to enable the builtin FSMonitor +* or to turn everything off. (This does imply that you can't +* use a hook script named "true" or "false", but that's OK.) +*/ +switch (repo_config_get_maybe_bool (r,"core.fsmonitor" , &bool_value)) { + +case 0 :/* config value was set to <bool> */ +if (bool_value) +fsm_settings__set_ipc (r); +return ; + +case 1 :/* config value was unset */ + const_str =getenv ("GIT_TEST_FSMONITOR" ); +break ; + +case -1 :/* config value set to an arbitrary string */ +if (repo_config_get_pathname (r,"core.fsmonitor" , &const_str)) +return ;/* should not happen */ +break ; + +default :/* should not happen */ +return ; + } + +if (!const_str || !*const_str) +return ;
fsmonitor-settings.c line 111
Quoted 10 lines
fsmonitor-settings.c new file +9
-0+ +fsm_settings__set_hook (r, const_str); +} +void fsm_settings__set_disabled (struct repository *r) +{ +if (!r) + r = the_repository; + +lookup_fsmonitor_settings (r);
...which here will recurse into lookup_fsmonitor_settings and hit "MARK".
So isn't that fsm_settings__set_disabled() within that method pointless?
fsmonitor-settings.c line 114
Quoted 2 lines
fsmonitor-settings.c new file +2
-0+ r->settings.fsmonitor->mode =FSMONITOR_MODE_DISABLED ; +FREE_AND_NULL (r->settings.fsmonitor->hook_path);
It seems as though the intent was to reach this, but these all happen to be the same thing you'd get with CALLOC_ARRAY(), so I think this just happened to work out...
fsmonitor-settings.h line 7
Quoted 2 lines
fsmonitor-settings.h new file +2
-0+enum fsmonitor_mode { +FSMONITOR_MODE_DISABLED =0 ,
...I.e. this is luckily zero.
Re: [PATCH v6 03/30] fsmonitor: config settings are repository-specific
Quoted 74 lines · Ævar Arnfjörð Bjarmason
Quoted 1 line · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
I know this is in "next", just looking over this code again...
Quoted 1 line
+static void
lookup_fsmonitor_settings(struct repository *r)Here we'll start loading the settings...
fsmonitor-settings.c line 22
Quoted 7 lines
fsmonitor-settings.c new file +7
-0+{ +struct fsmonitor_settings *s; +const char *const_str; +int bool_value; + +if (r->settings.fsmonitor) +return ;MARK
fsmonitor-settings.c line 26
Quoted 3 lines
fsmonitor-settings.c new file +3
-0+CALLOC_ARRAY (s,1 ); + + r->settings.fsmonitor = s;And right after we alloc the
r->settings.fsmonitorwe'll ...fsmonitor-settings.c line 28
Quoted 1 line
fsmonitor-settings.c new file +1
-0+fsm_settings__set_disabled (r);...call this method...
Quoted 30 lines
fsmonitor-settings.c new file +30
-0+ +/* +* Overload the existing "core.fsmonitor" config setting (which +* has historically been either unset or a hook pathname) to +* now allow a boolean value to enable the builtin FSMonitor +* or to turn everything off. (This does imply that you can't +* use a hook script named "true" or "false", but that's OK.) +*/ +switch (repo_config_get_maybe_bool (r,"core.fsmonitor" , &bool_value)) { + +case 0 :/* config value was set to <bool> */ +if (bool_value) +fsm_settings__set_ipc (r); +return ; + +case 1 :/* config value was unset */ + const_str =getenv ("GIT_TEST_FSMONITOR" ); +break ; + +case -1 :/* config value set to an arbitrary string */ +if (repo_config_get_pathname (r,"core.fsmonitor" , &const_str)) +return ;/* should not happen */ +break ; + +default :/* should not happen */ +return ; + } + +if (!const_str || !*const_str) +return ;fsmonitor-settings.c line 111
Quoted 10 lines
fsmonitor-settings.c new file +9
-0+ +fsm_settings__set_hook (r, const_str); +} +void fsm_settings__set_disabled (struct repository *r) +{ +if (!r) + r = the_repository; + +lookup_fsmonitor_settings (r);...which here will recurse into
lookup_fsmonitor_settingsand hit "MARK".
Quoted 1 line
So isn't that
fsm_settings__set_disabled()within that method pointless?
Yeah, that got rolled up in one too many rebase/refactorings... I'll simplify.
thanks
jeff
Quoted 13 lines
fsmonitor-settings.c line 114
Quoted 3 lines
fsmonitor-settings.c new file +2
-0+ r->settings.fsmonitor->mode =FSMONITOR_MODE_DISABLED ; +FREE_AND_NULL (r->settings.fsmonitor->hook_path);It seems as though the intent was to reach this, but these all happen to be the same thing you'd get with
CALLOC_ARRAY(), so I think this just happened to work out...fsmonitor-settings.h line 7
Quoted 2 lines
fsmonitor-settings.h new file +2
-0+enum fsmonitor_mode { +FSMONITOR_MODE_DISABLED =0 ,...I.e. this is luckily zero.
PATCH v6 4/30 fsmonitor: use IPC to query the builtin FSMonitor daemon
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Use simple IPC to directly communicate with the new builtin file system monitor daemon when core.fsmonitor is set to true.
--- fsmonitor.c | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-)
fsmonitor.c +36 -2
@@ -241,8 +241,41 @@ void refresh_fsmonitor(struct index_state *istate)trace_printf_key (&trace_fsmonitor,"refresh fsmonitor" );if (fsm_mode ==FSMONITOR_MODE_IPC ) { -/* TODO */ -return ; + query_success = !fsmonitor_ipc__send_query ( + istate->fsmonitor_last_update ? + istate->fsmonitor_last_update :"builtin:fake" , + &query_result); +if (query_success) { +/* +* The response contains a series of nul terminated +* strings. The first is the new token. +* +* Use `char *buf` as an interlude to trick the CI +* static analysis to let us use `strbuf_addstr()` +* here (and only copy the token) rather than +* `strbuf_addbuf()`. +*/ + buf = query_result.buf; +strbuf_addstr (&last_update_token, buf); + bol = last_update_token.len +1 ; + is_trivial = query_result.buf[bol] =='/' ; +if (is_trivial) +trace2_data_intmax ("fsm_client" ,NULL , +"query/trivial-response" ,1 ); + }else { +/* +* The builtin daemon is not available on this +* platform -OR- we failed to get a response. +* +* Generate a fake token (rather than a V1 +* timestamp) for the index extension. (If +* they switch back to the hook API, we don't +* want ambiguous state.) +*/ +strbuf_addstr (&last_update_token,"builtin:fake" ); + } + + goto apply_results; }assert (fsm_mode ==FSMONITOR_MODE_HOOK );
@@ -315,6 +348,7 @@ void refresh_fsmonitor(struct index_state *istate) query_success ? "success" : "failure"); } +apply_results:/* * The response from FSMonitor (excluding the header token) is * either:
Signature · 1 line
gitgitgadget
PATCH v6 5/30 fsmonitor: document builtin fsmonitor
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Document how core.fsmonitor can be set to a boolean to enable or disable the builtin FSMonitor.
Update references to core.fsmonitor and core.fsmonitorHookVersion and pointers to Watchman to refer to it.
Create git-fsmonitor--daemon manual page and describe its features.
--- Documentation/config/core.txt | 60 +++++++++++++++----- Documentation/git-fsmonitor--daemon.txt | 75 +++++++++++++++++++++++++ Documentation/git-update-index.txt | 8 ++- 3 files changed, 126 insertions(+), 17 deletions(-) create mode 100644 Documentation/git-fsmonitor--daemon.txt
Documentation/config/core.txt +46 -14
@@ -62,22 +62,54 @@ core.protectNTFS:: Defaults to `true` on Windows, and `false` elsewhere. core.fsmonitor:: - If set, the value of this variable is used as a command which - will identify all files that may have changed since the - requested date/time. This information is used to speed up git by - avoiding unnecessary processing of files that have not changed. - See the "fsmonitor-watchman" section of linkgit:githooks[5]. + If set to true, enable the built-in file system monitor + daemon for this working directory (linkgit:git-fsmonitor--daemon[1]). ++ +Like hook-based file system monitors, the built-in file system monitor +can speed up Git commands that need to refresh the Git index +(e.g. `git status`) in a working directory with many files. The +built-in monitor eliminates the need to install and maintain an +external third-party tool. ++ +The built-in file system monitor is currently available only on a +limited set of supported platforms. Currently, this includes Windows +and MacOS. ++ + Otherwise, this variable contains the pathname of the "fsmonitor" + hook command. ++ +This hook command is used to identify all files that may have changed +since the requested date/time. This information is used to speed up +git by avoiding unnecessary scanning of files that have not changed. ++ +See the "fsmonitor-watchman" section of linkgit:githooks[5]. ++ +Note that if you concurrently use multiple versions of Git, such +as one version on the command line and another version in an IDE +tool, that the definition of `core.fsmonitor` was extended to +allow boolean values in addition to hook pathnames. Git versions +2.35.1 and prior will not understand the boolean values and will +consider the "true" or "false" values as hook pathnames to be +invoked. Git versions 2.26 thru 2.35.1 default to hook protocol +V2 and will fall back to no fsmonitor (full scan). Git versions +prior to 2.26 default to hook protocol V1 and will silently +assume there were no changes to report (no scan), so status +commands may report incomplete results. For this reason, it is +best to upgrade all of your Git versions before using the built-in +file system monitor. core.fsmonitorHookVersion:: - Sets the version of hook that is to be used when calling fsmonitor. - There are currently versions 1 and 2. When this is not set, - version 2 will be tried first and if it fails then version 1 - will be tried. Version 1 uses a timestamp as input to determine - which files have changes since that time but some monitors - like watchman have race conditions when used with a timestamp. - Version 2 uses an opaque string so that the monitor can return - something that can be used to determine what files have changed - without race conditions. + Sets the protocol version to be used when invoking the + "fsmonitor" hook. ++ +There are currently versions 1 and 2. When this is not set, +version 2 will be tried first and if it fails then version 1 +will be tried. Version 1 uses a timestamp as input to determine +which files have changes since that time but some monitors +like Watchman have race conditions when used with a timestamp. +Version 2 uses an opaque string so that the monitor can return +something that can be used to determine what files have changed +without race conditions. core.trustctime:: If false, the ctime differences between the index and the
Documentation/git-fsmonitor--daemon.txt new file +75 -0
75 lines not on this page · raw message
Documentation/git-update-index.txt +5 -3
@@ -498,7 +498,9 @@ FILE SYSTEM MONITOR This feature is intended to speed up git operations for repos that have large working directories. -It enables git to work together with a file system monitor (see the +It enables git to work together with a file system monitor (see +linkgit:git-fsmonitor--daemon[1] +and the "fsmonitor-watchman" section of linkgit:githooks[5]) that can inform it as to what files have been modified. This enables git to avoid having to lstat() every file to find modified files.
@@ -509,8 +511,8 @@ looking for new files. If you want to enable (or disable) this feature, it is easier to use the `core.fsmonitor` configuration variable (see -linkgit:git-config[1]) than using the `--fsmonitor` option to -`git update-index` in each repository, especially if you want to do so +linkgit:git-config[1]) than using the `--fsmonitor` option to `git +update-index` in each repository, especially if you want to do so across all repositories you use, because you can set the configuration variable in your `$HOME/.gitconfig` just once and have it affect all repositories you touch.
Signature · 1 line
gitgitgadget
PATCH v6 6/30 fsmonitor--daemon: add a built-in fsmonitor daemon
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create a built-in file system monitoring daemon that can be used by the existing fsmonitor feature (protocol API and index extension) to improve the performance of various Git commands, such as status.
The fsmonitor--daemon feature builds upon the Simple IPC API and provides an alternative to hook access to existing fsmonitors such as watchman.
This commit merely adds the new command without any functionality.
--- .gitignore | 1 + Makefile | 1 + builtin.h | 1 + builtin/fsmonitor--daemon.c | 46 +++++++++++++++++++++++++++++++++++++ git.c | 1 + 5 files changed, 50 insertions(+) create mode 100644 builtin/fsmonitor--daemon.c
.gitignore +1 -0
@@ -72,6 +72,7 @@ /git-format-patch /git-fsck /git-fsck-objects +/git-fsmonitor--daemon /git-gc /git-get-tar-commit-id /git-grep
Makefile +1 -0
@@ -1114,6 +1114,7 @@ BUILTIN_OBJS += builtin/fmt-merge-msg.o BUILTIN_OBJS += builtin/for-each-ref.o BUILTIN_OBJS += builtin/for-each-repo.o BUILTIN_OBJS += builtin/fsck.o +BUILTIN_OBJS += builtin/fsmonitor--daemon.o BUILTIN_OBJS += builtin/gc.o BUILTIN_OBJS += builtin/get-tar-commit-id.o BUILTIN_OBJS += builtin/grep.o
builtin.h +1 -0
@@ -159,6 +159,7 @@ int cmd_for_each_ref(int argc, const char **argv, const char *prefix);int cmd_for_each_repo (int argc,const char **argv,const char *prefix);int cmd_format_patch (int argc,const char **argv,const char *prefix);int cmd_fsck (int argc,const char **argv,const char *prefix); +int cmd_fsmonitor__daemon (int argc,const char **argv,const char *prefix);int cmd_gc (int argc,const char **argv,const char *prefix);int cmd_get_tar_commit_id (int argc,const char **argv,const char *prefix);int cmd_grep (int argc,const char **argv,const char *prefix);
builtin/fsmonitor--daemon.c new file +46 -0
46 lines not on this page · raw message
git.c +1 -0
@@ -537,6 +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 }, { "gc", cmd_gc, RUN_SETUP }, { "get-tar-commit-id", cmd_get_tar_commit_id, NO_PARSEOPT }, { "grep", cmd_grep, RUN_SETUP_GENTLY },
Signature · 1 line
gitgitgadget
PATCH v6 7/30 fsmonitor--daemon: implement 'stop' and 'status' commands
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Implement stop and status client commands to control and query the status of a fsmonitor--daemon server process (and implicitly start a server process if necessary).
Later commits will implement the actual server and monitor the file system.
--- builtin/fsmonitor--daemon.c | 51 +++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+)
builtin/fsmonitor--daemon.c +51 -0
@@ -7,10 +7,55 @@#include "khash.h" static const char *const builtin_fsmonitor__daemon_usage[] = { +N_ ("git fsmonitor--daemon stop" ), +N_ ("git fsmonitor--daemon status" ),NULL };#ifdef HAVE_FSMONITOR_DAEMON_BACKEND +/* +* Acting as a CLIENT. +* +* Send a "quit" command to the `git-fsmonitor--daemon` (if running) +* and wait for it to shutdown. +*/ +static int do_as_client__send_stop (void ) +{ +struct strbuf answer =STRBUF_INIT ; +int ret; + + ret =fsmonitor_ipc__send_command ("quit" , &answer); + +/* The quit command does not return any response data. */ +strbuf_release (&answer); + +if (ret) +return ret; + +trace2_region_enter ("fsm_client" ,"polling-for-daemon-exit" ,NULL ); +while (fsmonitor_ipc__get_state () ==IPC_STATE__LISTENING ) +sleep_millisec (50 ); +trace2_region_leave ("fsm_client" ,"polling-for-daemon-exit" ,NULL ); + +return 0 ; +} + +static int do_as_client__status (void ) +{ +enum ipc_active_state state =fsmonitor_ipc__get_state (); + +switch (state) { +case IPC_STATE__LISTENING : +printf (_ ("fsmonitor-daemon is watching '%s'\n" ), + the_repository->worktree); +return 0 ; + +default : +printf (_ ("fsmonitor-daemon is not watching '%s'\n" ), + the_repository->worktree); +return 1 ; + } +}int cmd_fsmonitor__daemon (int argc,const char **argv,const char *prefix) {
@@ -28,6 +73,12 @@ int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix)usage_with_options (builtin_fsmonitor__daemon_usage, options); subcmd = argv[0 ]; +if (!strcmp (subcmd,"stop" )) +return !!do_as_client__send_stop (); + +if (!strcmp (subcmd,"status" )) +return !!do_as_client__status (); +die (_ ("Unhandled subcommand '%s'" ), subcmd); }
Signature · 1 line
gitgitgadget
PATCH v6 8/30 compat/fsmonitor/fsm-listen-win32: stub in backend for Windows
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Stub in empty filesystem listener backend for fsmonitor--daemon on Windows.
--- Makefile | 13 ++++++++ compat/fsmonitor/fsm-listen-win32.c | 21 +++++++++++++ compat/fsmonitor/fsm-listen.h | 49 +++++++++++++++++++++++++++++ config.mak.uname | 10 ++++++ contrib/buildsystems/CMakeLists.txt | 7 +++++ repo-settings.c | 1 + 6 files changed, 101 insertions(+) create mode 100644 compat/fsmonitor/fsm-listen-win32.c create mode 100644 compat/fsmonitor/fsm-listen.h
Makefile +13 -0
@@ -470,6 +470,11 @@ all:: # directory, and the JSON compilation database 'compile_commands.json' will be # created at the root of the repository. # +# 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. +# # 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
@@ -1968,6 +1973,11 @@ ifdef NEED_ACCESS_ROOT_HANDLER COMPAT_OBJS += compat/access.o endif +ifdef FSMONITOR_DAEMON_BACKEND + COMPAT_CFLAGS += -DHAVE_FSMONITOR_DAEMON_BACKEND + COMPAT_OBJS += compat/fsmonitor/fsm-listen-$(FSMONITOR_DAEMON_BACKEND).o +endif + ifeq ($(TCLTK_PATH),) NO_TCLTK = NoThanks endif
@@ -2887,6 +2897,9 @@ GIT-BUILD-OPTIONS: FORCE @echo DC_SHA1=\''$(subst ','\'',$(subst ','\'',$(DC_SHA1)))'\' >>$@+ @echo SANITIZE_LEAK=\''$(subst ','\'',$(subst ','\'',$(SANITIZE_LEAK)))'\' >>$@+ @echo X=\'$(X)\' >>$@+ +ifdef FSMONITOR_DAEMON_BACKEND + @echo FSMONITOR_DAEMON_BACKEND=\''$(subst ','\'',$(subst ','\'',$(FSMONITOR_DAEMON_BACKEND)))'\' >>$@+ +endif ifdef TEST_OUTPUT_DIRECTORY @echo TEST_OUTPUT_DIRECTORY=\''$(subst ','\'',$(subst ','\'',$(TEST_OUTPUT_DIRECTORY)))'\' >>$@+ endif
compat/fsmonitor/fsm-listen-win32.c new file +21 -0
@@ -0,0 +1,21 @@ +#include "cache.h" +#include "config.h" +#include "fsmonitor.h" +#include "fsm-listen.h" + +void fsm_listen__stop_async (struct fsmonitor_daemon_state *state) +{ +} + +void fsm_listen__loop (struct fsmonitor_daemon_state *state) +{ +} + +int fsm_listen__ctor (struct fsmonitor_daemon_state *state) +{ +return -1 ; +} + +void fsm_listen__dtor (struct fsmonitor_daemon_state *state) +{ +}
compat/fsmonitor/fsm-listen.h new file +49 -0
49 lines not on this page · raw message
config.mak.uname +10 -0
@@ -435,6 +435,11 @@ ifeq ($(uname_S),Windows) # so we don't need this: # # SNPRINTF_RETURNS_BOGUS = YesPlease + + # The builtin FSMonitor requires Named Pipes and Threads on Windows. + # These are always available, so we do not have to conditionally + # support it. + FSMONITOR_DAEMON_BACKEND = win32 NO_SVN_TESTS = YesPlease RUNTIME_PREFIX = YesPlease HAVE_WPGMPTR = YesWeDo
@@ -619,6 +624,11 @@ ifeq ($(uname_S),MINGW) NO_STRTOUMAX = YesPlease NO_MKDTEMP = YesPlease NO_SVN_TESTS = YesPlease + + # The builtin FSMonitor requires Named Pipes and Threads on Windows. + # These are always available, so we do not have to conditionally + # support it. + FSMONITOR_DAEMON_BACKEND = win32 RUNTIME_PREFIX = YesPlease HAVE_WPGMPTR = YesWeDo NO_ST_BLOCKS_IN_STRUCT_STAT = YesPlease
contrib/buildsystems/CMakeLists.txt +7 -0
@@ -285,6 +285,13 @@ else() endif() endif() +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) + endif() +endif() + set(EXE_EXTENSION ${CMAKE_EXECUTABLE_SUFFIX}) #header checks
repo-settings.c +1 -0
@@ -2,6 +2,7 @@#include "config.h" #include "repository.h" #include "midx.h" +#include "compat/fsmonitor/fsm-listen.h" static void repo_cfg_bool (struct repository *r,const char *key,int *dest,int def)
Signature · 1 line
gitgitgadget
PATCH v6 9/30 compat/fsmonitor/fsm-listen-darwin: stub in backend for Darwin
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Stub in empty implementation of fsmonitor--daemon
backend for Darwin (aka MacOS).
--- compat/fsmonitor/fsm-listen-darwin.c | 20 ++++++++++++++++++++ config.mak.uname | 10 ++++++++++ contrib/buildsystems/CMakeLists.txt | 3 +++ 3 files changed, 33 insertions(+) create mode 100644 compat/fsmonitor/fsm-listen-darwin.c
compat/fsmonitor/fsm-listen-darwin.c new file +20 -0
@@ -0,0 +1,20 @@ +#include "cache.h" +#include "fsmonitor.h" +#include "fsm-listen.h" + +int fsm_listen__ctor (struct fsmonitor_daemon_state *state) +{ +return -1 ; +} + +void fsm_listen__dtor (struct fsmonitor_daemon_state *state) +{ +} + +void fsm_listen__stop_async (struct fsmonitor_daemon_state *state) +{ +} + +void fsm_listen__loop (struct fsmonitor_daemon_state *state) +{ +}
config.mak.uname +10 -0
@@ -157,6 +157,16 @@ ifeq ($(uname_S),Darwin) MSGFMT = /usr/local/opt/gettext/bin/msgfmt endif endif + + # The builtin FSMonitor on MacOS builds upon Simple-IPC. Both require + # Unix domain sockets and PThreads. + ifndef NO_PTHREADS + ifndef NO_UNIX_SOCKETS + FSMONITOR_DAEMON_BACKEND = darwin + endif + endif + + BASIC_LDFLAGS += -framework CoreServices endif ifeq ($(uname_S),SunOS) NEEDS_SOCKET = 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) + elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin") + add_compile_definitions(HAVE_FSMONITOR_DAEMON_BACKEND) + list(APPEND compat_SOURCES compat/fsmonitor/fsm-listen-darwin.c) endif() endif()
Signature · 1 line
gitgitgadget
PATCH v6 10/30 fsmonitor--daemon: implement 'run' command
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Implement run command to try to begin listening for file system events.
This version defines the thread structure with a single fsmonitor_fs_listen thread to watch for file system events and a simple IPC thread pool to watch for connection from Git clients over a well-known named pipe or Unix domain socket.
This commit does not actually do anything yet because the platform backends are still just stubs.
--- builtin/fsmonitor--daemon.c | 228 +++++++++++++++++++++++++++++++++++- fsmonitor--daemon.h | 34 ++++++ 2 files changed, 261 insertions(+), 1 deletion(-) create mode 100644 fsmonitor--daemon.h
builtin/fsmonitor--daemon.c +227 -1
@@ -3,16 +3,52 @@#include "parse-options.h" #include "fsmonitor.h" #include "fsmonitor-ipc.h" +#include "compat/fsmonitor/fsm-listen.h" +#include "fsmonitor--daemon.h" #include "simple-ipc.h" #include "khash.h" static const char *const builtin_fsmonitor__daemon_usage[] = { +N_ ("git fsmonitor--daemon run [<options>]" ),N_ ("git fsmonitor--daemon stop" ),N_ ("git fsmonitor--daemon status" ),NULL };#ifdef HAVE_FSMONITOR_DAEMON_BACKEND +/* +* Global state loaded from config. +*/ +#define FSMONITOR__IPC_THREADS "fsmonitor.ipcthreads" +static int fsmonitor__ipc_threads =8 ; + +#define FSMONITOR__ANNOUNCE_STARTUP "fsmonitor.announcestartup" +static int fsmonitor__announce_startup =0 ; + +static int fsmonitor_config (const char *var,const char *value,void *cb) +{ +if (!strcmp (var,FSMONITOR__IPC_THREADS )) { +int i =git_config_int (var, value); +if (i <1 ) +return error (_ ("value of '%s' out of range: %d" ), +FSMONITOR__IPC_THREADS , i); + fsmonitor__ipc_threads = i; +return 0 ; + } + +if (!strcmp (var,FSMONITOR__ANNOUNCE_STARTUP )) { +int is_bool; +int i =git_config_bool_or_int (var, value, &is_bool); +if (i <0 ) +return error (_ ("value of '%s' not bool or int: %d" ), + var, i); + fsmonitor__announce_startup = i; +return 0 ; + } + +return git_default_config (var, value, cb); +} +/* * Acting as a CLIENT. *
@@ -57,15 +93,198 @@ static int do_as_client__status(void) } } +static ipc_server_application_cb handle_client; + +static int handle_client (void *data, +const char *command,size_t command_len, +ipc_server_reply_cb *reply, +struct ipc_server_reply_data *reply_data) +{ +/* struct fsmonitor_daemon_state *state = data; */ +int result; + +/* +* The Simple IPC API now supports {char*, len} arguments, but +* FSMonitor always uses proper null-terminated strings, so +* we can ignore the command_len argument. (Trust, but verify.) +*/ +if (command_len !=strlen (command)) +BUG ("FSMonitor assumes text messages" ); + +trace2_region_enter ("fsmonitor" ,"handle_client" , the_repository); +trace2_data_string ("fsmonitor" , the_repository,"request" , command); + + result =0 ;/* TODO Do something here. */ + +trace2_region_leave ("fsmonitor" ,"handle_client" , the_repository); + +return result; +} + +static void *fsm_listen__thread_proc (void *_state) +{ +struct fsmonitor_daemon_state *state = _state; + +trace2_thread_start ("fsm-listen" ); + +trace_printf_key (&trace_fsmonitor,"Watching: worktree '%s'" , + state->path_worktree_watch.buf); +if (state->nr_paths_watching >1 ) +trace_printf_key (&trace_fsmonitor,"Watching: gitdir '%s'" , + state->path_gitdir_watch.buf); + +fsm_listen__loop (state); + +trace2_thread_exit (); +return NULL ; +} + +static int fsmonitor_run_daemon_1 (struct fsmonitor_daemon_state *state) +{ +struct ipc_server_opts ipc_opts = { + .nr_threads = fsmonitor__ipc_threads, + +/* +* We know that there are no other active threads yet, +* so we can let the IPC layer temporarily chdir() if +* it needs to when creating the server side of the +* Unix domain socket. +*/ + .uds_disallow_chdir =0 + }; + +/* +* Start the IPC thread pool before the we've started the file +* system event listener thread so that we have the IPC handle +* before we need it. +*/ +if (ipc_server_run_async (&state->ipc_server_data, +fsmonitor_ipc__get_path (), &ipc_opts, + handle_client, state)) +return error_errno ( +_ ("could not start IPC thread pool on '%s'" ), +fsmonitor_ipc__get_path ()); + +/* +* Start the fsmonitor listener thread to collect filesystem +* events. +*/ +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" )); + } + +/* +* The daemon is now fully functional in background threads. +* Wait for the IPC thread pool to shutdown (whether by client +* request or from filesystem activity). +*/ +ipc_server_await (state->ipc_server_data); + +/* +* The fsmonitor listener thread may have received a shutdown +* 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 ); + +return state->error_code; +} + +static int fsmonitor_run_daemon (void ) +{ +struct fsmonitor_daemon_state state; +int err; + +memset (&state,0 ,sizeof (state)); + +pthread_mutex_init (&state.main_lock,NULL ); + state.error_code =0 ; + state.current_token_data =NULL ; + +/* Prepare to (recursively) watch the <worktree-root> directory. */ +strbuf_init (&state.path_worktree_watch,0 ); +strbuf_addstr (&state.path_worktree_watch,absolute_path (get_git_work_tree ())); + state.nr_paths_watching =1 ; + +/* +* We create and delete cookie files somewhere inside the .git +* directory to help us keep sync with the file system. If +* ".git" is not a directory, then <gitdir> is not inside the +* cone of <worktree-root>, so set up a second watch to watch +* the <gitdir> so that we get events for the cookie files. +*/ +strbuf_init (&state.path_gitdir_watch,0 ); +strbuf_addbuf (&state.path_gitdir_watch, &state.path_worktree_watch); +strbuf_addstr (&state.path_gitdir_watch,"/.git" ); +if (!is_directory (state.path_gitdir_watch.buf)) { +strbuf_reset (&state.path_gitdir_watch); +strbuf_addstr (&state.path_gitdir_watch,absolute_path (get_git_dir ())); + state.nr_paths_watching =2 ; + } + +/* +* Confirm that we can create platform-specific resources for the +* filesystem listener before we bother starting all the threads. +*/ +if (fsm_listen__ctor (&state)) { + err =error (_ ("could not initialize listener thread" )); + goto done; + } + + err =fsmonitor_run_daemon_1 (&state); + +done: +pthread_mutex_destroy (&state.main_lock); +fsm_listen__dtor (&state); + +ipc_server_free (state.ipc_server_data); + +strbuf_release (&state.path_worktree_watch); +strbuf_release (&state.path_gitdir_watch); + +return err; +} + +static int try_to_run_foreground_daemon (void ) +{ +/* +* Technically, we don't need to probe for an existing daemon +* process, since we could just call `fsmonitor_run_daemon()` +* and let it fail if the pipe/socket is busy. +* +* However, this method gives us a nicer error message for a +* common error case. +*/ +if (fsmonitor_ipc__get_state () ==IPC_STATE__LISTENING ) +die ("fsmonitor--daemon is already running '%s'" , + the_repository->worktree); + +if (fsmonitor__announce_startup) { +fprintf (stderr,_ ("running fsmonitor-daemon in '%s'\n" ), + the_repository->worktree); +fflush (stderr); + } + +return !!fsmonitor_run_daemon (); +} +int cmd_fsmonitor__daemon (int argc,const char **argv,const char *prefix) {const char *subcmd;struct option options[] = { +OPT_INTEGER (0 ,"ipc-threads" , + &fsmonitor__ipc_threads, +N_ ("use <n> ipc worker threads" )),OPT_END () }; -git_config (git_default_config,NULL ); +git_config (fsmonitor_config,NULL ); argc =parse_options (argc, argv, prefix, options, builtin_fsmonitor__daemon_usage,0 );
@@ -73,6 +292,13 @@ int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix)usage_with_options (builtin_fsmonitor__daemon_usage, options); subcmd = argv[0 ]; +if (fsmonitor__ipc_threads <1 ) +die (_ ("invalid 'ipc-threads' value (%d)" ), + fsmonitor__ipc_threads); + +if (!strcmp (subcmd,"run" )) +return !!try_to_run_foreground_daemon (); +if (!strcmp (subcmd,"stop" ))return !!do_as_client__send_stop ();
fsmonitor--daemon.h new file +34 -0
@@ -0,0 +1,34 @@ +#ifndef FSMONITOR_DAEMON_H +#define FSMONITOR_DAEMON_H + +#ifdef HAVE_FSMONITOR_DAEMON_BACKEND + +#include "cache.h" +#include "dir.h" +#include "run-command.h" +#include "simple-ipc.h" +#include "thread-utils.h" + +struct fsmonitor_batch ; +struct fsmonitor_token_data ; + +struct fsmonitor_daemon_backend_data ;/* opaque platform-specific data */ + +struct fsmonitor_daemon_state { +pthread_t listener_thread; +pthread_mutex_t main_lock; + +struct strbuf path_worktree_watch; +struct strbuf path_gitdir_watch; +int nr_paths_watching; + +struct fsmonitor_token_data *current_token_data; + +int error_code; +struct fsmonitor_daemon_backend_data *backend_data; + +struct ipc_server_data *ipc_server_data; +}; + +#endif /* HAVE_FSMONITOR_DAEMON_BACKEND */ +#endif /* FSMONITOR_DAEMON_H */
Signature · 1 line
gitgitgadget
PATCH v6 11/30 fsmonitor--daemon: implement 'start' command
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Implement 'git fsmonitor--daemon start' command. This command starts an instance of 'git fsmonitor--daemon run' in the background using the new 'start_bg_command()' function.
We avoid the fork-and-call technique on Unix systems in favor of a fork-and-exec technique. This gives us more uniform Trace2 child-* events. It also makes our usage more consistent with Windows usage.
On Windows, teach 'git fsmonitor--daemon run' to optionally call 'FreeConsole()' to release handles to the inherited Win32 console (despite being passed invalid handles for stdin/out/err). Without this, command prompts and powershell terminal windows could hang in "exit" until the last background child process exited or released their Win32 console handle. (This was not seen with git-bash shells because they don't have a Win32 console attached to them.)
--- builtin/fsmonitor--daemon.c | 109 +++++++++++++++++++++++++++++++++++- 1 file changed, 107 insertions(+), 2 deletions(-)
builtin/fsmonitor--daemon.c +107 -2
@@ -9,6 +9,7 @@#include "khash.h" static const char *const builtin_fsmonitor__daemon_usage[] = { +N_ ("git fsmonitor--daemon start [<options>]" ),N_ ("git fsmonitor--daemon run [<options>]" ),N_ ("git fsmonitor--daemon stop" ),N_ ("git fsmonitor--daemon status" ),
@@ -22,6 +23,9 @@ static const char * const builtin_fsmonitor__daemon_usage[] = {#define FSMONITOR__IPC_THREADS "fsmonitor.ipcthreads"static int fsmonitor__ipc_threads =8 ; +#define FSMONITOR__START_TIMEOUT "fsmonitor.starttimeout" +static int fsmonitor__start_timeout_sec =60 ; +#define FSMONITOR__ANNOUNCE_STARTUP "fsmonitor.announcestartup"static int fsmonitor__announce_startup =0 ;
@@ -36,6 +40,15 @@ static int fsmonitor_config(const char *var, const char *value, void *cb)return 0 ; } +if (!strcmp (var,FSMONITOR__START_TIMEOUT )) { +int i =git_config_int (var, value); +if (i <0 ) +return error (_ ("value of '%s' out of range: %d" ), +FSMONITOR__START_TIMEOUT , i); + fsmonitor__start_timeout_sec = i; +return 0 ; + } +if (!strcmp (var,FSMONITOR__ANNOUNCE_STARTUP )) {int is_bool;int i =git_config_bool_or_int (var, value, &is_bool);
@@ -250,7 +263,7 @@ done:return err; } -static int try_to_run_foreground_daemon (void ) +static int try_to_run_foreground_daemon (int detach_console) {/* * Technically, we don't need to probe for an existing daemon
@@ -270,17 +283,106 @@ static int try_to_run_foreground_daemon(void)fflush (stderr); } +#ifdef GIT_WINDOWS_NATIVE +if (detach_console) +FreeConsole (); +#endif +return !!fsmonitor_run_daemon (); } +static start_bg_wait_cb bg_wait_cb; + +static int bg_wait_cb (const struct child_process *cp,void *cb_data) +{ +enum ipc_active_state s =fsmonitor_ipc__get_state (); + +switch (s) { +case IPC_STATE__LISTENING : +/* child is "ready" */ +return 0 ; + +case IPC_STATE__NOT_LISTENING : +case IPC_STATE__PATH_NOT_FOUND : +/* give child more time */ +return 1 ; + +default : +case IPC_STATE__INVALID_PATH : +case IPC_STATE__OTHER_ERROR : +/* all the time in world won't help */ +return -1 ; + } +} + +static int try_to_start_background_daemon (void ) +{ +struct child_process cp =CHILD_PROCESS_INIT ; +enum start_bg_result sbgr; + +/* +* Before we try to create a background daemon process, see +* if a daemon process is already listening. This makes it +* easier for us to report an already-listening error to the +* console, since our spawn/daemon can only report the success +* of creating the background process (and not whether it +* immediately exited). +*/ +if (fsmonitor_ipc__get_state () ==IPC_STATE__LISTENING ) +die ("fsmonitor--daemon is already running '%s'" , + the_repository->worktree); + +if (fsmonitor__announce_startup) { +fprintf (stderr,_ ("starting fsmonitor-daemon in '%s'\n" ), + the_repository->worktree); +fflush (stderr); + } + + cp.git_cmd =1 ; + +strvec_push (&cp.args,"fsmonitor--daemon" ); +strvec_push (&cp.args,"run" ); +strvec_push (&cp.args,"--detach" ); +strvec_pushf (&cp.args,"--ipc-threads=%d" , fsmonitor__ipc_threads); + + cp.no_stdin =1 ; + cp.no_stdout =1 ; + cp.no_stderr =1 ; + + sbgr =start_bg_command (&cp, bg_wait_cb,NULL , + fsmonitor__start_timeout_sec); + +switch (sbgr) { +case SBGR_READY : +return 0 ; + +default : +case SBGR_ERROR : +case SBGR_CB_ERROR : +return error ("daemon failed to start" ); + +case SBGR_TIMEOUT : +return error ("daemon not online yet" ); + +case SBGR_DIED : +return error ("daemon terminated" ); + } +} +int cmd_fsmonitor__daemon (int argc,const char **argv,const char *prefix) {const char *subcmd; +int detach_console =0 ;struct option options[] = { +OPT_BOOL (0 ,"detach" , &detach_console,N_ ("detach from console" )),OPT_INTEGER (0 ,"ipc-threads" , &fsmonitor__ipc_threads,N_ ("use <n> ipc worker threads" )), +OPT_INTEGER (0 ,"start-timeout" , + &fsmonitor__start_timeout_sec, +N_ ("max seconds to wait for background daemon startup" )), +OPT_END () };
@@ -296,8 +398,11 @@ int cmd_fsmonitor__daemon(int argc, const char **argv, const char *prefix)die (_ ("invalid 'ipc-threads' value (%d)" ), fsmonitor__ipc_threads); +if (!strcmp (subcmd,"start" )) +return !!try_to_start_background_daemon (); +if (!strcmp (subcmd,"run" )) -return !!try_to_run_foreground_daemon (); +return !!try_to_run_foreground_daemon (detach_console);if (!strcmp (subcmd,"stop" ))return !!do_as_client__send_stop ();
Signature · 1 line
gitgitgadget
PATCH v6 12/30 fsmonitor--daemon: add pathname classification
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Teach fsmonitor--daemon to classify relative and absolute pathnames and decide how they should be handled. This will be used by the platform-specific backend to respond to each filesystem event.
When we register for filesystem notifications on a directory, we get events for everything (recursively) in the directory. We want to report to clients changes to tracked and untracked paths within the working directory proper. We do not want to report changes within the .git directory, for example.
This classification will be used in a later commit by the different backends to classify paths as events are received.
--- builtin/fsmonitor--daemon.c | 81 ++++++++++++++++++++++++++++++++++ fsmonitor--daemon.h | 87 +++++++++++++++++++++++++++++++++++++ 2 files changed, 168 insertions(+)
builtin/fsmonitor--daemon.c +81 -0
@@ -134,6 +134,87 @@ static int handle_client(void *data,return result; } +#define FSMONITOR_COOKIE_PREFIX ".fsmonitor-daemon-" + +enum fsmonitor_path_type fsmonitor_classify_path_workdir_relative ( +const char *rel) +{ +if (fspathncmp (rel,".git" ,4 )) +return IS_WORKDIR_PATH ; + rel +=4 ; + +if (!*rel) +return IS_DOT_GIT ; +if (*rel !='/' ) +return IS_WORKDIR_PATH ;/* e.g. .gitignore */ + rel++; + +if (!fspathncmp (rel,FSMONITOR_COOKIE_PREFIX , +strlen (FSMONITOR_COOKIE_PREFIX ))) +return IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX ; + +return IS_INSIDE_DOT_GIT ; +} + +enum fsmonitor_path_type fsmonitor_classify_path_gitdir_relative ( +const char *rel) +{ +if (!fspathncmp (rel,FSMONITOR_COOKIE_PREFIX , +strlen (FSMONITOR_COOKIE_PREFIX ))) +return IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX ; + +return IS_INSIDE_GITDIR ; +} + +static enum fsmonitor_path_type try_classify_workdir_abs_path ( +struct fsmonitor_daemon_state *state, +const char *path) +{ +const char *rel; + +if (fspathncmp (path, state->path_worktree_watch.buf, + state->path_worktree_watch.len)) +return IS_OUTSIDE_CONE ; + + rel = path + state->path_worktree_watch.len; + +if (!*rel) +return IS_WORKDIR_PATH ;/* it is the root dir exactly */ +if (*rel !='/' ) +return IS_OUTSIDE_CONE ; + rel++; + +return fsmonitor_classify_path_workdir_relative (rel); +} + +enum fsmonitor_path_type fsmonitor_classify_path_absolute ( +struct fsmonitor_daemon_state *state, +const char *path) +{ +const char *rel; +enum fsmonitor_path_type t; + + t =try_classify_workdir_abs_path (state, path); +if (state->nr_paths_watching ==1 ) +return t; +if (t !=IS_OUTSIDE_CONE ) +return t; + +if (fspathncmp (path, state->path_gitdir_watch.buf, + state->path_gitdir_watch.len)) +return IS_OUTSIDE_CONE ; + + rel = path + state->path_gitdir_watch.len; + +if (!*rel) +return IS_GITDIR ;/* it is the <gitdir> exactly */ +if (*rel !='/' ) +return IS_OUTSIDE_CONE ; + rel++; + +return fsmonitor_classify_path_gitdir_relative (rel); +} +static void *fsm_listen__thread_proc (void *_state) {struct fsmonitor_daemon_state *state = _state;
fsmonitor--daemon.h +87 -0
92 lines not on this page · raw message
Signature · 1 line
gitgitgadget
PATCH v6 13/30 fsmonitor--daemon: define token-ids
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Teach fsmonitor--daemon to create token-ids and define the overall token naming scheme.
--- builtin/fsmonitor--daemon.c | 116 +++++++++++++++++++++++++++++++++++- 1 file changed, 115 insertions(+), 1 deletion(-)
builtin/fsmonitor--daemon.c +115 -1
@@ -106,6 +106,120 @@ static int do_as_client__status(void) } } +/* +* Requests to and from a FSMonitor Protocol V2 provider use an opaque +* "token" as a virtual timestamp. Clients can request a summary of all +* created/deleted/modified files relative to a token. In the response, +* clients receive a new token for the next (relative) request. +* +* +* Token Format +* ============ +* +* The contents of the token are private and provider-specific. +* +* For the built-in fsmonitor--daemon, we define a token as follows: +* +* "builtin" ":" <token_id> ":" <sequence_nr> +* +* The "builtin" prefix is used as a namespace to avoid conflicts +* with other providers (such as Watchman). +* +* The <token_id> is an arbitrary OPAQUE string, such as a GUID, +* UUID, or {timestamp,pid}. It is used to group all filesystem +* events that happened while the daemon was monitoring (and in-sync +* with the filesystem). +* +* Unlike FSMonitor Protocol V1, it is not defined as a timestamp +* and does not define less-than/greater-than relationships. +* (There are too many race conditions to rely on file system +* event timestamps.) +* +* The <sequence_nr> is a simple integer incremented whenever the +* daemon needs to make its state public. For example, if 1000 file +* system events come in, but no clients have requested the data, +* the daemon can continue to accumulate file changes in the same +* bin and does not need to advance the sequence number. However, +* as soon as a client does arrive, the daemon needs to start a new +* bin and increment the sequence number. +* +* The sequence number serves as the boundary between 2 sets +* of bins -- the older ones that the client has already seen +* and the newer ones that it hasn't. +* +* When a new <token_id> is created, the <sequence_nr> is reset to +* zero. +* +* +* About Token Ids +* =============== +* +* A new token_id is created: +* +* [1] each time the daemon is started. +* +* [2] any time that the daemon must re-sync with the filesystem +* (such as when the kernel drops or we miss events on a very +* active volume). +* +* [3] in response to a client "flush" command (for dropped event +* testing). +* +* When a new token_id is created, the daemon is free to discard all +* cached filesystem events associated with any previous token_ids. +* Events associated with a non-current token_id will never be sent +* to a client. A token_id change implicitly means that the daemon +* has gap in its event history. +* +* Therefore, clients that present a token with a stale (non-current) +* token_id will always be given a trivial response. +*/ +struct fsmonitor_token_data { +struct strbuf token_id; +struct fsmonitor_batch *batch_head; +struct fsmonitor_batch *batch_tail; +uint64_t client_ref_count; +}; + +static struct fsmonitor_token_data *fsmonitor_new_token_data (void ) +{ +static int test_env_value =-1 ; +static uint64_t flush_count =0 ; +struct fsmonitor_token_data *token; + +CALLOC_ARRAY (token,1 ); + +strbuf_init (&token->token_id,0 ); + token->batch_head =NULL ; + token->batch_tail =NULL ; + token->client_ref_count =0 ; + +if (test_env_value <0 ) + test_env_value =git_env_bool ("GIT_TEST_FSMONITOR_TOKEN" ,0 ); + +if (!test_env_value) { +struct timeval tv; +struct tm tm; +time_t secs; + +gettimeofday (&tv,NULL ); + secs = tv.tv_sec; +gmtime_r (&secs, &tm); + +strbuf_addf (&token->token_id, +"%" PRIu64".%d.%4d%02d%02dT%02d%02d%02d.%06ldZ" , + flush_count++, +getpid (), + tm.tm_year +1900 , tm.tm_mon +1 , tm.tm_mday, + tm.tm_hour, tm.tm_min, tm.tm_sec, + (long )tv.tv_usec); + }else { +strbuf_addf (&token->token_id,"test_%08x" , test_env_value++); + } + +return token; +} +static ipc_server_application_cb handle_client; static int handle_client(void *data,
@@ -298,7 +412,7 @@ static int fsmonitor_run_daemon(void)pthread_mutex_init (&state.main_lock,NULL ); state.error_code =0 ; - state.current_token_data =NULL ; + state.current_token_data =fsmonitor_new_token_data ();/* Prepare to (recursively) watch the <worktree-root> directory. */ strbuf_init (&state.path_worktree_watch,0 );
Signature · 1 line
gitgitgadget
PATCH v6 14/30 fsmonitor--daemon: create token-based changed path cache
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Teach fsmonitor--daemon to build a list of changed paths and associate them with a token-id. This will be used by the platform-specific backends to accumulate changed paths in response to filesystem events.
The platform-specific file system listener thread receives file system events containing one or more changed pathnames (with whatever bucketing or grouping that is convenient for the file system). These paths are accumulated (without locking) by the file system layer into a fsmonitor_batch.
When the file system layer has drained the kernel event queue, it will "publish" them to our token queue and make them visible to concurrent client worker threads. The token layer is free to combine and/or de-dup paths within these batches for efficient presentation to clients.
--- builtin/fsmonitor--daemon.c | 230 +++++++++++++++++++++++++++++++++++- fsmonitor--daemon.h | 40 +++++++ 2 files changed, 268 insertions(+), 2 deletions(-)
builtin/fsmonitor--daemon.c +228 -2
@@ -181,17 +181,27 @@ struct fsmonitor_token_data {uint64_t client_ref_count; }; +struct fsmonitor_batch { +struct fsmonitor_batch *next; +uint64_t batch_seq_nr; +const char **interned_paths; +size_t nr, alloc; +time_t pinned_time; +}; +static struct fsmonitor_token_data *fsmonitor_new_token_data (void ) {static int test_env_value =-1 ;static uint64_t flush_count =0 ;struct fsmonitor_token_data *token; +struct fsmonitor_batch *batch;CALLOC_ARRAY (token,1 ); + batch =fsmonitor_batch__new ();strbuf_init (&token->token_id,0 ); - token->batch_head =NULL ; - token->batch_tail =NULL ; + token->batch_head = batch; + token->batch_tail = batch; token->client_ref_count =0 ;if (test_env_value <0 )
@@ -217,9 +227,143 @@ static struct fsmonitor_token_data *fsmonitor_new_token_data(void)strbuf_addf (&token->token_id,"test_%08x" , test_env_value++); } +/* +* We created a new <token_id> and are starting a new series +* of tokens with a zero <seq_nr>. +* +* Since clients cannot guess our new (non test) <token_id> +* they will always receive a trivial response (because of the +* mismatch on the <token_id>). The trivial response will +* tell them our new <token_id> so that subsequent requests +* will be relative to our new series. (And when sending that +* response, we pin the current head of the batch list.) +* +* Even if the client correctly guesses the <token_id>, their +* request of "builtin:<token_id>:0" asks for all changes MORE +* RECENT than batch/bin 0. +* +* This implies that it is a waste to accumulate paths in the +* initial batch/bin (because they will never be transmitted). +* +* So the daemon could be running for days and watching the +* file system, but doesn't need to actually accumulate any +* paths UNTIL we need to set a reference point for a later +* relative request. +* +* However, it is very useful for testing to always have a +* reference point set. Pin batch 0 to force early file system +* events to accumulate. +*/ +if (test_env_value) + batch->pinned_time =time (NULL ); +return token; } +struct fsmonitor_batch *fsmonitor_batch__new (void ) +{ +struct fsmonitor_batch *batch; + +CALLOC_ARRAY (batch,1 ); + +return batch; +} + +void fsmonitor_batch__free_list (struct fsmonitor_batch *batch) +{ +while (batch) { +struct fsmonitor_batch *next = batch->next; + +/* +* The actual strings within the array of this batch +* are interned, so we don't own them. We only own +* the array. +*/ +free (batch->interned_paths); +free (batch); + + batch = next; + } +} + +void fsmonitor_batch__add_path (struct fsmonitor_batch *batch, +const char *path) +{ +const char *interned_path =strintern (path); + +trace_printf_key (&trace_fsmonitor,"event: %s" , interned_path); + +ALLOC_GROW (batch->interned_paths, batch->nr +1 , batch->alloc); + batch->interned_paths[batch->nr++] = interned_path; +} + +static void fsmonitor_batch__combine (struct fsmonitor_batch *batch_dest, +const struct fsmonitor_batch *batch_src) +{ +size_t k; + +ALLOC_GROW (batch_dest->interned_paths, + batch_dest->nr + batch_src->nr +1 , + batch_dest->alloc); + +for (k =0 ; k < batch_src->nr; k++) + batch_dest->interned_paths[batch_dest->nr++] = + batch_src->interned_paths[k]; +} + +static void fsmonitor_free_token_data (struct fsmonitor_token_data *token) +{ +if (!token) +return ; + +assert (token->client_ref_count ==0 ); + +strbuf_release (&token->token_id); + +fsmonitor_batch__free_list (token->batch_head); + +free (token); +} + +/* +* Flush all of our cached data about the filesystem. Call this if we +* lose sync with the filesystem and miss some notification events. +* +* [1] If we are missing events, then we no longer have a complete +* history of the directory (relative to our current start token). +* We should create a new token and start fresh (as if we just +* booted up). +* +* If there are no concurrent threads reading the current token data +* series, we can free it now. Otherwise, let the last reader free +* it. +* +* Either way, the old token data series is no longer associated with +* our state data. +*/ +static void with_lock__do_force_resync (struct fsmonitor_daemon_state *state) +{ +/* assert current thread holding state->main_lock */ + +struct fsmonitor_token_data *free_me =NULL ; +struct fsmonitor_token_data *new_one =NULL ; + + new_one =fsmonitor_new_token_data (); + +if (state->current_token_data->client_ref_count ==0 ) + free_me = state->current_token_data; + state->current_token_data = new_one; + +fsmonitor_free_token_data (free_me); +} + +void fsmonitor_force_resync (struct fsmonitor_daemon_state *state) +{ +pthread_mutex_lock (&state->main_lock); +with_lock__do_force_resync (state); +pthread_mutex_unlock (&state->main_lock); +} +static ipc_server_application_cb handle_client; static int handle_client(void *data,
@@ -329,6 +473,81 @@ enum fsmonitor_path_type fsmonitor_classify_path_absolute(return fsmonitor_classify_path_gitdir_relative (rel); } +/* +* We try to combine small batches at the front of the batch-list to avoid +* having a long list. This hopefully makes it a little easier when we want +* to truncate and maintain the list. However, we don't want the paths array +* to just keep growing and growing with realloc, so we insert an arbitrary +* limit. +*/ +#define MY_COMBINE_LIMIT (1024) + +void fsmonitor_publish (struct fsmonitor_daemon_state *state, +struct fsmonitor_batch *batch, +const struct string_list *cookie_names) +{ +if (!batch && !cookie_names->nr) +return ; + +pthread_mutex_lock (&state->main_lock); + +if (batch) { +struct fsmonitor_batch *head; + + head = state->current_token_data->batch_head; +if (!head) { +BUG ("token does not have batch" ); + }else if (head->pinned_time) { +/* +* We cannot alter the current batch list +* because: +* +* [a] it is being transmitted to at least one +* client and the handle_client() thread has a +* ref-count, but not a lock on the batch list +* starting with this item. +* +* [b] it has been transmitted in the past to +* at least one client such that future +* requests are relative to this head batch. +* +* So, we can only prepend a new batch onto +* the front of the list. +*/ + batch->batch_seq_nr = head->batch_seq_nr +1 ; + batch->next = head; + state->current_token_data->batch_head = batch; + }else if (!head->batch_seq_nr) { +/* +* Batch 0 is unpinned. See the note in +* `fsmonitor_new_token_data()` about why we +* don't need to accumulate these paths. +*/ +fsmonitor_batch__free_list (batch); + }else if (head->nr + batch->nr >MY_COMBINE_LIMIT ) { +/* +* The head batch in the list has never been +* transmitted to a client, but folding the +* contents of the new batch onto it would +* exceed our arbitrary limit, so just prepend +* the new batch onto the list. +*/ + batch->batch_seq_nr = head->batch_seq_nr +1 ; + batch->next = head; + state->current_token_data->batch_head = batch; + }else { +/* +* We are free to add the paths in the given +* batch onto the end of the current head batch. +*/ +fsmonitor_batch__combine (head, batch); +fsmonitor_batch__free_list (batch); + } + } + +pthread_mutex_unlock (&state->main_lock); +} +static void *fsm_listen__thread_proc (void *_state) {struct fsmonitor_daemon_state *state = _state;
@@ -343,6 +562,13 @@ static void *fsm_listen__thread_proc(void *_state)fsm_listen__loop (state); +pthread_mutex_lock (&state->main_lock); +if (state->current_token_data && + state->current_token_data->client_ref_count ==0 ) +fsmonitor_free_token_data (state->current_token_data); + state->current_token_data =NULL ; +pthread_mutex_unlock (&state->main_lock); +trace2_thread_exit ();return NULL ; }
fsmonitor--daemon.h +40 -0
@@ -12,6 +12,27 @@struct fsmonitor_batch ;struct fsmonitor_token_data ; +/* +* Create a new batch of path(s). The returned batch is considered +* private and not linked into the fsmonitor daemon state. The caller +* should fill this batch with one or more paths and then publish it. +*/ +struct fsmonitor_batch *fsmonitor_batch__new (void ); + +/* +* Free the list of batches starting with this one. +*/ +void fsmonitor_batch__free_list (struct fsmonitor_batch *batch); + +/* +* Add this path to this batch of modified files. +* +* The batch should be private and NOT (yet) linked into the fsmonitor +* daemon state and therefore not yet visible to worker threads and so +* no locking is required. +*/ +void fsmonitor_batch__add_path (struct fsmonitor_batch *batch,const char *path); +struct fsmonitor_daemon_backend_data ;/* opaque platform-specific data */ struct fsmonitor_daemon_state {
@@ -117,5 +138,24 @@ enum fsmonitor_path_type fsmonitor_classify_path_absolute(struct fsmonitor_daemon_state *state, const char *path); +/* +* Prepend the this batch of path(s) onto the list of batches associated +* with the current token. This makes the batch visible to worker threads. +* +* The caller no longer owns the batch and must not free it. +* +* Wake up the client threads waiting on these cookies. +*/ +void fsmonitor_publish (struct fsmonitor_daemon_state *state, +struct fsmonitor_batch *batch, +const struct string_list *cookie_names); + +/* +* If the platform-specific layer loses sync with the filesystem, +* it should call this to invalidate cached data and abort waiting +* threads. +*/ +void fsmonitor_force_resync (struct fsmonitor_daemon_state *state); +#endif /* HAVE_FSMONITOR_DAEMON_BACKEND */ #endif /* FSMONITOR_DAEMON_H */
Signature · 1 line
gitgitgadget
PATCH v6 15/30 compat/fsmonitor/fsm-listen-win32: implement FSMonitor backend on Windows
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Teach the win32 backend to register a watch on the working tree root directory (recursively). Also watch the <gitdir> if it is not inside the working tree. And to collect path change notifications into batches and publish.
--- compat/fsmonitor/fsm-listen-win32.c | 565 ++++++++++++++++++++++++++++ 1 file changed, 565 insertions(+)
compat/fsmonitor/fsm-listen-win32.c +565 -0
@@ -2,20 +2,585 @@#include "config.h" #include "fsmonitor.h" #include "fsm-listen.h" +#include "fsmonitor--daemon.h" + +/* +* The documentation of ReadDirectoryChangesW() states that the maximum +* buffer size is 64K when the monitored directory is remote. +* +* Larger buffers may be used when the monitored directory is local and +* will help us receive events faster from the kernel and avoid dropped +* events. +* +* So we try to use a very large buffer and silently fallback to 64K if +* we get an error. +*/ +#define MAX_RDCW_BUF_FALLBACK (65536) +#define MAX_RDCW_BUF (65536 * 8) + +struct one_watch +{ +char buffer[MAX_RDCW_BUF ]; +DWORD buf_len; +DWORD count; + +struct strbuf path; +HANDLE hDir; +HANDLE hEvent; +OVERLAPPED overlapped; + +/* +* Is there an active ReadDirectoryChangesW() call pending. If so, we +* need to later call GetOverlappedResult() and possibly CancelIoEx(). +*/ +BOOL is_active; +}; + +struct fsmonitor_daemon_backend_data +{ +struct one_watch *watch_worktree; +struct one_watch *watch_gitdir; + +HANDLE hEventShutdown; + +HANDLE hListener[3 ];/* we don't own these handles */ +#define LISTENER_SHUTDOWN 0 +#define LISTENER_HAVE_DATA_WORKTREE 1 +#define LISTENER_HAVE_DATA_GITDIR 2 +int nr_listener_handles; +}; + +/* +* Convert the WCHAR path from the notification into UTF8 and +* then normalize it. +*/ +static int normalize_path_in_utf8 (FILE_NOTIFY_INFORMATION *info, +struct strbuf *normalized_path) +{ +int reserve; +int len =0 ; + +strbuf_reset (normalized_path); +if (!info->FileNameLength) + goto normalize; + +/* +* Pre-reserve enough space in the UTF8 buffer for +* each Unicode WCHAR character to be mapped into a +* sequence of 2 UTF8 characters. That should let us +* avoid ERROR_INSUFFICIENT_BUFFER 99.9+% of the time. +*/ + reserve = info->FileNameLength +1 ; +strbuf_grow (normalized_path, reserve); + +for (;;) { + len =WideCharToMultiByte (CP_UTF8 ,0 , info->FileName, + info->FileNameLength /sizeof (WCHAR ), + normalized_path->buf, +strbuf_avail (normalized_path) -1 , +NULL ,NULL ); +if (len >0 ) + 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); +return -1 ; + } + +strbuf_grow (normalized_path, +strbuf_avail (normalized_path) + reserve); + } + +normalize: +strbuf_setlen (normalized_path, len); +return strbuf_normalize_path (normalized_path); +}void fsm_listen__stop_async (struct fsmonitor_daemon_state *state) { +SetEvent (state->backend_data->hListener[LISTENER_SHUTDOWN ]); +} + +static struct one_watch *create_watch (struct fsmonitor_daemon_state *state, +const char *path) +{ +struct one_watch *watch =NULL ; +DWORD desired_access =FILE_LIST_DIRECTORY ; +DWORD share_mode = +FILE_SHARE_WRITE |FILE_SHARE_READ |FILE_SHARE_DELETE ; +HANDLE hDir; +wchar_t wpath[MAX_PATH ]; + +if (xutftowcs_path (wpath, path) <0 ) { +error (_ ("could not convert to wide characters: '%s'" ), path); +return NULL ; + } + + hDir =CreateFileW (wpath, + desired_access, share_mode,NULL ,OPEN_EXISTING , +FILE_FLAG_BACKUP_SEMANTICS |FILE_FLAG_OVERLAPPED , +NULL ); +if (hDir ==INVALID_HANDLE_VALUE ) { +error (_ ("[GLE %ld] could not watch '%s'" ), +GetLastError (), path); +return NULL ; + } + +CALLOC_ARRAY (watch,1 ); + + watch->buf_len =sizeof (watch->buffer);/* assume full MAX_RDCW_BUF */ + +strbuf_init (&watch->path,0 ); +strbuf_addstr (&watch->path, path); + + watch->hDir = hDir; + watch->hEvent =CreateEvent (NULL , TRUE, FALSE,NULL ); + +return watch; +} + +static void destroy_watch (struct one_watch *watch) +{ +if (!watch) +return ; + +strbuf_release (&watch->path); +if (watch->hDir !=INVALID_HANDLE_VALUE ) +CloseHandle (watch->hDir); +if (watch->hEvent !=INVALID_HANDLE_VALUE ) +CloseHandle (watch->hEvent); + +free (watch); +} + +static int start_rdcw_watch (struct fsmonitor_daemon_backend_data *data, +struct one_watch *watch) +{ +DWORD dwNotifyFilter = +FILE_NOTIFY_CHANGE_FILE_NAME | +FILE_NOTIFY_CHANGE_DIR_NAME | +FILE_NOTIFY_CHANGE_ATTRIBUTES | +FILE_NOTIFY_CHANGE_SIZE | +FILE_NOTIFY_CHANGE_LAST_WRITE | +FILE_NOTIFY_CHANGE_CREATION ; + +ResetEvent (watch->hEvent); + +memset (&watch->overlapped,0 ,sizeof (watch->overlapped)); + watch->overlapped.hEvent = watch->hEvent; + +/* +* Queue an async call using Overlapped IO. This returns immediately. +* Our event handle will be signalled when the real result is available. +* +* The return value here just means that we successfully queued it. +* We won't know if the Read...() actually produces data until later. +*/ + watch->is_active =ReadDirectoryChangesW ( + watch->hDir, watch->buffer, watch->buf_len, TRUE, + dwNotifyFilter, &watch->count, &watch->overlapped,NULL ); + +if (watch->is_active) +return 0 ; + +error ("ReadDirectoryChangedW failed on '%s' [GLE %ld]" , + watch->path.buf,GetLastError ()); +return -1 ; +} + +static int recv_rdcw_watch (struct one_watch *watch) +{ +DWORD gle; + + watch->is_active = FALSE; + +/* +* The overlapped result is ready. If the Read...() was successful +* we finally receive the actual result into our buffer. +*/ +if (GetOverlappedResult (watch->hDir, &watch->overlapped, &watch->count, + TRUE)) +return 0 ; + + gle =GetLastError (); +if (gle ==ERROR_INVALID_PARAMETER && +/* +* The kernel throws an invalid parameter error when our +* buffer is too big and we are pointed at a remote +* directory (and possibly for other reasons). Quietly +* set it down and try again. +* +* See note about MAX_RDCW_BUF at the top. +*/ + watch->buf_len >MAX_RDCW_BUF_FALLBACK ) { + watch->buf_len =MAX_RDCW_BUF_FALLBACK ; +return -2 ; + } + +/* +* 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. +*/ + +error ("GetOverlappedResult failed on '%s' [GLE %ld]" , + watch->path.buf, gle); +return -1 ; +} + +static void cancel_rdcw_watch (struct one_watch *watch) +{ +DWORD count; + +if (!watch || !watch->is_active) +return ; + +/* +* The calls to ReadDirectoryChangesW() and GetOverlappedResult() +* form a "pair" (my term) where we queue an IO and promise to +* hang around and wait for the kernel to give us the result. +* +* If for some reason after we queue the IO, we have to quit +* or otherwise not stick around for the second half, we must +* tell the kernel to abort the IO. This prevents the kernel +* from writing to our buffer and/or signalling our event +* after we free them. +* +* (Ask me how much fun it was to track that one down). +*/ +CancelIoEx (watch->hDir, &watch->overlapped); +GetOverlappedResult (watch->hDir, &watch->overlapped, &count, TRUE); + watch->is_active = FALSE; +} + +/* +* Process filesystem events that happen anywhere (recursively) under the +* <worktree> root directory. For a normal working directory, this includes +* both version controlled files and the contents of the .git/ directory. +* +* If <worktree>/.git is a file, then we only see events for the file +* itself. +*/ +static int process_worktree_events (struct fsmonitor_daemon_state *state) +{ +struct fsmonitor_daemon_backend_data *data = state->backend_data; +struct one_watch *watch = data->watch_worktree; +struct strbuf path =STRBUF_INIT ; +struct string_list cookie_list =STRING_LIST_INIT_DUP ; +struct fsmonitor_batch *batch =NULL ; +const char *p = watch->buffer; + +/* +* If the kernel gets more events than will fit in the kernel +* buffer associated with our RDCW handle, it drops them and +* returns a count of zero. +* +* Yes, the call returns WITHOUT error and with length zero. +* This is the documented behavior. (My testing has confirmed +* that it also sets the last error to ERROR_NOTIFY_ENUM_DIR, +* but we do not rely on that since the function did not +* return an error and it is not documented.) +* +* (The "overflow" case is not ambiguous with the "no data" case +* because we did an INFINITE wait.) +* +* This means we have a gap in coverage. Tell the daemon layer +* to resync. +*/ +if (!watch->count) { +trace2_data_string ("fsmonitor" ,NULL ,"fsm-listen/kernel" , +"overflow" ); +fsmonitor_force_resync (state); +return LISTENER_HAVE_DATA_WORKTREE ; + } + +/* +* On Windows, `info` contains an "array" of paths that are +* relative to the root of whichever directory handle received +* the event. +*/ +for (;;) { +FILE_NOTIFY_INFORMATION *info = (void *)p; +const char *slash; +enum fsmonitor_path_type t; + +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 ; + +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" ); + 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 ; + +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); + } + +skip_this_path: +if (!info->NextEntryOffset) +break ; + p += info->NextEntryOffset; + } + +fsmonitor_publish (state, batch, &cookie_list); + batch =NULL ; +string_list_clear (&cookie_list,0 ); +strbuf_release (&path); +return LISTENER_HAVE_DATA_WORKTREE ; + +force_shutdown: +fsmonitor_batch__free_list (batch); +string_list_clear (&cookie_list,0 ); +strbuf_release (&path); +return LISTENER_SHUTDOWN ; +} + +/* +* Process filesystem events that happened anywhere (recursively) under the +* external <gitdir> (such as non-primary worktrees or submodules). +* We only care about cookie files that our client threads created here. +* +* 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. +*/ +static int process_gitdir_events (struct fsmonitor_daemon_state *state) +{ +struct fsmonitor_daemon_backend_data *data = state->backend_data; +struct one_watch *watch = data->watch_gitdir; +struct strbuf path =STRBUF_INIT ; +struct string_list cookie_list =STRING_LIST_INIT_DUP ; +const char *p = watch->buffer; + +if (!watch->count) { +trace2_data_string ("fsmonitor" ,NULL ,"fsm-listen/kernel" , +"overflow" ); +fsmonitor_force_resync (state); +return LISTENER_HAVE_DATA_GITDIR ; + } + +for (;;) { +FILE_NOTIFY_INFORMATION *info = (void *)p; +const char *slash; +enum fsmonitor_path_type t; + +strbuf_reset (&path); +if (normalize_path_in_utf8 (info, &path) ==-1 ) + goto skip_this_path; + + t =fsmonitor_classify_path_gitdir_relative (path.buf); + +switch (t) { +case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX : +/* special case cookie files within gitdir */ + +/* 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_GITDIR : + goto skip_this_path; + +default : +BUG ("unexpected path classification '%d' for '%s'" , + t, path.buf); + } + +skip_this_path: +if (!info->NextEntryOffset) +break ; + p += info->NextEntryOffset; + } + +fsmonitor_publish (state,NULL , &cookie_list); +string_list_clear (&cookie_list,0 ); +strbuf_release (&path); +return LISTENER_HAVE_DATA_GITDIR ; }void fsm_listen__loop (struct fsmonitor_daemon_state *state) { +struct fsmonitor_daemon_backend_data *data = state->backend_data; +DWORD dwWait; +int result; + + state->error_code =0 ; + +if (start_rdcw_watch (data, data->watch_worktree) ==-1 ) + goto force_error_stop; + +if (data->watch_gitdir && +start_rdcw_watch (data, data->watch_gitdir) ==-1 ) + goto force_error_stop; + +for (;;) { + dwWait =WaitForMultipleObjects (data->nr_listener_handles, + data->hListener, + FALSE,INFINITE ); + +if (dwWait ==WAIT_OBJECT_0 +LISTENER_HAVE_DATA_WORKTREE ) { + result =recv_rdcw_watch (data->watch_worktree); +if (result ==-1 ) { +/* hard error */ + goto force_error_stop; + } +if (result ==-2 ) { +/* retryable error */ +if (start_rdcw_watch (data, data->watch_worktree) ==-1 ) + goto force_error_stop; +continue ; + } + +/* have data */ +if (process_worktree_events (state) ==LISTENER_SHUTDOWN ) + goto force_shutdown; +if (start_rdcw_watch (data, data->watch_worktree) ==-1 ) + goto force_error_stop; +continue ; + } + +if (dwWait ==WAIT_OBJECT_0 +LISTENER_HAVE_DATA_GITDIR ) { + result =recv_rdcw_watch (data->watch_gitdir); +if (result ==-1 ) { +/* hard error */ + goto force_error_stop; + } +if (result ==-2 ) { +/* retryable error */ +if (start_rdcw_watch (data, data->watch_gitdir) ==-1 ) + goto force_error_stop; +continue ; + } + +/* have data */ +if (process_gitdir_events (state) ==LISTENER_SHUTDOWN ) + goto force_shutdown; +if (start_rdcw_watch (data, data->watch_gitdir) ==-1 ) + goto force_error_stop; +continue ; + } + +if (dwWait ==WAIT_OBJECT_0 +LISTENER_SHUTDOWN ) + goto clean_shutdown; + +error (_ ("could not read directory changes [GLE %ld]" ), +GetLastError ()); + goto force_error_stop; + } + +force_error_stop: + state->error_code =-1 ; + +force_shutdown: +/* +* Tell the IPC thead pool to stop (which completes the await +* in the main thread (which will also signal this thread (if +* we are still alive))). +*/ +ipc_server_stop_async (state->ipc_server_data); + +clean_shutdown: +cancel_rdcw_watch (data->watch_worktree); +cancel_rdcw_watch (data->watch_gitdir); }int fsm_listen__ctor (struct fsmonitor_daemon_state *state) { +struct fsmonitor_daemon_backend_data *data; + +CALLOC_ARRAY (data,1 ); + + data->hEventShutdown =CreateEvent (NULL , TRUE, FALSE,NULL ); + + data->watch_worktree =create_watch (state, + state->path_worktree_watch.buf); +if (!data->watch_worktree) + goto failed; + +if (state->nr_paths_watching >1 ) { + data->watch_gitdir =create_watch (state, + state->path_gitdir_watch.buf); +if (!data->watch_gitdir) + goto failed; + } + + data->hListener[LISTENER_SHUTDOWN ] = data->hEventShutdown; + data->nr_listener_handles++; + + data->hListener[LISTENER_HAVE_DATA_WORKTREE ] = + data->watch_worktree->hEvent; + data->nr_listener_handles++; + +if (data->watch_gitdir) { + data->hListener[LISTENER_HAVE_DATA_GITDIR ] = + data->watch_gitdir->hEvent; + data->nr_listener_handles++; + } + + state->backend_data = data; +return 0 ; + +failed: +CloseHandle (data->hEventShutdown); +destroy_watch (data->watch_worktree); +destroy_watch (data->watch_gitdir); +return -1 ; }void fsm_listen__dtor (struct fsmonitor_daemon_state *state) { +struct fsmonitor_daemon_backend_data *data; + +if (!state || !state->backend_data) +return ; + + data = state->backend_data; + +CloseHandle (data->hEventShutdown); +destroy_watch (data->watch_worktree); +destroy_watch (data->watch_gitdir); + +FREE_AND_NULL (state->backend_data); }
Signature · 1 line
gitgitgadget
PATCH v6 16/30 compat/fsmonitor/fsm-listen-darwin: add MacOS header files for FSEvent
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Include MacOS system declarations to allow us to use FSEvent and CoreFoundation APIs. We need different versions of the declarations for GCC vs. clang because of compiler and header file conflicts.
While it is quite possible to #include Apple's CoreServices.h when compiling C source code with clang, trying to build it with GCC currently fails with this error:
In file included
from /Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk/System/... ...Library/Frameworks/Security.framework/Headers/AuthSession.h:32, from /Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk/System/... ...Library/Frameworks/Security.framework/Headers/Security.h:42, from /Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk/System/... ...Library/Frameworks/CoreServices.framework/Frameworks/... ...OSServices.framework/Headers/CSIdentity.h:43,
from /Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk/System/... ...Library/Frameworks/CoreServices.framework/Frameworks/... ...OSServices.framework/Headers/OSServices.h:29,
from /Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk/System/... ...Library/Frameworks/CoreServices.framework/Frameworks/... ...LaunchServices.framework/Headers/IconsCore.h:23,
from /Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk/System/... ...Library/Frameworks/CoreServices.framework/Frameworks/... ...LaunchServices.framework/Headers/LaunchServices.h:23, from /Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk/System/... ...Library/Frameworks/CoreServices.framework/Headers/CoreServices.h:45,
/Library/Developer/CommandLineTools/SDKs/MacOSX10.14.sdk/System/...
...Library/Frameworks/Security.framework/Headers/Authorization.h:193:7:
error: variably modified 'bytes' at file scope
193 | char bytes[kAuthorizationExternalFormLength];
| ^~~~~The underlying reason is that GCC (rightfully) objects that an enum value such as kAuthorizationExternalFormLength is not a constant (because it is not, the preprocessor has no knowledge of it, only the actual C compiler does) and can therefore not be used to define the size of a C array.
This is a known problem and tracked in GCC's bug tracker:
https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93082
In the meantime, let's not block things and go the slightly ugly route of declaring/defining the FSEvents constants, data structures and functions that we need, so that we can avoid above-mentioned issue.
Let's do this _only_ for GCC, though, so that the CI/PR builds (which build both with clang and with GCC) can guarantee that we _are_ using the correct data types.
--- compat/fsmonitor/fsm-listen-darwin.c | 96 ++++++++++++++++++++++++++++ 1 file changed, 96 insertions(+)
compat/fsmonitor/fsm-listen-darwin.c +96 -0
@@ -1,3 +1,99 @@ +#ifndef __clang__ +/* +* It is possible to #include CoreFoundation/CoreFoundation.h when compiling +* with clang, but not with GCC as of time of writing. +* +* See https://gcc.gnu.org/bugzilla/show_bug.cgi?id=93082 for details. +*/ +typedef unsigned int FSEventStreamCreateFlags ; +#define kFSEventStreamEventFlagNone 0x00000000 +#define kFSEventStreamEventFlagMustScanSubDirs 0x00000001 +#define kFSEventStreamEventFlagUserDropped 0x00000002 +#define kFSEventStreamEventFlagKernelDropped 0x00000004 +#define kFSEventStreamEventFlagEventIdsWrapped 0x00000008 +#define kFSEventStreamEventFlagHistoryDone 0x00000010 +#define kFSEventStreamEventFlagRootChanged 0x00000020 +#define kFSEventStreamEventFlagMount 0x00000040 +#define kFSEventStreamEventFlagUnmount 0x00000080 +#define kFSEventStreamEventFlagItemCreated 0x00000100 +#define kFSEventStreamEventFlagItemRemoved 0x00000200 +#define kFSEventStreamEventFlagItemInodeMetaMod 0x00000400 +#define kFSEventStreamEventFlagItemRenamed 0x00000800 +#define kFSEventStreamEventFlagItemModified 0x00001000 +#define kFSEventStreamEventFlagItemFinderInfoMod 0x00002000 +#define kFSEventStreamEventFlagItemChangeOwner 0x00004000 +#define kFSEventStreamEventFlagItemXattrMod 0x00008000 +#define kFSEventStreamEventFlagItemIsFile 0x00010000 +#define kFSEventStreamEventFlagItemIsDir 0x00020000 +#define kFSEventStreamEventFlagItemIsSymlink 0x00040000 +#define kFSEventStreamEventFlagOwnEvent 0x00080000 +#define kFSEventStreamEventFlagItemIsHardlink 0x00100000 +#define kFSEventStreamEventFlagItemIsLastHardlink 0x00200000 +#define kFSEventStreamEventFlagItemCloned 0x00400000 + +typedef struct __FSEventStream *FSEventStreamRef ; +typedef const FSEventStreamRef ConstFSEventStreamRef ; + +typedef unsigned int CFStringEncoding ; +#define kCFStringEncodingUTF8 0x08000100 + +typedef const struct __CFString *CFStringRef ; +typedef const struct __CFArray *CFArrayRef ; +typedef const struct __CFRunLoop *CFRunLoopRef ; + +struct FSEventStreamContext { +long long version; +void *cb_data, *retain, *release, *copy_description; +}; + +typedef struct FSEventStreamContext FSEventStreamContext ; +typedef unsigned int FSEventStreamEventFlags ; +#define kFSEventStreamCreateFlagNoDefer 0x02 +#define kFSEventStreamCreateFlagWatchRoot 0x04 +#define kFSEventStreamCreateFlagFileEvents 0x10 + +typedef unsigned long long FSEventStreamEventId ; +#define kFSEventStreamEventIdSinceNow 0xFFFFFFFFFFFFFFFFULL + +typedef void (*FSEventStreamCallback )(ConstFSEventStreamRef streamRef, +void *context, +__SIZE_TYPE__ num_of_events, +void *event_paths, +const FSEventStreamEventFlags event_flags[], +const FSEventStreamEventId event_ids[]); +typedef double CFTimeInterval ; +FSEventStreamRef FSEventStreamCreate (void *allocator, +FSEventStreamCallback callback, +FSEventStreamContext *context, +CFArrayRef paths_to_watch, +FSEventStreamEventId since_when, +CFTimeInterval latency, +FSEventStreamCreateFlags flags); +CFStringRef CFStringCreateWithCString (void *allocator,const char *string, +CFStringEncoding encoding); +CFArrayRef CFArrayCreate (void *allocator,const void **items,long long count, +void *callbacks); +void CFRunLoopRun (void ); +void CFRunLoopStop (CFRunLoopRef run_loop); +CFRunLoopRef CFRunLoopGetCurrent (void ); +extern CFStringRef kCFRunLoopDefaultMode; +void FSEventStreamScheduleWithRunLoop (FSEventStreamRef stream, +CFRunLoopRef run_loop, +CFStringRef run_loop_mode); +unsigned char FSEventStreamStart (FSEventStreamRef stream); +void FSEventStreamStop (FSEventStreamRef stream); +void FSEventStreamInvalidate (FSEventStreamRef stream); +void FSEventStreamRelease (FSEventStreamRef stream); +#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> +#endif +#include "cache.h" #include "fsmonitor.h" #include "fsm-listen.h"
Signature · 1 line
gitgitgadget
Re: [PATCH v6 16/30] compat/fsmonitor/fsm-listen-darwin: add MacOS header files for FSEvent
Quoted 16 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
[...]compat/fsmonitor/fsm-listen-darwin.c +14
-0+#define kFSEventStreamEventFlagNone 0x00000000 +#define kFSEventStreamEventFlagMustScanSubDirs 0x00000001 +#define kFSEventStreamEventFlagUserDropped 0x00000002 +#define kFSEventStreamEventFlagKernelDropped 0x00000004 +#define kFSEventStreamEventFlagEventIdsWrapped 0x00000008 +#define kFSEventStreamEventFlagHistoryDone 0x00000010 +#define kFSEventStreamEventFlagRootChanged 0x00000020 +#define kFSEventStreamEventFlagMount 0x00000040 +#define kFSEventStreamEventFlagUnmount 0x00000080 +#define kFSEventStreamEventFlagItemCreated 0x00000100 +#define kFSEventStreamEventFlagItemRemoved 0x00000200 +#define kFSEventStreamEventFlagItemInodeMetaMod 0x00000400 +#define kFSEventStreamEventFlagItemRenamed 0x00000800 +#define kFSEventStreamEventFlagItemModified 0x00001000
compat/fsmonitor/fsm-listen-darwin.c line 32
Quoted 10 lines
compat/fsmonitor/fsm-listen-darwin.c +10
-0+#define kFSEventStreamEventFlagItemFinderInfoMod 0x00002000 +#define kFSEventStreamEventFlagItemChangeOwner 0x00004000 +#define kFSEventStreamEventFlagItemXattrMod 0x00008000 +#define kFSEventStreamEventFlagItemIsFile 0x00010000 +#define kFSEventStreamEventFlagItemIsDir 0x00020000 +#define kFSEventStreamEventFlagItemIsSymlink 0x00040000 +#define kFSEventStreamEventFlagOwnEvent 0x00080000 +#define kFSEventStreamEventFlagItemIsHardlink 0x00100000 +#define kFSEventStreamEventFlagItemIsLastHardlink 0x00200000 +#define kFSEventStreamEventFlagItemCloned 0x00400000
Can we define these as 1<<0, 1<<1, 1<<2 etc.? We do that in most other places, and it helps to quickly eyeball these and see that they don't have gaps.
Quoted 1 line
+#define kCFStringEncodingUTF8
0x08000100
Should this be an OR of some of the above, or is it unrelated?
compat/fsmonitor/fsm-listen-darwin.c line 53
Quoted 5 lines
compat/fsmonitor/fsm-listen-darwin.c +5
-0+typedef struct FSEventStreamContext FSEventStreamContext ; +typedef unsigned int FSEventStreamEventFlags ; +#define kFSEventStreamCreateFlagNoDefer 0x02 +#define kFSEventStreamCreateFlagWatchRoot 0x04 +#define kFSEventStreamCreateFlagFileEvents 0x10
Ditto 1<<0 etc.
compat/fsmonitor/fsm-listen-darwin.c line 95
Quoted 13 lines
compat/fsmonitor/fsm-listen-darwin.c +9
-0+#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> +#endif
In cache.h which you'rejust about to include we don't include string.h, but we do in git-compat-util.h, but that one includes string.h before doing those overrides.
This either isn't needed, or really should be some addition to git-compat-util.h instead. I.e. if we've missed some edge case with string.h and ctype.h on OSX we should handle that in git-compat-util.h rather than <some other file/header> needing a portability workaround.
Quoted 4 lines
+
#include "cache.h"
#include "fsmonitor.h"
#include "fsm-listen.h"
Re: [PATCH v6 16/30] compat/fsmonitor/fsm-listen-darwin: add MacOS header files for FSEvent
Quoted 6 lines · Ævar Arnfjörð Bjarmason
Quoted 3 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
[...]
+#define kFSEventStreamEventFlagNone0x00000000
[...]
Quoted 5 lines
Quoted 1 line
+#define kFSEventStreamEventFlagItemCloned
0x00400000Can we define these as 1<<0, 1<<1, 1<<2 etc.? We do that in most other places, and it helps to quickly eyeball these and see that they don't have gaps.
All of these constants are defined by Apple in their header files and system documentation. For example, see:
https://developer.apple.com/documentation/coreservices/1455361-fseventstreameventflags/kfseventstreameventflagitemcloned
The set is relatively fixed by Apple and we won't be adding any (since they define the bits in a FS event from the kernel).
Changing them to shifts would be wrong.
thanks
Jeff
Re: [PATCH v6 16/30] compat/fsmonitor/fsm-listen-darwin: add MacOS header files for FSEvent
Quoted 13 lines · Jeff Hostetler
Quoted 5 lines · Ævar Arnfjörð Bjarmason
Quoted 3 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
[...]
+#define kFSEventStreamEventFlagNone0x00000000[...]
Quoted 5 lines
Quoted 1 line
+#define kFSEventStreamEventFlagItemCloned
0x00400000Can we define these as 1<<0, 1<<1, 1<<2 etc.? We do that in most other
places, and it helps to quickly eyeball these and see that they don't have gaps.
Quoted 8 lines
All of these constants are defined by Apple in their header files and system documentation. For example, see:
https://developer.apple.com/documentation/coreservices/1455361-fseventstreameventflags/kfseventstreameventflagitemclonedThe set is relatively fixed by Apple and we won't be adding any (since they define the bits in a FS event from the kernel).
Changing them to shifts would be wrong.
Ah, I missed the "ifndef __clang__" at the start, so most of it is not needed except with gcc.
FWIW I think having that whole part just split into compat/fsmonitor/darwin-gcc.h would make it obvious where all the GCC-specific hackery is.
Re: [PATCH v6 16/30] compat/fsmonitor/fsm-listen-darwin: add MacOS header files for FSEvent
In reply to Ævar Arnfjörð BjarmasonQuoted 5 lines · Ævar Arnfjörð Bjarmason
Quoted 2 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
[...]
[...]
Quoted 19 lines
Quoted 4 lines
compat/fsmonitor/fsm-listen-darwin.c +3
-0+#else +/* +* Let Apple's headers declare `isalnum()` first, before compat/fsmonitor/fsm-listen-darwin.c line 95
Quoted 10 lines
compat/fsmonitor/fsm-listen-darwin.c +6
-0+* Git's headers override it via a constant +*/ +#include <string.h> +#include <CoreFoundation/CoreFoundation.h> +#include <CoreServices/CoreServices.h> +#endif In
cache.hwhich you'rejust about to include we don't includestring.h, but we do ingit-compat-util.h, but that one includesstring.hbefore doing those overrides.
Quoted 10 lines
This either isn't needed, or really should be some addition to
git-compat-util.hinstead. I.e. if we've missed some edge case withstring.handctype.hon OSX we should handle that ingit-compat-util.hrather than <some other file/header> needing a portability workaround.Quoted 4 lines
+
#include "cache.h"
#include "fsmonitor.h"
#include "fsm-listen.h"
You may be right here. I commented out the <string.h> and the <...CoreFoundation.h> lines and everything still compiled and t7527 passed.
I'm not sure why <string.h> was added here (I inherited that file when I took over the feature). It may be that recent SDK updates have eliminated the need for it. Or it may be that it was never necessary. (However, the comment above it suggests that there was a problem in the past.)
While it may not (now at least) be necessary, it's not doing any harm, so I'd rather leave it and not interrupt things. We can always revisit it later if we want.
Thanks,
Jeff
Re: [PATCH v6 16/30] compat/fsmonitor/fsm-listen-darwin: add MacOS header files for FSEvent
Quoted 43 lines · Jeff Hostetler
Quoted 4 lines · Ævar Arnfjörð Bjarmason
Quoted 2 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
[...][...]
Quoted 8 lines
Quoted 6 lines
compat/fsmonitor/fsm-listen-darwin.c +5
-0+#else +/* +* Let Apple's headers declare `isalnum()` first, before +* Git's headers override it via a constant +*/ Quoted 18 lines
compat/fsmonitor/fsm-listen-darwin.c line 95
Quoted 4 lines
compat/fsmonitor/fsm-listen-darwin.c +4
-0+#include <string.h> +#include <CoreFoundation/CoreFoundation.h> +#include <CoreServices/CoreServices.h> +#endif In
cache.hwhich you'rejust about to include we don't includestring.h,
but we do ingit-compat-util.h, but that one includesstring.hbefore doing those overrides.
This either isn't needed, or really should be some addition togit-compat-util.hinstead. I.e. if we've missed some edge case withstring.handctype.hon OSX we should handle that ingit-compat-util.hrather than <some other file/header> needing a portability workaround.Quoted 4 lines
+
#include "cache.h"
#include "fsmonitor.h"
#include "fsm-listen.h"You may be right here. I commented out the <
string.h> and the <...CoreFoundation.h> lines and everything still compiled and t7527 passed.I'm not sure why <
string.h> was added here (I inherited that file when I took over the feature). It may be that recent SDK updates have eliminated the need for it. Or it may be that it was never necessary. (However, the comment above it suggests that there was a problem in the past.)
Quoted 3 lines
While it may not (now at least) be necessary, it's not doing any harm, so I'd rather leave it and not interrupt things. We can always revisit it later if we want.
In terms of figuring out some mysterious portability issue, I think the right time is now rather than later.
I.e. now this doesn't have anyone relying on it, so we can easily (re)discover whatever issue this was trying to solve.
Whereas anyone who'd need to figure out why we include string.h on OSX early in this case later would be left with this otherwise dead-end thread, and a change at that point would possibly break existing code...
Re: [PATCH v6 16/30] compat/fsmonitor/fsm-listen-darwin: add MacOS header files for FSEvent
Hi Ævar,
Quoted 30 lines · Ævar Arnfjörð Bjarmason
Quoted 28 lines · Jeff Hostetler
Quoted 4 lines · Ævar Arnfjörð Bjarmason
Quoted 2 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
[...][...]
Quoted 21 lines
compat/fsmonitor/fsm-listen-darwin.c line 95
Quoted 12 lines
compat/fsmonitor/fsm-listen-darwin.c +9
-0+#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> +#endif In
cache.hwhich you'rejust about to include we don't includestring.h, but we do ingit-compat-util.h, but that one includesstring.hbefore doing those overrides.This either isn't needed, or really should be some addition to
git-compat-util.hinstead. I.e. if we've missed some edge case withstring.handctype.hon OSX we should handle that ingit-compat-util.hrather than <some other file/header> needing a portability workaround.
Quoted 8 lines
Quoted 5 lines
[...]
While it may not (now at least) be necessary, it's not doing any harm, so I'd rather leave it and not interrupt things. We can always revisit it later if we want.
In terms of figuring out some mysterious portability issue, I think the right time is now rather than later.
I do not see that.
In FSMonitor, this was clearly a problem that needed to be solved (and if you try to compile on an older macOS, you will run into those problems again).
If you are talking about the mysterious portability issue with an eye on git-compat-util.h, well... you can successfully compile Git's source code without this hack in git-compat-util.h. That's why the hack is not needed. Problem solved. Actually, there was not even a problem.
Quoted 6 lines
I.e. now this doesn't have anyone relying on it, so we can easily (re)discover whatever issue this was trying to solve.
Whereas anyone who'd need to figure out why we include
string.hon OSX early in this case later would be left with this otherwise dead-end thread, and a change at that point would possibly break existing code...
Anyone who would need to figure out why we #include this header early would read the comment about isalnum(), I would hope, and understand that there are circumstances when Git's isalnum() macro interferes with Apple's, and that this #include order addresses that problem.
They might even get to the point where they find
https://github.com/dscho/git/commit/0f89c726a1912dce2bdab14aff8ebfec8550340d,
maybe even the "original original" commit at
https://github.com/kewillford/git/commit/d11ee4698c63347f40a8993ab86ee4e97f695d9b,
which was a still-experimental version of the macOS backend, and where the #include order clearly mattered, else why would Kevin have bothered.
Further, I strongly suspect that it had something to do with CoreFoundation.h or with CoreServices.h being #included, and if you care to check the code above the quoted lines, you will see that you cannot even #include those headers using GCC, it only works with clang:
https://github.com/jeffhostetler/git/commit/cdef9730b3f93a6f0f98d68ffb81bcb89d6e698e#diff-4e865160016fe490b80ad11273a10daca8bc412a75f2da4c6b08fb9e5e3b5e18R3
At this stage, anybody investigating this issue who is a little bit like me would then be a bit bored with the investigation because there is actually no breakage here, just a curious #include order, and nothing else. So they might then drop it and move along.
Even you might find it a more satisfying use of your time to implement, say, a Linux backend for FSMonitor on top of Jeff's work, instead of worrying about non-issues ;-)
Really, there are so many more interesting issues to discuss than this #include non-issue. And on this note, I will steer my attention to precisely such more interesting issues.
Ciao,
Johannes
Re: [PATCH v6 16/30] compat/fsmonitor/fsm-listen-darwin: add MacOS header files for FSEvent
Quoted 40 lines · Johannes Schindelin
Hi Ævar,
Quoted 36 lines · Ævar Arnfjörð Bjarmason
Quoted 33 lines · Jeff Hostetler
Quoted 4 lines · Ævar Arnfjörð Bjarmason
Quoted 2 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
[...][...]
Quoted 17 lines
compat/fsmonitor/fsm-listen-darwin.c line 95
Quoted 12 lines
compat/fsmonitor/fsm-listen-darwin.c +9
-0+#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> +#endif In
cache.hwhich you'rejust about to include we don't includestring.h, but we do ingit-compat-util.h, but that one includesstring.hbefore doing those overrides.Quoted 4 lines
This either isn't needed, or really should be some addition to
git-compat-util.hinstead. I.e. if we've missed some edge case withstring.handctype.hon OSX we should handle that ingit-compat-util.hrather than <some other file/header> needing a portability workaround.[...]
While it may not (now at least) be necessary, it's not doing any harm, so I'd rather leave it and not interrupt things. We can always revisit it later if we want.
Quoted 8 lines
Quoted 2 lines
In terms of figuring out some mysterious portability issue, I think the right time is now rather than later.
I do not see that.
In FSMonitor, this was clearly a problem that needed to be solved (and if you try to compile on an older macOS, you will run into those problems again).
So you can reproduce an issue where removing the "#include <string.h>" from compat/fsmonitor/fsm-listen-darwin.c has an effect? Does swaping it for "ctype.h" also solve that issue?
I was asking why the non-obvious portability hack was needed, and it seems Jeff suggested it might not be upthread in <[email protected]>, but here you seem to have a reproduction of in being needed, without more of the relevant details (e.g. what OSX version(s))?
Quoted 4 lines
If you are talking about the mysterious portability issue with an eye on
git-compat-util.h, well... you can successfully compile Git's source code without this hack ingit-compat-util.h. That's why the hack is not needed. Problem solved. Actually, there was not even a problem.
Do you mean we don't need the ctype.h overrides in git-compat-util.h at all? I haven't looked into it, but needing to
Quoted 25 lines
Quoted 6 lines
I.e. now this doesn't have anyone relying on it, so we can easily (re)discover whatever issue this was trying to solve.
Whereas anyone who'd need to figure out why we include
string.hon OSX early in this case later would be left with this otherwise dead-end thread, and a change at that point would possibly break existing code...Anyone who would need to figure out why we
#includethis header early would read the comment aboutisalnum(), I would hope, and understand that there are circumstances when Git'sisalnum()macro interferes with Apple's, and that this#includeorder addresses that problem.They might even get to the point where they find
https://github.com/dscho/git/commit/0f89c726a1912dce2bdab14aff8ebfec8550340d,
maybe even the "original original" commit at
https://github.com/kewillford/git/commit/d11ee4698c63347f40a8993ab86ee4e97f695d9b,
which was a still-experimental version of the macOS backend, and where the#includeorder clearly mattered, else why would Kevin have bothered.Further, I strongly suspect that it had something to do with
CoreFoundation.hor withCoreServices.hbeing#included, and if you care to check the code above the quoted lines, you will see that you cannot even#includethose headers using GCC, it only works with clang:
https://github.com/jeffhostetler/git/commit/cdef9730b3f93a6f0f98d68ffb81bcb89d6e698e#diff-4e865160016fe490b80ad11273a10daca8bc412a75f2da4c6b08fb9e5e3b5e18R3
Quoted 4 lines
At this stage, anybody investigating this issue who is a little bit like me would then be a bit bored with the investigation because there is actually no breakage here, just a curious
#includeorder, and nothing else. So they might then drop it and move along.
My implicit observation upthread is that those sorts of details would ideally be included in a comment or the commit message. I.e. I didn't quite see why it was needed, and neither could the person submitting the patch series when asked.
Sure, it's a minor issue, but patch review is also meant to uncover such small issues.
Re: [PATCH v6 16/30] compat/fsmonitor/fsm-listen-darwin: add MacOS header files for FSEvent
Quoted 100 lines · Ævar Arnfjörð Bjarmason
Quoted 40 lines · Johannes Schindelin
Hi Ævar,
Quoted 36 lines · Ævar Arnfjörð Bjarmason
Quoted 33 lines · Jeff Hostetler
Quoted 4 lines · Ævar Arnfjörð Bjarmason
Quoted 2 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
[...][...]
Quoted 17 lines
compat/fsmonitor/fsm-listen-darwin.c line 95
Quoted 12 lines
compat/fsmonitor/fsm-listen-darwin.c +9
-0+#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> +#endif In
cache.hwhich you'rejust about to include we don't includestring.h, but we do ingit-compat-util.h, but that one includesstring.hbefore doing those overrides.Quoted 4 lines
This either isn't needed, or really should be some addition to
git-compat-util.hinstead. I.e. if we've missed some edge case withstring.handctype.hon OSX we should handle that ingit-compat-util.hrather than <some other file/header> needing a portability workaround.[...]
While it may not (now at least) be necessary, it's not doing any harm, so I'd rather leave it and not interrupt things. We can always revisit it later if we want.
Quoted 8 lines
Quoted 2 lines
In terms of figuring out some mysterious portability issue, I think the right time is now rather than later.
I do not see that.
In FSMonitor, this was clearly a problem that needed to be solved (and if you try to compile on an older macOS, you will run into those problems again).
So you can reproduce an issue where removing the "#include <
string.h>" fromcompat/fsmonitor/fsm-listen-darwin.chas an effect? Does swaping it for "ctype.h" also solve that issue?I was asking why the non-obvious portability hack was needed, and it seems Jeff suggested it might not be upthread in <[email protected]>, but here you seem to have a reproduction of in being needed, without more of the relevant details (e.g. what OSX version(s))?
Quoted 4 lines
If you are talking about the mysterious portability issue with an eye on
git-compat-util.h, well... you can successfully compile Git's source code without this hack ingit-compat-util.h. That's why the hack is not needed. Problem solved. Actually, there was not even a problem.Do you mean we don't need the
ctype.hoverrides ingit-compat-util.hat all? I haven't looked into it, but needing toQuoted 25 lines
Quoted 6 lines
I.e. now this doesn't have anyone relying on it, so we can easily (re)discover whatever issue this was trying to solve.
Whereas anyone who'd need to figure out why we include
string.hon OSX early in this case later would be left with this otherwise dead-end thread, and a change at that point would possibly break existing code...Anyone who would need to figure out why we
#includethis header early would read the comment aboutisalnum(), I would hope, and understand that there are circumstances when Git'sisalnum()macro interferes with Apple's, and that this#includeorder addresses that problem.They might even get to the point where they find
https://github.com/dscho/git/commit/0f89c726a1912dce2bdab14aff8ebfec8550340d,
maybe even the "original original" commit at
https://github.com/kewillford/git/commit/d11ee4698c63347f40a8993ab86ee4e97f695d9b,
which was a still-experimental version of the macOS backend, and where the#includeorder clearly mattered, else why would Kevin have bothered.Further, I strongly suspect that it had something to do with
CoreFoundation.hor withCoreServices.hbeing#included, and if you care to check the code above the quoted lines, you will see that you cannot even#includethose headers using GCC, it only works with clang:
https://github.com/jeffhostetler/git/commit/cdef9730b3f93a6f0f98d68ffb81bcb89d6e698e#diff-4e865160016fe490b80ad11273a10daca8bc412a75f2da4c6b08fb9e5e3b5e18R3Quoted 4 lines
At this stage, anybody investigating this issue who is a little bit like me would then be a bit bored with the investigation because there is actually no breakage here, just a curious
#includeorder, and nothing else. So they might then drop it and move along.
Quoted 8 lines
My implicit observation upthread is that those sorts of details would ideally be included in a comment or the commit message. I.e. I didn't quite see why it was needed, and neither could the person submitting the patch series when asked.
Sure, it's a minor issue, but patch review is also meant to uncover such small issues.
There are two independent issues here.
(1) compiling something that includes <CoreServices.h> with GCC.
(2) the need for the #include <string.h> when compiling with clang.
To address (1), we've #ifdef'd the top of the file and insert just the essential typedefs and prototypes. (I'll pull them into a separate local header file as you suggested earlier to make that easier to see.) But otherwise, GCC is not an issue.
WRT (2) I have tried clang-11 on macOS 10.15 and clang-13 on 11.6 both with and without the <string.h> and it doesn't matter. Everything compiles and t7527 passes in all [2x2] cases. So I have to assume that something has changed in the Apple/clang SDK or runtime libraries or our source code in that single source file in the ~2 years since Kevin needed to add it. I do not have access to an older Mac (Apple makes it hard to test back-compat with older OS's), so I cannot reproduce the error. But I don't doubt that there was an error at one point -- I just
don't know what it was. So that's my context for saying that I don't
think it is needed now, but I was willing to carry it forward in case it is still helpful for people with older Macs. FWIW, it really seems pretty isolated and trivial and would only affect code in this single source file -- which from a quick scan, doesn't actually reference any of the functions in <string.h>, so there shouldn't be any need to think about git-compat-util.h or ctype.h, right?
Jeff
Re: [PATCH v6 16/30] compat/fsmonitor/fsm-listen-darwin: add MacOS header files for FSEvent
In reply to Ævar Arnfjörð BjarmasonQuoted 5 lines · Ævar Arnfjörð Bjarmason
Quoted 2 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
[...]
[...]
Ævar sent feedback (thanks!) on 8 commits in the V6 version on March 7. I started responding to each as I got to them in my inbox yesterday, but I'd like to take a break from responding individually to each of them inside of Part 2.
Since most of the feedback is for "general cleanup" and since Part 2 V6 is already in "next", I'd like to revisit these issues with a few "cleanup" commits on top of Part 3 (which is still in active review), rather than re-rolling or appending "fixup" commits onto Part 2.
I think this would be quicker and easier in the long run and give us the same net result.
Thanks,
Jeff
Re: [PATCH v6 16/30] compat/fsmonitor/fsm-listen-darwin: add MacOS header files for FSEvent
Quoted 12 lines · Jeff Hostetler
Quoted 4 lines · Ævar Arnfjörð Bjarmason
Quoted 2 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
[...][...]
Ævar sent feedback (thanks!) on 8 commits in the V6 version on March 7. I started responding to each as I got to them in my inbox yesterday, but I'd like to take a break from responding individually to each of them inside of Part 2.
Quoted 5 lines
Since most of the feedback is for "general cleanup" and since Part 2 V6 is already in "next", I'd like to revisit these issues with a few "cleanup" commits on top of Part 3 (which is still in active review), rather than re-rolling or appending "fixup" commits onto Part 2.
Sounds good. Prepending "preliminary clean-up" before part 3 would be even cleaner, I would suspect.
In any case, let's consider part 2 "more or less done" unless we see a glaring mistake that requires us to revert and redo it from scratch.
Thanks.
Re: [PATCH v6 16/30] compat/fsmonitor/fsm-listen-darwin: add MacOS header files for FSEvent
Quoted 20 lines · Junio C Hamano
Quoted 12 lines · Jeff Hostetler
Quoted 4 lines · Ævar Arnfjörð Bjarmason
Quoted 2 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
[...][...]
Ævar sent feedback (thanks!) on 8 commits in the V6 version on March 7. I started responding to each as I got to them in my inbox yesterday, but I'd like to take a break from responding individually to each of them inside of Part 2.
Quoted 5 lines
Since most of the feedback is for "general cleanup" and since Part 2 V6 is already in "next", I'd like to revisit these issues with a few "cleanup" commits on top of Part 3 (which is still in active review), rather than re-rolling or appending "fixup" commits onto Part 2.
Quoted 6 lines
Sounds good. Prepending "preliminary clean-up" before part 3 would be even cleaner, I would suspect.
In any case, let's consider part 2 "more or less done" unless we see a glaring mistake that requires us to revert and redo it from scratch.
Sounds good, my comments on v6 today were before I'd noticed that it was in "next", I think all of those can (well, it would be that way either way at this point...) be left for some potential follow-up.
Thanks both, especially Jeff for sticking with this fsmonitor topic for so long & keeping it advancing.
Re: [PATCH v6 16/30] compat/fsmonitor/fsm-listen-darwin: add MacOS header files for FSEvent
In reply to Junio C HamanoQuoted 20 lines · Junio C Hamano
Quoted 12 lines · Jeff Hostetler
Quoted 4 lines · Ævar Arnfjörð Bjarmason
Quoted 2 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
[...][...]
Ævar sent feedback (thanks!) on 8 commits in the V6 version on March 7. I started responding to each as I got to them in my inbox yesterday, but I'd like to take a break from responding individually to each of them inside of Part 2.
Quoted 5 lines
Since most of the feedback is for "general cleanup" and since Part 2 V6 is already in "next", I'd like to revisit these issues with a few "cleanup" commits on top of Part 3 (which is still in active review), rather than re-rolling or appending "fixup" commits onto Part 2.
Quoted 9 lines
Sounds good. Prepending "preliminary clean-up" before part 3 would be even cleaner, I would suspect.
In any case, let's consider part 2 "more or less done" unless we see a glaring mistake that requires us to revert and redo it from scratch.
Thanks.
The cleanup here turned into 16 small commits. I'll send them as a "Part 2.5" so that they stand alone and can either be appended to part 2 or treated as a new branch.
GGG wouldn't let me send fixup! commits, so inside of each commit message is a "fixup! ..." line which you can use if you want to squash them into part 2. But otherwise they have a normal (non-fixup) commit summary.
After I send that I'll send a new version of part 3 that builds upon them.
Thanks
Jeff
PATCH v6 17/30 compat/fsmonitor/fsm-listen-darwin: implement FSEvent listener on MacOS
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Implement file system event listener on MacOS using FSEvent, CoreFoundation, and CoreServices.
--- compat/fsmonitor/fsm-listen-darwin.c | 383 +++++++++++++++++++++++++++ 1 file changed, 383 insertions(+)
compat/fsmonitor/fsm-listen-darwin.c +383 -0
@@ -97,20 +97,403 @@ void FSEventStreamRelease(FSEventStreamRef stream);#include "cache.h" #include "fsmonitor.h" #include "fsm-listen.h" +#include "fsmonitor--daemon.h" + +struct fsmonitor_daemon_backend_data +{ +CFStringRef cfsr_worktree_path; +CFStringRef cfsr_gitdir_path; + +CFArrayRef cfar_paths_to_watch; +int nr_paths_watching; + +FSEventStreamRef stream; + +CFRunLoopRef rl; + +enum shutdown_style { +SHUTDOWN_EVENT =0 , +FORCE_SHUTDOWN , +FORCE_ERROR_STOP , + } shutdown_style; + +unsigned int stream_scheduled:1 ; +unsigned int stream_started:1 ; +}; + +static void log_flags_set (const char *path,const FSEventStreamEventFlags flag) +{ +struct strbuf msg =STRBUF_INIT ; + +if (flag & kFSEventStreamEventFlagMustScanSubDirs) +strbuf_addstr (&msg,"MustScanSubDirs|" ); +if (flag & kFSEventStreamEventFlagUserDropped) +strbuf_addstr (&msg,"UserDropped|" ); +if (flag & kFSEventStreamEventFlagKernelDropped) +strbuf_addstr (&msg,"KernelDropped|" ); +if (flag & kFSEventStreamEventFlagEventIdsWrapped) +strbuf_addstr (&msg,"EventIdsWrapped|" ); +if (flag & kFSEventStreamEventFlagHistoryDone) +strbuf_addstr (&msg,"HistoryDone|" ); +if (flag & kFSEventStreamEventFlagRootChanged) +strbuf_addstr (&msg,"RootChanged|" ); +if (flag & kFSEventStreamEventFlagMount) +strbuf_addstr (&msg,"Mount|" ); +if (flag & kFSEventStreamEventFlagUnmount) +strbuf_addstr (&msg,"Unmount|" ); +if (flag & kFSEventStreamEventFlagItemChangeOwner) +strbuf_addstr (&msg,"ItemChangeOwner|" ); +if (flag & kFSEventStreamEventFlagItemCreated) +strbuf_addstr (&msg,"ItemCreated|" ); +if (flag & kFSEventStreamEventFlagItemFinderInfoMod) +strbuf_addstr (&msg,"ItemFinderInfoMod|" ); +if (flag & kFSEventStreamEventFlagItemInodeMetaMod) +strbuf_addstr (&msg,"ItemInodeMetaMod|" ); +if (flag & kFSEventStreamEventFlagItemIsDir) +strbuf_addstr (&msg,"ItemIsDir|" ); +if (flag & kFSEventStreamEventFlagItemIsFile) +strbuf_addstr (&msg,"ItemIsFile|" ); +if (flag & kFSEventStreamEventFlagItemIsHardlink) +strbuf_addstr (&msg,"ItemIsHardlink|" ); +if (flag & kFSEventStreamEventFlagItemIsLastHardlink) +strbuf_addstr (&msg,"ItemIsLastHardlink|" ); +if (flag & kFSEventStreamEventFlagItemIsSymlink) +strbuf_addstr (&msg,"ItemIsSymlink|" ); +if (flag & kFSEventStreamEventFlagItemModified) +strbuf_addstr (&msg,"ItemModified|" ); +if (flag & kFSEventStreamEventFlagItemRemoved) +strbuf_addstr (&msg,"ItemRemoved|" ); +if (flag & kFSEventStreamEventFlagItemRenamed) +strbuf_addstr (&msg,"ItemRenamed|" ); +if (flag & kFSEventStreamEventFlagItemXattrMod) +strbuf_addstr (&msg,"ItemXattrMod|" ); +if (flag & kFSEventStreamEventFlagOwnEvent) +strbuf_addstr (&msg,"OwnEvent|" ); +if (flag & kFSEventStreamEventFlagItemCloned) +strbuf_addstr (&msg,"ItemCloned|" ); + +trace_printf_key (&trace_fsmonitor,"fsevent: '%s', flags=%u %s" , + path, flag, msg.buf); + +strbuf_release (&msg); +} + +static int ef_is_root_delete (const FSEventStreamEventFlags ef) +{ +return (ef & kFSEventStreamEventFlagItemIsDir && + ef & kFSEventStreamEventFlagItemRemoved); +} + +static int ef_is_root_renamed (const FSEventStreamEventFlags ef) +{ +return (ef & kFSEventStreamEventFlagItemIsDir && + ef & kFSEventStreamEventFlagItemRenamed); +} + +static int ef_is_dropped (const FSEventStreamEventFlags ef) +{ +return (ef & kFSEventStreamEventFlagMustScanSubDirs || + ef & kFSEventStreamEventFlagKernelDropped || + ef & kFSEventStreamEventFlagUserDropped); +} + +static void fsevent_callback (ConstFSEventStreamRef streamRef, +void *ctx, +size_t num_of_events, +void *event_paths, +const FSEventStreamEventFlags event_flags[], +const FSEventStreamEventId event_ids[]) +{ +struct fsmonitor_daemon_state *state = ctx; +struct fsmonitor_daemon_backend_data *data = state->backend_data; +char **paths = (char **)event_paths; +struct fsmonitor_batch *batch =NULL ; +struct string_list cookie_list =STRING_LIST_INIT_DUP ; +const char *path_k; +const char *slash; +int k; +struct strbuf tmp =STRBUF_INIT ; + +/* +* Build a list of all filesystem changes into a private/local +* list and without holding any locks. +*/ +for (k =0 ; k < num_of_events; k++) { +/* +* On Mac, we receive an array of absolute paths. +*/ + path_k = paths[k]; + +/* +* If you want to debug FSEvents, log them to GIT_TRACE_FSMONITOR. +* Please don't log them to Trace2. +* +* trace_printf_key(&trace_fsmonitor, "Path: '%s'", path_k); +*/ + +/* +* If event[k] is marked as dropped, we assume that we have +* lost sync with the filesystem and should flush our cached +* data. We need to: +* +* [1] Abort/wake any client threads waiting for a cookie and +* flush the cached state data (the current token), and +* create a new token. +* +* [2] Discard the batch that we were locally building (since +* they are conceptually relative to the just flushed +* token). +*/ +if (ef_is_dropped (event_flags[k])) { +if (trace_pass_fl (&trace_fsmonitor)) +log_flags_set (path_k, event_flags[k]); + +fsmonitor_force_resync (state); +fsmonitor_batch__free_list (batch); +string_list_clear (&cookie_list,0 ); + +/* +* We assume that any events that we received +* in this callback after this dropped event +* may still be valid, so we continue rather +* than break. (And just in case there is a +* delete of ".git" hiding in there.) +*/ +continue ; + } + +switch (fsmonitor_classify_path_absolute (state, path_k)) { + +case IS_INSIDE_DOT_GIT_WITH_COOKIE_PREFIX : +case IS_INSIDE_GITDIR_WITH_COOKIE_PREFIX : +/* special case cookie files within .git or gitdir */ + +/* Use just the filename of the cookie file. */ + slash =find_last_dir_sep (path_k); +string_list_append (&cookie_list, + slash ? slash +1 : path_k); +break ; + +case IS_INSIDE_DOT_GIT : +case IS_INSIDE_GITDIR : +/* ignore all other paths inside of .git or gitdir */ +break ; + +case IS_DOT_GIT : +case IS_GITDIR : +/* +* If .git directory is deleted or renamed away, +* we have to quit. +*/ +if (ef_is_root_delete (event_flags[k])) { +trace_printf_key (&trace_fsmonitor, +"event: gitdir removed" ); + goto force_shutdown; + } +if (ef_is_root_renamed (event_flags[k])) { +trace_printf_key (&trace_fsmonitor, +"event: gitdir renamed" ); + goto force_shutdown; + } +break ; + +case IS_WORKDIR_PATH : +/* try to queue normal pathnames */ + +if (trace_pass_fl (&trace_fsmonitor)) +log_flags_set (path_k, event_flags[k]); + +/* +* Because of the implicit "binning" (the +* kernel calls us at a given frequency) and +* de-duping (the kernel is free to combine +* multiple events for a given pathname), an +* individual fsevent could be marked as both +* a file and directory. Add it to the queue +* with both spellings so that the client will +* know how much to invalidate/refresh. +*/ + +if (event_flags[k] & kFSEventStreamEventFlagItemIsFile) { +const char *rel = path_k + + state->path_worktree_watch.len +1 ; + +if (!batch) + batch =fsmonitor_batch__new (); +fsmonitor_batch__add_path (batch, rel); + } + +if (event_flags[k] & kFSEventStreamEventFlagItemIsDir) { +const char *rel = path_k + + state->path_worktree_watch.len +1 ; + +strbuf_reset (&tmp); +strbuf_addstr (&tmp, rel); +strbuf_addch (&tmp,'/' ); + +if (!batch) + batch =fsmonitor_batch__new (); +fsmonitor_batch__add_path (batch, tmp.buf); + } + +break ; + +case IS_OUTSIDE_CONE : +default : +trace_printf_key (&trace_fsmonitor, +"ignoring '%s'" , path_k); +break ; + } + } + +fsmonitor_publish (state, batch, &cookie_list); +string_list_clear (&cookie_list,0 ); +strbuf_release (&tmp); +return ; + +force_shutdown: +fsmonitor_batch__free_list (batch); +string_list_clear (&cookie_list,0 ); + + data->shutdown_style =FORCE_SHUTDOWN ; +CFRunLoopStop (data->rl); +strbuf_release (&tmp); +return ; +} + +/* +* In the call to `FSEventStreamCreate()` to setup our watch, the +* `latency` argument determines the frequency of calls to our callback +* with new FS events. Too slow and events get dropped; too fast and +* we burn CPU unnecessarily. Since it is rather obscure, I don't +* think this needs to be a config setting. I've done extensive +* testing on my systems and chosen the value below. It gives good +* results and I've not seen any dropped events. +* +* With a latency of 0.1, I was seeing lots of dropped events during +* the "touch 100000" files test within t/perf/p7519, but with a +* latency of 0.001 I did not see any dropped events. So I'm going +* to assume that this is the "correct" value. +* +* https://developer.apple.com/documentation/coreservices/1443980-fseventstreamcreate +*/ int fsm_listen__ctor (struct fsmonitor_daemon_state *state) { +FSEventStreamCreateFlags flags = kFSEventStreamCreateFlagNoDefer | + kFSEventStreamCreateFlagWatchRoot | + kFSEventStreamCreateFlagFileEvents; +FSEventStreamContext ctx = { +0 , + state, +NULL , +NULL , +NULL + }; +struct fsmonitor_daemon_backend_data *data; +const void *dir_array[2 ]; + +CALLOC_ARRAY (data,1 ); + state->backend_data = data; + + data->cfsr_worktree_path =CFStringCreateWithCString ( +NULL , state->path_worktree_watch.buf, kCFStringEncodingUTF8); + dir_array[data->nr_paths_watching++] = data->cfsr_worktree_path; + +if (state->nr_paths_watching >1 ) { + data->cfsr_gitdir_path =CFStringCreateWithCString ( +NULL , state->path_gitdir_watch.buf, + kCFStringEncodingUTF8); + dir_array[data->nr_paths_watching++] = data->cfsr_gitdir_path; + } + + data->cfar_paths_to_watch =CFArrayCreate (NULL , dir_array, + data->nr_paths_watching, +NULL ); + data->stream =FSEventStreamCreate (NULL , fsevent_callback, &ctx, + data->cfar_paths_to_watch, + kFSEventStreamEventIdSinceNow, +0.001 , flags); +if (data->stream ==NULL ) + goto failed; + +/* +* `data->rl` needs to be set inside the listener thread. +*/ + +return 0 ; + +failed: +error ("Unable to create FSEventStream." ); + +FREE_AND_NULL (state->backend_data);return -1 ; }void fsm_listen__dtor (struct fsmonitor_daemon_state *state) { +struct fsmonitor_daemon_backend_data *data; + +if (!state || !state->backend_data) +return ; + + data = state->backend_data; + +if (data->stream) { +if (data->stream_started) +FSEventStreamStop (data->stream); +if (data->stream_scheduled) +FSEventStreamInvalidate (data->stream); +FSEventStreamRelease (data->stream); + } + +FREE_AND_NULL (state->backend_data); }void fsm_listen__stop_async (struct fsmonitor_daemon_state *state) { +struct fsmonitor_daemon_backend_data *data; + + data = state->backend_data; + data->shutdown_style =SHUTDOWN_EVENT ; + +CFRunLoopStop (data->rl); }void fsm_listen__loop (struct fsmonitor_daemon_state *state) { +struct fsmonitor_daemon_backend_data *data; + + data = state->backend_data; + + data->rl =CFRunLoopGetCurrent (); + +FSEventStreamScheduleWithRunLoop (data->stream, data->rl, kCFRunLoopDefaultMode); + data->stream_scheduled =1 ; + +if (!FSEventStreamStart (data->stream)) { +error ("Failed to start the FSEventStream" ); + goto force_error_stop_without_loop; + } + data->stream_started =1 ; + +CFRunLoopRun (); + +switch (data->shutdown_style) { +case FORCE_ERROR_STOP : + state->error_code =-1 ; +/* fall thru */ +case FORCE_SHUTDOWN : +ipc_server_stop_async (state->ipc_server_data); +/* fall thru */ +case SHUTDOWN_EVENT : +default : +break ; + } +return ; + +force_error_stop_without_loop: + state->error_code =-1 ; +ipc_server_stop_async (state->ipc_server_data); +return ; }
Signature · 1 line
gitgitgadget
PATCH v6 18/30 fsmonitor--daemon: implement handle_client callback
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Teach fsmonitor--daemon to respond to IPC requests from client Git processes and respond with a list of modified pathnames relative to the provided token.
--- builtin/fsmonitor--daemon.c | 312 +++++++++++++++++++++++++++++++++++- 1 file changed, 310 insertions(+), 2 deletions(-)
builtin/fsmonitor--daemon.c +310 -2
@@ -7,6 +7,7 @@#include "fsmonitor--daemon.h" #include "simple-ipc.h" #include "khash.h" +#include "pkt-line.h" static const char *const builtin_fsmonitor__daemon_usage[] = {N_ ("git fsmonitor--daemon start [<options>]" ),
@@ -364,6 +365,311 @@ void fsmonitor_force_resync(struct fsmonitor_daemon_state *state)pthread_mutex_unlock (&state->main_lock); } +/* +* Format an opaque token string to send to the client. +*/ +static void with_lock__format_response_token ( +struct strbuf *response_token, +const struct strbuf *response_token_id, +const struct fsmonitor_batch *batch) +{ +/* assert current thread holding state->main_lock */ + +strbuf_reset (response_token); +strbuf_addf (response_token,"builtin:%s:%" PRIu64, + response_token_id->buf, batch->batch_seq_nr); +} + +/* +* Parse an opaque token from the client. +* Returns -1 on error. +*/ +static int fsmonitor_parse_client_token (const char *buf_token, +struct strbuf *requested_token_id, +uint64_t *seq_nr) +{ +const char *p; +char *p_end; + +strbuf_reset (requested_token_id); + *seq_nr =0 ; + +if (!skip_prefix (buf_token,"builtin:" , &p)) +return -1 ; + +while (*p && *p !=':' ) +strbuf_addch (requested_token_id, *p++); +if (!*p++) +return -1 ; + + *seq_nr = (uint64_t )strtoumax (p, &p_end,10 ); +if (*p_end) +return -1 ; + +return 0 ; +} + +KHASH_INIT(str, const char *, int, 0, kh_str_hash_func, kh_str_hash_equal) + +static int do_handle_client (struct fsmonitor_daemon_state *state, +const char *command, +ipc_server_reply_cb *reply, +struct ipc_server_reply_data *reply_data) +{ +struct fsmonitor_token_data *token_data =NULL ; +struct strbuf response_token =STRBUF_INIT ; +struct strbuf requested_token_id =STRBUF_INIT ; +struct strbuf payload =STRBUF_INIT ; +uint64_t requested_oldest_seq_nr =0 ; +uint64_t total_response_len =0 ; +const char *p; +const struct fsmonitor_batch *batch_head; +const struct fsmonitor_batch *batch; +intmax_t count =0 , duplicates =0 ; +kh_str_t *shown; +int hash_ret; +int do_trivial =0 ; +int do_flush =0 ; + +/* +* We expect `command` to be of the form: +* +* <command> := quit NUL +* | flush NUL +* | <V1-time-since-epoch-ns> NUL +* | <V2-opaque-fsmonitor-token> NUL +*/ + +if (!strcmp (command,"quit" )) { +/* +* A client has requested over the socket/pipe that the +* daemon shutdown. +* +* Tell the IPC thread pool to shutdown (which completes +* the await in the main thread (which can stop the +* fsmonitor listener thread)). +* +* There is no reply to the client. +*/ +return SIMPLE_IPC_QUIT ; + + }else if (!strcmp (command,"flush" )) { +/* +* Flush all of our cached data and generate a new token +* just like if we lost sync with the filesystem. +* +* Then send a trivial response using the new token. +*/ + do_flush =1 ; + do_trivial =1 ; + + }else if (!skip_prefix (command,"builtin:" , &p)) { +/* assume V1 timestamp or garbage */ + +char *p_end; + +strtoumax (command, &p_end,10 ); +trace_printf_key (&trace_fsmonitor, + ((*p_end) ? +"fsmonitor: invalid command line '%s'" : +"fsmonitor: unsupported V1 protocol '%s'" ), + command); + do_trivial =1 ; + + }else { +/* We have "builtin:*" */ +if (fsmonitor_parse_client_token (command, &requested_token_id, + &requested_oldest_seq_nr)) { +trace_printf_key (&trace_fsmonitor, +"fsmonitor: invalid V2 protocol token '%s'" , + command); + do_trivial =1 ; + + }else { +/* +* We have a V2 valid token: +* "builtin:<token_id>:<seq_nr>" +*/ + } + } + +pthread_mutex_lock (&state->main_lock); + +if (!state->current_token_data) +BUG ("fsmonitor state does not have a current token" ); + +if (do_flush) +with_lock__do_force_resync (state); + +/* +* We mark the current head of the batch list as "pinned" so +* that the listener thread will treat this item as read-only +* (and prevent any more paths from being added to it) from +* now on. +*/ + token_data = state->current_token_data; + batch_head = token_data->batch_head; + ((struct fsmonitor_batch *)batch_head)->pinned_time =time (NULL ); + +/* +* FSMonitor Protocol V2 requires that we send a response header +* with a "new current token" and then all of the paths that changed +* since the "requested token". We send the seq_nr of the just-pinned +* head batch so that future requests from a client will be relative +* to it. +*/ +with_lock__format_response_token (&response_token, + &token_data->token_id, batch_head); + +reply (reply_data, response_token.buf, response_token.len +1 ); + total_response_len += response_token.len +1 ; + +trace2_data_string ("fsmonitor" , the_repository,"response/token" , + response_token.buf); +trace_printf_key (&trace_fsmonitor,"response token: %s" , + response_token.buf); + +if (!do_trivial) { +if (strcmp (requested_token_id.buf, token_data->token_id.buf)) { +/* +* The client last spoke to a different daemon +* instance -OR- the daemon had to resync with +* the filesystem (and lost events), so reject. +*/ +trace2_data_string ("fsmonitor" , the_repository, +"response/token" ,"different" ); + do_trivial =1 ; + + }else if (requested_oldest_seq_nr < + token_data->batch_tail->batch_seq_nr) { +/* +* The client wants older events than we have for +* this token_id. This means that the end of our +* batch list was truncated and we cannot give the +* client a complete snapshot relative to their +* request. +*/ +trace_printf_key (&trace_fsmonitor, +"client requested truncated data" ); + do_trivial =1 ; + } + } + +if (do_trivial) { +pthread_mutex_unlock (&state->main_lock); + +reply (reply_data,"/" ,2 ); + +trace2_data_intmax ("fsmonitor" , the_repository, +"response/trivial" ,1 ); + +strbuf_release (&response_token); +strbuf_release (&requested_token_id); +return 0 ; + } + +/* +* We're going to hold onto a pointer to the current +* token-data while we walk the list of batches of files. +* During this time, we will NOT be under the lock. +* So we ref-count it. +* +* This allows the listener thread to continue prepending +* new batches of items to the token-data (which we'll ignore). +* +* AND it allows the listener thread to do a token-reset +* (and install a new `current_token_data`). +*/ + token_data->client_ref_count++; + +pthread_mutex_unlock (&state->main_lock); + +/* +* The client request is relative to the token that they sent, +* so walk the batch list backwards from the current head back +* to the batch (sequence number) they named. +* +* We use khash to de-dup the list of pathnames. +* +* NEEDSWORK: each batch contains a list of interned strings, +* so we only need to do pointer comparisons here to build the +* hash table. Currently, we're still comparing the string +* values. +*/ + shown =kh_init_str (); +for (batch = batch_head; + batch && batch->batch_seq_nr > requested_oldest_seq_nr; + batch = batch->next) { +size_t k; + +for (k =0 ; k < batch->nr; k++) { +const char *s = batch->interned_paths[k]; +size_t s_len; + +if (kh_get_str (shown, s) !=kh_end (shown)) + duplicates++; +else { +kh_put_str (shown, s, &hash_ret); + +trace_printf_key (&trace_fsmonitor, +"send[%" PRIuMAX"]: %s" , + count, s); + +/* Each path gets written with a trailing NUL */ + s_len =strlen (s) +1 ; + +if (payload.len + s_len >= +LARGE_PACKET_DATA_MAX ) { +reply (reply_data, payload.buf, + payload.len); + total_response_len += payload.len; +strbuf_reset (&payload); + } + +strbuf_add (&payload, s, s_len); + count++; + } + } + } + +if (payload.len) { +reply (reply_data, payload.buf, payload.len); + total_response_len += payload.len; + } + +kh_release_str (shown); + +pthread_mutex_lock (&state->main_lock); + +if (token_data->client_ref_count >0 ) + token_data->client_ref_count--; + +if (token_data->client_ref_count ==0 ) { +if (token_data != state->current_token_data) { +/* +* The listener thread did a token-reset while we were +* walking the batch list. Therefore, this token is +* stale and can be discarded completely. If we are +* the last reader thread using this token, we own +* that work. +*/ +fsmonitor_free_token_data (token_data); + } + } + +pthread_mutex_unlock (&state->main_lock); + +trace2_data_intmax ("fsmonitor" , the_repository,"response/length" , total_response_len); +trace2_data_intmax ("fsmonitor" , the_repository,"response/count/files" , count); +trace2_data_intmax ("fsmonitor" , the_repository,"response/count/duplicates" , duplicates); + +strbuf_release (&response_token); +strbuf_release (&requested_token_id); +strbuf_release (&payload); + +return 0 ; +} +static ipc_server_application_cb handle_client; static int handle_client(void *data,
@@ -371,7 +677,7 @@ static int handle_client(void *data,ipc_server_reply_cb *reply, struct ipc_server_reply_data *reply_data) { - /* struct fsmonitor_daemon_state *state = data; */ + struct fsmonitor_daemon_state *state = data;int result;/*
@@ -382,10 +688,12 @@ static int handle_client(void *data,if (command_len !=strlen (command))BUG ("FSMonitor assumes text messages" ); +trace_printf_key (&trace_fsmonitor,"requested token: %s" , command); +trace2_region_enter ("fsmonitor" ,"handle_client" , the_repository);trace2_data_string ("fsmonitor" , the_repository,"request" , command); - result =0 ;/* TODO Do something here. */ + result =do_handle_client (state, command, reply, reply_data);trace2_region_leave ("fsmonitor" ,"handle_client" , the_repository);
Signature · 1 line
gitgitgadget
PATCH v6 19/30 help: include fsmonitor--daemon feature flag in version info
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Add the "feature: fsmonitor--daemon" message to the output of git version --build-options.
The builtin FSMonitor is only available on certain platforms and even then only when certain Makefile flags are enabled, so print a message in the verbose version output when it is available.
This can be used by test scripts for prereq testing. Granted, tests could just try git fsmonitor--daemon status and look for a 128 exit code or grep for a "not supported" message on stderr, but these methods are rather obscure.
The main advantage is that the feature message will automatically appear in bug reports and other support requests.
This concept was also used during the development of Scalar for similar reasons.
--- help.c | 4 ++++ t/test-lib.sh | 6 ++++++ 2 files changed, 10 insertions(+)
help.c +4 -0
@@ -12,6 +12,7 @@#include "refs.h" #include "parse-options.h" #include "prompt.h" +#include "fsmonitor-ipc.h" struct category_description {uint32_t category;
@@ -695,6 +696,9 @@ void get_version_info(struct strbuf *buf, int show_build_options)strbuf_addf (buf,"sizeof-size_t: %d\n" , (int )sizeof (size_t ));strbuf_addf (buf,"shell-path: %s\n" ,SHELL_PATH );/* NEEDSWORK: also save and output GIT-BUILD_OPTIONS? */ + +if (fsmonitor_ipc__is_supported ()) +strbuf_addstr (buf,"feature: fsmonitor--daemon\n" ); } }
t/test-lib.sh +6 -0
@@ -1799,3 +1799,9 @@ test_lazy_prereq SHA1 ' # Tests that verify the scheduler integration must set this locally # to avoid errors. GIT_TEST_MAINT_SCHEDULER="none:exit 1" + +# Does this platform support `git fsmonitor--daemon` +# +test_lazy_prereq FSMONITOR_DAEMON ' + git version --build-options | grep "feature:" | grep "fsmonitor--daemon" +'
Signature · 1 line
gitgitgadget
Re: [PATCH v6 19/30] help: include fsmonitor--daemon feature flag in version info
Quoted 54 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
Add the "feature: fsmonitor--daemon" message to the output of
git version --build-options.The builtin FSMonitor is only available on certain platforms and even then only when certain
Makefileflags are enabled, so print a message in the verbose version output when it is available.This can be used by test scripts for prereq testing. Granted, tests could just try
git fsmonitor--daemon statusand look for a 128 exit code or grep for a "not supported" message on stderr, but these methods are rather obscure.The main advantage is that the feature message will automatically appear in bug reports and other support requests.
This concept was also used during the development of Scalar for similar reasons.
Signed-off-by Jeff Hostetler <[email protected]>Signed-off-by Junio C Hamano <[email protected]>--- help.c | 4 ++++ t/test-lib.sh | 6 ++++++ 2 files changed, 10 insertions(+)help.c +4
-0@@ -12,6 +12,7 @@#include "refs.h" #include "parse-options.h" #include "prompt.h" +#include "fsmonitor-ipc.h" struct category_description {uint32_t category;@@ -695,6 +696,9 @@ void get_version_info(struct strbuf *buf, int show_build_options)strbuf_addf (buf,"sizeof-size_t: %d\n" , (int )sizeof (size_t ));strbuf_addf (buf,"shell-path: %s\n" ,SHELL_PATH );/* NEEDSWORK: also save and output GIT-BUILD_OPTIONS? */ + +if (fsmonitor_ipc__is_supported ()) +strbuf_addstr (buf,"feature: fsmonitor--daemon\n" ); } }t/test-lib.sh +0
-0
t/test-lib.sh test_lazy_prereq SHA1 ' · line 1807
Quoted 9 lines
t/test-lib.sh +6
-0@@ -1799,3 +1799,9 @@ test_lazy_prereq SHA1 ' # Tests that verify the scheduler integration must set this locally # to avoid errors. GIT_TEST_MAINT_SCHEDULER="none:exit 1" + +# Does this platform support `git fsmonitor--daemon` +# +test_lazy_prereq FSMONITOR_DAEMON ' + git version --build-options | grep "feature:" | grep "fsmonitor--daemon" +'
As I found recently (referenced in another series) the test_lazy_prereq doesn't currently catch segfaults etc. in git even if test_must_fail and friends are used.
But it's still better to future-proof things and not add more cases of git on the LHS of a pipe. So instead:
git version .. >out &&
grep ...The prereqs are run in their own temporary directory, so creating those files is OK.
Also: You run "grep" here twice, but as the code context shown we could just run it once.
Re: [PATCH v6 19/30] help: include fsmonitor--daemon feature flag in version info
Quoted 5 lines · Ævar Arnfjörð Bjarmason
Quoted 2 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
[...]
Quoted 10 lines
t/test-lib.sh test_lazy_prereq SHA1 ' · line 1807
Quoted 5 lines
t/test-lib.sh +5
-0+# Does this platform support `git fsmonitor--daemon` +# +test_lazy_prereq FSMONITOR_DAEMON ' + git version --build-options | grep "feature:" | grep "fsmonitor--daemon" +'As I found recently (referenced in another series) the
test_lazy_prereqdoesn't currently catch segfaults etc. in git even iftest_must_failand friends are used.
Quoted 8 lines
But it's still better to future-proof things and not add more cases of git on the LHS of a pipe. So instead:
git version .. >out && grep ...The prereqs are run in their own temporary directory, so creating those files is OK.
Yes, I think I saw a series on this topic in my inbox recently.
This command is very safe, so I'd rather not reroll just for this.
Quoted 4 lines
Also: You run "grep" here twice, but as the code context shown we could just run it once.
True, but I think this can wait too.
Thanks,
Jeff
PATCH v6 20/30 t/helper/fsmonitor-client: create IPC client to talk to FSMonitor Daemon
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create an IPC client to send query and flush commands to the daemon.
--- Makefile | 1 + t/helper/test-fsmonitor-client.c | 121 +++++++++++++++++++++++++++++++ t/helper/test-tool.c | 1 + t/helper/test-tool.h | 1 + 4 files changed, 124 insertions(+) create mode 100644 t/helper/test-fsmonitor-client.c
Makefile +1 -0
@@ -716,6 +716,7 @@ TEST_BUILTINS_OBJS += test-dump-split-index.o TEST_BUILTINS_OBJS += test-dump-untracked-cache.o TEST_BUILTINS_OBJS += test-example-decorate.o TEST_BUILTINS_OBJS += test-fast-rebase.o +TEST_BUILTINS_OBJS += test-fsmonitor-client.o TEST_BUILTINS_OBJS += test-genrandom.o TEST_BUILTINS_OBJS += test-genzeros.o TEST_BUILTINS_OBJS += test-getcwd.o
t/helper/test-fsmonitor-client.c new file +121 -0
121 lines not on this page · raw message
t/helper/test-tool.c +1 -0
@@ -32,6 +32,7 @@ static struct test_cmd cmds[] = { { "dump-untracked-cache", cmd__dump_untracked_cache }, { "example-decorate", cmd__example_decorate }, { "fast-rebase", cmd__fast_rebase }, + { "fsmonitor-client", cmd__fsmonitor_client }, { "genrandom", cmd__genrandom }, { "genzeros", cmd__genzeros }, { "getcwd", cmd__getcwd },
t/helper/test-tool.h +1 -0
@@ -23,6 +23,7 @@ int cmd__dump_untracked_cache(int argc, const char **argv);int cmd__dump_reftable (int argc,const char **argv);int cmd__example_decorate (int argc,const char **argv);int cmd__fast_rebase (int argc,const char **argv); +int cmd__fsmonitor_client (int argc,const char **argv);int cmd__genrandom (int argc,const char **argv);int cmd__genzeros (int argc,const char **argv);int cmd__getcwd (int argc,const char **argv);
Signature · 1 line
gitgitgadget
Re: [PATCH v6 20/30] t/helper/fsmonitor-client: create IPC client to talk to FSMonitor Daemon
Quoted 6 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
[...]t/helper/test-fsmonitor-client.c new file +4
-0+/* +* test-fsmonitor-client.c: client code to send commands/requests to +* a `git fsmonitor--daemon` daemon. +*/
t/helper/test-fsmonitor-client.c line 14
Quoted 10 lines
t/helper/test-fsmonitor-client.c new file +10
-0+ +#include "test-tool.h" +#include "cache.h" +#include "parse-options.h" +#include "fsmonitor-ipc.h" + +#ifndef HAVE_FSMONITOR_DAEMON_BACKEND +int cmd__fsmonitor_client (int argc,const char **argv) +{ +die ("fsmonitor--daemon not available on this platform" );
UX strings added in the series should be marked for translation with _(), but in this case this is correct as this is a test helper, so only git developers will see it. We shouldn't waste translator time on it.
t/helper/test-fsmonitor-client.c line 27
Quoted 1 line
t/helper/test-fsmonitor-client.c new file +1
-0+die ("unable to read index file" );
...ditto
t/helper/test-fsmonitor-client.c line 52
Quoted 1 line
t/helper/test-fsmonitor-client.c new file +1
-0+die (_ ("could not query fsmonitor--daemon" ));
...but this one is marked for translation, but shouldn't be.
There are other such cases in this series, found by e.g. diffing origin/master.. and grepping for '"', not noting all of those in review, I trust you can eyeball them & see what's missing ... :)
t/helper/test-fsmonitor-client.c line 103
Quoted 2 lines
t/helper/test-fsmonitor-client.c new file +2
-0+if (argc ==2 && !strcmp (argv[1 ],"-h" )) +usage_with_options (fsmonitor_client_usage, options);
I don't think you need this special handling for -h...
t/helper/test-fsmonitor-client.c line 109
Quoted 6 lines
t/helper/test-fsmonitor-client.c new file +6
-0+ + subcmd = argv[1 ]; + argv--; + argc++; + + argc =parse_options (argc, argv,NULL , options, fsmonitor_client_usage,0 );
...because this will do it for you.
PATCH v6 21/30 t7527: create test for fsmonitor--daemon
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
--- t/t7527-builtin-fsmonitor.sh | 511 +++++++++++++++++++++++++++++++++++ 1 file changed, 511 insertions(+) create mode 100755 t/t7527-builtin-fsmonitor.sh
t/t7527-builtin-fsmonitor.sh new file +511 -0
@@ -0,0 +1,511 @@ +#!/bin/sh + +test_description='built-in file system watcher' + +. ./test-lib.sh + +if ! test_have_prereq FSMONITOR_DAEMON +then + skip_all="fsmonitor--daemon is not supported on this platform" + test_done +fi + +stop_daemon_delete_repo () { + r=$1 + git -C $r fsmonitor--daemon stop >/dev/null 2>/dev/null + rm -rf $1 + return 0 +} + +start_daemon () { + case "$#" in + 1) r="-C $1";; + *) r=""; + esac + + git $r fsmonitor--daemon start || return $? + git $r fsmonitor--daemon status || return $? + + return 0 +} + +# 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 + + grep -e '"event":"data".*"category":"'"$c"'".*"key":"'"$k"'"' +} + +test_expect_success 'explicit daemon start and stop' ' + test_when_finished "stop_daemon_delete_repo test_explicit" && + + git init test_explicit && + start_daemon test_explicit && + + git -C test_explicit fsmonitor--daemon stop && + test_must_fail git -C test_explicit fsmonitor--daemon status +' + +test_expect_success 'implicit daemon start' ' + test_when_finished "stop_daemon_delete_repo test_implicit" && + + git init test_implicit && + test_must_fail git -C test_implicit fsmonitor--daemon status && + + # query will implicitly start the daemon. + # + # for test-script simplicity, we send a V1 timestamp rather than + # a V2 token. either way, the daemon response to any query contains + # a new V2 token. (the daemon may complain that we sent a V1 request, + # but this test case is only concerned with whether the daemon was + # implicitly started.) + + 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 && + + # confirm that a daemon was started in the background. + # + # since the mechanism for starting the background daemon is platform + # dependent, just confirm that the foreground command received a + # response from the daemon. + + have_t2_data_event fsm_client query/response-length <.git/trace && + + git -C test_implicit fsmonitor--daemon status && + git -C test_implicit fsmonitor--daemon stop && + test_must_fail git -C test_implicit fsmonitor--daemon status +' + +test_expect_success 'implicit daemon stop (delete .git)' ' + test_when_finished "stop_daemon_delete_repo test_implicit_1" && + + git init test_implicit_1 && + + start_daemon test_implicit_1 && + + # deleting the .git directory will implicitly stop the daemon. + rm -rf test_implicit_1/.git && + + # [1] Create an empty .git directory so that the following Git + # command will stay relative to the `-C` directory. + # + # Without this, the Git command will override the requested + # -C argument and crawl out to the containing Git source tree. + # 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 +' + +test_expect_success 'implicit daemon stop (rename .git)' ' + test_when_finished "stop_daemon_delete_repo test_implicit_2" && + + git init test_implicit_2 && + + start_daemon test_implicit_2 && + + # renaming the .git directory will implicitly stop the daemon. + mv test_implicit_2/.git test_implicit_2/.xxx && + + # See [1] above. + # + sleep 1 && + mkdir test_implicit_2/.git && + + test_must_fail git -C test_implicit_2 fsmonitor--daemon status +' + +test_expect_success 'cannot start multiple daemons' ' + test_when_finished "stop_daemon_delete_repo test_multiple" && + + git init test_multiple && + + start_daemon test_multiple && + + test_must_fail git -C test_multiple fsmonitor--daemon start 2>actual && + grep "fsmonitor--daemon is already running" actual && + + git -C test_multiple fsmonitor--daemon stop && + test_must_fail git -C test_multiple fsmonitor--daemon status +' + +# These tests use the main repo in the trash directory + +test_expect_success 'setup' ' + >tracked && + >modified && + >delete && + >rename && + mkdir dir1 && + >dir1/tracked && + >dir1/modified && + >dir1/delete && + >dir1/rename && + mkdir dir2 && + >dir2/tracked && + >dir2/modified && + >dir2/delete && + >dir2/rename && + mkdir dirtorename && + >dirtorename/a && + >dirtorename/b && + + cat >.gitignore <<-\EOF && + .gitignore + expect* + actual* + EOF + + git -c core.fsmonitor=false add . && + test_tick && + git -c core.fsmonitor=false commit -m initial && + + git config core.fsmonitor true +' + +# The test already explicitly stopped (or tried to stop) the daemon. +# This is here in case something else fails first. +# +redundant_stop_daemon () { + git fsmonitor--daemon stop + return 0 +} + +test_expect_success 'update-index implicitly starts daemon' ' + test_when_finished redundant_stop_daemon && + + test_must_fail git fsmonitor--daemon status && + + GIT_TRACE2_EVENT="$(pwd)/.git/trace_implicit_1" \ + git update-index --fsmonitor && + + git fsmonitor--daemon status && + test_might_fail git fsmonitor--daemon stop && + + # Confirm that the trace2 log contains a record of the + # daemon starting. + test_subcommand git fsmonitor--daemon start <.git/trace_implicit_1 +' + +test_expect_success 'status implicitly starts daemon' ' + test_when_finished redundant_stop_daemon && + + test_must_fail git fsmonitor--daemon status && + + GIT_TRACE2_EVENT="$(pwd)/.git/trace_implicit_2" \ + git status >actual && + + git fsmonitor--daemon status && + test_might_fail git fsmonitor--daemon stop && + + # Confirm that the trace2 log contains a record of the + # daemon starting. + test_subcommand git fsmonitor--daemon start <.git/trace_implicit_2 +' + +edit_files () { + echo 1 >modified + echo 2 >dir1/modified + echo 3 >dir2/modified + >dir1/untracked +} + +delete_files () { + rm -f delete + rm -f dir1/delete + rm -f dir2/delete +} + +create_files () { + echo 1 >new + echo 2 >dir1/new + echo 3 >dir2/new +} + +rename_files () { + mv rename renamed + mv dir1/rename dir1/renamed + mv dir2/rename dir2/renamed +} + +file_to_directory () { + rm -f delete + mkdir delete + echo 1 >delete/new +} + +directory_to_file () { + 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 +# is reported by `git status`. +# +# We run a simple query after modifying the filesystem just to introduce +# a bit of a delay so that the trace logging from the daemon has time to +# get flushed to disk. +# +# We `reset` and `clean` at the bottom of each test (and before stopping the +# 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 + 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 + ) && + + edit_files && + + test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 && + + grep "^event: dir1/modified$" .git/trace && + grep "^event: dir2/modified$" .git/trace && + grep "^event: modified$" .git/trace && + grep "^event: dir1/untracked$" .git/trace +' + +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 + ) && + + create_files && + + test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 && + + grep "^event: dir1/new$" .git/trace && + grep "^event: dir2/new$" .git/trace && + grep "^event: new$" .git/trace +' + +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 + ) && + + delete_files && + + test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 && + + grep "^event: dir1/delete$" .git/trace && + grep "^event: dir2/delete$" .git/trace && + grep "^event: delete$" .git/trace +' + +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 + ) && + + rename_files && + + test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 && + + grep "^event: dir1/rename$" .git/trace && + grep "^event: dir2/rename$" .git/trace && + grep "^event: rename$" .git/trace && + grep "^event: dir1/renamed$" .git/trace && + grep "^event: dir2/renamed$" .git/trace && + grep "^event: renamed$" .git/trace +' + +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 + ) && + + mv dirtorename dirrenamed && + + test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 && + + grep "^event: dirtorename/*$" .git/trace && + grep "^event: dirrenamed/*$" .git/trace +' + +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 + ) && + + file_to_directory && + + test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 && + + grep "^event: delete$" .git/trace && + grep "^event: delete/new$" .git/trace +' + +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 + ) && + + directory_to_file && + + test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 && + + grep "^event: dir1$" .git/trace +' + +# The next few test cases exercise the token-resync code. When filesystem +# drops events (because of filesystem velocity or because the daemon isn't +# polling fast enough), we need to discard the cached data (relative to the +# current token) and start collecting events under a new token. +# +# the 'test-tool fsmonitor-client flush' command can be used to send a +# "flush" message to a running daemon and ask it to do a flush/resync. + +test_expect_success 'flush cached data' ' + test_when_finished "stop_daemon_delete_repo test_flush" && + + 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 + ) && + + # The daemon should have an initial token with no events in _0 and + # then a few (probably platform-specific number of) events in _1. + # These should both have the same <token_id>. + + 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-tool -C test_flush fsmonitor-client query --token "builtin:test_00000001:0" >actual_1 && + nul_to_q <actual_1 >actual_q1 && + + grep "file_1" actual_q1 && + + # Force a flush. This will change the <token_id>, reset the <seq_nr>, and + # flush the file data. Then create some events and ensure that the file + # again appears in the cache. It should have the new <token_id>. + + test-tool -C test_flush fsmonitor-client flush >flush_0 && + nul_to_q <flush_0 >flush_q0 && + grep "^builtin:test_00000002:0Q/Q$" flush_q0 && + + test-tool -C test_flush fsmonitor-client query --token "builtin:test_00000002:0" >actual_2 && + nul_to_q <actual_2 >actual_q2 && + + grep "^builtin:test_00000002:0Q$" actual_q2 && + + touch 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 && + + grep "file_3" actual_q3 +' + +# The next few test cases create repos where the .git directory is NOT +# inside the one of the working directory. That is, where .git is a file +# that points to a directory elsewhere. This happens for submodules and +# non-primary worktrees. + +test_expect_success 'setup worktree base' ' + git init wt-base && + echo 1 >wt-base/file1 && + git -C wt-base add file1 && + git -C wt-base commit -m "c1" +' + +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 + ) && + + git -C wt-secondary fsmonitor--daemon stop && + test_must_fail git -C wt-secondary fsmonitor--daemon status +' + +# NEEDSWORK: Repeat one of the "edit" tests on wt-secondary and +# confirm that we get the same events and behavior -- that is, that +# fsmonitor--daemon correctly watches BOTH the working directory and +# the external GITDIR directory and behaves the same as when ".git" +# is a directory inside the working directory. + +test_expect_success 'cleanup worktrees' ' + stop_daemon_delete_repo wt-secondary && + stop_daemon_delete_repo wt-base +' + +test_done
Signature · 1 line
gitgitgadget
Re: [PATCH v6 21/30] t7527: create test for fsmonitor--daemon
Quoted 20 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
Signed-off-by Jeff Hostetler <[email protected]>Signed-off-by Junio C Hamano <[email protected]>--- t/t7527-builtin-fsmonitor.sh | 511 +++++++++++++++++++++++++++++++++++ 1 file changed, 511 insertions(+) create mode 100755 t/t7527-builtin-fsmonitor.sht/t7527-builtin-fsmonitor.sh new file +5
-0@@ -0,0 +1,511 @@ +#!/bin/sh + +test_description='built-in file system watcher' + +. ./test-lib.sh
t/t7527-builtin-fsmonitor.sh line 15
Quoted 10 lines
t/t7527-builtin-fsmonitor.sh new file +10
-0+ +if ! test_have_prereq FSMONITOR_DAEMON +then + skip_all="fsmonitor--daemon is not supported on this platform" + test_done +fi + +stop_daemon_delete_repo () { + r=$1 + git -C $r fsmonitor--daemon stop >/dev/null 2>/dev/null
Do we really need to quiet all its output? Why not just have the --verbose option do its thing?
t/t7527-builtin-fsmonitor.sh line 17
Quoted 2 lines
t/t7527-builtin-fsmonitor.sh new file +2
-0+ rm -rf $1 + return 0
Missing &&-chaining
t/t7527-builtin-fsmonitor.sh line 23
Quoted 6 lines
t/t7527-builtin-fsmonitor.sh new file +6
-0+} + +start_daemon () { + case "$#" in + 1) r="-C $1";; + *) r="";
Our usual style is not to indent these.
But maybe just use the same pattern as test_commit et al use? It's a bit more verbose, but IMO clearer.
t/t7527-builtin-fsmonitor.sh line 27
Quoted 4 lines
t/t7527-builtin-fsmonitor.sh new file +4
-0+ esac + + git $r fsmonitor--daemon start || return $? + git $r fsmonitor--daemon status || return $?
Maybe don't do all this "return" and just &&-chain these instead (including the case/esac)?
t/t7527-builtin-fsmonitor.sh line 40
Quoted 13 lines
t/t7527-builtin-fsmonitor.sh new file +13
-0+ + return 0 +} + +# 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 + + grep -e '"event":"data".*"category":"'"$c"'".*"key":"'"$k"'"' +}
Optional & aside: But it would be really nice to just have this amend the test_region function into something more general, so this could be:
test_trace2 --event data --category "$c" --key "$k"
And have "test_region" then be a thin wrapper for that, and do the appropriate "maybe only the start one, not the end one?" logic there.
Quoted 15 lines
t/t7527-builtin-fsmonitor.sh new file +15
-0+test_expect_success 'explicit daemon start and stop' ' + test_when_finished "stop_daemon_delete_repo test_explicit" && + + git init test_explicit && + start_daemon test_explicit && + + git -C test_explicit fsmonitor--daemon stop && + test_must_fail git -C test_explicit fsmonitor--daemon status +' + +test_expect_success 'implicit daemon start' ' + test_when_finished "stop_daemon_delete_repo test_implicit" && + + git init test_implicit && + test_must_fail git -C test_implicit fsmonitor--daemon status &&
t/t7527-builtin-fsmonitor.sh line 66
Quoted 10 lines
t/t7527-builtin-fsmonitor.sh new file +10
-0+ + # query will implicitly start the daemon. + # + # for test-script simplicity, we send a V1 timestamp rather than + # a V2 token. either way, the daemon response to any query contains + # a new V2 token. (the daemon may complain that we sent a V1 request, + # but this test case is only concerned with whether the daemon was + # implicitly started.) + + GIT_TRACE2_EVENT="$(pwd)/.git/trace" \
Better to use $PWD than $(pwd)
t/t7527-builtin-fsmonitor.sh line 224
Quoted 4 lines
t/t7527-builtin-fsmonitor.sh new file +4
-0+delete_files () { + rm -f delete + rm -f dir1/delete + rm -f dir2/delete
More missing &&-chaining.
t/t7527-builtin-fsmonitor.sh line 236
Quoted 12 lines
t/t7527-builtin-fsmonitor.sh new file +12
-0+} + +create_files () { + echo 1 >new + echo 2 >dir1/new + echo 3 >dir2/new +} + +rename_files () { + mv rename renamed + mv dir1/rename dir1/renamed + mv dir2/rename dir2/renamed
ditto.
t/t7527-builtin-fsmonitor.sh line 242
Quoted 6 lines
t/t7527-builtin-fsmonitor.sh new file +6
-0+} + +file_to_directory () { + rm -f delete + mkdir delete + echo 1 >delete/new
ditto.
t/t7527-builtin-fsmonitor.sh line 247
Quoted 5 lines
t/t7527-builtin-fsmonitor.sh new file +5
-0+} + +directory_to_file () { + rm -rf dir1 + echo 1 >dir1
ditto.
t/t7527-builtin-fsmonitor.sh line 250
Quoted 3 lines
t/t7527-builtin-fsmonitor.sh new file +3
-0+} + +verify_status () {
This is used by nothing? Maybe it'll be used later, but that commit could/should add it then?
Hrm, nope, just read ahead and nothing uses it at all.
t/t7527-builtin-fsmonitor.sh line 276
Quoted 5 lines
t/t7527-builtin-fsmonitor.sh new file +5
-0+clean_up_repo_and_stop_daemon () { + git reset --hard HEAD + git clean -fd + git fsmonitor--daemon stop + rm -f .git/trace
Missing &&-chaining (will stop noting these now, please look through the rest...)
t/t7527-builtin-fsmonitor.sh line 286
Quoted 10 lines
t/t7527-builtin-fsmonitor.sh new file +10
-0+} + +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
Maybe have this "start_daemon" take an optional --trace argument or something, allowing us to skip all these subsequent subshells.
t/t7527-builtin-fsmonitor.sh line 291
Quoted 5 lines
t/t7527-builtin-fsmonitor.sh new file +5
-0+ ) && + + edit_files && + + test-tool fsmonitor-client query --token 0 >/dev/null 2>&1 &&
For these & the rest: just skip the quieting of the output? I.e. let the test's --verbose do its job?
Quoted 14 lines
t/t7527-builtin-fsmonitor.sh new file +14
-0+test_expect_success 'flush cached data' ' + test_when_finished "stop_daemon_delete_repo test_flush" && + + 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 + ) &&
t/t7527-builtin-fsmonitor.sh line 443
Quoted 10 lines
t/t7527-builtin-fsmonitor.sh new file +10
-0+ + # The daemon should have an initial token with no events in _0 and + # then a few (probably platform-specific number of) events in _1. + # These should both have the same <token_id>. + + 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 &&
I may be missinga subtlety here, but is "touch" needed v.s. ">", i.e. we just created "test_flush", so if we create a new file it'll have the current timestamp.
Or did it get created by the helpers?
PATCH v6 22/30 t/perf: avoid copying builtin fsmonitor files into test repo
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Do not copy any of the various fsmonitor--daemon files from the .git directory of the (GIT_PREF_REPO or GIT_PERF_LARGE_REPO) source repo into the test's trash directory.
When perf tests start, they copy the contents of the source repo into the test's trash directory. If fsmonitor is running in the source repo, there may be control files, such as the IPC socket and/or fsmonitor cookie files. These should not be copied into the test repo.
Unix domain sockets cannot be copied in the manner used by the test setup, so if present, the test setup fails.
Cookie files are harmless, but we should avoid them.
The builtin fsmonitor keeps all such control files/sockets in .git/fsmonitor--daemon*, so it is simple to exclude them.
--- t/perf/perf-lib.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-)
t/perf/perf-lib.sh +1 -1
@@ -78,7 +78,7 @@ test_perf_copy_repo_contents () { for stuff in "$1"/* do case "$stuff" in - */objects|*/hooks|*/config|*/commondir|*/gitdir|*/worktrees) + */objects|*/hooks|*/config|*/commondir|*/gitdir|*/worktrees|*/fsmonitor--daemon*) ;; *) cp -R "$stuff" "$repo/.git/" || exit 1
Signature · 1 line
gitgitgadget
PATCH v6 23/30 t/helper/test-chmtime: skip directories on Windows
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Teach test-tool.exe chmtime to ignore errors when setting the mtime on a directory on Windows.
NEEDSWORK: The Windows version of utime() (aka mingw_utime()) does not properly handle directories because it uses _wopen(). It should be converted to using CreateFileW() and backup semantics at a minimum. Since I'm already in the middle of a large patch series, I did not want to destabilize other callers of utime() right now. The problem has only been observed in the t/perf/p7519 test when the test repo contains an empty directory on disk.
--- t/helper/test-chmtime.c | 15 +++++++++++++++ 1 file changed, 15 insertions(+)
t/helper/test-chmtime.c +15 -0
@@ -134,6 +134,21 @@ int cmd__chmtime(int argc, const char **argv) }if (utb.modtime != sb.st_mtime &&utime (argv[i], &utb) <0 ) { +#ifdef GIT_WINDOWS_NATIVE +if (S_ISDIR (sb.st_mode)) { +/* +* NEEDSWORK: The Windows version of `utime()` +* (aka `mingw_utime()`) does not correctly +* handle directory arguments, since it uses +* `_wopen()`. Ignore it for now since this +* is just a test. +*/ +fprintf (stderr, + ("Failed to modify time on directory %s. " +"Skipping\n" ), argv[i]); +continue ; + } +#endif fprintf (stderr,"Failed to modify time on %s: %s\n" , argv[i],strerror (errno));return 1 ;
Signature · 1 line
gitgitgadget
PATCH v6 24/30 t/perf/p7519: speed up test on Windows
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Change p7519 to use test_seq and xargs rather than a for loop to touch thousands of files. This takes minutes off of test runs on Windows because of process creation overhead.
--- t/perf/p7519-fsmonitor.sh | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-)
t/perf/p7519-fsmonitor.sh +20 -12
@@ -72,7 +72,7 @@ then fi fi -trace_start() { +trace_start () { if test -n "$GIT_PERF_7519_TRACE" then name="$1"
@@ -91,13 +91,20 @@ trace_start() { fi } -trace_stop() { +trace_stop () { if test -n "$GIT_PERF_7519_TRACE" then unset GIT_TRACE2_PERF fi } +touch_files () { + n=$1 + d="$n"_files + + (cd $d ; test_seq 1 $n | xargs touch ) +} + test_expect_success "one time repo setup" ' # set untrackedCache depending on the environment if test -n "$GIT_PERF_7519_UNTRACKED_CACHE"
@@ -119,10 +126,11 @@ test_expect_success "one time repo setup" ' fi && mkdir 1_file 10_files 100_files 1000_files 10000_files && - for i in $(test_seq 1 10); do touch 10_files/$i || return 1; done && - for i in $(test_seq 1 100); do touch 100_files/$i || return 1; done && - for i in $(test_seq 1 1000); do touch 1000_files/$i || return 1; done && - for i in $(test_seq 1 10000); do touch 10000_files/$i || return 1; done && + : 1_file directory should be left empty && + touch_files 10 && + touch_files 100 && + touch_files 1000 && + touch_files 10000 && git add 1_file 10_files 100_files 1000_files 10000_files && git commit -qm "Add files" &&
@@ -133,7 +141,7 @@ test_expect_success "one time repo setup" ' fi ' -setup_for_fsmonitor() { +setup_for_fsmonitor () { # set INTEGRATION_SCRIPT depending on the environment if test -n "$INTEGRATION_PATH" then
@@ -173,7 +181,7 @@ test_perf_w_drop_caches () { test_perf "$@" } -test_fsmonitor_suite() { +test_fsmonitor_suite () { if test -n "$INTEGRATION_SCRIPT"; then DESC="fsmonitor=$(basename $INTEGRATION_SCRIPT)" else
@@ -199,15 +207,15 @@ test_fsmonitor_suite() { # Update the mtimes on upto 100k files to make status think # that they are dirty. For simplicity, omit any files with - # LFs (i.e. anything that ls-files thinks it needs to dquote). - # Then fully backslash-quote the paths to capture any - # whitespace so that they pass thru xargs properly. + # LFs (i.e. anything that ls-files thinks it needs to dquote) + # and any files with whitespace so that they pass thru xargs + # properly. # test_perf_w_drop_caches "status (dirty) ($DESC)" ' git ls-files | \ head -100000 | \ grep -v \" | \ - sed '\''s/\(.\)/\\\1/g'\'' | \ + egrep -v " ." | \ xargs test-tool chmtime -300 && git status '
Signature · 1 line
gitgitgadget
Re: [PATCH v6 24/30] t/perf/p7519: speed up test on Windows
Quoted 26 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
Change p7519 to use
test_seqandxargsrather than aforloop to touch thousands of files. This takes minutes off of test runs on Windows because of process creation overhead.Signed-off-by Jeff Hostetler <[email protected]>Signed-off-by Junio C Hamano <[email protected]>--- t/perf/p7519-fsmonitor.sh | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-)t/perf/p7519-fsmonitor.sh +1
-1@@ -72,7 +72,7 @@ then fi fi -trace_start() { +trace_start () { if test -n "$GIT_PERF_7519_TRACE" then name="$1"
t/perf/p7519-fsmonitor.sh trace_start() { · line 99
Quoted 10 lines
t/perf/p7519-fsmonitor.sh +1
-1@@ -91,13 +91,20 @@ trace_start() { fi } -trace_stop() { +trace_stop () { if test -n "$GIT_PERF_7519_TRACE" then unset GIT_TRACE2_PERF fi }
These minor unrelated style fixups could be split up / sent seperately? Especially as the seem not to conflict hunk-wise with the actual changes here.
t/perf/p7519-fsmonitor.sh trace_start() { · line 105
Quoted 5 lines
t/perf/p7519-fsmonitor.sh +5
-0+touch_files () { + n=$1 + d="$n"_files + + (cd $d ; test_seq 1 $n | xargs touch )
Missing &&-chaining for "cd"
t/perf/p7519-fsmonitor.sh test_perf_w_drop_caches () { · line 184
Quoted 11 lines
t/perf/p7519-fsmonitor.sh +2
-2-setup_for_fsmonitor() { +setup_for_fsmonitor () { # set INTEGRATION_SCRIPT depending on the environment if test -n "$INTEGRATION_PATH" then@@ -173,7 +181,7 @@ test_perf_w_drop_caches () { test_perf "$@" } -test_fsmonitor_suite() { +test_fsmonitor_suite () {
ditto unrelated style changes.
Quoted 10 lines
t/perf/p7519-fsmonitor.sh +0
-3if test -n "$INTEGRATION_SCRIPT"; then DESC="fsmonitor=$(basename $INTEGRATION_SCRIPT)" else@@ -199,15 +207,15 @@ test_fsmonitor_suite() { # Update the mtimes on upto 100k files to make status think # that they are dirty. For simplicity, omit any files with - # LFs (i.e. anything that ls-files thinks it needs to dquote). - # Then fully backslash-quote the paths to capture any - # whitespace so that they pass thru xargs properly.
t/perf/p7519-fsmonitor.sh test_fsmonitor_suite() { · line 218
Quoted 10 lines
t/perf/p7519-fsmonitor.sh +4
-1+ # LFs (i.e. anything that ls-files thinks it needs to dquote) + # and any files with whitespace so that they pass thru xargs + # properly. # test_perf_w_drop_caches "status (dirty) ($DESC)" ' git ls-files | \ head -100000 | \ grep -v \" | \ - sed '\''s/\(.\)/\\\1/g'\'' | \ + egrep -v " ." | \
Per dcf9a748cab (t7700: replace egrep with grep, 2019-12-04) should we be adding more egrep?
Also, even if we did want that, why does a the " ." regex need ERE over BRE here?
PATCH v6 25/30 t/perf/p7519: add fsmonitor--daemon test cases
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Repeat all of the fsmonitor perf tests using git fsmonitor--daemon and the "Simple IPC" interface.
--- t/perf/p7519-fsmonitor.sh | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-)
t/perf/p7519-fsmonitor.sh +28 -1
@@ -182,7 +182,10 @@ test_perf_w_drop_caches () { } test_fsmonitor_suite () { - if test -n "$INTEGRATION_SCRIPT"; then + if test -n "$USE_FSMONITOR_DAEMON" + then + DESC="builtin fsmonitor--daemon" + elif test -n "$INTEGRATION_SCRIPT"; then DESC="fsmonitor=$(basename $INTEGRATION_SCRIPT)" else DESC="fsmonitor=disabled"
@@ -293,4 +296,28 @@ test_expect_success "setup without fsmonitor" ' test_fsmonitor_suite trace_stop +# +# Run a full set of perf tests using the built-in fsmonitor--daemon. +# It does not use the Hook API, so it has a different setup. +# Explicitly start the daemon here and before we start client commands +# so that we can later add custom tracing. +# +if test_have_prereq FSMONITOR_DAEMON +then + USE_FSMONITOR_DAEMON=t + + trace_start fsmonitor--daemon--server + git fsmonitor--daemon start + + trace_start fsmonitor--daemon--client + + git config core.fsmonitor true + git update-index --fsmonitor + + test_fsmonitor_suite + + git fsmonitor--daemon stop + trace_stop +fi + test_done
Signature · 1 line
gitgitgadget
Re: [PATCH v6 25/30] t/perf/p7519: add fsmonitor--daemon test cases
Quoted 16 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
Repeat all of the fsmonitor perf tests using
git fsmonitor--daemonand the "Simple IPC" interface.Signed-off-by Jeff Hostetler <[email protected]>Signed-off-by Junio C Hamano <[email protected]>--- t/perf/p7519-fsmonitor.sh | 29 ++++++++++++++++++++++++++++- 1 file changed, 28 insertions(+), 1 deletion(-)t/perf/p7519-fsmonitor.sh +0
-0
t/perf/p7519-fsmonitor.sh test_perf_w_drop_caches () { · line 186
Quoted 6 lines
t/perf/p7519-fsmonitor.sh +2
-1@@ -182,7 +182,10 @@ test_perf_w_drop_caches () { } test_fsmonitor_suite () { - if test -n "$INTEGRATION_SCRIPT"; then + if test -n "$USE_FSMONITOR_DAEMON" + then
...another candidate for "shell script style change" re my comment on 25/30.
t/perf/p7519-fsmonitor.sh test_perf_w_drop_caches () { · line 188
Quoted 2 lines
t/perf/p7519-fsmonitor.sh +2
-0+ DESC="builtin fsmonitor--daemon" + elif test -n "$INTEGRATION_SCRIPT"; then
...but it's especially odd in this case, becuse here the added code *doesn't* follow our usual style... :)
Since this hunk-wise would conflict with the meaningful change I don't mind if we "fix it while we're at it", but it really should use the preferred style for new code then.
Quoted 20 lines
t/perf/p7519-fsmonitor.sh +13
-0DESC="fsmonitor=$(basename $INTEGRATION_SCRIPT)" else DESC="fsmonitor=disabled"@@ -293,4 +296,28 @@ test_expect_success "setup without fsmonitor" ' test_fsmonitor_suite trace_stop +# +# Run a full set of perf tests using the built-in fsmonitor--daemon. +# It does not use the Hook API, so it has a different setup. +# Explicitly start the daemon here and before we start client commands +# so that we can later add custom tracing. +# +if test_have_prereq FSMONITOR_DAEMON +then + USE_FSMONITOR_DAEMON=t + + trace_start fsmonitor--daemon--server + git fsmonitor--daemon start +
t/perf/p7519-fsmonitor.sh test_expect_success "setup without fsmonitor" ' · line 321
Quoted 10 lines
t/perf/p7519-fsmonitor.sh +10
-0+ trace_start fsmonitor--daemon--client + + git config core.fsmonitor true + git update-index --fsmonitor + + test_fsmonitor_suite + + git fsmonitor--daemon stop + trace_stop +fi
Urm, shouldn't this be in a test_perf or test_expect_success?
Quoted 1 line
t/perf/p7519-fsmonitor.sh +0
-0test_done
PATCH v6 26/30 fsmonitor--daemon: periodically truncate list of modified files
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Teach fsmonitor--daemon to periodically truncate the list of modified files to save some memory.
Clients will ask for the set of changes relative to a token that they found in the FSMN index extension in the index. (This token is like a point in time, but different). Clients will then update the index to contain the response token (so that subsequent commands will be relative to this new token).
Therefore, the daemon can gradually truncate the in-memory list of changed paths as they become obsolete (older than the previous token). Since we may have multiple clients making concurrent requests with a skew of tokens and clients may be racing to the talk to the daemon, we lazily truncate the list.
We introduce a 5 minute delay and truncate batches 5 minutes after they are considered obsolete.
--- builtin/fsmonitor--daemon.c | 88 +++++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+)
builtin/fsmonitor--daemon.c +88 -0
@@ -312,6 +312,75 @@ static void fsmonitor_batch__combine(struct fsmonitor_batch *batch_dest, batch_src->interned_paths[k]; } +/* +* To keep the batch list from growing unbounded in response to filesystem +* activity, we try to truncate old batches from the end of the list as +* they become irrelevant. +* +* We assume that the .git/index will be updated with the most recent token +* any time the index is updated. And future commands will only ask for +* recent changes *since* that new token. So as tokens advance into the +* future, older batch items will never be requested/needed. So we can +* truncate them without loss of functionality. +* +* However, multiple commands may be talking to the daemon concurrently +* or perform a slow command, so a little "token skew" is possible. +* Therefore, we want this to be a little bit lazy and have a generous +* delay. +* +* The current reader thread walked backwards in time from `token->batch_head` +* back to `batch_marker` somewhere in the middle of the batch list. +* +* Let's walk backwards in time from that marker an arbitrary delay +* and truncate the list there. Note that these timestamps are completely +* artificial (based on when we pinned the batch item) and not on any +* filesystem activity. +* +* Return the obsolete portion of the list after we have removed it from +* the official list so that the caller can free it after leaving the lock. +*/ +#define MY_TIME_DELAY_SECONDS (5 * 60)/* seconds */ + +static struct fsmonitor_batch *with_lock__truncate_old_batches ( +struct fsmonitor_daemon_state *state, +const struct fsmonitor_batch *batch_marker) +{ +/* assert current thread holding state->main_lock */ + +const struct fsmonitor_batch *batch; +struct fsmonitor_batch *remainder; + +if (!batch_marker) +return NULL ; + +trace_printf_key (&trace_fsmonitor,"Truncate: mark (%" PRIu64",%" PRIu64")" , + batch_marker->batch_seq_nr, + (uint64_t )batch_marker->pinned_time); + +for (batch = batch_marker; batch; batch = batch->next) { +time_t t; + +if (!batch->pinned_time)/* an overflow batch */ +continue ; + + t = batch->pinned_time +MY_TIME_DELAY_SECONDS ; +if (t > batch_marker->pinned_time)/* too close to marker */ +continue ; + + goto truncate_past_here; + } + +return NULL ; + +truncate_past_here: + state->current_token_data->batch_tail = (struct fsmonitor_batch *)batch; + + remainder = ((struct fsmonitor_batch *)batch)->next; + ((struct fsmonitor_batch *)batch)->next =NULL ; + +return remainder; +} +static void fsmonitor_free_token_data (struct fsmonitor_token_data *token) {if (!token)
@@ -425,6 +494,7 @@ static int do_handle_client(struct fsmonitor_daemon_state *state,const char *p;const struct fsmonitor_batch *batch_head;const struct fsmonitor_batch *batch; +struct fsmonitor_batch *remainder =NULL ;intmax_t count =0 , duplicates =0 ;kh_str_t *shown;int hash_ret;
@@ -654,11 +724,29 @@ static int do_handle_client(struct fsmonitor_daemon_state *state,* that work. */ fsmonitor_free_token_data (token_data); + }else if (batch) { +/* +* We are holding the lock and are the only +* reader of the ref-counted portion of the +* list, so we get the honor of seeing if the +* list can be truncated to save memory. +* +* The main loop did not walk to the end of the +* list, so this batch is the first item in the +* batch-list that is older than the requested +* end-point sequence number. See if the tail +* end of the list is obsolete. +*/ + remainder =with_lock__truncate_old_batches (state, + batch); } }pthread_mutex_unlock (&state->main_lock); +if (remainder) +fsmonitor_batch__free_list (remainder); +trace2_data_intmax ("fsmonitor" , the_repository,"response/length" , total_response_len);trace2_data_intmax ("fsmonitor" , the_repository,"response/count/files" , count);trace2_data_intmax ("fsmonitor" , the_repository,"response/count/duplicates" , duplicates);
Signature · 1 line
gitgitgadget
PATCH v6 27/30 fsmonitor--daemon: use a cookie file to sync with file system
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Teach fsmonitor--daemon client threads to create a cookie file inside the .git directory and then wait until FS events for the cookie are observed by the FS listener thread.
This helps address the racy nature of file system events by blocking the client response until the kernel has drained any event backlog.
This is especially important on MacOS where kernel events are only issued with a limited frequency. See the latency argument of FSeventStreamCreate(). The kernel only signals every latency seconds, but does not guarantee that the kernel queue is completely drained, so we may have to wait more than one interval. If we increase the latency, the system is more likely to drop events. We avoid these issues by having each client thread create a unique cookie file and then wait until it is seen in the event stream.
--- builtin/fsmonitor--daemon.c | 230 +++++++++++++++++++++++++++++++++++- fsmonitor--daemon.h | 5 + 2 files changed, 234 insertions(+), 1 deletion(-)
builtin/fsmonitor--daemon.c +229 -1
@@ -107,6 +107,155 @@ static int do_as_client__status(void) } } +enum fsmonitor_cookie_item_result { +FCIR_ERROR =-1 ,/* could not create cookie file ? */ +FCIR_INIT =0 , +FCIR_SEEN , +FCIR_ABORT , +}; + +struct fsmonitor_cookie_item { +struct hashmap_entry entry; +const char *name; +enum fsmonitor_cookie_item_result result; +}; + +static int cookies_cmp (const void *data,const struct hashmap_entry *he1, +const struct hashmap_entry *he2,const void *keydata) +{ +const struct fsmonitor_cookie_item *a = + container_of(he1, const struct fsmonitor_cookie_item, entry); +const struct fsmonitor_cookie_item *b = + container_of(he2, const struct fsmonitor_cookie_item, entry); + +return strcmp (a->name, keydata ? keydata : b->name); +} + +static enum fsmonitor_cookie_item_result with_lock__wait_for_cookie ( +struct fsmonitor_daemon_state *state) +{ +/* assert current thread holding state->main_lock */ + +int fd; +struct fsmonitor_cookie_item *cookie; +struct strbuf cookie_pathname =STRBUF_INIT ; +struct strbuf cookie_filename =STRBUF_INIT ; +enum fsmonitor_cookie_item_result result; +int my_cookie_seq; + +CALLOC_ARRAY (cookie,1 ); + + my_cookie_seq = state->cookie_seq++; + +strbuf_addf (&cookie_filename,"%i-%i" ,getpid (), my_cookie_seq); + +strbuf_addbuf (&cookie_pathname, &state->path_cookie_prefix); +strbuf_addbuf (&cookie_pathname, &cookie_filename); + + cookie->name =strbuf_detach (&cookie_filename,NULL ); + cookie->result =FCIR_INIT ; +hashmap_entry_init (&cookie->entry,strhash (cookie->name)); + +hashmap_add (&state->cookies, &cookie->entry); + +trace_printf_key (&trace_fsmonitor,"cookie-wait: '%s' '%s'" , + cookie->name, cookie_pathname.buf); + +/* +* Create the cookie file on disk and then wait for a notification +* 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 { +error_errno (_ ("could not create fsmonitor cookie '%s'" ), + cookie->name); + + cookie->result =FCIR_ERROR ; + } + +hashmap_remove (&state->cookies, &cookie->entry,NULL ); + + result = cookie->result; + +free ((char *)cookie->name); +free (cookie); +strbuf_release (&cookie_pathname); + +return result; +} + +/* +* Mark these cookies as _SEEN and wake up the corresponding client threads. +*/ +static void with_lock__mark_cookies_seen (struct fsmonitor_daemon_state *state, +const struct string_list *cookie_names) +{ +/* assert current thread holding state->main_lock */ + +int k; +int nr_seen =0 ; + +for (k =0 ; k < cookie_names->nr; k++) { +struct fsmonitor_cookie_item key; +struct fsmonitor_cookie_item *cookie; + + key.name = cookie_names->items[k].string; +hashmap_entry_init (&key.entry,strhash (key.name)); + + cookie =hashmap_get_entry (&state->cookies, &key, entry,NULL ); +if (cookie) { +trace_printf_key (&trace_fsmonitor,"cookie-seen: '%s'" , + cookie->name); + cookie->result =FCIR_SEEN ; + nr_seen++; + } + } + +if (nr_seen) +pthread_cond_broadcast (&state->cookies_cond); +} + +/* +* Set _ABORT on all pending cookies and wake up all client threads. +*/ +static void with_lock__abort_all_cookies (struct fsmonitor_daemon_state *state) +{ +/* assert current thread holding state->main_lock */ + +struct hashmap_iter iter; +struct fsmonitor_cookie_item *cookie; +int nr_aborted =0 ; + +hashmap_for_each_entry (&state->cookies, &iter, cookie, entry) { +trace_printf_key (&trace_fsmonitor,"cookie-abort: '%s'" , + cookie->name); + cookie->result =FCIR_ABORT ; + nr_aborted++; + } + +if (nr_aborted) +pthread_cond_broadcast (&state->cookies_cond); +} +/* * Requests to and from a FSMonitor Protocol V2 provider use an opaque * "token" as a virtual timestamp. Clients can request a summary of all
@@ -404,6 +553,9 @@ static void fsmonitor_free_token_data(struct fsmonitor_token_data *token)* We should create a new token and start fresh (as if we just * booted up). * +* [2] Some of those lost events may have been for cookie files. We +* should assume the worst and abort them rather letting them starve. +* * If there are no concurrent threads reading the current token data * series, we can free it now. Otherwise, let the last reader free * it.
@@ -425,6 +577,8 @@ static void with_lock__do_force_resync(struct fsmonitor_daemon_state *state) state->current_token_data = new_one;fsmonitor_free_token_data (free_me); + +with_lock__abort_all_cookies (state); }void fsmonitor_force_resync (struct fsmonitor_daemon_state *state)
@@ -500,6 +654,8 @@ static int do_handle_client(struct fsmonitor_daemon_state *state,int hash_ret;int do_trivial =0 ;int do_flush =0 ; +int do_cookie =0 ; +enum fsmonitor_cookie_item_result cookie_result;/* * We expect `command` to be of the form:
@@ -560,6 +716,7 @@ static int do_handle_client(struct fsmonitor_daemon_state *state,* We have a V2 valid token: * "builtin:<token_id>:<seq_nr>" */ + do_cookie =1 ; } }
@@ -568,6 +725,30 @@ static int do_handle_client(struct fsmonitor_daemon_state *state,if (!state->current_token_data)BUG ("fsmonitor state does not have a current token" ); +/* +* Write a cookie file inside the directory being watched in +* an effort to flush out existing filesystem events that we +* actually care about. Suspend this client thread until we +* see the filesystem events for this cookie file. +* +* Creating the cookie lets us guarantee that our FS listener +* thread has drained the kernel queue and we are caught up +* with the kernel. +* +* If we cannot create the cookie (or otherwise guarantee that +* we are caught up), we send a trivial response. We have to +* assume that there might be some very, very recent activity +* on the FS still in flight. +*/ +if (do_cookie) { + cookie_result =with_lock__wait_for_cookie (state); +if (cookie_result !=FCIR_SEEN ) { +error (_ ("fsmonitor: cookie_result '%d' != SEEN" ), + cookie_result); + do_trivial =1 ; + } + } +if (do_flush)with_lock__do_force_resync (state);
@@ -788,7 +969,9 @@ static int handle_client(void *data,return result; } -#define FSMONITOR_COOKIE_PREFIX ".fsmonitor-daemon-" +#define FSMONITOR_DIR "fsmonitor--daemon" +#define FSMONITOR_COOKIE_DIR "cookies" +#define FSMONITOR_COOKIE_PREFIX (FSMONITOR_DIR "/" FSMONITOR_COOKIE_DIR "/")enum fsmonitor_path_type fsmonitor_classify_path_workdir_relative (const char *rel)
@@ -941,6 +1124,9 @@ void fsmonitor_publish(struct fsmonitor_daemon_state *state, } } +if (cookie_names->nr) +with_lock__mark_cookies_seen (state, cookie_names); +pthread_mutex_unlock (&state->main_lock); }
@@ -1032,7 +1218,9 @@ static int fsmonitor_run_daemon(void)memset (&state,0 ,sizeof (state)); +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.current_token_data =fsmonitor_new_token_data ();
@@ -1057,6 +1245,44 @@ static int fsmonitor_run_daemon(void) state.nr_paths_watching =2 ; } +/* +* We will write filesystem syncing cookie files into +* <gitdir>/<fsmonitor-dir>/<cookie-dir>/<pid>-<seq>. +* +* The extra layers of subdirectories here keep us from +* changing the mtime on ".git/" or ".git/foo/" when we create +* or delete cookie files. +* +* There have been problems with some IDEs that do a +* non-recursive watch of the ".git/" directory and run a +* series of commands any time something happens. +* +* For example, if we place our cookie files directly in +* ".git/" or ".git/foo/" then a `git status` (or similar +* command) from the IDE will cause a cookie file to be +* created in one of those dirs. This causes the mtime of +* those dirs to change. This triggers the IDE's watch +* notification. This triggers the IDE to run those commands +* again. And the process repeats and the machine never goes +* idle. +* +* Adding the extra layers of subdirectories prevents the +* mtime of ".git/" and ".git/foo" from changing when a +* cookie file is created. +*/ +strbuf_init (&state.path_cookie_prefix,0 ); +strbuf_addbuf (&state.path_cookie_prefix, &state.path_gitdir_watch); + +strbuf_addch (&state.path_cookie_prefix,'/' ); +strbuf_addstr (&state.path_cookie_prefix,FSMONITOR_DIR ); +mkdir (state.path_cookie_prefix.buf,0777 ); + +strbuf_addch (&state.path_cookie_prefix,'/' ); +strbuf_addstr (&state.path_cookie_prefix,FSMONITOR_COOKIE_DIR ); +mkdir (state.path_cookie_prefix.buf,0777 ); + +strbuf_addch (&state.path_cookie_prefix,'/' ); +/* * Confirm that we can create platform-specific resources for the * filesystem listener before we bother starting all the threads.
@@ -1069,6 +1295,7 @@ static int fsmonitor_run_daemon(void) err =fsmonitor_run_daemon_1 (&state); done: +pthread_cond_destroy (&state.cookies_cond);pthread_mutex_destroy (&state.main_lock);fsm_listen__dtor (&state);
@@ -1076,6 +1303,7 @@ done:strbuf_release (&state.path_worktree_watch);strbuf_release (&state.path_gitdir_watch); +strbuf_release (&state.path_cookie_prefix);return err; }
fsmonitor--daemon.h +5 -0
@@ -45,6 +45,11 @@ struct fsmonitor_daemon_state {struct fsmonitor_token_data *current_token_data; +struct strbuf path_cookie_prefix; +pthread_cond_t cookies_cond; +int cookie_seq; +struct hashmap cookies; +int error_code;struct fsmonitor_daemon_backend_data *backend_data;
Signature · 1 line
gitgitgadget
Re: [PATCH v6 27/30] fsmonitor--daemon: use a cookie file to sync with file system
builtin/fsmonitor--daemon.c static int do_as_client__status(void) · line 112
Quoted 5 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
[...]builtin/fsmonitor--daemon.c +3
-0+enum fsmonitor_cookie_item_result { +FCIR_ERROR =-1 ,/* could not create cookie file ? */ +FCIR_INIT =0 ,
nit: redundant = 0 assignment, i.e....
builtin/fsmonitor--daemon.c static int do_as_client__status(void) · line 114
Quoted 2 lines
builtin/fsmonitor--daemon.c +2
-0+FCIR_SEEN , + FCIR_ABORT,
If we're going to make these implicit (which we usually do) let's do the same for FCIR_INIT.
builtin/fsmonitor--daemon.c static int do_as_client__status(void) · line 150
Quoted 1 line
builtin/fsmonitor--daemon.c +1
-0+strbuf_addf (&cookie_filename,"%i-%i" ,getpid (), my_cookie_seq);
For trace2 we encoded getpid() in the SID using a fixed-width encoding format, maybe do the same here?
builtin/fsmonitor--daemon.c static int do_as_client__status(void) · line 170
Quoted 7 lines
builtin/fsmonitor--daemon.c +7
-0+/* +* Create the cookie file on disk and then wait for a notification +* that the listener thread has seen it. +*/ + fd =open (cookie_pathname.buf,O_WRONLY |O_CREAT |O_EXCL ,0600 ); +if (fd >=0 ) { +close (fd);
Also check the return value of close()...
builtin/fsmonitor--daemon.c static int do_as_client__status(void) · line 171
Quoted 1 line
builtin/fsmonitor--daemon.c +1
-0+unlink (cookie_pathname.buf);
...and unlink()? If we explicitly prefer not to, note why?
Quoted 11 lines
builtin/fsmonitor--daemon.c +11
-0+ +/* +* 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
builtin/fsmonitor--daemon.c static int do_as_client__status(void) · line 192
Quoted 10 lines
builtin/fsmonitor--daemon.c +10
-0+* of getting stuck are pretty slim. +*/ +while (cookie->result ==FCIR_INIT ) +pthread_cond_wait (&state->cookies_cond, + &state->main_lock); + }else { +error_errno (_ ("could not create fsmonitor cookie '%s'" ), + cookie->name); + + cookie->result =FCIR_ERROR ;
...more readable if we just:
if (open(...) < 0) {
...;
goto cleanup;So no need to indent the comment/main logic of the function above?
builtin/fsmonitor--daemon.c static int do_as_client__status(void) · line 199
Quoted 7 lines
builtin/fsmonitor--daemon.c +7
-0+ } + +hashmap_remove (&state->cookies, &cookie->entry,NULL ); + + result = cookie->result; + +free ((char *)cookie->name);
Since the "name" here is always an allocated string we should just make it "char *" in the struct and avoid these casts.
In other places we play this casting game because the struct is a public API, but in this case it's all ours.
PATCH v6 28/30 fsmonitor: force update index after large responses
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Measure the time taken to apply the FSMonitor query result to the index and the untracked-cache.
Set the FSMONITOR_CHANGED bit on istate->cache_changed when FSMonitor returns a very large repsonse to ensure that the index is written to disk.
Normally, when the FSMonitor response includes a tracked file, the index is always updated. Similarly, the index might be updated when the response alters the untracked-cache (when enabled). However, in cases where neither of those cause the index to be considered changed, the FSMonitor response is wasted. Subsequent Git commands will make requests with the same token and receive the same response.
If that response is very large, performance may suffer. It would be more efficient to force update the index now (and the token in the index extension) in order to reduce the size of the response received by future commands.
This was observed on Windows after a large checkout. On Windows, the kernel emits events for the files that are changed as they are changed. However, it might delay events for the containing directories until the system is more idle (or someone scans the directory (so it seems)). The first status following a checkout would get the list of files. The subsequent status commands would get the list of directories as the events trickled out. But they would never catch up because the token was not advanced because the index wasn't updated.
This list of directories caused wt_status_collect_untracked() to unnecessarily spend time actually scanning them during each command.
--- fsmonitor.c | 55 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-)
fsmonitor.c +54 -1
@@ -219,6 +219,43 @@ static void fsmonitor_refresh_callback(struct index_state *istate, char *name)untracked_cache_invalidate_path (istate, name,0 ); } +/* +* The number of pathnames that we need to receive from FSMonitor +* before we force the index to be updated. +* +* Note that any pathname within the set of received paths MAY cause +* cache-entry or istate flag bits to be updated and thus cause the +* index to be updated on disk. +* +* However, the response may contain many paths (such as ignored +* paths) that will not update any flag bits. And thus not force the +* index to be updated. (This is fine and normal.) It also means +* that the token will not be updated in the FSMonitor index +* extension. So the next Git command will find the same token in the +* index, make the same token-relative request, and receive the same +* response (plus any newly changed paths). If this response is large +* (and continues to grow), performance could be impacted. +* +* For example, if the user runs a build and it writes 100K object +* files but doesn't modify any source files, the index would not need +* to be updated. The FSMonitor response (after the build and +* relative to a pre-build token) might be 5MB. Each subsequent Git +* command will receive that same 100K/5MB response until something +* causes the index to be updated. And `refresh_fsmonitor()` will +* have to iterate over those 100K paths each time. +* +* Performance could be improved if we optionally force update the +* index after a very large response and get an updated token into +* the FSMonitor index extension. This should allow subsequent +* commands to get smaller and more current responses. +* +* The value chosen here does not need to be precise. The index +* will be updated automatically the first time the user touches +* a tracked file and causes a command like `git status` to +* update an mtime to be updated and/or set a flag bit. +*/ +static int fsmonitor_force_update_threshold =100 ; +void refresh_fsmonitor (struct index_state *istate) {struct strbuf query_result =STRBUF_INIT ;
@@ -362,25 +399,39 @@ apply_results:* information and that we should consider everything * invalid. We call this a trivial response. */ +trace2_region_enter ("fsmonitor" ,"apply_results" , istate->repo); +if (query_success && !is_trivial) {/* * Mark all pathnames returned by the monitor as dirty. * * This updates both the cache-entries and the untracked-cache. */ +int count =0 ; + buf = query_result.buf;for (i = bol; i < query_result.len; i++) {if (buf[i] !='\0' )continue ;fsmonitor_refresh_callback (istate, buf + bol); bol = i +1 ; + count++; } -if (bol < query_result.len) +if (bol < query_result.len) {fsmonitor_refresh_callback (istate, buf + bol); + count++; + }/* Now mark the untracked cache for fsmonitor usage */ if (istate->untracked) istate->untracked->use_fsmonitor =1 ; + +if (count > fsmonitor_force_update_threshold) + istate->cache_changed |=FSMONITOR_CHANGED ; + +trace2_data_intmax ("fsmonitor" , istate->repo,"apply_count" , + count); + }else {/* * We failed to get a response or received a trivial response,
@@ -409,6 +460,8 @@ apply_results:if (istate->untracked) istate->untracked->use_fsmonitor =0 ; } +trace2_region_leave ("fsmonitor" ,"apply_results" , istate->repo); +strbuf_release (&query_result);/* Now that we've updated istate, save the last_update_token */
Signature · 1 line
gitgitgadget
PATCH v6 29/30 t7527: test status with untracked-cache and fsmonitor--daemon
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create 2x2 test matrix with the untracked-cache and fsmonitor--daemon features and a series of edits and verify that status output is identical.
--- t/t7527-builtin-fsmonitor.sh | 93 ++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+)
t/t7527-builtin-fsmonitor.sh +93 -0
@@ -162,6 +162,8 @@ test_expect_success 'setup' ' .gitignore expect* actual* + flush* + trace* EOF git -c core.fsmonitor=false add . &&
@@ -508,4 +510,95 @@ test_expect_success 'cleanup worktrees' ' stop_daemon_delete_repo wt-base ' +# The next few tests perform arbitrary/contrived file operations and +# confirm that status is correct. That is, that the data (or lack of +# data) from fsmonitor doesn't cause incorrect results. And doesn't +# cause incorrect results when the untracked-cache is enabled. + +test_lazy_prereq UNTRACKED_CACHE ' + git update-index --test-untracked-cache +' + +test_expect_success 'Matrix: setup for untracked-cache,fsmonitor matrix' ' + test_unconfig core.fsmonitor && + git update-index --no-fsmonitor && + test_might_fail git fsmonitor--daemon stop +' + +matrix_clean_up_repo () { + git reset --hard HEAD && + git clean -fd +} + +matrix_try () { + uc=$1 + fsm=$2 + fn=$3 + + test_expect_success "Matrix[uc:$uc][fsm:$fsm] $fn" ' + matrix_clean_up_repo && + $fn && + if test $uc = false && test $fsm = false + then + git status --porcelain=v1 >.git/expect.$fn + else + git status --porcelain=v1 >.git/actual.$fn && + test_cmp .git/expect.$fn .git/actual.$fn + fi + ' +} + +uc_values="false" +test_have_prereq UNTRACKED_CACHE && uc_values="false true" +for uc_val in $uc_values +do + if test $uc_val = false + then + test_expect_success "Matrix[uc:$uc_val] disable untracked cache" ' + git config core.untrackedcache false && + git update-index --no-untracked-cache + ' + else + test_expect_success "Matrix[uc:$uc_val] enable untracked cache" ' + git config core.untrackedcache true && + git update-index --untracked-cache + ' + fi + + fsm_values="false true" + for fsm_val in $fsm_values + do + if test $fsm_val = false + then + test_expect_success "Matrix[uc:$uc_val][fsm:$fsm_val] disable fsmonitor" ' + test_unconfig core.fsmonitor && + git update-index --no-fsmonitor && + test_might_fail git fsmonitor--daemon stop + ' + else + test_expect_success "Matrix[uc:$uc_val][fsm:$fsm_val] enable fsmonitor" ' + git config core.fsmonitor true && + git fsmonitor--daemon start && + git update-index --fsmonitor + ' + fi + + matrix_try $uc_val $fsm_val edit_files + matrix_try $uc_val $fsm_val delete_files + matrix_try $uc_val $fsm_val create_files + matrix_try $uc_val $fsm_val rename_files + matrix_try $uc_val $fsm_val file_to_directory + matrix_try $uc_val $fsm_val directory_to_file + + if test $fsm_val = true + then + test_expect_success "Matrix[uc:$uc_val][fsm:$fsm_val] disable fsmonitor at end" ' + test_unconfig core.fsmonitor && + git update-index --no-fsmonitor && + test_might_fail git fsmonitor--daemon stop + ' + fi + done +done + test_done
Signature · 1 line
gitgitgadget
PATCH v6 30/30 update-index: convert fsmonitor warnings to advise
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
--- builtin/update-index.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-)
builtin/update-index.c +6 -6
@@ -1238,18 +1238,18 @@ 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);if (fsm_mode ==FSMONITOR_MODE_DISABLED ) { -warning (_ ("core.fsmonitor is unset; " -"set it if you really want to " -"enable fsmonitor" )); +advise (_ ("core.fsmonitor is unset; " +"set it if you really want to " +"enable fsmonitor" )); }add_fsmonitor (&the_index);report (_ ("fsmonitor enabled" )); }else if (!fsmonitor) {enum fsmonitor_mode fsm_mode =fsm_settings__get_mode (r);if (fsm_mode >FSMONITOR_MODE_DISABLED ) -warning (_ ("core.fsmonitor is set; " -"remove it if you really want to " -"disable fsmonitor" )); +advise (_ ("core.fsmonitor is set; " +"remove it if you really want to " +"disable fsmonitor" ));remove_fsmonitor (&the_index);report (_ ("fsmonitor disabled" )); }
Signature · 1 line
gitgitgadget
Re: [PATCH v6 30/30] update-index: convert fsmonitor warnings to advise
Quoted 27 lines · Jeff Hostetler via GitGitGadget
From: Jeff Hostetler <[email protected]>
Signed-off-by Jeff Hostetler <[email protected]>Signed-off-by Junio C Hamano <[email protected]>--- builtin/update-index.c | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-)builtin/update-index.c +3
-3@@ -1238,18 +1238,18 @@ 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);if (fsm_mode ==FSMONITOR_MODE_DISABLED ) { -warning (_ ("core.fsmonitor is unset; " -"set it if you really want to " -"enable fsmonitor" )); +advise (_ ("core.fsmonitor is unset; " +"set it if you really want to " +"enable fsmonitor" )); }add_fsmonitor (&the_index);report (_ ("fsmonitor enabled" )); }else if (!fsmonitor) {enum fsmonitor_mode fsm_mode =fsm_settings__get_mode (r);
builtin/update-index.c int cmd_update_index(int argc, const char **argv, const char *prefix) · line 1255
Quoted 10 lines
builtin/update-index.c +3
-3if (fsm_mode >FSMONITOR_MODE_DISABLED ) -warning (_ ("core.fsmonitor is set; " -"remove it if you really want to " -"disable fsmonitor" )); +advise (_ ("core.fsmonitor is set; " +"remove it if you really want to " +"disable fsmonitor" ));remove_fsmonitor (&the_index);report (_ ("fsmonitor disabled" )); }
For any new advice you should:
* Add it to advice.[ch]
* Add it to Documentation/config/advise.txt
* This should use advise_if_enabled() with the relevant enum you add (which should also be documented in the *.txt)
I'm not oppposed to this change per-se, but I think this narrow change makes the post-image of builtin/update-index.c worse.
Why are we issuing an advice() only for this config, but not changing the other two uses of warning()? In the post-image:
6 matches for "\(advise\|warning\)" in buffer: update-index.c 1195: warning(_("core.splitIndex is set to false; "
1204: warning(_("core.splitIndex is set to true; "
1216: warning(_("core.untrackedCache is set to true; "
1228: warning(_("core.untrackedCache is set to false; "
1241: advise(_("core.fsmonitor is unset; "
1250: advise(_("core.fsmonitor is set; "
We should either leave these alone, or give them all the treatment of adding a new advise.updateIndexConfig or whatever, and have them all use advise_if_enabled(ADVICE_UPDATE_INDEX_CONFIG, ...).
Also these existing messages seem really confusing both for the fsmonitor & others. We're telling the user "do XYZ to disable ABC", but then right afterwards we say "ABC disabled" via report()?
Quoted 14 lines · Jeff Hostetler via GitGitGadget
Here is V6 of Part 2 of my builtin FSmonitor series.
This version contains mostly cleanup based on feedback from V5. Of note:
* I squashed in the 1_file fix for p7519.
* I squashed in a commit from part 3 to optionally print the "running daemon on..." message on stderr.
* I added a note to the documentation about incompatible changes around core.fsmonitor.
* Removed/rephrased some obsolete NEEDSWORK items.Tao has an ongoing parallel series to fix test-chmtime on Windows.
https://lore.kernel.org/all/[email protected]/
Quoted 6 lines
If that lands first, we should be able to drop my 't/helper/test-chmtime:
skip directories on Windows' commit.A followup Part 3 will contain additional refinements to the daemon and additional tests. I drew the line here between Part 2 and 3 to make it easier to review.
Hopefully this round will quickly be reviewed solidly to merge it down to 'next' and lower.
Will queue.
Thanks.
Hi Jeff,
Quoted 10 lines · Jeff Hostetler via GitGitGadget
Here is V6 of Part 2 of my builtin FSmonitor series.
This version contains mostly cleanup based on feedback from V5. Of note:
* I squashed in the 1_file fix for p7519.
* I squashed in a commit from part 3 to optionally print the "running daemon on..." message on stderr.
* I added a note to the documentation about incompatible changes around core.fsmonitor.
* Removed/rephrased some obsolete NEEDSWORK items.
Thank you! I also saw a grammar fix ;-)
Quoted 5 lines
Tao has an ongoing parallel series to fix test-chmtime on Windows.
https://lore.kernel.org/all/[email protected]/If that lands first, we should be able to drop my 't/helper/test-chmtime:
skip directories on Windows' commit.
Thank you for this note so that this overwhelmed reviewer knows about this.
Quoted 3 lines
A followup Part 3 will contain additional refinements to the daemon and additional tests. I drew the line here between Part 2 and 3 to make it easier to review.
Excellent!
I only found one slight issue with the range-diff: I _assume_ that Junio likes to add his own Sign-off (but I am being told frequently that my assumptions are typically incorrect, so take that with a grain of salt ;-)).
Apart from that, I saw that you addressed all of my concerns, and I liked in particular this elegant change:
Quoted 19 lines
Range-diff vs v5:
[...]
3:384516ce1a1! 3:ae622a517cffsmonitor: config settings are repository-specific
[...]@@ fsmonitor-settings.c (new) + +enum fsmonitor_mode fsm_settings__get_mode(struct repository *r) +{ ++ if (!r) ++ r = the_repository; ++ + lookup_fsmonitor_settings(r); + + return r->settings.fsmonitor->mode; [...] @@ fsmonitor.c: void remove_fsmonitor(struct index_state *istate) { unsigned int i;
Quoted 8 lines
- int fsmonitor_enabled = git_config_get_fsmonitor();
-+ struct repository *r = istate->repo ? istate->repo : the_repository;
-+ int fsmonitor_enabled = (fsm_settings__get_mode(r) > FSMONITOR_MODE_DISABLED);
++ int fsmonitor_enabled = (fsm_settings__get_mode(istate->repo)
++ > FSMONITOR_MODE_DISABLED);
if (istate->fsmonitor_dirty) {
if (fsmonitor_enabled) {From my side, this patch series is all clear to advance to next.
Thank you,
Dscho
v7 cover Builtin FSMonitor Part 2
In reply to Jeff Hostetler via GitGitGadgetHere 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 <[email protected]>
Signed-off-by: Junio C Hamano <[email protected]> - ## 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 <[email protected]>
## 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
## 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 <[email protected]>
Signed-off-by: Junio C Hamano <[email protected]>
- ## 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 <[email protected]>
## 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
## 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
Signature · 1 line
gitgitgadget
PATCH v7 1/29 fsmonitor: enhance existing comments, clarify trivial response handling
From: Jeff Hostetler <[email protected]>
--- fsmonitor.c | 64 ++++++++++++++++++++++++++++++++++------------------- 1 file changed, 41 insertions(+), 23 deletions(-)
fsmonitor.c +41 -23
@@ -168,29 +168,15 @@ static int query_fsmonitor(int version, const char *last_update, struct strbuf *if (result)trace2_data_intmax ("fsm_hook" ,NULL ,"query/failed" , result); -else { +else trace2_data_intmax ("fsm_hook" ,NULL ,"query/response-length" , query_result->len); -if (fsmonitor_is_trivial_response (query_result)) -trace2_data_intmax ("fsm_hook" ,NULL , -"query/trivial-response" ,1 ); - } -trace2_region_leave ("fsm_hook" ,"query" ,NULL );return result; } -int fsmonitor_is_trivial_response (const struct strbuf *query_result) -{ -static char trivial_response[3 ] = {'\0' ,'/' ,'\0' }; - -return query_result->len >=3 && - !memcmp (trivial_response, - &query_result->buf[query_result->len -3 ],3 ); -} -static void fsmonitor_refresh_callback (struct index_state *istate,char *name) {int i, len =strlen (name);
@@ -238,6 +224,7 @@ void refresh_fsmonitor(struct index_state *istate)struct strbuf last_update_token =STRBUF_INIT ;char *buf;unsigned int i; +int is_trivial =0 ;if (!core_fsmonitor || istate->fsmonitor_has_run_once)return ;
@@ -283,6 +270,7 @@ void refresh_fsmonitor(struct index_state *istate) query_success =0 ; }else { bol = last_update_token.len +1 ; + is_trivial = query_result.buf[bol] =='/' ; } }else if (hook_version <0 ) { hook_version =HOOK_INTERFACE_VERSION1 ;
@@ -294,16 +282,38 @@ void refresh_fsmonitor(struct index_state *istate)if (hook_version ==HOOK_INTERFACE_VERSION1 ) { query_success = !query_fsmonitor (HOOK_INTERFACE_VERSION1 , istate->fsmonitor_last_update, &query_result); +if (query_success) + is_trivial = query_result.buf[0 ] =='/' ; } +if (is_trivial) +trace2_data_intmax ("fsm_hook" ,NULL , +"query/trivial-response" ,1 ); +trace_performance_since (last_update,"fsmonitor process '%s'" , core_fsmonitor);trace_printf_key (&trace_fsmonitor,"fsmonitor process '%s' returned %s" , core_fsmonitor, query_success ?"success" :"failure" ); } -/* a fsmonitor process can return '/' to indicate all entries are invalid */ -if (query_success && query_result.buf[bol] !='/' ) { -/* Mark all entries returned by the monitor as dirty */ +/* +* The response from FSMonitor (excluding the header token) is +* either: +* +* [a] a (possibly empty) list of NUL delimited relative +* pathnames of changed paths. This list can contain +* files and directories. Directories have a trailing +* slash. +* +* [b] a single '/' to indicate the provider had no +* information and that we should consider everything +* invalid. We call this a trivial response. +*/ +if (query_success && !is_trivial) { +/* +* Mark all pathnames returned by the monitor as dirty. +* +* This updates both the cache-entries and the untracked-cache. +*/ buf = query_result.buf;for (i = bol; i < query_result.len; i++) {if (buf[i] !='\0' )
@@ -318,11 +328,16 @@ void refresh_fsmonitor(struct index_state *istate)if (istate->untracked) istate->untracked->use_fsmonitor =1 ; }else { - -/* We only want to run the post index changed hook if we've actually changed entries, so keep track -* if we actually changed entries or not */ +/* +* We failed to get a response or received a trivial response, +* so invalidate everything. +* +* We only want to run the post index changed hook if +* we've actually changed entries, so keep track if we +* actually changed entries or not. +*/ int is_cache_changed =0 ; -/* Mark all entries invalid */ +for (i =0 ; i < istate->cache_nr; i++) {if (istate->cache[i]->ce_flags &CE_FSMONITOR_VALID ) { is_cache_changed =1 ;
@@ -330,7 +345,10 @@ void refresh_fsmonitor(struct index_state *istate) } } -/* If we're going to check every file, ensure we save the results */ +/* +* If we're going to check every file, ensure we save +* the results. +*/ if (is_cache_changed) istate->cache_changed |=FSMONITOR_CHANGED ;
Signature · 1 line
gitgitgadget
PATCH v7 2/29 fsmonitor-ipc: create client routines for git-fsmonitor--daemon
In reply to Jeff Hostetler via GitGitGadgetFrom: Jeff Hostetler <[email protected]>
Create fsmonitor_ipc__*() client routines to spawn the built-in file system monitor daemon and send it an IPC request using the Simple IPC API.
Stub in empty fsmonitor_ipc__*() functions for unsupported platforms.
--- Makefile | 1 + fsmonitor-ipc.c | 171 ++++++++++++++++++++++++++++++++++++++++++++++++ fsmonitor-ipc.h | 48 ++++++++++++++ 3 files changed, 220 insertions(+) create mode 100644 fsmonitor-ipc.c create mode 100644 fsmonitor-ipc.h
Makefile +1 -0
@@ -907,6 +907,7 @@ LIB_OBJS += fetch-pack.o LIB_OBJS += fmt-merge-msg.o LIB_OBJS += fsck.o LIB_OBJS += fsmonitor.o +LIB_OBJS += fsmonitor-ipc.o LIB_OBJS += gettext.o LIB_OBJS += gpg-interface.o LIB_OBJS += graph.o
fsmonitor-ipc.c new file +171 -0
171 lines not on this page · raw message
fsmonitor-ipc.h new file +48 -0
48 lines not on this page · raw message
Signature · 1 line
gitgitgadget