5 #define IGNORE_ROOT_GIT
11 #define _XOPEN_SOURCE 500
18 #include <sys/inotify.h>
19 #include <sys/select.h>
20 #include <sys/types.h>
28 * - Every N seconds / cron
29 * - N seconds after changes 'settle'
30 * Never wait longer than N seconds from a change to commit
32 * For each of N repositories...
35 * - Every N seconds / cron
36 * - N seconds after changes 'settle'
37 * - N seconds after commits 'settle'
38 * Never wait longer than N seconds from a (change | commit) to push
40 * TODO: resume counter-acted schedule based on original start time?
46 #define BUFFER_SIZE (sizeof(struct inotify_event) + PATH_MAX) * 10
49 # define LOG_DEBUG(fmt, stuff...) fprintf(stderr, fmt "\n", stuff)
51 # define LOG_DEBUG(...) 0
54 #define MY_WATCH_EVENTS (IN_CREATE | IN_DELETE | IN_DELETE_SELF | IN_MODIFY | IN_MOVED_FROM | IN_MOVED_TO | IN_Q_OVERFLOW)
58 read_will_block(int fd
) {
67 return !select(fd
+ 1, &fds
, NULL
, NULL
, &tv
);
72 my_system2(const char*fp
, char *const argv
[]) {
73 #if defined(VERBOSE) || defined(DEBUG)
75 printf("exec: %s\n", fp
);
76 for (int i
= 1; argv
[i
]; ++i
)
77 printf(" argv[%d]: %s\n", i
, argv
[i
]);
80 for (int i
= 1; argv
[i
]; ++i
)
81 printf(" '%s'", argv
[i
]);
101 waitpid(pid
, &status
, 0);
102 return WEXITSTATUS(status
);
107 int (*f_exec
)(const char *, char *const argv
[]);
108 f_exec
= (fp
[0] == '/') ? execv
: execvp
;
114 #define my_system(f, argv...) my_system2(f, (char *const[]){f, argv, NULL})
132 vll_set_action(struct vll
**top
, const char*p
, enum action action
, uint32_t flags
) {
133 for (struct vll
*this, **thisp
= top
; this = *thisp
; thisp
= &this->next
)
135 if (!strcmp(this->p
, p
))
138 enum action actIn
= action
;
140 switch ((this->action
<< 8) | action
) {
141 case (A_RM
<< 8) + A_ADD
:
144 case (A_ADD
<< 8) + A_RM
:
145 // Delete the plan to add
148 case (A_REPLACE
<< 8) + A_RM
:
149 case (A_MODIFY
<< 8) + A_RM
:
150 // Prior replace/modify is irrelevant now
152 case (A_ADD
<< 8) + A_REPLACE
:
153 case (A_RM
<< 8) + A_REPLACE
:
154 case (A_REPLACE
<< 8) + A_REPLACE
:
155 case (A_MODIFY
<< 8) + A_REPLACE
:
156 // "Moved to" can unconditionally replace anything
158 case (A_ADD
<< 8) + A_MODIFY
:
159 // Still need to make initial add
160 case (A_MODIFY
<< 8) + A_MODIFY
:
163 fprintf(stderr
, "Not sure how to handle action %d => %d (%d %s)\n", this->action
, action
, flags
, p
);
166 LOG_DEBUG("Change action of %s: %d =%d> %d (flags %d => %d)", p
, this->action
, actIn
, action
, this->flags
, flags
);
174 this->action
= action
;
181 LOG_DEBUG("Set action of %s: %d (flags %d)", p
, action
, flags
);
182 struct vll
*n
= malloc(sizeof(struct vll
));
192 vll_free(struct vll
**top
) {
193 for (struct vll
*next
, *this = *top
; this; this = next
)
209 // TODO: Probably a good idea to bail out early if the inotify fd is ready for reading
212 vll_stage_all(struct vll
**changes_p
) {
213 struct vll
*changes
= *changes_p
;
214 struct cmdbuilder act
[1];
216 char *add
[6 + MAX_ARGS
] = {"git", "add", "-A", "--ignore-errors", "--"};
217 act
[A_ADD
].cmd
= add
;
218 act
[A_ADD
].args
= &add
[5];
223 (p)->args[(p)->count] = NULL; \
224 my_system2((p)->cmd[0], (p)->cmd); \
228 for (struct vll
*this = changes
; this; this = this->next
)
230 struct cmdbuilder
*c
;
233 c
->args
[c
->count
++] = this->p
;
234 if (c
->count
< MAX_ARGS
)
239 if (act
[A_ADD
].count
)
247 struct inotify_watch
{
250 struct inotify_watch
*next
;
255 my_add_watch2(struct inotify_watch
**top
, int fd
, const char *pathname
, uint32_t mask
, int recursive
) {
258 DIR *D
= opendir(pathname
);
259 assert(D
|| errno
== ENOTDIR
);
263 for (struct dirent
*DE
; DE
= readdir(D
); )
265 if (DE
->d_name
[0] == '.' && ((DE
->d_name
[1] == '.' && DE
->d_name
[2] == '\0') || DE
->d_name
[1] == '\0'))
267 #ifdef IGNORE_ROOT_GIT
268 if (recursive
== 1 && !strcmp(".git", DE
->d_name
))
271 if (DE
->d_type
== DT_DIR
)
273 // FIXME: these snprintf/sprintf could easily be replaced with strlen etc (inline func?)
274 char npn
[1 + snprintf(DE
->d_name
, 0, "%s/%s", pathname
, DE
->d_name
)];
275 sprintf(npn
, "%s/%s", pathname
, DE
->d_name
);
276 my_add_watch2(top
, fd
, npn
, mask
, 2);
284 LOG_DEBUG("Adding watch on %s", pathname
);
286 struct inotify_watch
*n
= malloc(sizeof(struct inotify_watch
));
287 n
->path
= strdup(pathname
);
288 n
->wd
= inotify_add_watch(fd
, pathname
, mask
);
295 #define my_add_watch(top, fd, path, mask) my_add_watch2(top, fd, path, mask, 0)
296 #define my_add_watch_recursive(top, fd, path, mask) my_add_watch2(top, fd, path, mask, 1)
300 my_rm_watch(struct inotify_watch
**top
, int fd
, int wd
) {
301 for (struct inotify_watch
*i
, **ii
= top
; i
= *ii
; ii
= &i
->next
)
306 inotify_rm_watch(fd
, wd
);
312 fprintf(stderr
, "Tried to remove non-existent watch: %d\n", wd
);
319 get_wd_path(struct inotify_watch
**top
, int fd
, int wd
) {
320 for (struct inotify_watch
*i
= *top
; i
; i
= i
->next
)
327 main(int argc
, char**argv
) {
328 setbuf(stdout
, NULL
);
329 setbuf(stderr
, NULL
);
332 const char *dir
= argv
[1];
335 LOG_DEBUG("Changed to %s", dir
);
337 int fd_inotify
= inotify_init();
338 assert(fd_inotify
!= -1);
340 struct inotify_watch
*wlist
= NULL
;
341 my_add_watch_recursive(&wlist
, fd_inotify
, ".", MY_WATCH_EVENTS
);
343 // Initial global update check
344 my_system("git", "add", "--all", "--ignore-errors");
345 my_system("git", "commit", "-m", "Initial global update commit");
347 struct vll
*changes
= NULL
;
352 char buf
[BUFFER_SIZE
];
359 FD_SET(fd_inotify
, &rfds
);
363 switch (changes
? 1 : todo
) {
365 LOG_DEBUG("Waiting for changes...%s","");
367 case 1: // add/remove
369 LOG_DEBUG("Scheduling add/remove in %d seconds...", tv
.tv_sec
);
373 LOG_DEBUG("Scheduling commit in %d seconds...", tv
.tv_sec
);
377 LOG_DEBUG("Scheduling push in %d seconds...", tv
.tv_sec
);
382 select(fd_inotify
+ 1, &rfds
, NULL
, NULL
, (changes
|| todo
) ? &tv
: NULL
);
386 size_t rrv
= read(fd_inotify
, buf
, sizeof(buf
));
397 struct inotify_event
*ev
;
398 for (size_t offset
= 0; offset
< buf_len
; offset
+= sizeof(struct inotify_event
) + ev
->len
)
400 ev
= (struct inotify_event
*)&buf
[offset
];
401 // WARNING: "If successive output inotify events produced on the inotify file descriptor are identical (same wd, mask, cookie, and name) then they are coalesced into a single event."
403 const char *fullpath
= get_wd_path(&wlist
, fd_inotify
, ev
->wd
);
404 size_t fullpath_slen
= 0;
405 if (ev
->len
&& ev
->name
[0])
406 fullpath_slen
= snprintf(buf
, 0, "%s/%s", fullpath
, ev
->name
) + 1;
407 char fullpath_s
[fullpath_slen
];
410 sprintf(fullpath_s
, "%s/%s", fullpath
, ev
->name
);
411 fullpath
= fullpath_s
;
416 if (ev
->mask
& IN_ACCESS
)
418 if (ev
->mask
& IN_ATTRIB
)
420 if (ev
->mask
& IN_CLOSE_WRITE
)
421 printf(" CLOSE_WRITE");
422 if (ev
->mask
& IN_CLOSE_NOWRITE
)
423 printf(" CLOSE_NOWRITE");
424 if (ev
->mask
& IN_CREATE
)
426 if (ev
->mask
& IN_DELETE
)
428 if (ev
->mask
& IN_DELETE_SELF
)
429 printf(" Self-deleted");
430 if (ev
->mask
& IN_MODIFY
)
432 if (ev
->mask
& IN_MOVE_SELF
)
433 printf(" Self-move");
434 if (ev
->mask
& IN_MOVED_FROM
)
435 printf(" Moved-from");
436 if (ev
->mask
& IN_MOVED_TO
)
438 if (ev
->mask
& IN_OPEN
)
440 if (ev
->mask
& IN_Q_OVERFLOW
)
441 printf(" QueueOVERFLOW");
442 printf(": %s\n", fullpath
);
444 if (ev
->mask
& (IN_CREATE
| IN_MOVED_TO
))
446 assert(ev
->len
&& ev
->name
[0]);
447 if (ev
->mask
& IN_ISDIR
)
448 my_add_watch(&wlist
, fd_inotify
, fullpath
, MY_WATCH_EVENTS
);
449 // FIXME: only need to add watch immediately before 'git add'
450 vll_set_action(&changes
, fullpath
, (ev
->mask
& IN_MOVED_TO
) ? A_REPLACE
: A_ADD
, ev
->mask
);
452 if (ev
->mask
& (IN_MODIFY
))
453 vll_set_action(&changes
, fullpath
, A_MODIFY
, ev
->mask
);
454 if (ev
->mask
& (IN_DELETE
| IN_MOVED_FROM
))
455 vll_set_action(&changes
, fullpath
, A_RM
, ev
->mask
);
456 if (ev
->mask
& IN_DELETE_SELF
)
457 my_rm_watch(&wlist
, fd_inotify
, ev
->wd
);
458 if (ev
->mask
& IN_Q_OVERFLOW
)
460 fprintf(stderr
, "FIXME: this Q_OVERFLOW method will FAIL TO MONITOR NEW DIRECTORIES\n");
472 if (!have_inotify
&& (todo
= changes
? 1 : todo
))
476 my_system("git", "add", "--all");
482 vll_stage_all(&changes
);
486 my_system("git", "commit", "-m", "_");
491 my_system("git", "push");
497 if (!changes
&& read_will_block(fd_inotify
))
499 if (!system("git ls-files -omd | grep ."))
501 if (read_will_block(fd_inotify
))
503 fprintf(stderr
, "git reports index de-sync!\n");
508 LOG_DEBUG("git reports index fully in-sync%s","");