Skip to content

Commit

Permalink
make ignore_vcs and ignore_dirs configurable
Browse files Browse the repository at this point in the history
Addresses #12

This makes the previously hard-coded list of VCS dirs configurable
via .watchmanconfig, and introduces a new option to ignore specific
dirs in the tree.

This is done as exact matches, not regexps; you need to statically
describe which portions should be ignored.
  • Loading branch information
wez committed Dec 4, 2013
1 parent e0762ed commit 56fcdba
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 42 deletions.
25 changes: 25 additions & 0 deletions README.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -1048,6 +1048,31 @@ The following parameters are accepted in the global configuration file only:
will allow watches only in the top level of Git or Mercurial repositories.
The following parameters are accepted in the .watchmanconfig file only:
* `ignore_vcs` - apply special VCS ignore logic to the set of named dirs.
This option has a default value of `[".git", ".hg", ".svn"]`. Dirs that
match this option are observed and watched using special shallow logic.
The shallow watch allows watchman to mildly abuse the version control
directories to store its query cookie files and to observe VCS locking
activity without having to watch the entire set of VCS data for large trees.
* `ignore_dirs` - dirs that match are completely ignored by watchman.
This is useful to ignore a directory that contains only build products and
where file change notifications are unwanted because of the sheer volume of
files.
For example,
```json
{
"ignore_dirs": ["build"]
}
```
would ignore the `build` directory at the top level of the watched tree,
and everything below it. It will never appear in the watchman query
results for the tree.
## Build/Install
Expand Down
171 changes: 129 additions & 42 deletions root.c
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,102 @@ static bool w_root_init(w_root_t *root, char **errmsg)
return root;
}

static void apply_ignore_vcs_configuration(w_root_t *root)
{
w_string_t *name;
w_string_t *fullname;
uint8_t i;
json_t *ignores;
char hostname[256];
struct stat st;

ignores = cfg_get_json(root, "ignore_vcs");
if (ignores && !json_is_array(ignores)) {
w_log(W_LOG_ERR, "ignore_vcs must be an array of strings\n");
ignores = NULL;
}
if (ignores) {
// incref so we can more simply dispose of the default ignore
// set that we create in the else branch of this
json_incref(ignores);
} else {
// default to a well-known set of vcs's
ignores = json_pack("[sss]", ".git", ".svn", ".hg");
}

for (i = 0; i < json_array_size(ignores); i++) {
const char *ignore = json_string_value(json_array_get(ignores, i));

if (!ignore) {
w_log(W_LOG_ERR, "ignore_vcs must be an array of strings\n");
continue;
}

name = w_string_new(ignore);
fullname = w_string_path_cat(root->root_path, name);
w_ht_set(root->ignore_vcs, w_ht_ptr_val(fullname),
w_ht_ptr_val(fullname));

// While we're at it, see if we can find out where to put our
// query cookie information
if (root->query_cookie_dir == NULL &&
lstat(fullname->buf, &st) == 0 && S_ISDIR(st.st_mode)) {
// root/{.hg,.git,.svn}
root->query_cookie_dir = w_string_path_cat(root->root_path, name);
}
w_string_delref(name);
w_string_delref(fullname);
}

json_decref(ignores);

if (root->query_cookie_dir == NULL) {
w_string_addref(root->root_path);
root->query_cookie_dir = root->root_path;
}
gethostname(hostname, sizeof(hostname));
hostname[sizeof(hostname) - 1] = '\0';

root->query_cookie_prefix = w_string_make_printf(
"%.*s/" WATCHMAN_COOKIE_PREFIX "%s-%d-", root->query_cookie_dir->len,
root->query_cookie_dir->buf, hostname, (int)getpid());
}

static void apply_ignore_configuration(w_root_t *root)
{
w_string_t *name;
w_string_t *fullname;
uint8_t i;
json_t *ignores;

ignores = cfg_get_json(root, "ignore_dirs");
if (!ignores) {
return;
}
if (!json_is_array(ignores)) {
w_log(W_LOG_ERR, "ignore_dirs must be an array of strings\n");
return;
}

for (i = 0; i < json_array_size(ignores); i++) {
const char *ignore = json_string_value(json_array_get(ignores, i));

if (!ignore) {
w_log(W_LOG_ERR, "ignore_dirs must be an array of strings\n");
continue;
}

name = w_string_new(ignore);
fullname = w_string_path_cat(root->root_path, name);
w_ht_set(root->ignore_dirs, w_ht_ptr_val(fullname),
w_ht_ptr_val(fullname));
w_log(W_LOG_DBG, "ignoring %.*s recursively\n",
fullname->len, fullname->buf);
w_string_delref(fullname);
w_string_delref(name);
}
}

