From: "Nguyễn Thái Ngọc Duy" <pclouds@gmail.com>
To: git@vger.kernel.org
Cc: "Nguyễn Thái Ngọc Duy" <pclouds@gmail.com>
Subject: [PATCH v3 17/26] file-watcher: inotify support, watching part
Date: Mon, 3 Feb 2014 11:29:05 +0700 [thread overview]
Message-ID: <1391401754-15347-18-git-send-email-pclouds@gmail.com> (raw)
In-Reply-To: <1391401754-15347-1-git-send-email-pclouds@gmail.com>
"struct dir" manages inotify file descriptor and forms a tree. "struct
file" manages a file. When a file is watched, all dirs up to the file
is watched. Any changes on a directory impacts all subdirs and files.
The way data structure is made might be inotify-specific. I haven't
thought of how other file notification mechanisms may be
implemented. So there may be some refactoring later when a new OS is
supported.
Room for improvement: consecutive watched paths likely share the same
directory part (even though they are sorted by mtime, not name). Try
remember the last "dir" sequence and reduce lookups.
Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com>
---
Documentation/git-file-watcher.txt | 13 +++
file-watcher.c | 226 ++++++++++++++++++++++++++++++++++++-
2 files changed, 238 insertions(+), 1 deletion(-)
diff --git a/Documentation/git-file-watcher.txt b/Documentation/git-file-watcher.txt
index d91caf3..d694fea 100644
--- a/Documentation/git-file-watcher.txt
+++ b/Documentation/git-file-watcher.txt
@@ -18,11 +18,24 @@ lstat(2) to detect that itself. Config key filewatcher.path needs to
be set to `<socket directory>` so Git knows how to contact to the file
watcher.
+This program is only supported under Linux with inotify(7) support.
+
OPTIONS
-------
--detach::
Run in background.
+BUGS
+----
+On Linux, file watcher may fail to detect changes if you move the work
+tree from outside. For example if you have work tree at
+`/tmp/foo/work`, you move `/tmp/foo` to `/tmp/bar`, make some changes
+in `/tmp/bar/work` and move `/tmp/bar` back to `/tmp/foo`, changes
+won't get noticed. Moving `/tmp/foo/work` to something else is fine.
+
+inotify may not work well with network filesystems and a few special
+others. Check inotify documents.
+
SEE ALSO
--------
linkgit:git-update-index[1],
diff --git a/file-watcher.c b/file-watcher.c
index aa2daf6..d0762e6 100644
--- a/file-watcher.c
+++ b/file-watcher.c
@@ -11,6 +11,28 @@ static const char *const file_watcher_usage[] = {
NULL
};
+struct dir;
+struct repository;
+
+struct file {
+ char *name;
+ struct dir *parent;
+ struct repository *repo;
+ struct file *next;
+};
+
+struct dir {
+ char *name;
+ struct dir *parent;
+ struct dir **subdirs;
+ struct file **files;
+ struct repository *repo; /* only for root note */
+ int wd, nr_subdirs, nr_files;
+};
+
+static struct dir **wds;
+static int wds_alloc;
+
struct repository {
char *work_tree;
char index_signature[41];
@@ -25,6 +47,7 @@ struct repository {
struct string_list updated;
int updated_sorted;
int updating;
+ struct dir *root;
};
const char *invalid_signature = "0000000000000000000000000000000000000000";
@@ -42,10 +65,199 @@ struct connection {
static struct connection **conns;
static struct pollfd *pfd;
static int conns_alloc, pfd_nr, pfd_alloc;
+static int inotify_fd;
+
+/*
+ * IN_DONT_FOLLOW does not matter now as we do not monitor
+ * symlinks. See ce_watchable().
+ */
+#define INOTIFY_MASKS (IN_DELETE_SELF | IN_MOVE_SELF | \
+ IN_CREATE | IN_ATTRIB | IN_DELETE | IN_MODIFY | \
+ IN_MOVED_FROM | IN_MOVED_TO)
+static struct dir *create_dir(struct dir *parent, const char *path,
+ const char *basename)
+{
+ struct dir *d;
+ int wd = inotify_add_watch(inotify_fd, path, INOTIFY_MASKS);
+ if (wd < 0)
+ return NULL;
+
+ d = xmalloc(sizeof(*d));
+ memset(d, 0, sizeof(*d));
+ d->wd = wd;
+ d->parent = parent;
+ d->name = xstrdup(basename);
+
+ ALLOC_GROW(wds, wd + 1, wds_alloc);
+ wds[wd] = d;
+ return d;
+}
+
+static int get_dir_pos(struct dir *d, const char *name)
+{
+ int first, last;
+
+ first = 0;
+ last = d->nr_subdirs;
+ while (last > first) {
+ int next = (last + first) >> 1;
+ int cmp = strcmp(name, d->subdirs[next]->name);
+ if (!cmp)
+ return next;
+ if (cmp < 0) {
+ last = next;
+ continue;
+ }
+ first = next+1;
+ }
+
+ return -first-1;
+}
+
+static void free_file(struct dir *d, int pos, int topdown);
+static void free_dir(struct dir *d, int topdown)
+{
+ struct dir *p = d->parent;
+ int pos;
+ if (!topdown && p && (pos = get_dir_pos(p, d->name)) < 0)
+ die("How come this directory is not registered in its parent?");
+ if (d->repo)
+ d->repo->root = NULL;
+ wds[d->wd] = NULL;
+ inotify_rm_watch(inotify_fd, d->wd);
+ if (topdown) {
+ int i;
+ for (i = 0; i < d->nr_subdirs; i++)
+ free_dir(d->subdirs[i], topdown);
+ for (i = 0; i < d->nr_files; i++)
+ free_file(d, i, topdown);
+ }
+ free(d->name);
+ free(d->subdirs);
+ free(d->files);
+ free(d);
+ if (p && !topdown) {
+ p->nr_subdirs--;
+ memmove(p->subdirs + pos, p->subdirs + pos + 1,
+ (p->nr_subdirs - pos) * sizeof(*p->subdirs));
+ if (!p->nr_subdirs && !p->nr_files)
+ free_dir(p, topdown);
+ }
+}
+
+static int get_file_pos(struct dir *d, const char *name)
+{
+ int first, last;
+
+ first = 0;
+ last = d->nr_files;
+ while (last > first) {
+ int next = (last + first) >> 1;
+ int cmp = strcmp(name, d->files[next]->name);
+ if (!cmp)
+ return next;
+ if (cmp < 0) {
+ last = next;
+ continue;
+ }
+ first = next+1;
+ }
+
+ return -first-1;
+}
+
+static void free_file(struct dir *d, int pos, int topdown)
+{
+ struct file *f = d->files[pos];
+ free(f->name);
+ free(f);
+ if (!topdown) {
+ d->nr_files--;
+ memmove(d->files + pos, d->files + pos + 1,
+ (d->nr_files - pos) * sizeof(*d->files));
+ if (!d->nr_subdirs && !d->nr_files)
+ free_dir(d, topdown);
+ }
+}
+
+static struct dir *add_dir(struct dir *d,
+ const char *path, const char *basename)
+{
+ struct dir *new;
+ int pos = get_dir_pos(d, basename);
+ if (pos >= 0)
+ return d->subdirs[pos];
+ pos = -pos-1;
+
+ new = create_dir(d, path, basename);
+ if (!new)
+ return NULL;
+
+ d->nr_subdirs++;
+ d->subdirs = xrealloc(d->subdirs, sizeof(*d->subdirs) * d->nr_subdirs);
+ if (d->nr_subdirs > pos + 1)
+ memmove(d->subdirs + pos + 1, d->subdirs + pos,
+ (d->nr_subdirs - pos - 1) * sizeof(*d->subdirs));
+ d->subdirs[pos] = new;
+ return new;
+}
+
+static struct file *add_file(struct dir *d, const char *name)
+{
+ struct file *new;
+ int pos = get_file_pos(d, name);
+ if (pos >= 0)
+ return d->files[pos];
+ pos = -pos-1;
+
+ new = xmalloc(sizeof(*new));
+ memset(new, 0, sizeof(*new));
+ new->parent = d;
+ new->name = xstrdup(name);
+
+ d->nr_files++;
+ d->files = xrealloc(d->files, sizeof(*d->files) * d->nr_files);
+ if (d->nr_files > pos + 1)
+ memmove(d->files + pos + 1, d->files + pos,
+ (d->nr_files - pos - 1) * sizeof(*d->files));
+ d->files[pos] = new;
+ return new;
+}
static int watch_path(struct repository *repo, char *path)
{
- return -1;
+ struct dir *d = repo->root;
+ char *p = path;
+
+ if (!d) {
+ d = create_dir(NULL, ".", "");
+ if (!d)
+ return -1;
+ repo->root = d;
+ d->repo = repo;
+ }
+
+ for (;;) {
+ char *next, *sep;
+ sep = strchr(p, '/');
+ if (!sep) {
+ struct file *file;
+ file = add_file(d, p);
+ if (!file->repo)
+ file->repo = repo;
+ break;
+ }
+
+ next = sep + 1;
+ *sep = '\0';
+ d = add_dir(d, path, p);
+ if (!d)
+ /* we could free oldest watches and try again */
+ return -1;
+ *sep = '/';
+ p = next;
+ }
+ return 0;
}
static void get_changed_list(int conn_id)
@@ -195,8 +407,15 @@ static struct repository *get_repo(const char *work_tree)
return repo;
}
+static void reset_watches(struct repository *repo)
+{
+ if (repo->root)
+ free_dir(repo->root, 1);
+}
+
static void reset_repo(struct repository *repo, ino_t inode)
{
+ reset_watches(repo);
string_list_clear(&repo->updated, 0);
memcpy(repo->index_signature, invalid_signature, 40);
repo->inode = inode;
@@ -497,6 +716,11 @@ int main(int argc, const char **argv)
git_extract_argv0_path(argv[0]);
git_setup_gettext();
+
+ inotify_fd = inotify_init();
+ if (inotify_fd < 0)
+ die_errno("unable to initialize inotify");
+
argc = parse_options(argc, argv, NULL, options,
file_watcher_usage, 0);
if (argc < 1)
--
1.8.5.2.240.g8478abd
next prev parent reply other threads:[~2014-02-03 4:31 UTC|newest]
Thread overview: 72+ messages / expand[flat|nested] mbox.gz Atom feed top
2014-01-12 11:03 [PATCH 0/6] inotify support Nguyễn Thái Ngọc Duy
2014-01-12 11:03 ` [PATCH 1/6] read-cache: save trailing sha-1 Nguyễn Thái Ngọc Duy
2014-01-12 11:03 ` [PATCH 2/6] read-cache: new extension to mark what file is watched Nguyễn Thái Ngọc Duy
2014-01-13 17:02 ` Jonathan Nieder
2014-01-14 1:25 ` Duy Nguyen
2014-01-14 1:39 ` Duy Nguyen
2014-01-12 11:03 ` [PATCH 3/6] read-cache: connect to file watcher Nguyễn Thái Ngọc Duy
2014-01-15 10:58 ` Jeff King
2014-01-12 11:03 ` [PATCH 4/6] read-cache: get "updated" path list from " Nguyễn Thái Ngọc Duy
2014-01-12 11:03 ` [PATCH 5/6] read-cache: ask file watcher to watch files Nguyễn Thái Ngọc Duy
2014-01-12 11:03 ` [PATCH 6/6] file-watcher: support inotify Nguyễn Thái Ngọc Duy
2014-01-17 9:47 ` [PATCH/WIP v2 00/14] inotify support Nguyễn Thái Ngọc Duy
2014-01-17 9:47 ` [PATCH/WIP v2 01/14] read-cache: save trailing sha-1 Nguyễn Thái Ngọc Duy
2014-01-17 9:47 ` [PATCH/WIP v2 02/14] read-cache: new extension to mark what file is watched Nguyễn Thái Ngọc Duy
2014-01-17 11:19 ` Thomas Gummerer
2014-01-19 17:06 ` Thomas Rast
2014-01-20 1:38 ` Duy Nguyen
2014-01-17 9:47 ` [PATCH/WIP v2 03/14] read-cache: connect to file watcher Nguyễn Thái Ngọc Duy
2014-01-17 15:24 ` Torsten Bögershausen
2014-01-17 16:21 ` Duy Nguyen
2014-01-17 9:47 ` [PATCH/WIP v2 04/14] read-cache: ask file watcher to watch files Nguyễn Thái Ngọc Duy
2014-01-17 9:47 ` [PATCH/WIP v2 05/14] read-cache: put some limits on file watching Nguyễn Thái Ngọc Duy
2014-01-19 17:06 ` Thomas Rast
2014-01-20 1:36 ` Duy Nguyen
2014-01-17 9:47 ` [PATCH/WIP v2 06/14] read-cache: get modified file list from file watcher Nguyễn Thái Ngọc Duy
2014-01-17 9:47 ` [PATCH/WIP v2 07/14] read-cache: add config to start file watcher automatically Nguyễn Thái Ngọc Duy
2014-01-17 9:47 ` [PATCH/WIP v2 08/14] read-cache: add GIT_TEST_FORCE_WATCHER for testing Nguyễn Thái Ngọc Duy
2014-01-19 17:04 ` Thomas Rast
2014-01-20 1:32 ` Duy Nguyen
2014-01-17 9:47 ` [PATCH/WIP v2 09/14] file-watcher: add --shutdown and --log options Nguyễn Thái Ngọc Duy
2014-01-17 9:47 ` [PATCH/WIP v2 10/14] file-watcher: automatically quit Nguyễn Thái Ngọc Duy
2014-01-17 9:47 ` [PATCH/WIP v2 11/14] file-watcher: support inotify Nguyễn Thái Ngọc Duy
2014-01-19 17:04 ` [PATCH/WIP v2 00/14] inotify support Thomas Rast
2014-01-20 1:28 ` Duy Nguyen
2014-01-20 21:51 ` Thomas Rast
2014-01-28 10:46 ` Duy Nguyen
2014-02-03 4:28 ` [PATCH v3 00/26] " Nguyễn Thái Ngọc Duy
2014-02-03 4:28 ` [PATCH v3 01/26] pkt-line.c: rename global variable buffer[] to something less generic Nguyễn Thái Ngọc Duy
2014-02-03 4:28 ` [PATCH v3 02/26] pkt-line.c: add packet_write_timeout() Nguyễn Thái Ngọc Duy
2014-02-03 4:28 ` [PATCH v3 03/26] pkt-line.c: add packet_read_line_timeout() Nguyễn Thái Ngọc Duy
2014-02-03 4:28 ` [PATCH v3 04/26] unix-socket: make unlink() optional in unix_stream_listen() Nguyễn Thái Ngọc Duy
2014-02-03 4:28 ` [PATCH v3 05/26] Add git-file-watcher and basic connection handling logic Nguyễn Thái Ngọc Duy
2014-02-03 4:28 ` [PATCH v3 06/26] file-watcher: check socket directory permission Nguyễn Thái Ngọc Duy
2014-02-03 4:28 ` [PATCH v3 07/26] file-watcher: remove socket on exit Nguyễn Thái Ngọc Duy
2014-02-03 4:28 ` [PATCH v3 08/26] file-watcher: add --detach Nguyễn Thái Ngọc Duy
2014-02-03 4:28 ` [PATCH v3 09/26] read-cache: save trailing sha-1 Nguyễn Thái Ngọc Duy
2014-02-03 4:28 ` [PATCH v3 10/26] read-cache: new flag CE_WATCHED to mark what file is watched Nguyễn Thái Ngọc Duy
2014-02-03 4:28 ` [PATCH v3 11/26] Clear CE_WATCHED when set CE_VALID alone Nguyễn Thái Ngọc Duy
2014-02-03 4:29 ` [PATCH v3 12/26] read-cache: basic hand shaking to the file watcher Nguyễn Thái Ngọc Duy
2014-02-03 4:29 ` [PATCH v3 13/26] read-cache: ask file watcher to watch files Nguyễn Thái Ngọc Duy
2014-02-03 4:29 ` [PATCH v3 14/26] read-cache: put some limits on file watching Nguyễn Thái Ngọc Duy
2014-02-03 4:29 ` [PATCH v3 15/26] read-cache: get changed file list from file watcher Nguyễn Thái Ngọc Duy
2014-02-03 4:29 ` [PATCH v3 16/26] git-compat-util.h: add inotify stubs on non-Linux platforms Nguyễn Thái Ngọc Duy
2014-02-03 4:29 ` Nguyễn Thái Ngọc Duy [this message]
2014-02-03 4:29 ` [PATCH v3 18/26] file-watcher: inotify support, notification part Nguyễn Thái Ngọc Duy
2014-02-03 4:29 ` [PATCH v3 19/26] Wrap CE_VALID test with ce_valid() Nguyễn Thái Ngọc Duy
2014-02-03 4:29 ` [PATCH v3 20/26] read-cache: new variable to verify file-watcher results Nguyễn Thái Ngọc Duy
2014-02-03 4:29 ` [PATCH v3 21/26] Support running file watcher with the test suite Nguyễn Thái Ngọc Duy
2014-02-03 4:29 ` [PATCH v3 22/26] file-watcher: quit if $WATCHER/socket is gone Nguyễn Thái Ngọc Duy
2014-02-03 4:29 ` [PATCH v3 23/26] file-watcher: tests for the daemon Nguyễn Thái Ngọc Duy
2014-02-03 4:29 ` [PATCH v3 24/26] ls-files: print CE_WATCHED as W (or "w" with CE_VALID) Nguyễn Thái Ngọc Duy
2014-02-03 4:29 ` [PATCH v3 25/26] file-watcher: tests for the client side Nguyễn Thái Ngọc Duy
2014-02-03 4:29 ` [PATCH v3 26/26] Disable file-watcher with system inotify on some tests Nguyễn Thái Ngọc Duy
2014-02-08 8:04 ` [PATCH v3 00/26] inotify support Torsten Bögershausen
2014-02-08 8:53 ` Duy Nguyen
2014-02-09 20:19 ` Torsten Bögershausen
2014-02-10 10:37 ` Duy Nguyen
2014-02-10 16:55 ` Torsten Bögershausen
2014-02-10 23:34 ` Duy Nguyen
2014-02-17 12:36 ` Duy Nguyen
2014-02-19 20:35 ` [PATCH 0/6] " Shawn Pearce
2014-02-19 23:45 ` Duy Nguyen
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=1391401754-15347-18-git-send-email-pclouds@gmail.com \
--to=pclouds@gmail.com \
--cc=git@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).