Merge branch 'bp/fsmonitor'
We learned to talk to watchman to speed up "git status" and other operations that need to see which paths have been modified. * bp/fsmonitor: fsmonitor: preserve utf8 filenames in fsmonitor-watchman log fsmonitor: read entirety of watchman output fsmonitor: MINGW support for watchman integration fsmonitor: add a performance test fsmonitor: add a sample integration script for Watchman fsmonitor: add test cases for fsmonitor extension split-index: disable the fsmonitor extension when running the split index test fsmonitor: add a test tool to dump the index extension update-index: add fsmonitor support to update-index ls-files: Add support in ls-files to display the fsmonitor valid bit fsmonitor: add documentation for the fsmonitor extension. fsmonitor: teach git to optionally utilize a file system monitor to speed up detecting new or changed files. update-index: add a new --force-write-index option preload-index: add override to enable testing preload-index bswap: add 64 bit endianness helper get_be64
This commit is contained in:
commit
e05336bdda
@ -416,6 +416,13 @@ core.protectNTFS::
|
|||||||
8.3 "short" names.
|
8.3 "short" names.
|
||||||
Defaults to `true` on Windows, and `false` elsewhere.
|
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].
|
||||||
|
|
||||||
core.trustctime::
|
core.trustctime::
|
||||||
If false, the ctime differences between the index and the
|
If false, the ctime differences between the index and the
|
||||||
working tree are ignored; useful when the inode change time
|
working tree are ignored; useful when the inode change time
|
||||||
|
@ -9,7 +9,7 @@ git-ls-files - Show information about files in the index and the working tree
|
|||||||
SYNOPSIS
|
SYNOPSIS
|
||||||
--------
|
--------
|
||||||
[verse]
|
[verse]
|
||||||
'git ls-files' [-z] [-t] [-v]
|
'git ls-files' [-z] [-t] [-v] [-f]
|
||||||
(--[cached|deleted|others|ignored|stage|unmerged|killed|modified])*
|
(--[cached|deleted|others|ignored|stage|unmerged|killed|modified])*
|
||||||
(-[c|d|o|i|s|u|k|m])*
|
(-[c|d|o|i|s|u|k|m])*
|
||||||
[--eol]
|
[--eol]
|
||||||
@ -133,6 +133,11 @@ a space) at the start of each line:
|
|||||||
that are marked as 'assume unchanged' (see
|
that are marked as 'assume unchanged' (see
|
||||||
linkgit:git-update-index[1]).
|
linkgit:git-update-index[1]).
|
||||||
|
|
||||||
|
-f::
|
||||||
|
Similar to `-t`, but use lowercase letters for files
|
||||||
|
that are marked as 'fsmonitor valid' (see
|
||||||
|
linkgit:git-update-index[1]).
|
||||||
|
|
||||||
--full-name::
|
--full-name::
|
||||||
When run from a subdirectory, the command usually
|
When run from a subdirectory, the command usually
|
||||||
outputs paths relative to the current directory. This
|
outputs paths relative to the current directory. This
|
||||||
|
@ -16,9 +16,11 @@ SYNOPSIS
|
|||||||
[--chmod=(+|-)x]
|
[--chmod=(+|-)x]
|
||||||
[--[no-]assume-unchanged]
|
[--[no-]assume-unchanged]
|
||||||
[--[no-]skip-worktree]
|
[--[no-]skip-worktree]
|
||||||
|
[--[no-]fsmonitor-valid]
|
||||||
[--ignore-submodules]
|
[--ignore-submodules]
|
||||||
[--[no-]split-index]
|
[--[no-]split-index]
|
||||||
[--[no-|test-|force-]untracked-cache]
|
[--[no-|test-|force-]untracked-cache]
|
||||||
|
[--[no-]fsmonitor]
|
||||||
[--really-refresh] [--unresolve] [--again | -g]
|
[--really-refresh] [--unresolve] [--again | -g]
|
||||||
[--info-only] [--index-info]
|
[--info-only] [--index-info]
|
||||||
[-z] [--stdin] [--index-version <n>]
|
[-z] [--stdin] [--index-version <n>]
|
||||||
@ -111,6 +113,12 @@ you will need to handle the situation manually.
|
|||||||
set and unset the "skip-worktree" bit for the paths. See
|
set and unset the "skip-worktree" bit for the paths. See
|
||||||
section "Skip-worktree bit" below for more information.
|
section "Skip-worktree bit" below for more information.
|
||||||
|
|
||||||
|
--[no-]fsmonitor-valid::
|
||||||
|
When one of these flags is specified, the object name recorded
|
||||||
|
for the paths are not updated. Instead, these options
|
||||||
|
set and unset the "fsmonitor valid" bit for the paths. See
|
||||||
|
section "File System Monitor" below for more information.
|
||||||
|
|
||||||
-g::
|
-g::
|
||||||
--again::
|
--again::
|
||||||
Runs 'git update-index' itself on the paths whose index
|
Runs 'git update-index' itself on the paths whose index
|
||||||
@ -201,6 +209,15 @@ will remove the intended effect of the option.
|
|||||||
`--untracked-cache` used to imply `--test-untracked-cache` but
|
`--untracked-cache` used to imply `--test-untracked-cache` but
|
||||||
this option would enable the extension unconditionally.
|
this option would enable the extension unconditionally.
|
||||||
|
|
||||||
|
--fsmonitor::
|
||||||
|
--no-fsmonitor::
|
||||||
|
Enable or disable files system monitor feature. These options
|
||||||
|
take effect whatever the value of the `core.fsmonitor`
|
||||||
|
configuration variable (see linkgit:git-config[1]). But a warning
|
||||||
|
is emitted when the change goes against the configured value, as
|
||||||
|
the configured value will take effect next time the index is
|
||||||
|
read and this will remove the intended effect of the option.
|
||||||
|
|
||||||
\--::
|
\--::
|
||||||
Do not interpret any more arguments as options.
|
Do not interpret any more arguments as options.
|
||||||
|
|
||||||
@ -447,6 +464,34 @@ command reads the index; while when `--[no-|force-]untracked-cache`
|
|||||||
are used, the untracked cache is immediately added to or removed from
|
are used, the untracked cache is immediately added to or removed from
|
||||||
the index.
|
the index.
|
||||||
|
|
||||||
|
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
|
||||||
|
"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.
|
||||||
|
|
||||||
|
When used in conjunction with the untracked cache, it can further improve
|
||||||
|
performance by avoiding the cost of scanning the entire working directory
|
||||||
|
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
|
||||||
|
across all repositories you use, because you can set the configuration
|
||||||
|
variable to `true` (or `false`) in your `$HOME/.gitconfig` just once
|
||||||
|
and have it affect all repositories you touch.
|
||||||
|
|
||||||
|
When the `core.fsmonitor` configuration variable is changed, the
|
||||||
|
file system monitor is added to or removed from the index the next time
|
||||||
|
a command reads the index. When `--[no-]fsmonitor` are used, the file
|
||||||
|
system monitor is immediately added to or removed from the index.
|
||||||
|
|
||||||
Configuration
|
Configuration
|
||||||
-------------
|
-------------
|
||||||
|
|
||||||
|
@ -454,6 +454,34 @@ the name of the file that holds the e-mail to be sent. Exiting with a
|
|||||||
non-zero status causes 'git send-email' to abort before sending any
|
non-zero status causes 'git send-email' to abort before sending any
|
||||||
e-mails.
|
e-mails.
|
||||||
|
|
||||||
|
fsmonitor-watchman
|
||||||
|
~~~~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
|
This hook is invoked when the configuration option core.fsmonitor is
|
||||||
|
set to .git/hooks/fsmonitor-watchman. It takes two arguments, a version
|
||||||
|
(currently 1) and the time in elapsed nanoseconds since midnight,
|
||||||
|
January 1, 1970.
|
||||||
|
|
||||||
|
The hook should output to stdout the list of all files in the working
|
||||||
|
directory that may have changed since the requested time. The logic
|
||||||
|
should be inclusive so that it does not miss any potential changes.
|
||||||
|
The paths should be relative to the root of the working directory
|
||||||
|
and be separated by a single NUL.
|
||||||
|
|
||||||
|
It is OK to include files which have not actually changed. All changes
|
||||||
|
including newly-created and deleted files should be included. When
|
||||||
|
files are renamed, both the old and the new name should be included.
|
||||||
|
|
||||||
|
Git will limit what files it checks for changes as well as which
|
||||||
|
directories are checked for untracked files based on the path names
|
||||||
|
given.
|
||||||
|
|
||||||
|
An optimized way to tell git "all files have changed" is to return
|
||||||
|
the filename '/'.
|
||||||
|
|
||||||
|
The exit status determines whether git will use the data from the
|
||||||
|
hook to limit its search. On error, it will fall back to verifying
|
||||||
|
all files and folders.
|
||||||
|
|
||||||
GIT
|
GIT
|
||||||
---
|
---
|
||||||
|
@ -295,3 +295,22 @@ The remaining data of each directory block is grouped by type:
|
|||||||
in the previous ewah bitmap.
|
in the previous ewah bitmap.
|
||||||
|
|
||||||
- One NUL.
|
- One NUL.
|
||||||
|
|
||||||
|
== File System Monitor cache
|
||||||
|
|
||||||
|
The file system monitor cache tracks files for which the core.fsmonitor
|
||||||
|
hook has told us about changes. The signature for this extension is
|
||||||
|
{ 'F', 'S', 'M', 'N' }.
|
||||||
|
|
||||||
|
The extension starts with
|
||||||
|
|
||||||
|
- 32-bit version number: the current supported version is 1.
|
||||||
|
|
||||||
|
- 64-bit time: the extension data reflects all changes through the given
|
||||||
|
time which is stored as the nanoseconds elapsed since midnight,
|
||||||
|
January 1, 1970.
|
||||||
|
|
||||||
|
- 32-bit bitmap size: the size of the CE_FSMONITOR_VALID bitmap.
|
||||||
|
|
||||||
|
- An ewah bitmap, the n-th bit indicates whether the n-th index entry
|
||||||
|
is not CE_FSMONITOR_VALID.
|
||||||
|
3
Makefile
3
Makefile
@ -646,7 +646,9 @@ TEST_PROGRAMS_NEED_X += test-ctype
|
|||||||
TEST_PROGRAMS_NEED_X += test-config
|
TEST_PROGRAMS_NEED_X += test-config
|
||||||
TEST_PROGRAMS_NEED_X += test-date
|
TEST_PROGRAMS_NEED_X += test-date
|
||||||
TEST_PROGRAMS_NEED_X += test-delta
|
TEST_PROGRAMS_NEED_X += test-delta
|
||||||
|
TEST_PROGRAMS_NEED_X += test-drop-caches
|
||||||
TEST_PROGRAMS_NEED_X += test-dump-cache-tree
|
TEST_PROGRAMS_NEED_X += test-dump-cache-tree
|
||||||
|
TEST_PROGRAMS_NEED_X += test-dump-fsmonitor
|
||||||
TEST_PROGRAMS_NEED_X += test-dump-split-index
|
TEST_PROGRAMS_NEED_X += test-dump-split-index
|
||||||
TEST_PROGRAMS_NEED_X += test-dump-untracked-cache
|
TEST_PROGRAMS_NEED_X += test-dump-untracked-cache
|
||||||
TEST_PROGRAMS_NEED_X += test-fake-ssh
|
TEST_PROGRAMS_NEED_X += test-fake-ssh
|
||||||
@ -794,6 +796,7 @@ LIB_OBJS += ewah/ewah_rlw.o
|
|||||||
LIB_OBJS += exec_cmd.o
|
LIB_OBJS += exec_cmd.o
|
||||||
LIB_OBJS += fetch-pack.o
|
LIB_OBJS += fetch-pack.o
|
||||||
LIB_OBJS += fsck.o
|
LIB_OBJS += fsck.o
|
||||||
|
LIB_OBJS += fsmonitor.o
|
||||||
LIB_OBJS += gettext.o
|
LIB_OBJS += gettext.o
|
||||||
LIB_OBJS += gpg-interface.o
|
LIB_OBJS += gpg-interface.o
|
||||||
LIB_OBJS += graph.o
|
LIB_OBJS += graph.o
|
||||||
|
@ -31,6 +31,7 @@ static int show_resolve_undo;
|
|||||||
static int show_modified;
|
static int show_modified;
|
||||||
static int show_killed;
|
static int show_killed;
|
||||||
static int show_valid_bit;
|
static int show_valid_bit;
|
||||||
|
static int show_fsmonitor_bit;
|
||||||
static int line_terminator = '\n';
|
static int line_terminator = '\n';
|
||||||
static int debug_mode;
|
static int debug_mode;
|
||||||
static int show_eol;
|
static int show_eol;
|
||||||
@ -86,7 +87,8 @@ static const char *get_tag(const struct cache_entry *ce, const char *tag)
|
|||||||
{
|
{
|
||||||
static char alttag[4];
|
static char alttag[4];
|
||||||
|
|
||||||
if (tag && *tag && show_valid_bit && (ce->ce_flags & CE_VALID)) {
|
if (tag && *tag && ((show_valid_bit && (ce->ce_flags & CE_VALID)) ||
|
||||||
|
(show_fsmonitor_bit && (ce->ce_flags & CE_FSMONITOR_VALID)))) {
|
||||||
memcpy(alttag, tag, 3);
|
memcpy(alttag, tag, 3);
|
||||||
|
|
||||||
if (isalpha(tag[0])) {
|
if (isalpha(tag[0])) {
|
||||||
@ -515,6 +517,8 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
|
|||||||
N_("identify the file status with tags")),
|
N_("identify the file status with tags")),
|
||||||
OPT_BOOL('v', NULL, &show_valid_bit,
|
OPT_BOOL('v', NULL, &show_valid_bit,
|
||||||
N_("use lowercase letters for 'assume unchanged' files")),
|
N_("use lowercase letters for 'assume unchanged' files")),
|
||||||
|
OPT_BOOL('f', NULL, &show_fsmonitor_bit,
|
||||||
|
N_("use lowercase letters for 'fsmonitor clean' files")),
|
||||||
OPT_BOOL('c', "cached", &show_cached,
|
OPT_BOOL('c', "cached", &show_cached,
|
||||||
N_("show cached files in the output (default)")),
|
N_("show cached files in the output (default)")),
|
||||||
OPT_BOOL('d', "deleted", &show_deleted,
|
OPT_BOOL('d', "deleted", &show_deleted,
|
||||||
@ -584,7 +588,7 @@ int cmd_ls_files(int argc, const char **argv, const char *cmd_prefix)
|
|||||||
for (i = 0; i < exclude_list.nr; i++) {
|
for (i = 0; i < exclude_list.nr; i++) {
|
||||||
add_exclude(exclude_list.items[i].string, "", 0, el, --exclude_args);
|
add_exclude(exclude_list.items[i].string, "", 0, el, --exclude_args);
|
||||||
}
|
}
|
||||||
if (show_tag || show_valid_bit) {
|
if (show_tag || show_valid_bit || show_fsmonitor_bit) {
|
||||||
tag_cached = "H ";
|
tag_cached = "H ";
|
||||||
tag_unmerged = "M ";
|
tag_unmerged = "M ";
|
||||||
tag_removed = "R ";
|
tag_removed = "R ";
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
#include "pathspec.h"
|
#include "pathspec.h"
|
||||||
#include "dir.h"
|
#include "dir.h"
|
||||||
#include "split-index.h"
|
#include "split-index.h"
|
||||||
|
#include "fsmonitor.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Default to not allowing changes to the list of files. The
|
* Default to not allowing changes to the list of files. The
|
||||||
@ -32,6 +33,7 @@ static int force_remove;
|
|||||||
static int verbose;
|
static int verbose;
|
||||||
static int mark_valid_only;
|
static int mark_valid_only;
|
||||||
static int mark_skip_worktree_only;
|
static int mark_skip_worktree_only;
|
||||||
|
static int mark_fsmonitor_only;
|
||||||
#define MARK_FLAG 1
|
#define MARK_FLAG 1
|
||||||
#define UNMARK_FLAG 2
|
#define UNMARK_FLAG 2
|
||||||
static struct strbuf mtime_dir = STRBUF_INIT;
|
static struct strbuf mtime_dir = STRBUF_INIT;
|
||||||
@ -228,6 +230,7 @@ static int mark_ce_flags(const char *path, int flag, int mark)
|
|||||||
int namelen = strlen(path);
|
int namelen = strlen(path);
|
||||||
int pos = cache_name_pos(path, namelen);
|
int pos = cache_name_pos(path, namelen);
|
||||||
if (0 <= pos) {
|
if (0 <= pos) {
|
||||||
|
mark_fsmonitor_invalid(&the_index, active_cache[pos]);
|
||||||
if (mark)
|
if (mark)
|
||||||
active_cache[pos]->ce_flags |= flag;
|
active_cache[pos]->ce_flags |= flag;
|
||||||
else
|
else
|
||||||
@ -460,6 +463,11 @@ static void update_one(const char *path)
|
|||||||
die("Unable to mark file %s", path);
|
die("Unable to mark file %s", path);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (mark_fsmonitor_only) {
|
||||||
|
if (mark_ce_flags(path, CE_FSMONITOR_VALID, mark_fsmonitor_only == MARK_FLAG))
|
||||||
|
die("Unable to mark file %s", path);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
if (force_remove) {
|
if (force_remove) {
|
||||||
if (remove_file_from_cache(path))
|
if (remove_file_from_cache(path))
|
||||||
@ -917,6 +925,8 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
|
|||||||
struct refresh_params refresh_args = {0, &has_errors};
|
struct refresh_params refresh_args = {0, &has_errors};
|
||||||
int lock_error = 0;
|
int lock_error = 0;
|
||||||
int split_index = -1;
|
int split_index = -1;
|
||||||
|
int force_write = 0;
|
||||||
|
int fsmonitor = -1;
|
||||||
struct lock_file lock_file = LOCK_INIT;
|
struct lock_file lock_file = LOCK_INIT;
|
||||||
struct parse_opt_ctx_t ctx;
|
struct parse_opt_ctx_t ctx;
|
||||||
strbuf_getline_fn getline_fn;
|
strbuf_getline_fn getline_fn;
|
||||||
@ -1008,6 +1018,16 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
|
|||||||
N_("test if the filesystem supports untracked cache"), UC_TEST),
|
N_("test if the filesystem supports untracked cache"), UC_TEST),
|
||||||
OPT_SET_INT(0, "force-untracked-cache", &untracked_cache,
|
OPT_SET_INT(0, "force-untracked-cache", &untracked_cache,
|
||||||
N_("enable untracked cache without testing the filesystem"), UC_FORCE),
|
N_("enable untracked cache without testing the filesystem"), UC_FORCE),
|
||||||
|
OPT_SET_INT(0, "force-write-index", &force_write,
|
||||||
|
N_("write out the index even if is not flagged as changed"), 1),
|
||||||
|
OPT_BOOL(0, "fsmonitor", &fsmonitor,
|
||||||
|
N_("enable or disable file system monitor")),
|
||||||
|
{OPTION_SET_INT, 0, "fsmonitor-valid", &mark_fsmonitor_only, NULL,
|
||||||
|
N_("mark files as fsmonitor valid"),
|
||||||
|
PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, MARK_FLAG},
|
||||||
|
{OPTION_SET_INT, 0, "no-fsmonitor-valid", &mark_fsmonitor_only, NULL,
|
||||||
|
N_("clear fsmonitor valid bit"),
|
||||||
|
PARSE_OPT_NOARG | PARSE_OPT_NONEG, NULL, UNMARK_FLAG},
|
||||||
OPT_END()
|
OPT_END()
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -1146,7 +1166,23 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
|
|||||||
die("BUG: bad untracked_cache value: %d", untracked_cache);
|
die("BUG: bad untracked_cache value: %d", untracked_cache);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (active_cache_changed) {
|
if (fsmonitor > 0) {
|
||||||
|
if (git_config_get_fsmonitor() == 0)
|
||||||
|
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)
|
||||||
|
warning(_("core.fsmonitor is set; "
|
||||||
|
"remove it if you really want to "
|
||||||
|
"disable fsmonitor"));
|
||||||
|
remove_fsmonitor(&the_index);
|
||||||
|
report(_("fsmonitor disabled"));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (active_cache_changed || force_write) {
|
||||||
if (newfd < 0) {
|
if (newfd < 0) {
|
||||||
if (refresh_args.flags & REFRESH_QUIET)
|
if (refresh_args.flags & REFRESH_QUIET)
|
||||||
exit(128);
|
exit(128);
|
||||||
|
10
cache.h
10
cache.h
@ -204,6 +204,7 @@ struct cache_entry {
|
|||||||
#define CE_ADDED (1 << 19)
|
#define CE_ADDED (1 << 19)
|
||||||
|
|
||||||
#define CE_HASHED (1 << 20)
|
#define CE_HASHED (1 << 20)
|
||||||
|
#define CE_FSMONITOR_VALID (1 << 21)
|
||||||
#define CE_WT_REMOVE (1 << 22) /* remove in work directory */
|
#define CE_WT_REMOVE (1 << 22) /* remove in work directory */
|
||||||
#define CE_CONFLICTED (1 << 23)
|
#define CE_CONFLICTED (1 << 23)
|
||||||
|
|
||||||
@ -327,6 +328,7 @@ static inline unsigned int canon_mode(unsigned int mode)
|
|||||||
#define CACHE_TREE_CHANGED (1 << 5)
|
#define CACHE_TREE_CHANGED (1 << 5)
|
||||||
#define SPLIT_INDEX_ORDERED (1 << 6)
|
#define SPLIT_INDEX_ORDERED (1 << 6)
|
||||||
#define UNTRACKED_CHANGED (1 << 7)
|
#define UNTRACKED_CHANGED (1 << 7)
|
||||||
|
#define FSMONITOR_CHANGED (1 << 8)
|
||||||
|
|
||||||
struct split_index;
|
struct split_index;
|
||||||
struct untracked_cache;
|
struct untracked_cache;
|
||||||
@ -345,6 +347,7 @@ struct index_state {
|
|||||||
struct hashmap dir_hash;
|
struct hashmap dir_hash;
|
||||||
unsigned char sha1[20];
|
unsigned char sha1[20];
|
||||||
struct untracked_cache *untracked;
|
struct untracked_cache *untracked;
|
||||||
|
uint64_t fsmonitor_last_update;
|
||||||
};
|
};
|
||||||
|
|
||||||
extern struct index_state the_index;
|
extern struct index_state the_index;
|
||||||
@ -700,8 +703,10 @@ extern void *read_blob_data_from_index(const struct index_state *, const char *,
|
|||||||
#define CE_MATCH_IGNORE_MISSING 0x08
|
#define CE_MATCH_IGNORE_MISSING 0x08
|
||||||
/* enable stat refresh */
|
/* enable stat refresh */
|
||||||
#define CE_MATCH_REFRESH 0x10
|
#define CE_MATCH_REFRESH 0x10
|
||||||
extern int ie_match_stat(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
|
/* don't refresh_fsmonitor state or do stat comparison even if CE_FSMONITOR_VALID is true */
|
||||||
extern int ie_modified(const struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
|
#define CE_MATCH_IGNORE_FSMONITOR 0X20
|
||||||
|
extern int ie_match_stat(struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
|
||||||
|
extern int ie_modified(struct index_state *, const struct cache_entry *, struct stat *, unsigned int);
|
||||||
|
|
||||||
#define HASH_WRITE_OBJECT 1
|
#define HASH_WRITE_OBJECT 1
|
||||||
#define HASH_FORMAT_CHECK 2
|
#define HASH_FORMAT_CHECK 2
|
||||||
@ -799,6 +804,7 @@ extern int core_apply_sparse_checkout;
|
|||||||
extern int precomposed_unicode;
|
extern int precomposed_unicode;
|
||||||
extern int protect_hfs;
|
extern int protect_hfs;
|
||||||
extern int protect_ntfs;
|
extern int protect_ntfs;
|
||||||
|
extern const char *core_fsmonitor;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Include broken refs in all ref iterations, which will
|
* Include broken refs in all ref iterations, which will
|
||||||
|
@ -158,7 +158,9 @@ static inline uint64_t git_bswap64(uint64_t x)
|
|||||||
|
|
||||||
#define get_be16(p) ntohs(*(unsigned short *)(p))
|
#define get_be16(p) ntohs(*(unsigned short *)(p))
|
||||||
#define get_be32(p) ntohl(*(unsigned int *)(p))
|
#define get_be32(p) ntohl(*(unsigned int *)(p))
|
||||||
|
#define get_be64(p) ntohll(*(uint64_t *)(p))
|
||||||
#define put_be32(p, v) do { *(unsigned int *)(p) = htonl(v); } while (0)
|
#define put_be32(p, v) do { *(unsigned int *)(p) = htonl(v); } while (0)
|
||||||
|
#define put_be64(p, v) do { *(uint64_t *)(p) = htonll(v); } while (0)
|
||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
@ -178,6 +180,13 @@ static inline uint32_t get_be32(const void *ptr)
|
|||||||
(uint32_t)p[3] << 0;
|
(uint32_t)p[3] << 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline uint64_t get_be64(const void *ptr)
|
||||||
|
{
|
||||||
|
const unsigned char *p = ptr;
|
||||||
|
return (uint64_t)get_be32(&p[0]) << 32 |
|
||||||
|
(uint64_t)get_be32(&p[4]) << 0;
|
||||||
|
}
|
||||||
|
|
||||||
static inline void put_be32(void *ptr, uint32_t value)
|
static inline void put_be32(void *ptr, uint32_t value)
|
||||||
{
|
{
|
||||||
unsigned char *p = ptr;
|
unsigned char *p = ptr;
|
||||||
@ -187,4 +196,17 @@ static inline void put_be32(void *ptr, uint32_t value)
|
|||||||
p[3] = value >> 0;
|
p[3] = value >> 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static inline void put_be64(void *ptr, uint64_t value)
|
||||||
|
{
|
||||||
|
unsigned char *p = ptr;
|
||||||
|
p[0] = value >> 56;
|
||||||
|
p[1] = value >> 48;
|
||||||
|
p[2] = value >> 40;
|
||||||
|
p[3] = value >> 32;
|
||||||
|
p[4] = value >> 24;
|
||||||
|
p[5] = value >> 16;
|
||||||
|
p[6] = value >> 8;
|
||||||
|
p[7] = value >> 0;
|
||||||
|
}
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
14
config.c
14
config.c
@ -2156,6 +2156,20 @@ int git_config_get_max_percent_split_change(void)
|
|||||||
return -1; /* default value */
|
return -1; /* default value */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int git_config_get_fsmonitor(void)
|
||||||
|
{
|
||||||
|
if (git_config_get_pathname("core.fsmonitor", &core_fsmonitor))
|
||||||
|
core_fsmonitor = getenv("GIT_FSMONITOR_TEST");
|
||||||
|
|
||||||
|
if (core_fsmonitor && !*core_fsmonitor)
|
||||||
|
core_fsmonitor = NULL;
|
||||||
|
|
||||||
|
if (core_fsmonitor)
|
||||||
|
return 1;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
NORETURN
|
NORETURN
|
||||||
void git_die_config_linenr(const char *key, const char *filename, int linenr)
|
void git_die_config_linenr(const char *key, const char *filename, int linenr)
|
||||||
{
|
{
|
||||||
|
1
config.h
1
config.h
@ -212,6 +212,7 @@ extern int git_config_get_pathname(const char *key, const char **dest);
|
|||||||
extern int git_config_get_untracked_cache(void);
|
extern int git_config_get_untracked_cache(void);
|
||||||
extern int git_config_get_split_index(void);
|
extern int git_config_get_split_index(void);
|
||||||
extern int git_config_get_max_percent_split_change(void);
|
extern int git_config_get_max_percent_split_change(void);
|
||||||
|
extern int git_config_get_fsmonitor(void);
|
||||||
|
|
||||||
/* This dies if the configured or default date is in the future */
|
/* This dies if the configured or default date is in the future */
|
||||||
extern int git_config_get_expiry(const char *key, const char **output);
|
extern int git_config_get_expiry(const char *key, const char **output);
|
||||||
|
@ -12,6 +12,7 @@
|
|||||||
#include "refs.h"
|
#include "refs.h"
|
||||||
#include "submodule.h"
|
#include "submodule.h"
|
||||||
#include "dir.h"
|
#include "dir.h"
|
||||||
|
#include "fsmonitor.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* diff-files
|
* diff-files
|
||||||
@ -229,6 +230,7 @@ int run_diff_files(struct rev_info *revs, unsigned int option)
|
|||||||
|
|
||||||
if (!changed && !dirty_submodule) {
|
if (!changed && !dirty_submodule) {
|
||||||
ce_mark_uptodate(ce);
|
ce_mark_uptodate(ce);
|
||||||
|
mark_fsmonitor_valid(ce);
|
||||||
if (!revs->diffopt.flags.find_copies_harder)
|
if (!revs->diffopt.flags.find_copies_harder)
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
27
dir.c
27
dir.c
@ -18,6 +18,7 @@
|
|||||||
#include "utf8.h"
|
#include "utf8.h"
|
||||||
#include "varint.h"
|
#include "varint.h"
|
||||||
#include "ewah/ewok.h"
|
#include "ewah/ewok.h"
|
||||||
|
#include "fsmonitor.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Tells read_directory_recursive how a file or directory should be treated.
|
* Tells read_directory_recursive how a file or directory should be treated.
|
||||||
@ -1733,17 +1734,23 @@ static int valid_cached_dir(struct dir_struct *dir,
|
|||||||
if (!untracked)
|
if (!untracked)
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
if (stat(path->len ? path->buf : ".", &st)) {
|
/*
|
||||||
invalidate_directory(dir->untracked, untracked);
|
* With fsmonitor, we can trust the untracked cache's valid field.
|
||||||
memset(&untracked->stat_data, 0, sizeof(untracked->stat_data));
|
*/
|
||||||
return 0;
|
refresh_fsmonitor(istate);
|
||||||
}
|
if (!(dir->untracked->use_fsmonitor && untracked->valid)) {
|
||||||
if (!untracked->valid ||
|
if (stat(path->len ? path->buf : ".", &st)) {
|
||||||
match_stat_data_racy(istate, &untracked->stat_data, &st)) {
|
|
||||||
if (untracked->valid)
|
|
||||||
invalidate_directory(dir->untracked, untracked);
|
invalidate_directory(dir->untracked, untracked);
|
||||||
fill_stat_data(&untracked->stat_data, &st);
|
memset(&untracked->stat_data, 0, sizeof(untracked->stat_data));
|
||||||
return 0;
|
return 0;
|
||||||
|
}
|
||||||
|
if (!untracked->valid ||
|
||||||
|
match_stat_data_racy(istate, &untracked->stat_data, &st)) {
|
||||||
|
if (untracked->valid)
|
||||||
|
invalidate_directory(dir->untracked, untracked);
|
||||||
|
fill_stat_data(&untracked->stat_data, &st);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (untracked->check_only != !!check_only) {
|
if (untracked->check_only != !!check_only) {
|
||||||
|
2
dir.h
2
dir.h
@ -139,6 +139,8 @@ struct untracked_cache {
|
|||||||
int gitignore_invalidated;
|
int gitignore_invalidated;
|
||||||
int dir_invalidated;
|
int dir_invalidated;
|
||||||
int dir_opened;
|
int dir_opened;
|
||||||
|
/* fsmonitor invalidation data */
|
||||||
|
unsigned int use_fsmonitor : 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct dir_struct {
|
struct dir_struct {
|
||||||
|
2
entry.c
2
entry.c
@ -4,6 +4,7 @@
|
|||||||
#include "streaming.h"
|
#include "streaming.h"
|
||||||
#include "submodule.h"
|
#include "submodule.h"
|
||||||
#include "progress.h"
|
#include "progress.h"
|
||||||
|
#include "fsmonitor.h"
|
||||||
|
|
||||||
static void create_directories(const char *path, int path_len,
|
static void create_directories(const char *path, int path_len,
|
||||||
const struct checkout *state)
|
const struct checkout *state)
|
||||||
@ -373,6 +374,7 @@ finish:
|
|||||||
ce->name);
|
ce->name);
|
||||||
fill_stat_cache_info(ce, &st);
|
fill_stat_cache_info(ce, &st);
|
||||||
ce->ce_flags |= CE_UPDATE_IN_BASE;
|
ce->ce_flags |= CE_UPDATE_IN_BASE;
|
||||||
|
mark_fsmonitor_invalid(state->istate, ce);
|
||||||
state->istate->cache_changed |= CE_ENTRY_CHANGED;
|
state->istate->cache_changed |= CE_ENTRY_CHANGED;
|
||||||
}
|
}
|
||||||
delayed:
|
delayed:
|
||||||
|
@ -76,6 +76,7 @@ int protect_hfs = PROTECT_HFS_DEFAULT;
|
|||||||
#define PROTECT_NTFS_DEFAULT 0
|
#define PROTECT_NTFS_DEFAULT 0
|
||||||
#endif
|
#endif
|
||||||
int protect_ntfs = PROTECT_NTFS_DEFAULT;
|
int protect_ntfs = PROTECT_NTFS_DEFAULT;
|
||||||
|
const char *core_fsmonitor;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* The character that begins a commented line in user-editable file
|
* The character that begins a commented line in user-editable file
|
||||||
|
253
fsmonitor.c
Normal file
253
fsmonitor.c
Normal file
@ -0,0 +1,253 @@
|
|||||||
|
#include "cache.h"
|
||||||
|
#include "config.h"
|
||||||
|
#include "dir.h"
|
||||||
|
#include "ewah/ewok.h"
|
||||||
|
#include "fsmonitor.h"
|
||||||
|
#include "run-command.h"
|
||||||
|
#include "strbuf.h"
|
||||||
|
|
||||||
|
#define INDEX_EXTENSION_VERSION (1)
|
||||||
|
#define HOOK_INTERFACE_VERSION (1)
|
||||||
|
|
||||||
|
struct trace_key trace_fsmonitor = TRACE_KEY_INIT(FSMONITOR);
|
||||||
|
|
||||||
|
static void fsmonitor_ewah_callback(size_t pos, void *is)
|
||||||
|
{
|
||||||
|
struct index_state *istate = (struct index_state *)is;
|
||||||
|
struct cache_entry *ce = istate->cache[pos];
|
||||||
|
|
||||||
|
ce->ce_flags &= ~CE_FSMONITOR_VALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
int read_fsmonitor_extension(struct index_state *istate, const void *data,
|
||||||
|
unsigned long sz)
|
||||||
|
{
|
||||||
|
const char *index = data;
|
||||||
|
uint32_t hdr_version;
|
||||||
|
uint32_t ewah_size;
|
||||||
|
struct ewah_bitmap *fsmonitor_dirty;
|
||||||
|
int i;
|
||||||
|
int ret;
|
||||||
|
|
||||||
|
if (sz < sizeof(uint32_t) + sizeof(uint64_t) + sizeof(uint32_t))
|
||||||
|
return error("corrupt fsmonitor extension (too short)");
|
||||||
|
|
||||||
|
hdr_version = get_be32(index);
|
||||||
|
index += sizeof(uint32_t);
|
||||||
|
if (hdr_version != INDEX_EXTENSION_VERSION)
|
||||||
|
return error("bad fsmonitor version %d", hdr_version);
|
||||||
|
|
||||||
|
istate->fsmonitor_last_update = get_be64(index);
|
||||||
|
index += sizeof(uint64_t);
|
||||||
|
|
||||||
|
ewah_size = get_be32(index);
|
||||||
|
index += sizeof(uint32_t);
|
||||||
|
|
||||||
|
fsmonitor_dirty = ewah_new();
|
||||||
|
ret = ewah_read_mmap(fsmonitor_dirty, index, ewah_size);
|
||||||
|
if (ret != ewah_size) {
|
||||||
|
ewah_free(fsmonitor_dirty);
|
||||||
|
return error("failed to parse ewah bitmap reading fsmonitor index extension");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (git_config_get_fsmonitor()) {
|
||||||
|
/* Mark all entries valid */
|
||||||
|
for (i = 0; i < istate->cache_nr; i++)
|
||||||
|
istate->cache[i]->ce_flags |= CE_FSMONITOR_VALID;
|
||||||
|
|
||||||
|
/* Mark all previously saved entries as dirty */
|
||||||
|
ewah_each_bit(fsmonitor_dirty, fsmonitor_ewah_callback, istate);
|
||||||
|
|
||||||
|
/* Now mark the untracked cache for fsmonitor usage */
|
||||||
|
if (istate->untracked)
|
||||||
|
istate->untracked->use_fsmonitor = 1;
|
||||||
|
}
|
||||||
|
ewah_free(fsmonitor_dirty);
|
||||||
|
|
||||||
|
trace_printf_key(&trace_fsmonitor, "read fsmonitor extension successful");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void write_fsmonitor_extension(struct strbuf *sb, struct index_state *istate)
|
||||||
|
{
|
||||||
|
uint32_t hdr_version;
|
||||||
|
uint64_t tm;
|
||||||
|
struct ewah_bitmap *bitmap;
|
||||||
|
int i;
|
||||||
|
uint32_t ewah_start;
|
||||||
|
uint32_t ewah_size = 0;
|
||||||
|
int fixup = 0;
|
||||||
|
|
||||||
|
put_be32(&hdr_version, INDEX_EXTENSION_VERSION);
|
||||||
|
strbuf_add(sb, &hdr_version, sizeof(uint32_t));
|
||||||
|
|
||||||
|
put_be64(&tm, istate->fsmonitor_last_update);
|
||||||
|
strbuf_add(sb, &tm, sizeof(uint64_t));
|
||||||
|
fixup = sb->len;
|
||||||
|
strbuf_add(sb, &ewah_size, sizeof(uint32_t)); /* we'll fix this up later */
|
||||||
|
|
||||||
|
ewah_start = sb->len;
|
||||||
|
bitmap = ewah_new();
|
||||||
|
for (i = 0; i < istate->cache_nr; i++)
|
||||||
|
if (!(istate->cache[i]->ce_flags & CE_FSMONITOR_VALID))
|
||||||
|
ewah_set(bitmap, i);
|
||||||
|
ewah_serialize_strbuf(bitmap, sb);
|
||||||
|
ewah_free(bitmap);
|
||||||
|
|
||||||
|
/* fix up size field */
|
||||||
|
put_be32(&ewah_size, sb->len - ewah_start);
|
||||||
|
memcpy(sb->buf + fixup, &ewah_size, sizeof(uint32_t));
|
||||||
|
|
||||||
|
trace_printf_key(&trace_fsmonitor, "write fsmonitor extension successful");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Call the query-fsmonitor hook passing the time of the last saved results.
|
||||||
|
*/
|
||||||
|
static int query_fsmonitor(int version, uint64_t last_update, struct strbuf *query_result)
|
||||||
|
{
|
||||||
|
struct child_process cp = CHILD_PROCESS_INIT;
|
||||||
|
char ver[64];
|
||||||
|
char date[64];
|
||||||
|
const char *argv[4];
|
||||||
|
|
||||||
|
if (!(argv[0] = core_fsmonitor))
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
snprintf(ver, sizeof(version), "%d", version);
|
||||||
|
snprintf(date, sizeof(date), "%" PRIuMAX, (uintmax_t)last_update);
|
||||||
|
argv[1] = ver;
|
||||||
|
argv[2] = date;
|
||||||
|
argv[3] = NULL;
|
||||||
|
cp.argv = argv;
|
||||||
|
cp.use_shell = 1;
|
||||||
|
|
||||||
|
return capture_command(&cp, query_result, 1024);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void fsmonitor_refresh_callback(struct index_state *istate, const char *name)
|
||||||
|
{
|
||||||
|
int pos = index_name_pos(istate, name, strlen(name));
|
||||||
|
|
||||||
|
if (pos >= 0) {
|
||||||
|
struct cache_entry *ce = istate->cache[pos];
|
||||||
|
ce->ce_flags &= ~CE_FSMONITOR_VALID;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Mark the untracked cache dirty even if it wasn't found in the index
|
||||||
|
* as it could be a new untracked file.
|
||||||
|
*/
|
||||||
|
trace_printf_key(&trace_fsmonitor, "fsmonitor_refresh_callback '%s'", name);
|
||||||
|
untracked_cache_invalidate_path(istate, name);
|
||||||
|
}
|
||||||
|
|
||||||
|
void refresh_fsmonitor(struct index_state *istate)
|
||||||
|
{
|
||||||
|
static int has_run_once = 0;
|
||||||
|
struct strbuf query_result = STRBUF_INIT;
|
||||||
|
int query_success = 0;
|
||||||
|
size_t bol; /* beginning of line */
|
||||||
|
uint64_t last_update;
|
||||||
|
char *buf;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!core_fsmonitor || has_run_once)
|
||||||
|
return;
|
||||||
|
has_run_once = 1;
|
||||||
|
|
||||||
|
trace_printf_key(&trace_fsmonitor, "refresh fsmonitor");
|
||||||
|
/*
|
||||||
|
* This could be racy so save the date/time now and query_fsmonitor
|
||||||
|
* should be inclusive to ensure we don't miss potential changes.
|
||||||
|
*/
|
||||||
|
last_update = getnanotime();
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If we have a last update time, call query_fsmonitor for the set of
|
||||||
|
* changes since that time, else assume everything is possibly dirty
|
||||||
|
* and check it all.
|
||||||
|
*/
|
||||||
|
if (istate->fsmonitor_last_update) {
|
||||||
|
query_success = !query_fsmonitor(HOOK_INTERFACE_VERSION,
|
||||||
|
istate->fsmonitor_last_update, &query_result);
|
||||||
|
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[0] != '/') {
|
||||||
|
/* Mark all entries returned by the monitor as dirty */
|
||||||
|
buf = query_result.buf;
|
||||||
|
bol = 0;
|
||||||
|
for (i = 0; i < query_result.len; i++) {
|
||||||
|
if (buf[i] != '\0')
|
||||||
|
continue;
|
||||||
|
fsmonitor_refresh_callback(istate, buf + bol);
|
||||||
|
bol = i + 1;
|
||||||
|
}
|
||||||
|
if (bol < query_result.len)
|
||||||
|
fsmonitor_refresh_callback(istate, buf + bol);
|
||||||
|
} else {
|
||||||
|
/* Mark all entries invalid */
|
||||||
|
for (i = 0; i < istate->cache_nr; i++)
|
||||||
|
istate->cache[i]->ce_flags &= ~CE_FSMONITOR_VALID;
|
||||||
|
|
||||||
|
if (istate->untracked)
|
||||||
|
istate->untracked->use_fsmonitor = 0;
|
||||||
|
}
|
||||||
|
strbuf_release(&query_result);
|
||||||
|
|
||||||
|
/* Now that we've updated istate, save the last_update time */
|
||||||
|
istate->fsmonitor_last_update = last_update;
|
||||||
|
}
|
||||||
|
|
||||||
|
void add_fsmonitor(struct index_state *istate)
|
||||||
|
{
|
||||||
|
int i;
|
||||||
|
|
||||||
|
if (!istate->fsmonitor_last_update) {
|
||||||
|
trace_printf_key(&trace_fsmonitor, "add fsmonitor");
|
||||||
|
istate->cache_changed |= FSMONITOR_CHANGED;
|
||||||
|
istate->fsmonitor_last_update = getnanotime();
|
||||||
|
|
||||||
|
/* reset the fsmonitor state */
|
||||||
|
for (i = 0; i < istate->cache_nr; i++)
|
||||||
|
istate->cache[i]->ce_flags &= ~CE_FSMONITOR_VALID;
|
||||||
|
|
||||||
|
/* reset the untracked cache */
|
||||||
|
if (istate->untracked) {
|
||||||
|
add_untracked_cache(istate);
|
||||||
|
istate->untracked->use_fsmonitor = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update the fsmonitor state */
|
||||||
|
refresh_fsmonitor(istate);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void remove_fsmonitor(struct index_state *istate)
|
||||||
|
{
|
||||||
|
if (istate->fsmonitor_last_update) {
|
||||||
|
trace_printf_key(&trace_fsmonitor, "remove fsmonitor");
|
||||||
|
istate->cache_changed |= FSMONITOR_CHANGED;
|
||||||
|
istate->fsmonitor_last_update = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void tweak_fsmonitor(struct index_state *istate)
|
||||||
|
{
|
||||||
|
switch (git_config_get_fsmonitor()) {
|
||||||
|
case -1: /* keep: do nothing */
|
||||||
|
break;
|
||||||
|
case 0: /* false */
|
||||||
|
remove_fsmonitor(istate);
|
||||||
|
break;
|
||||||
|
case 1: /* true */
|
||||||
|
add_fsmonitor(istate);
|
||||||
|
break;
|
||||||
|
default: /* unknown value: do nothing */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
66
fsmonitor.h
Normal file
66
fsmonitor.h
Normal file
@ -0,0 +1,66 @@
|
|||||||
|
#ifndef FSMONITOR_H
|
||||||
|
#define FSMONITOR_H
|
||||||
|
|
||||||
|
extern struct trace_key trace_fsmonitor;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Read the fsmonitor index extension and (if configured) restore the
|
||||||
|
* CE_FSMONITOR_VALID state.
|
||||||
|
*/
|
||||||
|
extern int read_fsmonitor_extension(struct index_state *istate, const void *data, unsigned long sz);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Write the CE_FSMONITOR_VALID state into the fsmonitor index extension.
|
||||||
|
*/
|
||||||
|
extern void write_fsmonitor_extension(struct strbuf *sb, struct index_state *istate);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add/remove the fsmonitor index extension
|
||||||
|
*/
|
||||||
|
extern void add_fsmonitor(struct index_state *istate);
|
||||||
|
extern void remove_fsmonitor(struct index_state *istate);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add/remove the fsmonitor index extension as necessary based on the current
|
||||||
|
* core.fsmonitor setting.
|
||||||
|
*/
|
||||||
|
extern void tweak_fsmonitor(struct index_state *istate);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Run the configured fsmonitor integration script and clear the
|
||||||
|
* CE_FSMONITOR_VALID bit for any files returned as dirty. Also invalidate
|
||||||
|
* any corresponding untracked cache directory structures. Optimized to only
|
||||||
|
* run the first time it is called.
|
||||||
|
*/
|
||||||
|
extern void refresh_fsmonitor(struct index_state *istate);
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Set the given cache entries CE_FSMONITOR_VALID bit. This should be
|
||||||
|
* called any time the cache entry has been updated to reflect the
|
||||||
|
* current state of the file on disk.
|
||||||
|
*/
|
||||||
|
static inline void mark_fsmonitor_valid(struct cache_entry *ce)
|
||||||
|
{
|
||||||
|
if (core_fsmonitor) {
|
||||||
|
ce->ce_flags |= CE_FSMONITOR_VALID;
|
||||||
|
trace_printf_key(&trace_fsmonitor, "mark_fsmonitor_clean '%s'", ce->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Clear the given cache entry's CE_FSMONITOR_VALID bit and invalidate
|
||||||
|
* any corresponding untracked cache directory structures. This should
|
||||||
|
* be called any time git creates or modifies a file that should
|
||||||
|
* trigger an lstat() or invalidate the untracked cache for the
|
||||||
|
* corresponding directory
|
||||||
|
*/
|
||||||
|
static inline void mark_fsmonitor_invalid(struct index_state *istate, struct cache_entry *ce)
|
||||||
|
{
|
||||||
|
if (core_fsmonitor) {
|
||||||
|
ce->ce_flags &= ~CE_FSMONITOR_VALID;
|
||||||
|
untracked_cache_invalidate_path(istate, ce->name);
|
||||||
|
trace_printf_key(&trace_fsmonitor, "mark_fsmonitor_invalid '%s'", ce->name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -4,6 +4,7 @@
|
|||||||
#include "cache.h"
|
#include "cache.h"
|
||||||
#include "pathspec.h"
|
#include "pathspec.h"
|
||||||
#include "dir.h"
|
#include "dir.h"
|
||||||
|
#include "fsmonitor.h"
|
||||||
|
|
||||||
#ifdef NO_PTHREADS
|
#ifdef NO_PTHREADS
|
||||||
static void preload_index(struct index_state *index,
|
static void preload_index(struct index_state *index,
|
||||||
@ -55,15 +56,18 @@ static void *preload_thread(void *_data)
|
|||||||
continue;
|
continue;
|
||||||
if (ce_skip_worktree(ce))
|
if (ce_skip_worktree(ce))
|
||||||
continue;
|
continue;
|
||||||
|
if (ce->ce_flags & CE_FSMONITOR_VALID)
|
||||||
|
continue;
|
||||||
if (!ce_path_match(ce, &p->pathspec, NULL))
|
if (!ce_path_match(ce, &p->pathspec, NULL))
|
||||||
continue;
|
continue;
|
||||||
if (threaded_has_symlink_leading_path(&cache, ce->name, ce_namelen(ce)))
|
if (threaded_has_symlink_leading_path(&cache, ce->name, ce_namelen(ce)))
|
||||||
continue;
|
continue;
|
||||||
if (lstat(ce->name, &st))
|
if (lstat(ce->name, &st))
|
||||||
continue;
|
continue;
|
||||||
if (ie_match_stat(index, ce, &st, CE_MATCH_RACY_IS_DIRTY))
|
if (ie_match_stat(index, ce, &st, CE_MATCH_RACY_IS_DIRTY|CE_MATCH_IGNORE_FSMONITOR))
|
||||||
continue;
|
continue;
|
||||||
ce_mark_uptodate(ce);
|
ce_mark_uptodate(ce);
|
||||||
|
mark_fsmonitor_valid(ce);
|
||||||
} while (--nr > 0);
|
} while (--nr > 0);
|
||||||
cache_def_clear(&cache);
|
cache_def_clear(&cache);
|
||||||
return NULL;
|
return NULL;
|
||||||
@ -79,6 +83,8 @@ static void preload_index(struct index_state *index,
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
threads = index->cache_nr / THREAD_COST;
|
threads = index->cache_nr / THREAD_COST;
|
||||||
|
if ((index->cache_nr > 1) && (threads < 2) && getenv("GIT_FORCE_PRELOAD_TEST"))
|
||||||
|
threads = 2;
|
||||||
if (threads < 2)
|
if (threads < 2)
|
||||||
return;
|
return;
|
||||||
if (threads > MAX_PARALLEL)
|
if (threads > MAX_PARALLEL)
|
||||||
|
45
read-cache.c
45
read-cache.c
@ -19,6 +19,7 @@
|
|||||||
#include "varint.h"
|
#include "varint.h"
|
||||||
#include "split-index.h"
|
#include "split-index.h"
|
||||||
#include "utf8.h"
|
#include "utf8.h"
|
||||||
|
#include "fsmonitor.h"
|
||||||
|
|
||||||
/* Mask for the name length in ce_flags in the on-disk index */
|
/* Mask for the name length in ce_flags in the on-disk index */
|
||||||
|
|
||||||
@ -38,11 +39,12 @@
|
|||||||
#define CACHE_EXT_RESOLVE_UNDO 0x52455543 /* "REUC" */
|
#define CACHE_EXT_RESOLVE_UNDO 0x52455543 /* "REUC" */
|
||||||
#define CACHE_EXT_LINK 0x6c696e6b /* "link" */
|
#define CACHE_EXT_LINK 0x6c696e6b /* "link" */
|
||||||
#define CACHE_EXT_UNTRACKED 0x554E5452 /* "UNTR" */
|
#define CACHE_EXT_UNTRACKED 0x554E5452 /* "UNTR" */
|
||||||
|
#define CACHE_EXT_FSMONITOR 0x46534D4E /* "FSMN" */
|
||||||
|
|
||||||
/* changes that can be kept in $GIT_DIR/index (basically all extensions) */
|
/* changes that can be kept in $GIT_DIR/index (basically all extensions) */
|
||||||
#define EXTMASK (RESOLVE_UNDO_CHANGED | CACHE_TREE_CHANGED | \
|
#define EXTMASK (RESOLVE_UNDO_CHANGED | CACHE_TREE_CHANGED | \
|
||||||
CE_ENTRY_ADDED | CE_ENTRY_REMOVED | CE_ENTRY_CHANGED | \
|
CE_ENTRY_ADDED | CE_ENTRY_REMOVED | CE_ENTRY_CHANGED | \
|
||||||
SPLIT_INDEX_ORDERED | UNTRACKED_CHANGED)
|
SPLIT_INDEX_ORDERED | UNTRACKED_CHANGED | FSMONITOR_CHANGED)
|
||||||
|
|
||||||
struct index_state the_index;
|
struct index_state the_index;
|
||||||
static const char *alternate_index_output;
|
static const char *alternate_index_output;
|
||||||
@ -62,6 +64,7 @@ static void replace_index_entry(struct index_state *istate, int nr, struct cache
|
|||||||
free(old);
|
free(old);
|
||||||
set_index_entry(istate, nr, ce);
|
set_index_entry(istate, nr, ce);
|
||||||
ce->ce_flags |= CE_UPDATE_IN_BASE;
|
ce->ce_flags |= CE_UPDATE_IN_BASE;
|
||||||
|
mark_fsmonitor_invalid(istate, ce);
|
||||||
istate->cache_changed |= CE_ENTRY_CHANGED;
|
istate->cache_changed |= CE_ENTRY_CHANGED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -150,8 +153,10 @@ void fill_stat_cache_info(struct cache_entry *ce, struct stat *st)
|
|||||||
if (assume_unchanged)
|
if (assume_unchanged)
|
||||||
ce->ce_flags |= CE_VALID;
|
ce->ce_flags |= CE_VALID;
|
||||||
|
|
||||||
if (S_ISREG(st->st_mode))
|
if (S_ISREG(st->st_mode)) {
|
||||||
ce_mark_uptodate(ce);
|
ce_mark_uptodate(ce);
|
||||||
|
mark_fsmonitor_valid(ce);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ce_compare_data(const struct cache_entry *ce, struct stat *st)
|
static int ce_compare_data(const struct cache_entry *ce, struct stat *st)
|
||||||
@ -301,7 +306,7 @@ int match_stat_data_racy(const struct index_state *istate,
|
|||||||
return match_stat_data(sd, st);
|
return match_stat_data(sd, st);
|
||||||
}
|
}
|
||||||
|
|
||||||
int ie_match_stat(const struct index_state *istate,
|
int ie_match_stat(struct index_state *istate,
|
||||||
const struct cache_entry *ce, struct stat *st,
|
const struct cache_entry *ce, struct stat *st,
|
||||||
unsigned int options)
|
unsigned int options)
|
||||||
{
|
{
|
||||||
@ -309,7 +314,10 @@ int ie_match_stat(const struct index_state *istate,
|
|||||||
int ignore_valid = options & CE_MATCH_IGNORE_VALID;
|
int ignore_valid = options & CE_MATCH_IGNORE_VALID;
|
||||||
int ignore_skip_worktree = options & CE_MATCH_IGNORE_SKIP_WORKTREE;
|
int ignore_skip_worktree = options & CE_MATCH_IGNORE_SKIP_WORKTREE;
|
||||||
int assume_racy_is_modified = options & CE_MATCH_RACY_IS_DIRTY;
|
int assume_racy_is_modified = options & CE_MATCH_RACY_IS_DIRTY;
|
||||||
|
int ignore_fsmonitor = options & CE_MATCH_IGNORE_FSMONITOR;
|
||||||
|
|
||||||
|
if (!ignore_fsmonitor)
|
||||||
|
refresh_fsmonitor(istate);
|
||||||
/*
|
/*
|
||||||
* If it's marked as always valid in the index, it's
|
* If it's marked as always valid in the index, it's
|
||||||
* valid whatever the checked-out copy says.
|
* valid whatever the checked-out copy says.
|
||||||
@ -320,6 +328,8 @@ int ie_match_stat(const struct index_state *istate,
|
|||||||
return 0;
|
return 0;
|
||||||
if (!ignore_valid && (ce->ce_flags & CE_VALID))
|
if (!ignore_valid && (ce->ce_flags & CE_VALID))
|
||||||
return 0;
|
return 0;
|
||||||
|
if (!ignore_fsmonitor && (ce->ce_flags & CE_FSMONITOR_VALID))
|
||||||
|
return 0;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Intent-to-add entries have not been added, so the index entry
|
* Intent-to-add entries have not been added, so the index entry
|
||||||
@ -357,7 +367,7 @@ int ie_match_stat(const struct index_state *istate,
|
|||||||
return changed;
|
return changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
int ie_modified(const struct index_state *istate,
|
int ie_modified(struct index_state *istate,
|
||||||
const struct cache_entry *ce,
|
const struct cache_entry *ce,
|
||||||
struct stat *st, unsigned int options)
|
struct stat *st, unsigned int options)
|
||||||
{
|
{
|
||||||
@ -778,6 +788,7 @@ int chmod_index_entry(struct index_state *istate, struct cache_entry *ce,
|
|||||||
}
|
}
|
||||||
cache_tree_invalidate_path(istate, ce->name);
|
cache_tree_invalidate_path(istate, ce->name);
|
||||||
ce->ce_flags |= CE_UPDATE_IN_BASE;
|
ce->ce_flags |= CE_UPDATE_IN_BASE;
|
||||||
|
mark_fsmonitor_invalid(istate, ce);
|
||||||
istate->cache_changed |= CE_ENTRY_CHANGED;
|
istate->cache_changed |= CE_ENTRY_CHANGED;
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@ -1229,10 +1240,13 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
|
|||||||
int ignore_valid = options & CE_MATCH_IGNORE_VALID;
|
int ignore_valid = options & CE_MATCH_IGNORE_VALID;
|
||||||
int ignore_skip_worktree = options & CE_MATCH_IGNORE_SKIP_WORKTREE;
|
int ignore_skip_worktree = options & CE_MATCH_IGNORE_SKIP_WORKTREE;
|
||||||
int ignore_missing = options & CE_MATCH_IGNORE_MISSING;
|
int ignore_missing = options & CE_MATCH_IGNORE_MISSING;
|
||||||
|
int ignore_fsmonitor = options & CE_MATCH_IGNORE_FSMONITOR;
|
||||||
|
|
||||||
if (!refresh || ce_uptodate(ce))
|
if (!refresh || ce_uptodate(ce))
|
||||||
return ce;
|
return ce;
|
||||||
|
|
||||||
|
if (!ignore_fsmonitor)
|
||||||
|
refresh_fsmonitor(istate);
|
||||||
/*
|
/*
|
||||||
* CE_VALID or CE_SKIP_WORKTREE means the user promised us
|
* CE_VALID or CE_SKIP_WORKTREE means the user promised us
|
||||||
* that the change to the work tree does not matter and told
|
* that the change to the work tree does not matter and told
|
||||||
@ -1246,6 +1260,10 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
|
|||||||
ce_mark_uptodate(ce);
|
ce_mark_uptodate(ce);
|
||||||
return ce;
|
return ce;
|
||||||
}
|
}
|
||||||
|
if (!ignore_fsmonitor && (ce->ce_flags & CE_FSMONITOR_VALID)) {
|
||||||
|
ce_mark_uptodate(ce);
|
||||||
|
return ce;
|
||||||
|
}
|
||||||
|
|
||||||
if (has_symlink_leading_path(ce->name, ce_namelen(ce))) {
|
if (has_symlink_leading_path(ce->name, ce_namelen(ce))) {
|
||||||
if (ignore_missing)
|
if (ignore_missing)
|
||||||
@ -1283,8 +1301,10 @@ static struct cache_entry *refresh_cache_ent(struct index_state *istate,
|
|||||||
* because CE_UPTODATE flag is in-core only;
|
* because CE_UPTODATE flag is in-core only;
|
||||||
* we are not going to write this change out.
|
* we are not going to write this change out.
|
||||||
*/
|
*/
|
||||||
if (!S_ISGITLINK(ce->ce_mode))
|
if (!S_ISGITLINK(ce->ce_mode)) {
|
||||||
ce_mark_uptodate(ce);
|
ce_mark_uptodate(ce);
|
||||||
|
mark_fsmonitor_valid(ce);
|
||||||
|
}
|
||||||
return ce;
|
return ce;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1392,6 +1412,7 @@ int refresh_index(struct index_state *istate, unsigned int flags,
|
|||||||
*/
|
*/
|
||||||
ce->ce_flags &= ~CE_VALID;
|
ce->ce_flags &= ~CE_VALID;
|
||||||
ce->ce_flags |= CE_UPDATE_IN_BASE;
|
ce->ce_flags |= CE_UPDATE_IN_BASE;
|
||||||
|
mark_fsmonitor_invalid(istate, ce);
|
||||||
istate->cache_changed |= CE_ENTRY_CHANGED;
|
istate->cache_changed |= CE_ENTRY_CHANGED;
|
||||||
}
|
}
|
||||||
if (quiet)
|
if (quiet)
|
||||||
@ -1554,6 +1575,9 @@ static int read_index_extension(struct index_state *istate,
|
|||||||
case CACHE_EXT_UNTRACKED:
|
case CACHE_EXT_UNTRACKED:
|
||||||
istate->untracked = read_untracked_extension(data, sz);
|
istate->untracked = read_untracked_extension(data, sz);
|
||||||
break;
|
break;
|
||||||
|
case CACHE_EXT_FSMONITOR:
|
||||||
|
read_fsmonitor_extension(istate, data, sz);
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
if (*ext < 'A' || 'Z' < *ext)
|
if (*ext < 'A' || 'Z' < *ext)
|
||||||
return error("index uses %.4s extension, which we do not understand",
|
return error("index uses %.4s extension, which we do not understand",
|
||||||
@ -1729,6 +1753,7 @@ static void post_read_index_from(struct index_state *istate)
|
|||||||
check_ce_order(istate);
|
check_ce_order(istate);
|
||||||
tweak_untracked_cache(istate);
|
tweak_untracked_cache(istate);
|
||||||
tweak_split_index(istate);
|
tweak_split_index(istate);
|
||||||
|
tweak_fsmonitor(istate);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* remember to discard_cache() before reading a different cache! */
|
/* remember to discard_cache() before reading a different cache! */
|
||||||
@ -2320,6 +2345,16 @@ static int do_write_index(struct index_state *istate, struct tempfile *tempfile,
|
|||||||
if (err)
|
if (err)
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
if (!strip_extensions && istate->fsmonitor_last_update) {
|
||||||
|
struct strbuf sb = STRBUF_INIT;
|
||||||
|
|
||||||
|
write_fsmonitor_extension(&sb, istate);
|
||||||
|
err = write_index_ext_header(&c, newfd, CACHE_EXT_FSMONITOR, sb.len) < 0
|
||||||
|
|| ce_write(&c, newfd, sb.buf, sb.len) < 0;
|
||||||
|
strbuf_release(&sb);
|
||||||
|
if (err)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
if (ce_flush(&c, newfd, istate->sha1))
|
if (ce_flush(&c, newfd, istate->sha1))
|
||||||
return -1;
|
return -1;
|
||||||
|
@ -62,7 +62,7 @@ int is_staging_gitmodules_ok(const struct index_state *istate)
|
|||||||
if ((pos >= 0) && (pos < istate->cache_nr)) {
|
if ((pos >= 0) && (pos < istate->cache_nr)) {
|
||||||
struct stat st;
|
struct stat st;
|
||||||
if (lstat(GITMODULES_FILE, &st) == 0 &&
|
if (lstat(GITMODULES_FILE, &st) == 0 &&
|
||||||
ce_match_stat(istate->cache[pos], &st, 0) & DATA_CHANGED)
|
ce_match_stat(istate->cache[pos], &st, CE_MATCH_IGNORE_FSMONITOR) & DATA_CHANGED)
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2
t/helper/.gitignore
vendored
2
t/helper/.gitignore
vendored
@ -3,7 +3,9 @@
|
|||||||
/test-config
|
/test-config
|
||||||
/test-date
|
/test-date
|
||||||
/test-delta
|
/test-delta
|
||||||
|
/test-drop-caches
|
||||||
/test-dump-cache-tree
|
/test-dump-cache-tree
|
||||||
|
/test-dump-fsmonitor
|
||||||
/test-dump-split-index
|
/test-dump-split-index
|
||||||
/test-dump-untracked-cache
|
/test-dump-untracked-cache
|
||||||
/test-fake-ssh
|
/test-fake-ssh
|
||||||
|
164
t/helper/test-drop-caches.c
Normal file
164
t/helper/test-drop-caches.c
Normal file
@ -0,0 +1,164 @@
|
|||||||
|
#include "git-compat-util.h"
|
||||||
|
|
||||||
|
#if defined(GIT_WINDOWS_NATIVE)
|
||||||
|
|
||||||
|
static int cmd_sync(void)
|
||||||
|
{
|
||||||
|
char Buffer[MAX_PATH];
|
||||||
|
DWORD dwRet;
|
||||||
|
char szVolumeAccessPath[] = "\\\\.\\X:";
|
||||||
|
HANDLE hVolWrite;
|
||||||
|
int success = 0;
|
||||||
|
|
||||||
|
dwRet = GetCurrentDirectory(MAX_PATH, Buffer);
|
||||||
|
if ((0 == dwRet) || (dwRet > MAX_PATH))
|
||||||
|
return error("Error getting current directory");
|
||||||
|
|
||||||
|
if ((Buffer[0] < 'A') || (Buffer[0] > 'Z'))
|
||||||
|
return error("Invalid drive letter '%c'", Buffer[0]);
|
||||||
|
|
||||||
|
szVolumeAccessPath[4] = Buffer[0];
|
||||||
|
hVolWrite = CreateFile(szVolumeAccessPath, GENERIC_READ | GENERIC_WRITE,
|
||||||
|
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
|
||||||
|
if (INVALID_HANDLE_VALUE == hVolWrite)
|
||||||
|
return error("Unable to open volume for writing, need admin access");
|
||||||
|
|
||||||
|
success = FlushFileBuffers(hVolWrite);
|
||||||
|
if (!success)
|
||||||
|
error("Unable to flush volume");
|
||||||
|
|
||||||
|
CloseHandle(hVolWrite);
|
||||||
|
|
||||||
|
return !success;
|
||||||
|
}
|
||||||
|
|
||||||
|
#define STATUS_SUCCESS (0x00000000L)
|
||||||
|
#define STATUS_PRIVILEGE_NOT_HELD (0xC0000061L)
|
||||||
|
|
||||||
|
typedef enum _SYSTEM_INFORMATION_CLASS {
|
||||||
|
SystemMemoryListInformation = 80,
|
||||||
|
} SYSTEM_INFORMATION_CLASS;
|
||||||
|
|
||||||
|
typedef enum _SYSTEM_MEMORY_LIST_COMMAND {
|
||||||
|
MemoryCaptureAccessedBits,
|
||||||
|
MemoryCaptureAndResetAccessedBits,
|
||||||
|
MemoryEmptyWorkingSets,
|
||||||
|
MemoryFlushModifiedList,
|
||||||
|
MemoryPurgeStandbyList,
|
||||||
|
MemoryPurgeLowPriorityStandbyList,
|
||||||
|
MemoryCommandMax
|
||||||
|
} SYSTEM_MEMORY_LIST_COMMAND;
|
||||||
|
|
||||||
|
static BOOL GetPrivilege(HANDLE TokenHandle, LPCSTR lpName, int flags)
|
||||||
|
{
|
||||||
|
BOOL bResult;
|
||||||
|
DWORD dwBufferLength;
|
||||||
|
LUID luid;
|
||||||
|
TOKEN_PRIVILEGES tpPreviousState;
|
||||||
|
TOKEN_PRIVILEGES tpNewState;
|
||||||
|
|
||||||
|
dwBufferLength = 16;
|
||||||
|
bResult = LookupPrivilegeValueA(0, lpName, &luid);
|
||||||
|
if (bResult) {
|
||||||
|
tpNewState.PrivilegeCount = 1;
|
||||||
|
tpNewState.Privileges[0].Luid = luid;
|
||||||
|
tpNewState.Privileges[0].Attributes = 0;
|
||||||
|
bResult = AdjustTokenPrivileges(TokenHandle, 0, &tpNewState,
|
||||||
|
(DWORD)((LPBYTE)&(tpNewState.Privileges[1]) - (LPBYTE)&tpNewState),
|
||||||
|
&tpPreviousState, &dwBufferLength);
|
||||||
|
if (bResult) {
|
||||||
|
tpPreviousState.PrivilegeCount = 1;
|
||||||
|
tpPreviousState.Privileges[0].Luid = luid;
|
||||||
|
tpPreviousState.Privileges[0].Attributes = flags != 0 ? 2 : 0;
|
||||||
|
bResult = AdjustTokenPrivileges(TokenHandle, 0, &tpPreviousState,
|
||||||
|
dwBufferLength, 0, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return bResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cmd_dropcaches(void)
|
||||||
|
{
|
||||||
|
HANDLE hProcess = GetCurrentProcess();
|
||||||
|
HANDLE hToken;
|
||||||
|
HMODULE ntdll;
|
||||||
|
DWORD(WINAPI *NtSetSystemInformation)(INT, PVOID, ULONG);
|
||||||
|
SYSTEM_MEMORY_LIST_COMMAND command;
|
||||||
|
int status;
|
||||||
|
|
||||||
|
if (!OpenProcessToken(hProcess, TOKEN_QUERY | TOKEN_ADJUST_PRIVILEGES, &hToken))
|
||||||
|
return error("Can't open current process token");
|
||||||
|
|
||||||
|
if (!GetPrivilege(hToken, "SeProfileSingleProcessPrivilege", 1))
|
||||||
|
return error("Can't get SeProfileSingleProcessPrivilege");
|
||||||
|
|
||||||
|
CloseHandle(hToken);
|
||||||
|
|
||||||
|
ntdll = LoadLibrary("ntdll.dll");
|
||||||
|
if (!ntdll)
|
||||||
|
return error("Can't load ntdll.dll, wrong Windows version?");
|
||||||
|
|
||||||
|
NtSetSystemInformation =
|
||||||
|
(DWORD(WINAPI *)(INT, PVOID, ULONG))GetProcAddress(ntdll, "NtSetSystemInformation");
|
||||||
|
if (!NtSetSystemInformation)
|
||||||
|
return error("Can't get function addresses, wrong Windows version?");
|
||||||
|
|
||||||
|
command = MemoryPurgeStandbyList;
|
||||||
|
status = NtSetSystemInformation(
|
||||||
|
SystemMemoryListInformation,
|
||||||
|
&command,
|
||||||
|
sizeof(SYSTEM_MEMORY_LIST_COMMAND)
|
||||||
|
);
|
||||||
|
if (status == STATUS_PRIVILEGE_NOT_HELD)
|
||||||
|
error("Insufficient privileges to purge the standby list, need admin access");
|
||||||
|
else if (status != STATUS_SUCCESS)
|
||||||
|
error("Unable to execute the memory list command %d", status);
|
||||||
|
|
||||||
|
FreeLibrary(ntdll);
|
||||||
|
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif defined(__linux__)
|
||||||
|
|
||||||
|
static int cmd_sync(void)
|
||||||
|
{
|
||||||
|
return system("sync");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cmd_dropcaches(void)
|
||||||
|
{
|
||||||
|
return system("echo 3 | sudo tee /proc/sys/vm/drop_caches");
|
||||||
|
}
|
||||||
|
|
||||||
|
#elif defined(__APPLE__)
|
||||||
|
|
||||||
|
static int cmd_sync(void)
|
||||||
|
{
|
||||||
|
return system("sync");
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cmd_dropcaches(void)
|
||||||
|
{
|
||||||
|
return system("sudo purge");
|
||||||
|
}
|
||||||
|
|
||||||
|
#else
|
||||||
|
|
||||||
|
static int cmd_sync(void)
|
||||||
|
{
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int cmd_dropcaches(void)
|
||||||
|
{
|
||||||
|
return error("drop caches not implemented on this platform");
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
||||||
|
|
||||||
|
int cmd_main(int argc, const char **argv)
|
||||||
|
{
|
||||||
|
cmd_sync();
|
||||||
|
return cmd_dropcaches();
|
||||||
|
}
|
21
t/helper/test-dump-fsmonitor.c
Normal file
21
t/helper/test-dump-fsmonitor.c
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
#include "cache.h"
|
||||||
|
|
||||||
|
int cmd_main(int ac, const char **av)
|
||||||
|
{
|
||||||
|
struct index_state *istate = &the_index;
|
||||||
|
int i;
|
||||||
|
|
||||||
|
setup_git_directory();
|
||||||
|
if (do_read_index(istate, get_index_file(), 0) < 0)
|
||||||
|
die("unable to read index file");
|
||||||
|
if (!istate->fsmonitor_last_update) {
|
||||||
|
printf("no fsmonitor\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
printf("fsmonitor last update %"PRIuMAX"\n", (uintmax_t)istate->fsmonitor_last_update);
|
||||||
|
|
||||||
|
for (i = 0; i < istate->cache_nr; i++)
|
||||||
|
printf((istate->cache[i]->ce_flags & CE_FSMONITOR_VALID) ? "+" : "-");
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
184
t/perf/p7519-fsmonitor.sh
Executable file
184
t/perf/p7519-fsmonitor.sh
Executable file
@ -0,0 +1,184 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
test_description="Test core.fsmonitor"
|
||||||
|
|
||||||
|
. ./perf-lib.sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# Performance test for the fsmonitor feature which enables git to talk to a
|
||||||
|
# file system change monitor and avoid having to scan the working directory
|
||||||
|
# for new or modified files.
|
||||||
|
#
|
||||||
|
# By default, the performance test will utilize the Watchman file system
|
||||||
|
# monitor if it is installed. If Watchman is not installed, it will use a
|
||||||
|
# dummy integration script that does not report any new or modified files.
|
||||||
|
# The dummy script has very little overhead which provides optimistic results.
|
||||||
|
#
|
||||||
|
# The performance test will also use the untracked cache feature if it is
|
||||||
|
# available as fsmonitor uses it to speed up scanning for untracked files.
|
||||||
|
#
|
||||||
|
# There are 3 environment variables that can be used to alter the default
|
||||||
|
# behavior of the performance test:
|
||||||
|
#
|
||||||
|
# GIT_PERF_7519_UNTRACKED_CACHE: used to configure core.untrackedCache
|
||||||
|
# GIT_PERF_7519_SPLIT_INDEX: used to configure core.splitIndex
|
||||||
|
# GIT_PERF_7519_FSMONITOR: used to configure core.fsMonitor
|
||||||
|
#
|
||||||
|
# The big win for using fsmonitor is the elimination of the need to scan the
|
||||||
|
# working directory looking for changed and untracked files. If the file
|
||||||
|
# information is all cached in RAM, the benefits are reduced.
|
||||||
|
#
|
||||||
|
# GIT_PERF_7519_DROP_CACHE: if set, the OS caches are dropped between tests
|
||||||
|
#
|
||||||
|
|
||||||
|
test_perf_large_repo
|
||||||
|
test_checkout_worktree
|
||||||
|
|
||||||
|
test_lazy_prereq UNTRACKED_CACHE '
|
||||||
|
{ git update-index --test-untracked-cache; ret=$?; } &&
|
||||||
|
test $ret -ne 1
|
||||||
|
'
|
||||||
|
|
||||||
|
test_lazy_prereq WATCHMAN '
|
||||||
|
{ command -v watchman >/dev/null 2>&1; ret=$?; } &&
|
||||||
|
test $ret -ne 1
|
||||||
|
'
|
||||||
|
|
||||||
|
if test_have_prereq WATCHMAN
|
||||||
|
then
|
||||||
|
# Convert unix style paths to escaped Windows style paths for Watchman
|
||||||
|
case "$(uname -s)" in
|
||||||
|
MSYS_NT*)
|
||||||
|
GIT_WORK_TREE="$(cygpath -aw "$PWD" | sed 's,\\,/,g')"
|
||||||
|
;;
|
||||||
|
*)
|
||||||
|
GIT_WORK_TREE="$PWD"
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
if test -n "$GIT_PERF_7519_DROP_CACHE"
|
||||||
|
then
|
||||||
|
# When using GIT_PERF_7519_DROP_CACHE, GIT_PERF_REPEAT_COUNT must be 1 to
|
||||||
|
# generate valid results. Otherwise the caching that happens for the nth
|
||||||
|
# run will negate the validity of the comparisons.
|
||||||
|
if test "$GIT_PERF_REPEAT_COUNT" -ne 1
|
||||||
|
then
|
||||||
|
echo "warning: Setting GIT_PERF_REPEAT_COUNT=1" >&2
|
||||||
|
GIT_PERF_REPEAT_COUNT=1
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
test_expect_success "setup for fsmonitor" '
|
||||||
|
# set untrackedCache depending on the environment
|
||||||
|
if test -n "$GIT_PERF_7519_UNTRACKED_CACHE"
|
||||||
|
then
|
||||||
|
git config core.untrackedCache "$GIT_PERF_7519_UNTRACKED_CACHE"
|
||||||
|
else
|
||||||
|
if test_have_prereq UNTRACKED_CACHE
|
||||||
|
then
|
||||||
|
git config core.untrackedCache true
|
||||||
|
else
|
||||||
|
git config core.untrackedCache false
|
||||||
|
fi
|
||||||
|
fi &&
|
||||||
|
|
||||||
|
# set core.splitindex depending on the environment
|
||||||
|
if test -n "$GIT_PERF_7519_SPLIT_INDEX"
|
||||||
|
then
|
||||||
|
git config core.splitIndex "$GIT_PERF_7519_SPLIT_INDEX"
|
||||||
|
fi &&
|
||||||
|
|
||||||
|
# set INTEGRATION_SCRIPT depending on the environment
|
||||||
|
if test -n "$GIT_PERF_7519_FSMONITOR"
|
||||||
|
then
|
||||||
|
INTEGRATION_SCRIPT="$GIT_PERF_7519_FSMONITOR"
|
||||||
|
else
|
||||||
|
#
|
||||||
|
# Choose integration script based on existence of Watchman.
|
||||||
|
# If Watchman exists, watch the work tree and attempt a query.
|
||||||
|
# If everything succeeds, use Watchman integration script,
|
||||||
|
# else fall back to an empty integration script.
|
||||||
|
#
|
||||||
|
mkdir .git/hooks &&
|
||||||
|
if test_have_prereq WATCHMAN
|
||||||
|
then
|
||||||
|
INTEGRATION_SCRIPT=".git/hooks/fsmonitor-watchman" &&
|
||||||
|
cp "$TEST_DIRECTORY/../templates/hooks--fsmonitor-watchman.sample" "$INTEGRATION_SCRIPT" &&
|
||||||
|
watchman watch "$GIT_WORK_TREE" &&
|
||||||
|
watchman watch-list | grep -q -F "$GIT_WORK_TREE"
|
||||||
|
else
|
||||||
|
INTEGRATION_SCRIPT=".git/hooks/fsmonitor-empty" &&
|
||||||
|
write_script "$INTEGRATION_SCRIPT"<<-\EOF
|
||||||
|
EOF
|
||||||
|
fi
|
||||||
|
fi &&
|
||||||
|
|
||||||
|
git config core.fsmonitor "$INTEGRATION_SCRIPT" &&
|
||||||
|
git update-index --fsmonitor
|
||||||
|
'
|
||||||
|
|
||||||
|
if test -n "$GIT_PERF_7519_DROP_CACHE"; then
|
||||||
|
test-drop-caches
|
||||||
|
fi
|
||||||
|
|
||||||
|
test_perf "status (fsmonitor=$INTEGRATION_SCRIPT)" '
|
||||||
|
git status
|
||||||
|
'
|
||||||
|
|
||||||
|
if test -n "$GIT_PERF_7519_DROP_CACHE"; then
|
||||||
|
test-drop-caches
|
||||||
|
fi
|
||||||
|
|
||||||
|
test_perf "status -uno (fsmonitor=$INTEGRATION_SCRIPT)" '
|
||||||
|
git status -uno
|
||||||
|
'
|
||||||
|
|
||||||
|
if test -n "$GIT_PERF_7519_DROP_CACHE"; then
|
||||||
|
test-drop-caches
|
||||||
|
fi
|
||||||
|
|
||||||
|
test_perf "status -uall (fsmonitor=$INTEGRATION_SCRIPT)" '
|
||||||
|
git status -uall
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success "setup without fsmonitor" '
|
||||||
|
unset INTEGRATION_SCRIPT &&
|
||||||
|
git config --unset core.fsmonitor &&
|
||||||
|
git update-index --no-fsmonitor
|
||||||
|
'
|
||||||
|
|
||||||
|
if test -n "$GIT_PERF_7519_DROP_CACHE"; then
|
||||||
|
test-drop-caches
|
||||||
|
fi
|
||||||
|
|
||||||
|
test_perf "status (fsmonitor=$INTEGRATION_SCRIPT)" '
|
||||||
|
git status
|
||||||
|
'
|
||||||
|
|
||||||
|
if test -n "$GIT_PERF_7519_DROP_CACHE"; then
|
||||||
|
test-drop-caches
|
||||||
|
fi
|
||||||
|
|
||||||
|
test_perf "status -uno (fsmonitor=$INTEGRATION_SCRIPT)" '
|
||||||
|
git status -uno
|
||||||
|
'
|
||||||
|
|
||||||
|
if test -n "$GIT_PERF_7519_DROP_CACHE"; then
|
||||||
|
test-drop-caches
|
||||||
|
fi
|
||||||
|
|
||||||
|
test_perf "status -uall (fsmonitor=$INTEGRATION_SCRIPT)" '
|
||||||
|
git status -uall
|
||||||
|
'
|
||||||
|
|
||||||
|
if test_have_prereq WATCHMAN
|
||||||
|
then
|
||||||
|
watchman watch-del "$GIT_WORK_TREE" >/dev/null 2>&1 &&
|
||||||
|
|
||||||
|
# Work around Watchman bug on Windows where it holds on to handles
|
||||||
|
# preventing the removal of the trash directory
|
||||||
|
watchman shutdown-server >/dev/null 2>&1
|
||||||
|
fi
|
||||||
|
|
||||||
|
test_done
|
@ -6,6 +6,7 @@ test_description='split index mode tests'
|
|||||||
|
|
||||||
# We need total control of index splitting here
|
# We need total control of index splitting here
|
||||||
sane_unset GIT_TEST_SPLIT_INDEX
|
sane_unset GIT_TEST_SPLIT_INDEX
|
||||||
|
sane_unset GIT_FSMONITOR_TEST
|
||||||
|
|
||||||
test_expect_success 'enable split index' '
|
test_expect_success 'enable split index' '
|
||||||
git config splitIndex.maxPercentChange 100 &&
|
git config splitIndex.maxPercentChange 100 &&
|
||||||
|
304
t/t7519-status-fsmonitor.sh
Executable file
304
t/t7519-status-fsmonitor.sh
Executable file
@ -0,0 +1,304 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
test_description='git status with file system watcher'
|
||||||
|
|
||||||
|
. ./test-lib.sh
|
||||||
|
|
||||||
|
#
|
||||||
|
# To run the entire git test suite using fsmonitor:
|
||||||
|
#
|
||||||
|
# copy t/t7519/fsmonitor-all to a location in your path and then set
|
||||||
|
# GIT_FSMONITOR_TEST=fsmonitor-all and run your tests.
|
||||||
|
#
|
||||||
|
|
||||||
|
# Note, after "git reset --hard HEAD" no extensions exist other than 'TREE'
|
||||||
|
# "git update-index --fsmonitor" can be used to get the extension written
|
||||||
|
# before testing the results.
|
||||||
|
|
||||||
|
clean_repo () {
|
||||||
|
git reset --hard HEAD &&
|
||||||
|
git clean -fd
|
||||||
|
}
|
||||||
|
|
||||||
|
dirty_repo () {
|
||||||
|
: >untracked &&
|
||||||
|
: >dir1/untracked &&
|
||||||
|
: >dir2/untracked &&
|
||||||
|
echo 1 >modified &&
|
||||||
|
echo 2 >dir1/modified &&
|
||||||
|
echo 3 >dir2/modified &&
|
||||||
|
echo 4 >new &&
|
||||||
|
echo 5 >dir1/new &&
|
||||||
|
echo 6 >dir2/new
|
||||||
|
}
|
||||||
|
|
||||||
|
write_integration_script () {
|
||||||
|
write_script .git/hooks/fsmonitor-test<<-\EOF
|
||||||
|
if test "$#" -ne 2
|
||||||
|
then
|
||||||
|
echo "$0: exactly 2 arguments expected"
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
if test "$1" != 1
|
||||||
|
then
|
||||||
|
echo "Unsupported core.fsmonitor hook version." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
printf "untracked\0"
|
||||||
|
printf "dir1/untracked\0"
|
||||||
|
printf "dir2/untracked\0"
|
||||||
|
printf "modified\0"
|
||||||
|
printf "dir1/modified\0"
|
||||||
|
printf "dir2/modified\0"
|
||||||
|
printf "new\0"
|
||||||
|
printf "dir1/new\0"
|
||||||
|
printf "dir2/new\0"
|
||||||
|
EOF
|
||||||
|
}
|
||||||
|
|
||||||
|
test_lazy_prereq UNTRACKED_CACHE '
|
||||||
|
{ git update-index --test-untracked-cache; ret=$?; } &&
|
||||||
|
test $ret -ne 1
|
||||||
|
'
|
||||||
|
|
||||||
|
test_expect_success 'setup' '
|
||||||
|
mkdir -p .git/hooks &&
|
||||||
|
: >tracked &&
|
||||||
|
: >modified &&
|
||||||
|
mkdir dir1 &&
|
||||||
|
: >dir1/tracked &&
|
||||||
|
: >dir1/modified &&
|
||||||
|
mkdir dir2 &&
|
||||||
|
: >dir2/tracked &&
|
||||||
|
: >dir2/modified &&
|
||||||
|
git -c core.fsmonitor= add . &&
|
||||||
|
git -c core.fsmonitor= commit -m initial &&
|
||||||
|
git config core.fsmonitor .git/hooks/fsmonitor-test &&
|
||||||
|
cat >.gitignore <<-\EOF
|
||||||
|
.gitignore
|
||||||
|
expect*
|
||||||
|
actual*
|
||||||
|
marker*
|
||||||
|
EOF
|
||||||
|
'
|
||||||
|
|
||||||
|
# test that the fsmonitor extension is off by default
|
||||||
|
test_expect_success 'fsmonitor extension is off by default' '
|
||||||
|
test-dump-fsmonitor >actual &&
|
||||||
|
grep "^no fsmonitor" actual
|
||||||
|
'
|
||||||
|
|
||||||
|
# test that "update-index --fsmonitor" adds the fsmonitor extension
|
||||||
|
test_expect_success 'update-index --fsmonitor" adds the fsmonitor extension' '
|
||||||
|
git update-index --fsmonitor &&
|
||||||
|
test-dump-fsmonitor >actual &&
|
||||||
|
grep "^fsmonitor last update" actual
|
||||||
|
'
|
||||||
|
|
||||||
|
# test that "update-index --no-fsmonitor" removes the fsmonitor extension
|
||||||
|
test_expect_success 'update-index --no-fsmonitor" removes the fsmonitor extension' '
|
||||||
|
git update-index --no-fsmonitor &&
|
||||||
|
test-dump-fsmonitor >actual &&
|
||||||
|
grep "^no fsmonitor" actual
|
||||||
|
'
|
||||||
|
|
||||||
|
cat >expect <<EOF &&
|
||||||
|
h dir1/modified
|
||||||
|
H dir1/tracked
|
||||||
|
h dir2/modified
|
||||||
|
H dir2/tracked
|
||||||
|
h modified
|
||||||
|
H tracked
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# test that "update-index --fsmonitor-valid" sets the fsmonitor valid bit
|
||||||
|
test_expect_success 'update-index --fsmonitor-valid" sets the fsmonitor valid bit' '
|
||||||
|
git update-index --fsmonitor &&
|
||||||
|
git update-index --fsmonitor-valid dir1/modified &&
|
||||||
|
git update-index --fsmonitor-valid dir2/modified &&
|
||||||
|
git update-index --fsmonitor-valid modified &&
|
||||||
|
git ls-files -f >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
cat >expect <<EOF &&
|
||||||
|
H dir1/modified
|
||||||
|
H dir1/tracked
|
||||||
|
H dir2/modified
|
||||||
|
H dir2/tracked
|
||||||
|
H modified
|
||||||
|
H tracked
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# test that "update-index --no-fsmonitor-valid" clears the fsmonitor valid bit
|
||||||
|
test_expect_success 'update-index --no-fsmonitor-valid" clears the fsmonitor valid bit' '
|
||||||
|
git update-index --no-fsmonitor-valid dir1/modified &&
|
||||||
|
git update-index --no-fsmonitor-valid dir2/modified &&
|
||||||
|
git update-index --no-fsmonitor-valid modified &&
|
||||||
|
git ls-files -f >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
cat >expect <<EOF &&
|
||||||
|
H dir1/modified
|
||||||
|
H dir1/tracked
|
||||||
|
H dir2/modified
|
||||||
|
H dir2/tracked
|
||||||
|
H modified
|
||||||
|
H tracked
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# test that all files returned by the script get flagged as invalid
|
||||||
|
test_expect_success 'all files returned by integration script get flagged as invalid' '
|
||||||
|
write_integration_script &&
|
||||||
|
dirty_repo &&
|
||||||
|
git update-index --fsmonitor &&
|
||||||
|
git ls-files -f >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
cat >expect <<EOF &&
|
||||||
|
H dir1/modified
|
||||||
|
h dir1/new
|
||||||
|
H dir1/tracked
|
||||||
|
H dir2/modified
|
||||||
|
h dir2/new
|
||||||
|
H dir2/tracked
|
||||||
|
H modified
|
||||||
|
h new
|
||||||
|
H tracked
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# test that newly added files are marked valid
|
||||||
|
test_expect_success 'newly added files are marked valid' '
|
||||||
|
git add new &&
|
||||||
|
git add dir1/new &&
|
||||||
|
git add dir2/new &&
|
||||||
|
git ls-files -f >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
cat >expect <<EOF &&
|
||||||
|
H dir1/modified
|
||||||
|
h dir1/new
|
||||||
|
h dir1/tracked
|
||||||
|
H dir2/modified
|
||||||
|
h dir2/new
|
||||||
|
h dir2/tracked
|
||||||
|
H modified
|
||||||
|
h new
|
||||||
|
h tracked
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# test that all unmodified files get marked valid
|
||||||
|
test_expect_success 'all unmodified files get marked valid' '
|
||||||
|
# modified files result in update-index returning 1
|
||||||
|
test_must_fail git update-index --refresh --force-write-index &&
|
||||||
|
git ls-files -f >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
cat >expect <<EOF &&
|
||||||
|
H dir1/modified
|
||||||
|
h dir1/tracked
|
||||||
|
h dir2/modified
|
||||||
|
h dir2/tracked
|
||||||
|
h modified
|
||||||
|
h tracked
|
||||||
|
EOF
|
||||||
|
|
||||||
|
# test that *only* files returned by the integration script get flagged as invalid
|
||||||
|
test_expect_success '*only* files returned by the integration script get flagged as invalid' '
|
||||||
|
write_script .git/hooks/fsmonitor-test<<-\EOF &&
|
||||||
|
printf "dir1/modified\0"
|
||||||
|
EOF
|
||||||
|
clean_repo &&
|
||||||
|
git update-index --refresh --force-write-index &&
|
||||||
|
echo 1 >modified &&
|
||||||
|
echo 2 >dir1/modified &&
|
||||||
|
echo 3 >dir2/modified &&
|
||||||
|
test_must_fail git update-index --refresh --force-write-index &&
|
||||||
|
git ls-files -f >actual &&
|
||||||
|
test_cmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
# Ensure commands that call refresh_index() to move the index back in time
|
||||||
|
# properly invalidate the fsmonitor cache
|
||||||
|
test_expect_success 'refresh_index() invalidates fsmonitor cache' '
|
||||||
|
write_script .git/hooks/fsmonitor-test<<-\EOF &&
|
||||||
|
EOF
|
||||||
|
clean_repo &&
|
||||||
|
dirty_repo &&
|
||||||
|
git add . &&
|
||||||
|
git commit -m "to reset" &&
|
||||||
|
git reset HEAD~1 &&
|
||||||
|
git status >actual &&
|
||||||
|
git -c core.fsmonitor= status >expect &&
|
||||||
|
test_i18ncmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
# test fsmonitor with and without preloadIndex
|
||||||
|
preload_values="false true"
|
||||||
|
for preload_val in $preload_values
|
||||||
|
do
|
||||||
|
test_expect_success "setup preloadIndex to $preload_val" '
|
||||||
|
git config core.preloadIndex $preload_val &&
|
||||||
|
if test $preload_val = true
|
||||||
|
then
|
||||||
|
GIT_FORCE_PRELOAD_TEST=$preload_val; export GIT_FORCE_PRELOAD_TEST
|
||||||
|
else
|
||||||
|
unset GIT_FORCE_PRELOAD_TEST
|
||||||
|
fi
|
||||||
|
'
|
||||||
|
|
||||||
|
# test fsmonitor with and without the untracked cache (if available)
|
||||||
|
uc_values="false"
|
||||||
|
test_have_prereq UNTRACKED_CACHE && uc_values="false true"
|
||||||
|
for uc_val in $uc_values
|
||||||
|
do
|
||||||
|
test_expect_success "setup untracked cache to $uc_val" '
|
||||||
|
git config core.untrackedcache $uc_val
|
||||||
|
'
|
||||||
|
|
||||||
|
# Status is well tested elsewhere so we'll just ensure that the results are
|
||||||
|
# the same when using core.fsmonitor.
|
||||||
|
test_expect_success 'compare status with and without fsmonitor' '
|
||||||
|
write_integration_script &&
|
||||||
|
clean_repo &&
|
||||||
|
dirty_repo &&
|
||||||
|
git add new &&
|
||||||
|
git add dir1/new &&
|
||||||
|
git add dir2/new &&
|
||||||
|
git status >actual &&
|
||||||
|
git -c core.fsmonitor= status >expect &&
|
||||||
|
test_i18ncmp expect actual
|
||||||
|
'
|
||||||
|
|
||||||
|
# Make sure it's actually skipping the check for modified and untracked
|
||||||
|
# (if enabled) files unless it is told about them.
|
||||||
|
test_expect_success "status doesn't detect unreported modifications" '
|
||||||
|
write_script .git/hooks/fsmonitor-test<<-\EOF &&
|
||||||
|
:>marker
|
||||||
|
EOF
|
||||||
|
clean_repo &&
|
||||||
|
git status &&
|
||||||
|
test_path_is_file marker &&
|
||||||
|
dirty_repo &&
|
||||||
|
rm -f marker &&
|
||||||
|
git status >actual &&
|
||||||
|
test_path_is_file marker &&
|
||||||
|
test_i18ngrep ! "Changes not staged for commit:" actual &&
|
||||||
|
if test $uc_val = true
|
||||||
|
then
|
||||||
|
test_i18ngrep ! "Untracked files:" actual
|
||||||
|
fi &&
|
||||||
|
if test $uc_val = false
|
||||||
|
then
|
||||||
|
test_i18ngrep "Untracked files:" actual
|
||||||
|
fi &&
|
||||||
|
rm -f marker
|
||||||
|
'
|
||||||
|
done
|
||||||
|
done
|
||||||
|
|
||||||
|
test_done
|
24
t/t7519/fsmonitor-all
Executable file
24
t/t7519/fsmonitor-all
Executable file
@ -0,0 +1,24 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An test hook script to integrate with git to test fsmonitor.
|
||||||
|
#
|
||||||
|
# The hook is passed a version (currently 1) and a time in nanoseconds
|
||||||
|
# formatted as a string and outputs to stdout all files that have been
|
||||||
|
# modified since the given time. Paths must be relative to the root of
|
||||||
|
# the working tree and separated by a single NUL.
|
||||||
|
#
|
||||||
|
#echo "$0 $*" >&2
|
||||||
|
|
||||||
|
if test "$#" -ne 2
|
||||||
|
then
|
||||||
|
echo "$0: exactly 2 arguments expected" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if test "$1" != 1
|
||||||
|
then
|
||||||
|
echo "Unsupported core.fsmonitor hook version." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "/"
|
22
t/t7519/fsmonitor-none
Executable file
22
t/t7519/fsmonitor-none
Executable file
@ -0,0 +1,22 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
#
|
||||||
|
# An test hook script to integrate with git to test fsmonitor.
|
||||||
|
#
|
||||||
|
# The hook is passed a version (currently 1) and a time in nanoseconds
|
||||||
|
# formatted as a string and outputs to stdout all files that have been
|
||||||
|
# modified since the given time. Paths must be relative to the root of
|
||||||
|
# the working tree and separated by a single NUL.
|
||||||
|
#
|
||||||
|
#echo "$0 $*" >&2
|
||||||
|
|
||||||
|
if test "$#" -ne 2
|
||||||
|
then
|
||||||
|
echo "$0: exactly 2 arguments expected" >&2
|
||||||
|
exit 2
|
||||||
|
fi
|
||||||
|
|
||||||
|
if test "$1" != 1
|
||||||
|
then
|
||||||
|
echo "Unsupported core.fsmonitor hook version." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
139
t/t7519/fsmonitor-watchman
Executable file
139
t/t7519/fsmonitor-watchman
Executable file
@ -0,0 +1,139 @@
|
|||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use IPC::Open2;
|
||||||
|
|
||||||
|
# An example hook script to integrate Watchman
|
||||||
|
# (https://facebook.github.io/watchman/) with git to speed up detecting
|
||||||
|
# new and modified files.
|
||||||
|
#
|
||||||
|
# The hook is passed a version (currently 1) and a time in nanoseconds
|
||||||
|
# formatted as a string and outputs to stdout all files that have been
|
||||||
|
# modified since the given time. Paths must be relative to the root of
|
||||||
|
# the working tree and separated by a single NUL.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "query-watchman" and set
|
||||||
|
# 'git config core.fsmonitor .git/hooks/query-watchman'
|
||||||
|
#
|
||||||
|
my ($version, $time) = @ARGV;
|
||||||
|
#print STDERR "$0 $version $time\n";
|
||||||
|
|
||||||
|
# Check the hook interface version
|
||||||
|
|
||||||
|
if ($version == 1) {
|
||||||
|
# convert nanoseconds to seconds
|
||||||
|
$time = int $time / 1000000000;
|
||||||
|
} else {
|
||||||
|
die "Unsupported query-fsmonitor hook version '$version'.\n" .
|
||||||
|
"Falling back to scanning...\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Convert unix style paths to escaped Windows style paths when running
|
||||||
|
# in Windows command prompt
|
||||||
|
|
||||||
|
my $system = `uname -s`;
|
||||||
|
$system =~ s/[\r\n]+//g;
|
||||||
|
my $git_work_tree;
|
||||||
|
|
||||||
|
if ($system =~ m/^MSYS_NT/ || $system =~ m/^MINGW/) {
|
||||||
|
$git_work_tree = `cygpath -aw "\$PWD"`;
|
||||||
|
$git_work_tree =~ s/[\r\n]+//g;
|
||||||
|
$git_work_tree =~ s,\\,/,g;
|
||||||
|
} else {
|
||||||
|
$git_work_tree = $ENV{'PWD'};
|
||||||
|
}
|
||||||
|
|
||||||
|
my $retry = 1;
|
||||||
|
|
||||||
|
launch_watchman();
|
||||||
|
|
||||||
|
sub launch_watchman {
|
||||||
|
|
||||||
|
my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j')
|
||||||
|
or die "open2() failed: $!\n" .
|
||||||
|
"Falling back to scanning...\n";
|
||||||
|
|
||||||
|
# In the query expression below we're asking for names of files that
|
||||||
|
# changed since $time but were not transient (ie created after
|
||||||
|
# $time but no longer exist).
|
||||||
|
#
|
||||||
|
# To accomplish this, we're using the "since" generator to use the
|
||||||
|
# recency index to select candidate nodes and "fields" to limit the
|
||||||
|
# output to file names only. Then we're using the "expression" term to
|
||||||
|
# further constrain the results.
|
||||||
|
#
|
||||||
|
# The category of transient files that we want to ignore will have a
|
||||||
|
# creation clock (cclock) newer than $time_t value and will also not
|
||||||
|
# currently exist.
|
||||||
|
|
||||||
|
my $query = <<" END";
|
||||||
|
["query", "$git_work_tree", {
|
||||||
|
"since": $time,
|
||||||
|
"fields": ["name"],
|
||||||
|
"expression": ["not", ["allof", ["since", $time, "cclock"], ["not", "exists"]]]
|
||||||
|
}]
|
||||||
|
END
|
||||||
|
|
||||||
|
open (my $fh, ">", ".git/watchman-query.json");
|
||||||
|
print $fh $query;
|
||||||
|
close $fh;
|
||||||
|
|
||||||
|
print CHLD_IN $query;
|
||||||
|
close CHLD_IN;
|
||||||
|
my $response = do {local $/; <CHLD_OUT>};
|
||||||
|
|
||||||
|
open ($fh, ">", ".git/watchman-response.json");
|
||||||
|
print $fh $response;
|
||||||
|
close $fh;
|
||||||
|
|
||||||
|
die "Watchman: command returned no output.\n" .
|
||||||
|
"Falling back to scanning...\n" if $response eq "";
|
||||||
|
die "Watchman: command returned invalid output: $response\n" .
|
||||||
|
"Falling back to scanning...\n" unless $response =~ /^\{/;
|
||||||
|
|
||||||
|
my $json_pkg;
|
||||||
|
eval {
|
||||||
|
require JSON::XS;
|
||||||
|
$json_pkg = "JSON::XS";
|
||||||
|
1;
|
||||||
|
} or do {
|
||||||
|
require JSON::PP;
|
||||||
|
$json_pkg = "JSON::PP";
|
||||||
|
};
|
||||||
|
|
||||||
|
my $o = $json_pkg->new->utf8->decode($response);
|
||||||
|
|
||||||
|
if ($retry > 0 and $o->{error} and $o->{error} =~ m/unable to resolve root .* directory (.*) is not watched/) {
|
||||||
|
print STDERR "Adding '$git_work_tree' to watchman's watch list.\n";
|
||||||
|
$retry--;
|
||||||
|
qx/watchman watch "$git_work_tree"/;
|
||||||
|
die "Failed to make watchman watch '$git_work_tree'.\n" .
|
||||||
|
"Falling back to scanning...\n" if $? != 0;
|
||||||
|
|
||||||
|
# Watchman will always return all files on the first query so
|
||||||
|
# return the fast "everything is dirty" flag to git and do the
|
||||||
|
# Watchman query just to get it over with now so we won't pay
|
||||||
|
# the cost in git to look up each individual file.
|
||||||
|
|
||||||
|
open ($fh, ">", ".git/watchman-output.out");
|
||||||
|
print "/\0";
|
||||||
|
close $fh;
|
||||||
|
|
||||||
|
print "/\0";
|
||||||
|
eval { launch_watchman() };
|
||||||
|
exit 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
die "Watchman: $o->{error}.\n" .
|
||||||
|
"Falling back to scanning...\n" if $o->{error};
|
||||||
|
|
||||||
|
open ($fh, ">", ".git/watchman-output.out");
|
||||||
|
binmode $fh, ":utf8";
|
||||||
|
print $fh @{$o->{files}};
|
||||||
|
close $fh;
|
||||||
|
|
||||||
|
binmode STDOUT, ":utf8";
|
||||||
|
local $, = "\0";
|
||||||
|
print @{$o->{files}};
|
||||||
|
}
|
120
templates/hooks--fsmonitor-watchman.sample
Executable file
120
templates/hooks--fsmonitor-watchman.sample
Executable file
@ -0,0 +1,120 @@
|
|||||||
|
#!/usr/bin/perl
|
||||||
|
|
||||||
|
use strict;
|
||||||
|
use warnings;
|
||||||
|
use IPC::Open2;
|
||||||
|
|
||||||
|
# An example hook script to integrate Watchman
|
||||||
|
# (https://facebook.github.io/watchman/) with git to speed up detecting
|
||||||
|
# new and modified files.
|
||||||
|
#
|
||||||
|
# The hook is passed a version (currently 1) and a time in nanoseconds
|
||||||
|
# formatted as a string and outputs to stdout all files that have been
|
||||||
|
# modified since the given time. Paths must be relative to the root of
|
||||||
|
# the working tree and separated by a single NUL.
|
||||||
|
#
|
||||||
|
# To enable this hook, rename this file to "query-watchman" and set
|
||||||
|
# 'git config core.fsmonitor .git/hooks/query-watchman'
|
||||||
|
#
|
||||||
|
my ($version, $time) = @ARGV;
|
||||||
|
|
||||||
|
# Check the hook interface version
|
||||||
|
|
||||||
|
if ($version == 1) {
|
||||||
|
# convert nanoseconds to seconds
|
||||||
|
$time = int $time / 1000000000;
|
||||||
|
} else {
|
||||||
|
die "Unsupported query-fsmonitor hook version '$version'.\n" .
|
||||||
|
"Falling back to scanning...\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
# Convert unix style paths to escaped Windows style paths when running
|
||||||
|
# in Windows command prompt
|
||||||
|
|
||||||
|
my $system = `uname -s`;
|
||||||
|
$system =~ s/[\r\n]+//g;
|
||||||
|
my $git_work_tree;
|
||||||
|
|
||||||
|
if ($system =~ m/^MSYS_NT/ || $system =~ m/^MINGW/) {
|
||||||
|
$git_work_tree = `cygpath -aw "\$PWD"`;
|
||||||
|
$git_work_tree =~ s/[\r\n]+//g;
|
||||||
|
$git_work_tree =~ s,\\,/,g;
|
||||||
|
} else {
|
||||||
|
$git_work_tree = $ENV{'PWD'};
|
||||||
|
}
|
||||||
|
|
||||||
|
my $retry = 1;
|
||||||
|
|
||||||
|
launch_watchman();
|
||||||
|
|
||||||
|
sub launch_watchman {
|
||||||
|
|
||||||
|
my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j')
|
||||||
|
or die "open2() failed: $!\n" .
|
||||||
|
"Falling back to scanning...\n";
|
||||||
|
|
||||||
|
# In the query expression below we're asking for names of files that
|
||||||
|
# changed since $time but were not transient (ie created after
|
||||||
|
# $time but no longer exist).
|
||||||
|
#
|
||||||
|
# To accomplish this, we're using the "since" generator to use the
|
||||||
|
# recency index to select candidate nodes and "fields" to limit the
|
||||||
|
# output to file names only. Then we're using the "expression" term to
|
||||||
|
# further constrain the results.
|
||||||
|
#
|
||||||
|
# The category of transient files that we want to ignore will have a
|
||||||
|
# creation clock (cclock) newer than $time_t value and will also not
|
||||||
|
# currently exist.
|
||||||
|
|
||||||
|
my $query = <<" END";
|
||||||
|
["query", "$git_work_tree", {
|
||||||
|
"since": $time,
|
||||||
|
"fields": ["name"],
|
||||||
|
"expression": ["not", ["allof", ["since", $time, "cclock"], ["not", "exists"]]]
|
||||||
|
}]
|
||||||
|
END
|
||||||
|
|
||||||
|
print CHLD_IN $query;
|
||||||
|
close CHLD_IN;
|
||||||
|
my $response = do {local $/; <CHLD_OUT>};
|
||||||
|
|
||||||
|
die "Watchman: command returned no output.\n" .
|
||||||
|
"Falling back to scanning...\n" if $response eq "";
|
||||||
|
die "Watchman: command returned invalid output: $response\n" .
|
||||||
|
"Falling back to scanning...\n" unless $response =~ /^\{/;
|
||||||
|
|
||||||
|
my $json_pkg;
|
||||||
|
eval {
|
||||||
|
require JSON::XS;
|
||||||
|
$json_pkg = "JSON::XS";
|
||||||
|
1;
|
||||||
|
} or do {
|
||||||
|
require JSON::PP;
|
||||||
|
$json_pkg = "JSON::PP";
|
||||||
|
};
|
||||||
|
|
||||||
|
my $o = $json_pkg->new->utf8->decode($response);
|
||||||
|
|
||||||
|
if ($retry > 0 and $o->{error} and $o->{error} =~ m/unable to resolve root .* directory (.*) is not watched/) {
|
||||||
|
print STDERR "Adding '$git_work_tree' to watchman's watch list.\n";
|
||||||
|
$retry--;
|
||||||
|
qx/watchman watch "$git_work_tree"/;
|
||||||
|
die "Failed to make watchman watch '$git_work_tree'.\n" .
|
||||||
|
"Falling back to scanning...\n" if $? != 0;
|
||||||
|
|
||||||
|
# Watchman will always return all files on the first query so
|
||||||
|
# return the fast "everything is dirty" flag to git and do the
|
||||||
|
# Watchman query just to get it over with now so we won't pay
|
||||||
|
# the cost in git to look up each individual file.
|
||||||
|
print "/\0";
|
||||||
|
eval { launch_watchman() };
|
||||||
|
exit 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
die "Watchman: $o->{error}.\n" .
|
||||||
|
"Falling back to scanning...\n" if $o->{error};
|
||||||
|
|
||||||
|
binmode STDOUT, ":utf8";
|
||||||
|
local $, = "\0";
|
||||||
|
print @{$o->{files}};
|
||||||
|
}
|
@ -14,6 +14,7 @@
|
|||||||
#include "dir.h"
|
#include "dir.h"
|
||||||
#include "submodule.h"
|
#include "submodule.h"
|
||||||
#include "submodule-config.h"
|
#include "submodule-config.h"
|
||||||
|
#include "fsmonitor.h"
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Error messages expected by scripts out of plumbing commands such as
|
* Error messages expected by scripts out of plumbing commands such as
|
||||||
@ -408,6 +409,7 @@ static int apply_sparse_checkout(struct index_state *istate,
|
|||||||
ce->ce_flags &= ~CE_SKIP_WORKTREE;
|
ce->ce_flags &= ~CE_SKIP_WORKTREE;
|
||||||
if (was_skip_worktree != ce_skip_worktree(ce)) {
|
if (was_skip_worktree != ce_skip_worktree(ce)) {
|
||||||
ce->ce_flags |= CE_UPDATE_IN_BASE;
|
ce->ce_flags |= CE_UPDATE_IN_BASE;
|
||||||
|
mark_fsmonitor_invalid(istate, ce);
|
||||||
istate->cache_changed |= CE_ENTRY_CHANGED;
|
istate->cache_changed |= CE_ENTRY_CHANGED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user