static w_root_t *w_root_new(const char *path, char **errmsg)
{
w_root_t *root = calloc(1, sizeof(*root));
Expand All @@ -225,49 +321,13 @@ static w_root_t *w_root_new(const char *path, char **errmsg)
root->root_path = w_string_new(path);
root->commands = w_ht_new(2, &trigger_hash_funcs);
root->query_cookies = w_ht_new(2, &w_ht_string_funcs);
root->ignore_vcs = w_ht_new(2, &w_ht_string_funcs);
root->ignore_dirs = w_ht_new(2, &w_ht_string_funcs);
// Special handling for VCS control dirs
{
static const char *ignores[] = {
".git", ".svn", ".hg"
};
w_string_t *name;
w_string_t *fullname;
char hostname[256];
uint8_t i;
struct stat st;

for (i = 0; i < sizeof(ignores) / sizeof(ignores[0]); i++) {
name = w_string_new(ignores[i]);
fullname = w_string_path_cat(root->root_path, name);
w_ht_set(root->ignore_dirs, w_ht_ptr_val(fullname),
w_ht_ptr_val(fullname));
w_string_delref(fullname);

// While we're at it, see if we can find out where to put our
// query cookie information
if (root->query_cookie_dir == NULL &&
lstat(fullname->buf, &st) == 0 && S_ISDIR(st.st_mode)) {
// root/{.hg,.git,.svn}
root->query_cookie_dir = w_string_path_cat(root->root_path, name);
}
w_string_delref(name);
}

if (root->query_cookie_dir == NULL) {
w_string_addref(root->root_path);
root->query_cookie_dir = root->root_path;
}
gethostname(hostname, sizeof(hostname));
hostname[sizeof(hostname) - 1] = '\0';

root->query_cookie_prefix = w_string_make_printf(
"%.*s/" WATCHMAN_COOKIE_PREFIX "%s-%d-", root->query_cookie_dir->len,
root->query_cookie_dir->buf, hostname, (int)getpid());
}

load_root_config(root, path);
root->trigger_settle = cfg_get_int(root, "settle", DEFAULT_SETTLE_PERIOD);
apply_ignore_vcs_configuration(root);
apply_ignore_configuration(root);

if (!w_root_init(root, errmsg)) {
w_root_delref(root);
Expand Down Expand Up @@ -791,6 +851,12 @@ static void stat_path(w_root_t *root,
w_string_t *dir_name;
w_string_t *file_name;

if (w_ht_get(root->ignore_dirs, w_ht_ptr_val(full_path))) {
w_log(W_LOG_DBG, "%.*s matches ignore_dir rules\n",
full_path->len, full_path->buf);
return;
}

if (full_path->len > sizeof(path)-1) {
w_log(W_LOG_FATAL, "path %.*s is too big\n",
full_path->len, full_path->buf);
Expand Down Expand Up @@ -864,7 +930,7 @@ static void stat_path(w_root_t *root,
}

// Don't recurse if our parent is an ignore dir
if (!w_ht_get(root->ignore_dirs, w_ht_ptr_val(dir_name)) ||
if (!w_ht_get(root->ignore_vcs, w_ht_ptr_val(dir_name)) ||
// but do if we're looking at the cookie dir (stat_path is never
// called for the root itself)
w_string_equal(full_path, root->query_cookie_dir)) {
Expand Down Expand Up @@ -1651,6 +1717,27 @@ static bool is_ignored(w_root_t *root, const char *path, uint32_t pathlen)
if (w_ht_first(root->ignore_dirs, &i)) do {
w_string_t *ign = w_ht_val_ptr(i.value);

if (pathlen < ign->len) {
continue;
}

if (memcmp(ign->buf, path, ign->len) == 0) {
if (ign->len == pathlen) {
// Exact match
return true;
}

if (path[ign->len] == '/') {
// prefix match
return true;
}
}

} while (w_ht_next(root->ignore_dirs, &i));

if (w_ht_first(root->ignore_vcs, &i)) do {
w_string_t *ign = w_ht_val_ptr(i.value);

if (pathlen <= ign->len) {
continue;
}
Expand All @@ -1670,7 +1757,7 @@ static bool is_ignored(w_root_t *root, const char *path, uint32_t pathlen)
}
}

} while (w_ht_next(root->ignore_dirs, &i));
} while (w_ht_next(root->ignore_vcs, &i));

return false;
}
Expand Down Expand Up @@ -2413,7 +2500,7 @@ void w_root_delref(w_root_t *root)

pthread_mutex_destroy(&root->lock);
w_string_delref(root->root_path);
w_ht_free(root->ignore_dirs);
w_ht_free(root->ignore_vcs);
w_ht_free(root->commands);
w_ht_free(root->query_cookies);
if (root->config_file) {
Expand Down
41 changes: 41 additions & 0 deletions tests/integration/ignore.php
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,46 @@ function testIgnoreGit() {
'foo'
));
}

function testIgnoreGeneric() {
$dir = PhutilDirectoryFixture::newEmptyFixture();
$root = realpath($dir->getPath());

$cfg = array(
'ignore_dirs' => array('build')
);
file_put_contents("$root/.watchmanconfig", json_encode($cfg));

mkdir("$root/build");
mkdir("$root/build/lower");
mkdir("$root/builda");
touch("$root/foo");
touch("$root/build/bar");
touch("$root/buildfile");
touch("$root/build/lower/baz");
touch("$root/builda/hello");

$this->watch($root);
$this->assertFileList($root, array(
'.watchmanconfig',
'builda',
'builda/hello',
'buildfile',
'foo',
));

touch("$root/build/lower/dontlookatme");
touch("$root/build/orme");
touch("$root/buil");
$this->assertFileList($root, array(
'.watchmanconfig',
'buil',
'builda',
'builda/hello',
'buildfile',
'foo',
));

}
}

1 change: 1 addition & 0 deletions watchman.h
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,7 @@ struct watchman_root {

/* map of dir name => dirname
* if the map has an entry for a given dir, we're ignoring it */
w_ht_t *ignore_vcs;
w_ht_t *ignore_dirs;

int trigger_settle;
Expand Down

0 comments on commit 56fcdba

Please sign in to comment.