From 6af2a516e9c97b0585ae99f286c19f9ad93cc3fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Wed, 2 May 2012 20:53:27 +0000 Subject: App::Inotify::Hookable: a new module to solve the problem described in DESCRIPTION --- .gitignore | 3 + Changes | 5 + bin/inotify-hookable | 13 ++ dist.ini | 8 + lib/App/Inotify/Hookable.pm | 373 ++++++++++++++++++++++++++++++++++++++++++++ 5 files changed, 402 insertions(+) create mode 100644 .gitignore create mode 100644 Changes create mode 100755 bin/inotify-hookable create mode 100644 dist.ini create mode 100755 lib/App/Inotify/Hookable.pm diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..5e4a9c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.*.sw[nop] +.build/ +App-Inotify-Hookable-* diff --git a/Changes b/Changes new file mode 100644 index 0000000..5b17f34 --- /dev/null +++ b/Changes @@ -0,0 +1,5 @@ +Revision history for App::Inotify::Hookable + +{{$NEXT}} + + - Initial buggy version diff --git a/bin/inotify-hookable b/bin/inotify-hookable new file mode 100755 index 0000000..e516612 --- /dev/null +++ b/bin/inotify-hookable @@ -0,0 +1,13 @@ +#!/usr/bin/env perl +package main; +use strict; +use warnings; +use App::Inotify::Hookable; + +App::Inotify::Hookable->new_with_options->run; + +=head1 NAME + +inotify-hookable - See L for documentation + +=cut diff --git a/dist.ini b/dist.ini new file mode 100644 index 0000000..aebda69 --- /dev/null +++ b/dist.ini @@ -0,0 +1,8 @@ +name = App-Inotify-Hookable +author = Ævar Arnfjörð Bjarmason +copyright_holder = Ævar Arnfjörð Bjarmason +license = Perl_5 + +[@AVAR] +dist = App-Inotify-Hookable +bugtracker = rt diff --git a/lib/App/Inotify/Hookable.pm b/lib/App/Inotify/Hookable.pm new file mode 100755 index 0000000..e7f3feb --- /dev/null +++ b/lib/App/Inotify/Hookable.pm @@ -0,0 +1,373 @@ +#!/usr/bin/env perl +package App::Inotify::Hookable; +use Moose; +use MooseX::Types::Moose ':all'; +use Linux::Inotify; +use Time::HiRes qw(gettimeofday tv_interval ualarm); +use Try::Tiny; + +with 'MooseX::Getopt::Dashes'; + +has debug => ( + metaclass => 'MooseX::Getopt::Meta::Attribute', + is => 'ro', + isa => Bool, + default => 0, + cmd_aliases => 'd', + documentation => "Should we print debug info about what we're doing?", +); + +has watch_directories => ( + metaclass => 'MooseX::Getopt::Meta::Attribute', + is => 'ro', + isa => ArrayRef[Str], + default => sub { [] }, + cmd_aliases => 'w', + auto_deref => 1, + documentation => "What directories should we watch?", +); + +has recursive => ( + metaclass => 'MooseX::Getopt::Meta::Attribute', + is => 'ro', + isa => Bool, + default => 1, + cmd_aliases => 'r', + documentation => "Should we recursively watch the directories we're watching? On by default.", +); + +has on_modify_command => ( + metaclass => 'MooseX::Getopt::Meta::Attribute', + is => 'ro', + isa => ArrayRef[Str], + default => sub { [] }, + auto_deref => 1, + cmd_aliases => 'c', + documentation => "What commands should we run when something happens?", +); + +has on_modify_path_command => ( + metaclass => 'MooseX::Getopt::Meta::Attribute', + is => 'ro', + isa => HashRef[Str], + default => sub { +{} }, + cmd_aliases => 'C', + documentation => "What commands should we run for a given path when something happens? The key is a regex and the value is a command.", +); + +has buffer_time => ( + metaclass => 'MooseX::Getopt::Meta::Attribute', + is => 'ro', + isa => Int, + default => 100, + cmd_aliases => 't', + documentation => "How many ms should we buffer inotify for?", +); + +has _watches => ( + is => 'ro', + isa => 'HashRef', + default => sub { +{} }, + documentation => "What stuff are we watching?", +); + +has _notifier => ( + is => 'rw', + isa => 'Linux::Inotify', + lazy_build => 1, +); + +sub _build__notifier { Linux::Inotify->new } + +sub log { + my ($self, $message) = @_; + print STDERR scalar(localtime()), " : ", $message, "\n"; +}; + +sub run { + my ($self) = @_; + + # Catch sigint so DEMOLISH can run + local $SIG{INT} = sub { exit 1 }; + + my @watching = $self->watch_directories; + die "You need to give me something to watch" unless @watching; + $self->log( + "Starting up, " . + ($self->recursive ? "recursively " : "") . + "watching directories <@watching>" + ); + + my $notifier = $self->_notifier; + $self->setup_watch; + my $buffer_time = $self->buffer_time; + while (my @events = $notifier->read()) { + # At this point we have an event, but Linux sends these *really + # fast* so if someone does "touch foo bar zar" we might only get + # an event for the first file, then later the rest. + # + # So buffer up events for $sleep_ms and see if we stop getting + # them, then restart. + my $sleep_ms = $buffer_time; + my $sleep_us = $sleep_ms * 10**3; + + my %modified_paths; + my $should_log_modified_paths = keys %{ $self->on_modify_path_command }; + my $log_modified_paths = sub { + my $events = shift; + for my $event (@$events) { + my $fullname = $event->fullname; + $fullname =~ s[//][/]g; # We have double slashes for some reason + $modified_paths{$fullname} = undef; + } + return; + }; + + WAIT: while (1) { + for my $event (@events) { + $event->print if $self->debug; + } + $log_modified_paths->(\@events) if $should_log_modified_paths; + @events = (); + try { + local $SIG{ALRM} = sub { + die "Timeout waiting for ->read"; + }; + ualarm($sleep_us); + @events = $notifier->read; + ualarm(0); + } catch { + $self->log("We have no more events with a timeout of $sleep_ms us") if $self->debug; + }; + + if (@events) { + $self->log("We have events, waiting another $sleep_ms us and checking again") if $self->debug; + + $log_modified_paths->(\@events) if $should_log_modified_paths; + } else { + # No more events + last WAIT; + } + } + + $self->log("Had changes in your directories"); + + if ($should_log_modified_paths) { + $self->log("Checking for path-specific hooks") if $self->debug; + my %hooks_to_run; + my $on_modify_path_command = $self->on_modify_path_command; + for my $path (keys %modified_paths) { + for my $path_hook (keys %$on_modify_path_command) { + $hooks_to_run{$path_hook} = 1 + if $path =~ /$path_hook/; + } + } + if (keys %hooks_to_run) { + $self->log("Running path-specific hooks"); + my $t0 = [gettimeofday]; + for my $hook_to_run (keys %hooks_to_run) { + my $command = $on_modify_path_command->{$hook_to_run}; + $self->log("Running path hook <$hook_to_run>: <$command>"); + system $command; + } + my $elapsed = tv_interval ( $t0 ); + $self->log(sprintf "FINISHED running path-specific hooks. Took %.2fs", $elapsed); + } + } + + if (my @commands = $self->on_modify_command) { + $self->log("Running global hooks"); + my $t0 = [gettimeofday]; + for my $command (@commands) { + $self->log("Running <$command>"); + system $command; + } + my $elapsed = tv_interval ( $t0 ); + $self->log(sprintf "FINISHED restarting. Took %.2fs", $elapsed); + } + + # Re-setup the watching if needed, we may have new directories. + $self->setup_watch; + } + + return 1; +} + +sub all_directories_to_watch { + my ($self) = @_; + my @watch_directories = $self->watch_directories; + my @directories; + if ($self->recursive) { + chomp(@directories = qx[find @watch_directories -type d]); + } else { + @directories = @watch_directories; + } + # Don't notify on "git status" (creates a lock) and other similar + # operations. + grep { not m[/\.git/?] } @directories; +} + +sub setup_watch { + my ($self) = @_; + + my $notifier = $self->_notifier; + my $watches = $self->_watches; + + # Add new watches + NEW_WATCH: for my $directory ($self->all_directories_to_watch) { + next NEW_WATCH if exists $watches->{$directory}; + try { + my $watch = $notifier->add_watch( + $directory, + ( + # This is a directory + Linux::Inotify::ISDIR | + # Modifications I care about + Linux::Inotify::MODIFY + | + Linux::Inotify::ATTRIB + | + Linux::Inotify::CREATE + | + Linux::Inotify::DELETE + | + Linux::Inotify::DELETE_SELF + | + Linux::Inotify::MOVED_FROM + | + Linux::Inotify::MOVED_TO + ) + ); + $watches->{$directory} = undef; + } catch { + my $error = $_; + + if ($error =~ /No space left on device/) { + die <<"DIE" +We probably exceeded the maximum number of user watches since we had a +"No space left on device" error. Try something like this command and +try again: + + echo 65536 | sudo tee /proc/sys/fs/inotify/max_user_watches + +The original error was: + +$error +DIE + } else { + die $error; + } + }; + }; +} + +sub DEMOLISH { + my ($self) = @_; + my $notifier = $self->_notifier; + + $self->log("Demolishing $notifier"); + $notifier->close; +} + +1; + +__END__ + +=encoding utf8 + +=head1 NAME + +App::Inotify::Hookable - blocking command-line interface to inotify + +=head1 SYNOPSIS + +Watch a directory, tell us when things change in it: + + inotify-hookable --watch-directories /tmp/watch-this + +Watch a git tree, some configs, and a repository of static assets, +restart the webserver or compress those assets if anything changes: + + inotify-hookable \ + --watch-directories /etc/uwsgi \ + --watch-directories /git_tree/central \ + --watch-directories /etc/app-config \ + --watch-directories /git_tree/static_assets \ + --on-modify-path-command "^(/etc/uwsgi|/git_tree/central|/etc/app-config)=sudo /etc/init.d/uwsgi restart" \ + --on-modify-path-command "^/git_tree/static_assets=(cd /git_tree/static_assets && compress_static_assets)" + +=head1 DESCRIPTION + +This simple command-line program is my replacement for the +functionality offered by L's L. I +found that on very large git trees Plack would spend an inordinate +amount watching the filesystem for changes. + +This program uses L, so the kernel will notify it +B when something changes (actually it's so fast that we +have to work around how fast it sends us events). + +The result is that you can run this e.g. in a screen session and have +it watch your development environment, and your webserver will have +begun restarting before your finger leaves the I button. + +Currently the command-line interface for this is the only one that +really makes sense, this module is entirely blocking (although it +could probably run in another process via L or +something). Patches welcome. + +=head1 OPTIONS + +=head2 C<-w> or C<--watch-directories> + +Specify this to watch a directory, you can give this however many +times you like to watch lots of directories. + +=head2 C<-r> or C<--recursive> + +If you supply this any directory you give will be recursively +watched. This is on by default. + +=head2 C<-c> or C<--on-modify-command> + +A command that will be run when something is modified. + +=head2 C<-C> or C<--on-modify-path-command> + +A key-value pair where the key is a regex that'll be matched against a +modified path, and the value is a command that'll be run. See the +L for an example. + +Useful for e.g. restarting a webserver if you modify directory F +but compressing some static assets if you modify directory F. + +=head2 C<-t> or C<--buffer-time> + +Linux will send you inotify events B fast, so fast that if you +run something like: + + touch foo bar + +You might get an event for F in one batch, followed by an event +for F later on. + +To deal with this we enter a loop when we start getting events and +sleep for a default of 100 ms, as long as we keep getting events we +keep sleeping for 100 ms, but as soon as we haven't received anything +new we fire off our event handlers. + +=head2 C<-d> or C<--debug> + +Spew out some verbose debug output while running. + +=head1 ACKNOWLEDGMENT + +This module was originally developed at and for Booking.com. With +approval from Booking.com, this module was generalized and put on +CPAN, for which the authors would like to express their gratitude. + +=head1 AUTHOR + +Ævar Arnfjörð Bjarmason + +=cut -- cgit v1.2.3 From 91c92a8c33809c5be1911090b4c551621874b994 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Wed, 2 May 2012 20:55:34 +0000 Subject: v0.01 - Initial buggy version --- Changes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changes b/Changes index 5b17f34..82b0ff1 100644 --- a/Changes +++ b/Changes @@ -2,4 +2,6 @@ Revision history for App::Inotify::Hookable {{$NEXT}} +0.01 2012-05-02 20:55:31 + - Initial buggy version -- cgit v1.2.3 From 1dc5ebcbec8c660ce2c8fa55fb21a8136b32beaf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Thu, 30 Aug 2012 11:46:12 +0200 Subject: Remove watches on directories that have been deleted Instead of having watch objects lingering around delete them if their directories get nuked. --- Changes | 2 ++ lib/App/Inotify/Hookable.pm | 13 ++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/Changes b/Changes index 82b0ff1..213988a 100644 --- a/Changes +++ b/Changes @@ -2,6 +2,8 @@ Revision history for App::Inotify::Hookable {{$NEXT}} + - We'll now remove watches on directories that have been deleted + 0.01 2012-05-02 20:55:31 - Initial buggy version diff --git a/lib/App/Inotify/Hookable.pm b/lib/App/Inotify/Hookable.pm index e7f3feb..d0f6880 100755 --- a/lib/App/Inotify/Hookable.pm +++ b/lib/App/Inotify/Hookable.pm @@ -212,6 +212,16 @@ sub setup_watch { my $notifier = $self->_notifier; my $watches = $self->_watches; + my $debug = $self->debug; + + # Remove any watches for directories we're watching that have gone away + EXISTING_WATCH: for my $directory (keys %$watches) { + unless (-d $directory) { + $watches->{$directory}->remove; + delete $watches->{$directory}; + $self->log("Removed watch on directory: $directory") if $debug; + } + } # Add new watches NEW_WATCH: for my $directory ($self->all_directories_to_watch) { @@ -238,7 +248,8 @@ sub setup_watch { Linux::Inotify::MOVED_TO ) ); - $watches->{$directory} = undef; + $self->log("Now watching directory: $directory") if $debug; + $watches->{$directory} = $watch; } catch { my $error = $_; -- cgit v1.2.3 From c268421f0108e192e5f5e1361253cfcd56844ec2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Thu, 30 Aug 2012 09:47:25 +0000 Subject: v0.02 - We'll now remove watches on directories that have been deleted --- Changes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changes b/Changes index 213988a..0a3226e 100644 --- a/Changes +++ b/Changes @@ -2,6 +2,8 @@ Revision history for App::Inotify::Hookable {{$NEXT}} +0.02 2012-08-30 09:47:20 + - We'll now remove watches on directories that have been deleted 0.01 2012-05-02 20:55:31 -- cgit v1.2.3 From a54e0dce065a0ac7614d5f49a33d4bf73e09ca5b Mon Sep 17 00:00:00 2001 From: Rob Hoelz Date: Thu, 1 Nov 2012 09:24:27 +0100 Subject: Add --quiet option --- lib/App/Inotify/Hookable.pm | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/App/Inotify/Hookable.pm b/lib/App/Inotify/Hookable.pm index d0f6880..8094025 100755 --- a/lib/App/Inotify/Hookable.pm +++ b/lib/App/Inotify/Hookable.pm @@ -17,6 +17,15 @@ has debug => ( documentation => "Should we print debug info about what we're doing?", ); +has quiet => ( + metaclass => 'MooseX::Getopt::Meta::Attribute', + is => 'ro', + isa => Bool, + default => 0, + cmd_aliases => 'q', + documentation => q{Don't log noisy information}, +); + has watch_directories => ( metaclass => 'MooseX::Getopt::Meta::Attribute', is => 'ro', @@ -81,6 +90,7 @@ sub _build__notifier { Linux::Inotify->new } sub log { my ($self, $message) = @_; + return if $self->quiet; print STDERR scalar(localtime()), " : ", $message, "\n"; }; -- cgit v1.2.3 From 864ccb96f3c7183b16826b191ee1cd1dc651ad42 Mon Sep 17 00:00:00 2001 From: Rafael Kitover Date: Mon, 17 Dec 2012 17:37:38 -0500 Subject: support for watching individual files Add the --watch-files (-f) option for watching individual files, can be combined with directories. --- lib/App/Inotify/Hookable.pm | 119 +++++++++++++++++++++++++++++++------------- 1 file changed, 84 insertions(+), 35 deletions(-) diff --git a/lib/App/Inotify/Hookable.pm b/lib/App/Inotify/Hookable.pm index 8094025..1e43eac 100755 --- a/lib/App/Inotify/Hookable.pm +++ b/lib/App/Inotify/Hookable.pm @@ -36,6 +36,16 @@ has watch_directories => ( documentation => "What directories should we watch?", ); +has watch_files => ( + metaclass => 'MooseX::Getopt::Meta::Attribute', + is => 'ro', + isa => ArrayRef[Str], + default => sub { [] }, + cmd_aliases => 'f', + auto_deref => 1, + documentation => "What files should we watch?", +); + has recursive => ( metaclass => 'MooseX::Getopt::Meta::Attribute', is => 'ro', @@ -94,18 +104,24 @@ sub log { print STDERR scalar(localtime()), " : ", $message, "\n"; }; +sub watch_paths { + my $self = shift; + + return ($self->watch_directories, $self->watch_files); +} + sub run { my ($self) = @_; # Catch sigint so DEMOLISH can run local $SIG{INT} = sub { exit 1 }; - my @watching = $self->watch_directories; + my @watching = $self->watch_paths; die "You need to give me something to watch" unless @watching; $self->log( "Starting up, " . ($self->recursive ? "recursively " : "") . - "watching directories <@watching>" + "watching paths <@watching>" ); my $notifier = $self->_notifier; @@ -160,7 +176,7 @@ sub run { } } - $self->log("Had changes in your directories"); + $self->log("Had changes in your paths"); if ($should_log_modified_paths) { $self->log("Checking for path-specific hooks") if $self->debug; @@ -203,18 +219,21 @@ sub run { return 1; } -sub all_directories_to_watch { +sub all_paths_to_watch { my ($self) = @_; my @watch_directories = $self->watch_directories; + my @watch_files = $self->watch_files; my @directories; - if ($self->recursive) { - chomp(@directories = qx[find @watch_directories -type d]); - } else { - @directories = @watch_directories; + if (@watch_directories) { + if ($self->recursive) { + chomp(@directories = qx[find @watch_directories -type d]); + } else { + @directories = @watch_directories; + } } # Don't notify on "git status" (creates a lock) and other similar # operations. - grep { not m[/\.git/?] } @directories; + ((grep { not m[/\.git/?] } @directories), @watch_files); } sub setup_watch { @@ -225,41 +244,60 @@ sub setup_watch { my $debug = $self->debug; # Remove any watches for directories we're watching that have gone away - EXISTING_WATCH: for my $directory (keys %$watches) { - unless (-d $directory) { - $watches->{$directory}->remove; - delete $watches->{$directory}; - $self->log("Removed watch on directory: $directory") if $debug; + EXISTING_WATCH: for my $path (keys %$watches) { + my $type = $watches->{$path}{type}; + unless ($type eq 'directory' ? -d $path : -f $path) { + $watches->{$path}{watch}->remove; + delete $watches->{$path}; + $self->log("Removed watch on $type: $path") if $debug; } } # Add new watches - NEW_WATCH: for my $directory ($self->all_directories_to_watch) { - next NEW_WATCH if exists $watches->{$directory}; + NEW_WATCH: for my $path ($self->all_paths_to_watch) { + next NEW_WATCH if exists $watches->{$path}; try { my $watch = $notifier->add_watch( - $directory, + $path, ( - # This is a directory - Linux::Inotify::ISDIR | - # Modifications I care about - Linux::Inotify::MODIFY - | - Linux::Inotify::ATTRIB - | - Linux::Inotify::CREATE - | - Linux::Inotify::DELETE - | - Linux::Inotify::DELETE_SELF - | - Linux::Inotify::MOVED_FROM - | - Linux::Inotify::MOVED_TO + # Is this is a directory? + (-d $path ? + Linux::Inotify::ISDIR + | + # Modifications I care about + Linux::Inotify::MODIFY + | + Linux::Inotify::ATTRIB + | + Linux::Inotify::CREATE + | + Linux::Inotify::DELETE + | + Linux::Inotify::DELETE_SELF + | + Linux::Inotify::MOVED_FROM + | + Linux::Inotify::MOVED_TO + : + # modifications for files + Linux::Inotify::MODIFY + | + Linux::Inotify::ATTRIB + | + Linux::Inotify::CLOSE_WRITE + | + Linux::Inotify::DELETE_SELF + | + Linux::Inotify::MOVE + ) ) ); - $self->log("Now watching directory: $directory") if $debug; - $watches->{$directory} = $watch; + my $type = -d $path ? 'directory' : 'file'; + + $self->log("Now watching $type: $path") if $debug; + + $watches->{$path}{watch} = $watch; + $watches->{$path}{type} = $type; } catch { my $error = $_; @@ -317,6 +355,12 @@ restart the webserver or compress those assets if anything changes: --on-modify-path-command "^(/etc/uwsgi|/git_tree/central|/etc/app-config)=sudo /etc/init.d/uwsgi restart" \ --on-modify-path-command "^/git_tree/static_assets=(cd /git_tree/static_assets && compress_static_assets)" +Or watch specific files: + + inotify-hookable \ + --watch-files /var/www/cgi-bin/mod_perl_handler \ + --on-modify-command "apachectl restart" + =head1 DESCRIPTION This simple command-line program is my replacement for the @@ -344,6 +388,11 @@ something). Patches welcome. Specify this to watch a directory, you can give this however many times you like to watch lots of directories. +=head2 C<-f> or C<--watch-files> + +Watch a file, specify multiple times for multiple files. +You can watch files and directories in the same command. + =head2 C<-r> or C<--recursive> If you supply this any directory you give will be recursively -- cgit v1.2.3 From 357f80cf37482ebb7039f8ec6cce71bd17cd6d24 Mon Sep 17 00:00:00 2001 From: Rafael Kitover Date: Mon, 17 Dec 2012 22:48:45 -0500 Subject: switch from Linux::Inotify to Linux::Inotify2 Linux::Inotify hasn't been updated since 2007 and fails to install or run in some configurations, while Linux::Inotify2 is more modern and is actively maintained. This is a pretty simple conversion, changing the following: * constants are a little different and start with IN_ * ->watch sets $! instead of throwing an exception * events don't have a ->print method, we inspect the bitmask with Data::BitMask->explain_mask and print out some of the fields in debug mode --- Changes | 3 + lib/App/Inotify/Hookable.pm | 136 +++++++++++++++++++++++++++----------------- 2 files changed, 87 insertions(+), 52 deletions(-) diff --git a/Changes b/Changes index 0a3226e..f0e90fe 100644 --- a/Changes +++ b/Changes @@ -2,6 +2,9 @@ Revision history for App::Inotify::Hookable {{$NEXT}} + - Switch from Linux::Inotify to Linux::Inotify2 + - Support watching files (-f or --watch-files) + 0.02 2012-08-30 09:47:20 - We'll now remove watches on directories that have been deleted diff --git a/lib/App/Inotify/Hookable.pm b/lib/App/Inotify/Hookable.pm index 1e43eac..45aed51 100755 --- a/lib/App/Inotify/Hookable.pm +++ b/lib/App/Inotify/Hookable.pm @@ -2,9 +2,13 @@ package App::Inotify::Hookable; use Moose; use MooseX::Types::Moose ':all'; -use Linux::Inotify; +use Linux::Inotify2; +use POSIX ':errno_h'; use Time::HiRes qw(gettimeofday tv_interval ualarm); use Try::Tiny; +use Data::BitMask; +use Data::Dumper; +use Class::Inspector; with 'MooseX::Getopt::Dashes'; @@ -92,11 +96,17 @@ has _watches => ( has _notifier => ( is => 'rw', - isa => 'Linux::Inotify', + isa => 'Linux::Inotify2', lazy_build => 1, ); -sub _build__notifier { Linux::Inotify->new } +has _bitmask => ( + is => 'rw', + isa => 'Data::BitMask', + lazy_build => 1, +); + +sub _build__notifier { Linux::Inotify2->new } sub log { my ($self, $message) = @_; @@ -110,6 +120,14 @@ sub watch_paths { return ($self->watch_directories, $self->watch_files); } +my $dumper_squashed = sub { + my $val = shift; + + my $dd = Data::Dumper->new([]); + $dd->Terse(1)->Indent(1)->Useqq(1)->Deparse(1)->Quotekeys(0)->Sortkeys(1)->Indent(0); + return $dd->Values([ $val ])->Dump; +}; + sub run { my ($self) = @_; @@ -151,7 +169,11 @@ sub run { WAIT: while (1) { for my $event (@events) { - $event->print if $self->debug; + print "EVENT: ", $dumper_squashed->({ + cookie => $event->cookie, + fullname => $event->fullname, + mask => $self->_bitmask->explain_mask($event->mask), + }), "\n" if $self->debug; } $log_modified_paths->(\@events) if $should_log_modified_paths; @events = (); @@ -247,7 +269,7 @@ sub setup_watch { EXISTING_WATCH: for my $path (keys %$watches) { my $type = $watches->{$path}{type}; unless ($type eq 'directory' ? -d $path : -f $path) { - $watches->{$path}{watch}->remove; + $watches->{$path}{watch}->cancel; delete $watches->{$path}; $self->log("Removed watch on $type: $path") if $debug; } @@ -256,52 +278,46 @@ sub setup_watch { # Add new watches NEW_WATCH: for my $path ($self->all_paths_to_watch) { next NEW_WATCH if exists $watches->{$path}; - try { - my $watch = $notifier->add_watch( - $path, - ( - # Is this is a directory? - (-d $path ? - Linux::Inotify::ISDIR - | - # Modifications I care about - Linux::Inotify::MODIFY - | - Linux::Inotify::ATTRIB - | - Linux::Inotify::CREATE - | - Linux::Inotify::DELETE - | - Linux::Inotify::DELETE_SELF - | - Linux::Inotify::MOVED_FROM - | - Linux::Inotify::MOVED_TO - : - # modifications for files - Linux::Inotify::MODIFY - | - Linux::Inotify::ATTRIB - | - Linux::Inotify::CLOSE_WRITE - | - Linux::Inotify::DELETE_SELF - | - Linux::Inotify::MOVE - ) + my $watch = $notifier->watch( + $path, + ( + # Is this is a directory? + (-d $path ? + # Modifications I care about + IN_MODIFY + | + IN_ATTRIB + | + IN_CREATE + | + IN_DELETE + | + IN_DELETE_SELF + | + IN_MOVED_FROM + | + IN_MOVED_TO + | + IN_MOVE_SELF + : + # modifications for files + IN_MODIFY + | + IN_ATTRIB + | + IN_CLOSE_WRITE + | + IN_DELETE_SELF + | + IN_MOVE_SELF ) - ); - my $type = -d $path ? 'directory' : 'file'; - - $self->log("Now watching $type: $path") if $debug; + ) + ); - $watches->{$path}{watch} = $watch; - $watches->{$path}{type} = $type; - } catch { - my $error = $_; + if (not $watch) { + my $error = $!; - if ($error =~ /No space left on device/) { + if ($error == ENOSPC) { die <<"DIE" We probably exceeded the maximum number of user watches since we had a "No space left on device" error. Try something like this command and @@ -316,8 +332,25 @@ DIE } else { die $error; } - }; - }; + } + + my $type = -d $path ? 'directory' : 'file'; + + $self->log("Now watching $type: $path") if $debug; + + $watches->{$path}{watch} = $watch; + $watches->{$path}{type} = $type; + } +} + +sub _build__bitmask { + my %masks; + @masks{grep /^IN_/, @{ Class::Inspector->methods("Linux::Inotify2") }} = (); + foreach my $const (keys %masks) { + $masks{$const} = Linux::Inotify2->$const; + } + + return Data::BitMask->new(%masks); } sub DEMOLISH { @@ -325,7 +358,6 @@ sub DEMOLISH { my $notifier = $self->_notifier; $self->log("Demolishing $notifier"); - $notifier->close; } 1; @@ -368,7 +400,7 @@ functionality offered by L's L. I found that on very large git trees Plack would spend an inordinate amount watching the filesystem for changes. -This program uses L, so the kernel will notify it +This program uses L, so the kernel will notify it B when something changes (actually it's so fast that we have to work around how fast it sends us events). -- cgit v1.2.3 From b9cf711de73eecbcde6ef2d361d5720b345dad08 Mon Sep 17 00:00:00 2001 From: Rafael Kitover Date: Mon, 17 Dec 2012 23:34:49 -0500 Subject: v0.03 - Switch from Linux::Inotify to Linux::Inotify2 - Support watching files (-f or --watch-files) --- Changes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changes b/Changes index f0e90fe..6b2251a 100644 --- a/Changes +++ b/Changes @@ -2,6 +2,8 @@ Revision history for App::Inotify::Hookable {{$NEXT}} +0.03 2012-12-17 23:34:39 + - Switch from Linux::Inotify to Linux::Inotify2 - Support watching files (-f or --watch-files) -- cgit v1.2.3 From 4b556ae2ab38a2d2360391cb6cee6a374915b555 Mon Sep 17 00:00:00 2001 From: Rafael Kitover Date: Tue, 18 Dec 2012 23:11:22 -0500 Subject: fix -f, ignore vim/emacs temp files, refactor Includes the following changes: * detect files/dirs being replaced by inode number (this fixes watching files as well) * -i or --ignore-paths option for regexes to ignore, by default ignores vim and emacs temporary files * always replace the watch * use File::Find::Rule instead of shelling out to find --- Changes | 5 ++ lib/App/Inotify/Hookable.pm | 177 ++++++++++++++++++++++++++++---------------- 2 files changed, 117 insertions(+), 65 deletions(-) diff --git a/Changes b/Changes index 6b2251a..6a687cd 100644 --- a/Changes +++ b/Changes @@ -2,6 +2,11 @@ Revision history for App::Inotify::Hookable {{$NEXT}} + - fix watching files + - add -i or --ignore-paths for path regexes to ignore, by default vim and + emacs temp files are ignored + - detect files/dirs being replaced and replace the watch + 0.03 2012-12-17 23:34:39 - Switch from Linux::Inotify to Linux::Inotify2 diff --git a/lib/App/Inotify/Hookable.pm b/lib/App/Inotify/Hookable.pm index 45aed51..91cb1ac 100755 --- a/lib/App/Inotify/Hookable.pm +++ b/lib/App/Inotify/Hookable.pm @@ -9,6 +9,7 @@ use Try::Tiny; use Data::BitMask; use Data::Dumper; use Class::Inspector; +use File::Find::Rule; with 'MooseX::Getopt::Dashes'; @@ -84,7 +85,17 @@ has buffer_time => ( isa => Int, default => 100, cmd_aliases => 't', - documentation => "How many ms should we buffer inotify for?", + documentation => "How many us should we buffer inotify for? (default 100)", +); + +has ignore_paths => ( + metaclass => 'MooseX::Getopt::Meta::Attribute', + is => 'ro', + isa => ArrayRef[Str], + default => sub { [ '\..*sw.\z', '\.\#[^/]+\z' ] }, + cmd_aliases => 'i', + auto_deref => 1, + documentation => q|Regexes for which paths we should ignore (default '\..*sw.\z' and '\.\#[^/]+\z' for vim and emacs swap files.)| ); has _watches => ( @@ -106,6 +117,8 @@ has _bitmask => ( lazy_build => 1, ); +my $find = 'File::Find::Rule'; + sub _build__notifier { Linux::Inotify2->new } sub log { @@ -114,12 +127,6 @@ sub log { print STDERR scalar(localtime()), " : ", $message, "\n"; }; -sub watch_paths { - my $self = shift; - - return ($self->watch_directories, $self->watch_files); -} - my $dumper_squashed = sub { my $val = shift; @@ -134,12 +141,19 @@ sub run { # Catch sigint so DEMOLISH can run local $SIG{INT} = sub { exit 1 }; - my @watching = $self->watch_paths; - die "You need to give me something to watch" unless @watching; + my @watch_dirs = $self->watch_directories; + my @watch_files = $self->watch_files; + die "You need to give me something to watch" unless @watch_dirs || @watch_files; $self->log( "Starting up, " . - ($self->recursive ? "recursively " : "") . - "watching paths <@watching>" + (@watch_dirs ? + ($self->recursive ? "recursively " : "") . + "watching directories <@watch_dirs>" . + (@watch_files ? " and " : "") + : "") . + (@watch_files ? + "watching files <@watch_files>" + : "") ); my $notifier = $self->_notifier; @@ -156,26 +170,36 @@ sub run { my $sleep_us = $sleep_ms * 10**3; my %modified_paths; - my $should_log_modified_paths = keys %{ $self->on_modify_path_command }; + my $log_modified_paths = sub { my $events = shift; for my $event (@$events) { my $fullname = $event->fullname; $fullname =~ s[//][/]g; # We have double slashes for some reason - $modified_paths{$fullname} = undef; + + my $ignore_path = 0; + + IGNORE_PATHS: foreach my $re ($self->ignore_paths) { + if ($fullname =~ m{(?:/|^)$re}) { + $ignore_path = 1; + last IGNORE_PATHS; + } + } + + $modified_paths{$fullname} = undef if not $ignore_path; } return; }; WAIT: while (1) { for my $event (@events) { - print "EVENT: ", $dumper_squashed->({ + $self->log("EVENT: " . $dumper_squashed->({ cookie => $event->cookie, fullname => $event->fullname, mask => $self->_bitmask->explain_mask($event->mask), - }), "\n" if $self->debug; + })) if $self->debug; } - $log_modified_paths->(\@events) if $should_log_modified_paths; + $log_modified_paths->(\@events); @events = (); try { local $SIG{ALRM} = sub { @@ -191,47 +215,49 @@ sub run { if (@events) { $self->log("We have events, waiting another $sleep_ms us and checking again") if $self->debug; - $log_modified_paths->(\@events) if $should_log_modified_paths; + $log_modified_paths->(\@events); } else { # No more events last WAIT; } } - $self->log("Had changes in your paths"); - - if ($should_log_modified_paths) { - $self->log("Checking for path-specific hooks") if $self->debug; - my %hooks_to_run; - my $on_modify_path_command = $self->on_modify_path_command; - for my $path (keys %modified_paths) { - for my $path_hook (keys %$on_modify_path_command) { - $hooks_to_run{$path_hook} = 1 - if $path =~ /$path_hook/; + if (keys %modified_paths) { + $self->log("Had changes in your paths"); + + if (keys %{ $self->on_modify_path_command }) { + $self->log("Checking for path-specific hooks") if $self->debug; + my %hooks_to_run; + my $on_modify_path_command = $self->on_modify_path_command; + for my $path (keys %modified_paths) { + for my $path_hook (keys %$on_modify_path_command) { + $hooks_to_run{$path_hook} = 1 + if $path =~ /$path_hook/; + } + } + if (keys %hooks_to_run) { + $self->log("Running path-specific hooks"); + my $t0 = [gettimeofday]; + for my $hook_to_run (keys %hooks_to_run) { + my $command = $on_modify_path_command->{$hook_to_run}; + $self->log("Running path hook <$hook_to_run>: <$command>"); + system $command; + } + my $elapsed = tv_interval ( $t0 ); + $self->log(sprintf "FINISHED running path-specific hooks. Took %.2fs", $elapsed); } } - if (keys %hooks_to_run) { - $self->log("Running path-specific hooks"); + + if (my @commands = $self->on_modify_command) { + $self->log("Running global hooks"); my $t0 = [gettimeofday]; - for my $hook_to_run (keys %hooks_to_run) { - my $command = $on_modify_path_command->{$hook_to_run}; - $self->log("Running path hook <$hook_to_run>: <$command>"); + for my $command (@commands) { + $self->log("Running <$command>"); system $command; } my $elapsed = tv_interval ( $t0 ); - $self->log(sprintf "FINISHED running path-specific hooks. Took %.2fs", $elapsed); - } - } - - if (my @commands = $self->on_modify_command) { - $self->log("Running global hooks"); - my $t0 = [gettimeofday]; - for my $command (@commands) { - $self->log("Running <$command>"); - system $command; + $self->log(sprintf "FINISHED on-modify command. Took %.2fs", $elapsed); } - my $elapsed = tv_interval ( $t0 ); - $self->log(sprintf "FINISHED restarting. Took %.2fs", $elapsed); } # Re-setup the watching if needed, we may have new directories. @@ -248,14 +274,16 @@ sub all_paths_to_watch { my @directories; if (@watch_directories) { if ($self->recursive) { - chomp(@directories = qx[find @watch_directories -type d]); + @directories = $find->directory->not( + # Don't notify on "git status" (creates a lock) and other similar + # operations. + $find->new->name('.git') + )->in(@watch_directories); } else { @directories = @watch_directories; } } - # Don't notify on "git status" (creates a lock) and other similar - # operations. - ((grep { not m[/\.git/?] } @directories), @watch_files); + return (@directories, @watch_files); } sub setup_watch { @@ -265,24 +293,33 @@ sub setup_watch { my $watches = $self->_watches; my $debug = $self->debug; - # Remove any watches for directories we're watching that have gone away - EXISTING_WATCH: for my $path (keys %$watches) { - my $type = $watches->{$path}{type}; - unless ($type eq 'directory' ? -d $path : -f $path) { + # Add or re-setup watches + WATCH: for my $path ($self->all_paths_to_watch) { + my $have_watch = exists $watches->{$path}; + my $type = -d $path ? 'directory' : 'file'; + my $path_exists = -e $path; + + my $inode_number = (stat $path)[1] if $path_exists; + + # path has gone away + if ($have_watch && (not $path_exists)) { $watches->{$path}{watch}->cancel; delete $watches->{$path}; - $self->log("Removed watch on $type: $path") if $debug; + $self->log("$type '$path' has gone away, removing watch") if $debug; + next WATCH; + } + + # object got replaced, remove the watch (we'll add a new watch for the new object) + if ($have_watch && $watches->{$path}{inode} ne $inode_number) { + $watches->{$path}{watch}->cancel; + $self->log("$type '$path' was replaced, replacing watch") if $debug; } - } - # Add new watches - NEW_WATCH: for my $path ($self->all_paths_to_watch) { - next NEW_WATCH if exists $watches->{$path}; my $watch = $notifier->watch( $path, ( # Is this is a directory? - (-d $path ? + ($type eq 'directory' ? # Modifications I care about IN_MODIFY | @@ -334,12 +371,11 @@ DIE } } - my $type = -d $path ? 'directory' : 'file'; - - $self->log("Now watching $type: $path") if $debug; + $self->log("Now watching $type: $path") if !$have_watch && $debug; $watches->{$path}{watch} = $watch; $watches->{$path}{type} = $type; + $watches->{$path}{inode} = $inode_number; } } @@ -408,6 +444,9 @@ The result is that you can run this e.g. in a screen session and have it watch your development environment, and your webserver will have begun restarting before your finger leaves the I button. +vim and emacs temporary files are ignored by default (see C<--ignore-paths>.) +so you can edit your files without your server restarting unnecessarily. + Currently the command-line interface for this is the only one that really makes sense, this module is entirely blocking (although it could probably run in another process via L or @@ -453,10 +492,18 @@ run something like: You might get an event for F in one batch, followed by an event for F later on. -To deal with this we enter a loop when we start getting events and -sleep for a default of 100 ms, as long as we keep getting events we -keep sleeping for 100 ms, but as soon as we haven't received anything -new we fire off our event handlers. +To deal with this we enter a loop when we start getting events and sleep for a +default of 100 microseconds, as long as we keep getting events we keep sleeping +for 100 microseconds, but as soon as we haven't received anything new we fire +off our event handlers. + +=head2 C<-i> or C<--ignore-paths> + +Regexes for files/directories to ignore events for. By default this is set to +regexes for vim and emacs temporary files, C and +C respectively. + +The regexes match after any C in the path or the beginning of the string. =head2 C<-d> or C<--debug> -- cgit v1.2.3 From 4899f783a12d1db36ff3cea604f84c6cf379c48d Mon Sep 17 00:00:00 2001 From: Rafael Kitover Date: Tue, 18 Dec 2012 23:18:01 -0500 Subject: v0.04 - fix watching files - add -i or --ignore-paths for path regexes to ignore, by default vim and emacs temp files are ignored - detect files/dirs being replaced and replace the watch --- Changes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changes b/Changes index 6a687cd..70fdc91 100644 --- a/Changes +++ b/Changes @@ -2,6 +2,8 @@ Revision history for App::Inotify::Hookable {{$NEXT}} +0.04 2012-12-18 23:17:52 + - fix watching files - add -i or --ignore-paths for path regexes to ignore, by default vim and emacs temp files are ignored -- cgit v1.2.3 From 7e4b60a016b69f52d909790b8197e425075921ed Mon Sep 17 00:00:00 2001 From: Rafael Kitover Date: Wed, 19 Dec 2012 00:34:46 -0500 Subject: fix missing dep on File::Find::Rule --- Changes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changes b/Changes index 70fdc91..3d1511a 100644 --- a/Changes +++ b/Changes @@ -2,6 +2,8 @@ Revision history for App::Inotify::Hookable {{$NEXT}} + - fix missing dep on File::Find::Rule + 0.04 2012-12-18 23:17:52 - fix watching files -- cgit v1.2.3 From f852f44d8736229f69d5b48993dc466465af6907 Mon Sep 17 00:00:00 2001 From: Rafael Kitover Date: Wed, 19 Dec 2012 00:35:25 -0500 Subject: v0.05 - fix missing dep on File::Find::Rule --- Changes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changes b/Changes index 3d1511a..c3eb2a4 100644 --- a/Changes +++ b/Changes @@ -2,6 +2,8 @@ Revision history for App::Inotify::Hookable {{$NEXT}} +0.05 2012-12-19 00:35:16 + - fix missing dep on File::Find::Rule 0.04 2012-12-18 23:17:52 -- cgit v1.2.3 From 64f077201e94c0327ea8545d68f58c0ea0e460c4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Fri, 21 Dec 2012 15:44:10 +0100 Subject: Un-break the ignoring of .git directories The ignoring of .git directories was broken in 4b556ae, since then we've seemingly only been ignoring files (or maybe directories) called .git, not stuff with .git in the path. So e.g. "git fetch" would cause us to fire our rules, which wasn't the original intent. I don't see what the original issue was with just shelling out to find, it's probably faster, easier to test without learning two APIs, and I don't think anyone is going to try to run this on Windows any time soon given that the whole thing depends on a Linux-specific feature. --- Changes | 3 +++ lib/App/Inotify/Hookable.pm | 17 ++++++++++++++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/Changes b/Changes index c3eb2a4..552a73c 100644 --- a/Changes +++ b/Changes @@ -2,6 +2,9 @@ Revision history for App::Inotify::Hookable {{$NEXT}} + - Unbreak the ignoring of paths with ".git" in them (to not fire + on git operations) broken in 0.04. + 0.05 2012-12-19 00:35:16 - fix missing dep on File::Find::Rule diff --git a/lib/App/Inotify/Hookable.pm b/lib/App/Inotify/Hookable.pm index 91cb1ac..30f5007 100755 --- a/lib/App/Inotify/Hookable.pm +++ b/lib/App/Inotify/Hookable.pm @@ -275,9 +275,20 @@ sub all_paths_to_watch { if (@watch_directories) { if ($self->recursive) { @directories = $find->directory->not( - # Don't notify on "git status" (creates a lock) and other similar - # operations. - $find->new->name('.git') + $find->new->exec( + sub { + my ($shortname, $path, $fullname) = @_; + $fullname =~ m[ + (?: + # The .git directory + /\.git\z + | + # Something in the .git directory + /\.git/ + ) + ]x; + } + ) )->in(@watch_directories); } else { @directories = @watch_directories; -- cgit v1.2.3 From 9c23cdc026a5c8b74037b67ce7e8c02e3b63542b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Fri, 21 Dec 2012 15:55:41 +0100 Subject: Print the number of added/removed/replaced watches --- lib/App/Inotify/Hookable.pm | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/lib/App/Inotify/Hookable.pm b/lib/App/Inotify/Hookable.pm index 30f5007..86ae9fb 100755 --- a/lib/App/Inotify/Hookable.pm +++ b/lib/App/Inotify/Hookable.pm @@ -304,6 +304,10 @@ sub setup_watch { my $watches = $self->_watches; my $debug = $self->debug; + my $watches_added = 0; + my $watches_removed = 0; + my $watches_replaced = 0; + # Add or re-setup watches WATCH: for my $path ($self->all_paths_to_watch) { my $have_watch = exists $watches->{$path}; @@ -317,12 +321,14 @@ sub setup_watch { $watches->{$path}{watch}->cancel; delete $watches->{$path}; $self->log("$type '$path' has gone away, removing watch") if $debug; + $watches_removed++; next WATCH; } # object got replaced, remove the watch (we'll add a new watch for the new object) if ($have_watch && $watches->{$path}{inode} ne $inode_number) { $watches->{$path}{watch}->cancel; + $watches_replaced++; $self->log("$type '$path' was replaced, replacing watch") if $debug; } @@ -382,12 +388,15 @@ DIE } } + $watches_added++; $self->log("Now watching $type: $path") if !$have_watch && $debug; $watches->{$path}{watch} = $watch; $watches->{$path}{type} = $type; $watches->{$path}{inode} = $inode_number; } + + $self->log("Set up watches. $watches_added added, $watches_removed removed, $watches_replaced replaced."); } sub _build__bitmask { -- cgit v1.2.3 From a38041fee16de062f9f3d227aac0cde447cde96d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Fri, 21 Dec 2012 15:56:49 +0100 Subject: Line up assignments --- lib/App/Inotify/Hookable.pm | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/App/Inotify/Hookable.pm b/lib/App/Inotify/Hookable.pm index 86ae9fb..96b5a97 100755 --- a/lib/App/Inotify/Hookable.pm +++ b/lib/App/Inotify/Hookable.pm @@ -310,10 +310,9 @@ sub setup_watch { # Add or re-setup watches WATCH: for my $path ($self->all_paths_to_watch) { - my $have_watch = exists $watches->{$path}; - my $type = -d $path ? 'directory' : 'file'; - my $path_exists = -e $path; - + my $have_watch = exists $watches->{$path}; + my $type = -d $path ? 'directory' : 'file'; + my $path_exists = -e $path; my $inode_number = (stat $path)[1] if $path_exists; # path has gone away -- cgit v1.2.3 From bea0160b2898ef2bdc7d5f526ac1d3170339501d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Fri, 21 Dec 2012 15:57:13 +0100 Subject: Fix undefined behavior in assignment Any code in perl that does "my $x if $y" is has an undefined result. --- lib/App/Inotify/Hookable.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/App/Inotify/Hookable.pm b/lib/App/Inotify/Hookable.pm index 96b5a97..e25e8c1 100755 --- a/lib/App/Inotify/Hookable.pm +++ b/lib/App/Inotify/Hookable.pm @@ -313,7 +313,7 @@ sub setup_watch { my $have_watch = exists $watches->{$path}; my $type = -d $path ? 'directory' : 'file'; my $path_exists = -e $path; - my $inode_number = (stat $path)[1] if $path_exists; + my $inode_number; $inode_number = (stat $path)[1] if $path_exists; # path has gone away if ($have_watch && (not $path_exists)) { -- cgit v1.2.3 From 5b39772d6d64efb81ed6d38f440f09203b118160 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Fri, 21 Dec 2012 16:10:20 +0100 Subject: Print total number of watches and the time it took to set them up --- lib/App/Inotify/Hookable.pm | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/App/Inotify/Hookable.pm b/lib/App/Inotify/Hookable.pm index e25e8c1..d7ae4f2 100755 --- a/lib/App/Inotify/Hookable.pm +++ b/lib/App/Inotify/Hookable.pm @@ -300,6 +300,8 @@ sub all_paths_to_watch { sub setup_watch { my ($self) = @_; + my $t0 = [gettimeofday]; + my $notifier = $self->_notifier; my $watches = $self->_watches; my $debug = $self->debug; @@ -395,7 +397,10 @@ DIE $watches->{$path}{inode} = $inode_number; } - $self->log("Set up watches. $watches_added added, $watches_removed removed, $watches_replaced replaced."); + my $elapsed = tv_interval ( $t0 ); + my $total_num_watches = scalar keys %{ $notifier->{w} }; + $self->log(sprintf "FINISHED setting up watches. Took %.2fs with $watches_added watches added, $watches_removed removed, $watches_replaced replaced. Have $total_num_watches total watches", $elapsed); + return; } sub _build__bitmask { -- cgit v1.2.3 From 75017dc0579dbea94547b3a5bf79da7ca58581f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Fri, 21 Dec 2012 16:20:04 +0100 Subject: Fix feature for removing watches that never worked, now kinda works The whole feature for removing watches was predicated us finding it, but of course we're not going to find it if it's just been deleted. Hack around that by keeping around the last list of files we watched, and removing stuff from there that doesn't exist anymore. That'll also have a race condition and we'll forget to remove things if we end up doing something we don't get events for, but oh well. --- lib/App/Inotify/Hookable.pm | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/lib/App/Inotify/Hookable.pm b/lib/App/Inotify/Hookable.pm index d7ae4f2..4479463 100755 --- a/lib/App/Inotify/Hookable.pm +++ b/lib/App/Inotify/Hookable.pm @@ -10,6 +10,7 @@ use Data::BitMask; use Data::Dumper; use Class::Inspector; use File::Find::Rule; +use List::MoreUtils qw(uniq); with 'MooseX::Getopt::Dashes'; @@ -117,6 +118,11 @@ has _bitmask => ( lazy_build => 1, ); +has _previously_watched_directories => ( + is => 'rw', + isa => ArrayRef[Str], +); + my $find = 'File::Find::Rule'; sub _build__notifier { Linux::Inotify2->new } @@ -310,8 +316,20 @@ sub setup_watch { my $watches_removed = 0; my $watches_replaced = 0; + my $previously_watched_directories = $self->_previously_watched_directories; + my @previously_watched_directories = $previously_watched_directories ? @$previously_watched_directories : (); + my @current_paths_to_watch = $self->all_paths_to_watch; + + my @all_paths_to_watch = uniq( + # The stuff we're watching now + @current_paths_to_watch, + # what we were watching earlier, so we know to remove watches + # for that if they've been removed. + @previously_watched_directories, + ); + # Add or re-setup watches - WATCH: for my $path ($self->all_paths_to_watch) { + WATCH: for my $path (@all_paths_to_watch) { my $have_watch = exists $watches->{$path}; my $type = -d $path ? 'directory' : 'file'; my $path_exists = -e $path; @@ -320,6 +338,7 @@ sub setup_watch { # path has gone away if ($have_watch && (not $path_exists)) { $watches->{$path}{watch}->cancel; + my $type = $watches->{$path}{type}; # In this case we care what it *was* delete $watches->{$path}; $self->log("$type '$path' has gone away, removing watch") if $debug; $watches_removed++; @@ -329,6 +348,7 @@ sub setup_watch { # object got replaced, remove the watch (we'll add a new watch for the new object) if ($have_watch && $watches->{$path}{inode} ne $inode_number) { $watches->{$path}{watch}->cancel; + my $type = $watches->{$path}{type}; # In this case we care what it *was* $watches_replaced++; $self->log("$type '$path' was replaced, replacing watch") if $debug; } @@ -397,6 +417,10 @@ DIE $watches->{$path}{inode} = $inode_number; } + # Set this to the stuff we were just going over so we'll know to + # delete stuff from there in the future. + $self->_previously_watched_directories(\@current_paths_to_watch); + my $elapsed = tv_interval ( $t0 ); my $total_num_watches = scalar keys %{ $notifier->{w} }; $self->log(sprintf "FINISHED setting up watches. Took %.2fs with $watches_added watches added, $watches_removed removed, $watches_replaced replaced. Have $total_num_watches total watches", $elapsed); -- cgit v1.2.3 From d64d1aef8c55523929b390101b029c73c72a32be Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Fri, 21 Dec 2012 16:21:03 +0100 Subject: Use a bare type in Moose isa --- lib/App/Inotify/Hookable.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/App/Inotify/Hookable.pm b/lib/App/Inotify/Hookable.pm index 4479463..58df31d 100755 --- a/lib/App/Inotify/Hookable.pm +++ b/lib/App/Inotify/Hookable.pm @@ -101,7 +101,7 @@ has ignore_paths => ( has _watches => ( is => 'ro', - isa => 'HashRef', + isa => HashRef, default => sub { +{} }, documentation => "What stuff are we watching?", ); -- cgit v1.2.3 From 88edd19b79e9f5faf3a3aa3ac2783d82b971433d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Fri, 21 Dec 2012 16:28:02 +0100 Subject: Stop re-adding watches for paths that already have a watch Stop re-adding watches for paths that already have a watch under that inode number and path. It's completely pointless. --- lib/App/Inotify/Hookable.pm | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/lib/App/Inotify/Hookable.pm b/lib/App/Inotify/Hookable.pm index 58df31d..93092b5 100755 --- a/lib/App/Inotify/Hookable.pm +++ b/lib/App/Inotify/Hookable.pm @@ -345,12 +345,19 @@ sub setup_watch { next WATCH; } - # object got replaced, remove the watch (we'll add a new watch for the new object) - if ($have_watch && $watches->{$path}{inode} ne $inode_number) { - $watches->{$path}{watch}->cancel; - my $type = $watches->{$path}{type}; # In this case we care what it *was* - $watches_replaced++; - $self->log("$type '$path' was replaced, replacing watch") if $debug; + if ($have_watch) { + if ($watches->{$path}{inode} eq $inode_number) { + # We have this watch already, and it hasn't changed the + # inode number, so no need to go and add it again. + next WATCH; + } else { + # object got replaced, remove the watch (we'll add a + # new watch for the new object). + $watches->{$path}{watch}->cancel; + my $type = $watches->{$path}{type}; # In this case we care what it *was* + $watches_replaced++; + $self->log("$type '$path' was replaced, replacing watch") if $debug; + } } my $watch = $notifier->watch( -- cgit v1.2.3 From ed8da6ae30f129dff1b5ea54eb31572cc68a9390 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Fri, 21 Dec 2012 16:30:07 +0100 Subject: Changes: a bunch more changes since the last release. --- Changes | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/Changes b/Changes index 552a73c..c20f035 100644 --- a/Changes +++ b/Changes @@ -5,6 +5,15 @@ Revision history for App::Inotify::Hookable - Unbreak the ignoring of paths with ".git" in them (to not fire on git operations) broken in 0.04. + - Print the number of added/removed/replaced watches when things + change and show how long it took to do that. + + - Fix undefined behavior to be defined. + + - Fix feature for removing stale watches that never worked. + + - Stop re-adding watches for paths that already have a watch. + 0.05 2012-12-19 00:35:16 - fix missing dep on File::Find::Rule -- cgit v1.2.3 From 3c8e64c03afe32f0fc8c58235b9ac82d27a09664 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Fri, 21 Dec 2012 15:36:57 +0000 Subject: v0.06 - Unbreak the ignoring of paths with ".git" in them (to not fire on git operations) broken in 0.04. - Print the number of added/removed/replaced watches when things change and show how long it took to do that. - Fix undefined behavior to be defined. - Fix feature for removing stale watches that never worked. - Stop re-adding watches for paths that already have a watch. --- Changes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changes b/Changes index c20f035..7aa9e41 100644 --- a/Changes +++ b/Changes @@ -2,6 +2,8 @@ Revision history for App::Inotify::Hookable {{$NEXT}} +0.06 2012-12-21 15:36:41 + - Unbreak the ignoring of paths with ".git" in them (to not fire on git operations) broken in 0.04. -- cgit v1.2.3 From 4a7cddfb445cc1e1399ae68bedb2b626a730731a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Fri, 21 Dec 2012 16:46:23 +0100 Subject: Stop using File::Find::Rule and use find(1) again Undo the change to use File::Find::Rule added in 4b556ae. See note in 64f0772 for a suggestion of this. I tested this and the program is much faster (as expected) in the discovery phase and in adding watches, which we do all the time. I don't really see the point of not shelling out to POSIX utilities when convenient when the whole module depends on a Linux-specific feature. It's not like this is going to run on Win32 or Android. --- Changes | 4 ++++ lib/App/Inotify/Hookable.pm | 38 ++++++++++++++++++-------------------- 2 files changed, 22 insertions(+), 20 deletions(-) diff --git a/Changes b/Changes index 7aa9e41..b85c3be 100644 --- a/Changes +++ b/Changes @@ -2,6 +2,10 @@ Revision history for App::Inotify::Hookable {{$NEXT}} + - Use find(1) again instead of File::Find::Rule, it's noticably + faster and there's no point in this module being non-POSIX + postable since it relies on a Linux-specific facility. + 0.06 2012-12-21 15:36:41 - Unbreak the ignoring of paths with ".git" in them (to not fire diff --git a/lib/App/Inotify/Hookable.pm b/lib/App/Inotify/Hookable.pm index 93092b5..3331a3d 100755 --- a/lib/App/Inotify/Hookable.pm +++ b/lib/App/Inotify/Hookable.pm @@ -9,7 +9,6 @@ use Try::Tiny; use Data::BitMask; use Data::Dumper; use Class::Inspector; -use File::Find::Rule; use List::MoreUtils qw(uniq); with 'MooseX::Getopt::Dashes'; @@ -123,8 +122,6 @@ has _previously_watched_directories => ( isa => ArrayRef[Str], ); -my $find = 'File::Find::Rule'; - sub _build__notifier { Linux::Inotify2->new } sub log { @@ -280,27 +277,28 @@ sub all_paths_to_watch { my @directories; if (@watch_directories) { if ($self->recursive) { - @directories = $find->directory->not( - $find->new->exec( - sub { - my ($shortname, $path, $fullname) = @_; - $fullname =~ m[ - (?: - # The .git directory - /\.git\z - | - # Something in the .git directory - /\.git/ - ) - ]x; - } - ) - )->in(@watch_directories); + chomp(@directories = qx[find @watch_directories -type d]); } else { @directories = @watch_directories; } } - return (@directories, @watch_files); + + return ( + @watch_files, + grep { + # Don't notify on "git status" (creates a lock) and other similar + # operations. + not m[ + (?: + # The .git directory + /\.git\z + | + # Something in the .git directory + /\.git/ + ) + ]x + } @directories + ); } sub setup_watch { -- cgit v1.2.3 From 538be9e0749cb6b93f34aeeea68355ccd7e01f43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Fri, 21 Dec 2012 16:18:49 +0000 Subject: v0.07 - Use find(1) again instead of File::Find::Rule, it's noticably faster and there's no point in this module being non-POSIX postable since it relies on a Linux-specific facility. --- Changes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changes b/Changes index b85c3be..7ed8bc9 100644 --- a/Changes +++ b/Changes @@ -2,6 +2,8 @@ Revision history for App::Inotify::Hookable {{$NEXT}} +0.07 2012-12-21 16:18:36 + - Use find(1) again instead of File::Find::Rule, it's noticably faster and there's no point in this module being non-POSIX postable since it relies on a Linux-specific facility. -- cgit v1.2.3 From 97632d0460a103b776997128367d71833787e6c3 Mon Sep 17 00:00:00 2001 From: bjakubski Date: Thu, 19 Feb 2015 11:37:31 +0100 Subject: fix time units in doc and log messages --- lib/App/Inotify/Hookable.pm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/App/Inotify/Hookable.pm b/lib/App/Inotify/Hookable.pm index 3331a3d..629042a 100755 --- a/lib/App/Inotify/Hookable.pm +++ b/lib/App/Inotify/Hookable.pm @@ -85,7 +85,7 @@ has buffer_time => ( isa => Int, default => 100, cmd_aliases => 't', - documentation => "How many us should we buffer inotify for? (default 100)", + documentation => "How many milliseconds should we buffer inotify for? (default 100)", ); has ignore_paths => ( @@ -212,11 +212,11 @@ sub run { @events = $notifier->read; ualarm(0); } catch { - $self->log("We have no more events with a timeout of $sleep_ms us") if $self->debug; + $self->log("We have no more events with a timeout of $sleep_ms ms") if $self->debug; }; if (@events) { - $self->log("We have events, waiting another $sleep_ms us and checking again") if $self->debug; + $self->log("We have events, waiting another $sleep_ms ms and checking again") if $self->debug; $log_modified_paths->(\@events); } else { -- cgit v1.2.3 From 4b9ad02126bba104146ef6aa8f24407a2b53194f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Mon, 15 Feb 2016 20:05:18 +0000 Subject: Clarify that you can do --no-recursive to negate --recursive This resolves issue #4, note that we're using the standard GNU-style options where you can negate --boolean-option with --no-boolean-option if --boolean-option is on by default. While I'm at it clarify in the log output that we're starting up non-recursively instead of saying nothing about it. --- lib/App/Inotify/Hookable.pm | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/App/Inotify/Hookable.pm b/lib/App/Inotify/Hookable.pm index 629042a..b383f50 100755 --- a/lib/App/Inotify/Hookable.pm +++ b/lib/App/Inotify/Hookable.pm @@ -150,7 +150,8 @@ sub run { $self->log( "Starting up, " . (@watch_dirs ? - ($self->recursive ? "recursively " : "") . + ($self->recursive ? "recursively" : "non-recursively") . + " " . "watching directories <@watch_dirs>" . (@watch_files ? " and " : "") : "") . @@ -507,6 +508,10 @@ something). Patches welcome. =head1 OPTIONS +Note that boolean options can be negated with C<--no-OPTION>, +e.g. C<--no-r> or C<--no-recursive> to turn off the C<--recursive> +option which is on by default. + =head2 C<-w> or C<--watch-directories> Specify this to watch a directory, you can give this however many -- cgit v1.2.3 From 5ef51da85e14d373599a371106b85fe89d069412 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Mon, 15 Feb 2016 20:09:24 +0000 Subject: Add a README.pod This is purely to appease GitHub, generated with: grep -A 9999 =encoding lib/App/Inotify/Hookable.pm > README.pod --- README.pod | 124 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 124 insertions(+) create mode 100644 README.pod diff --git a/README.pod b/README.pod new file mode 100644 index 0000000..4a7f2dd --- /dev/null +++ b/README.pod @@ -0,0 +1,124 @@ +=encoding utf8 + +=head1 NAME + +App::Inotify::Hookable - blocking command-line interface to inotify + +=head1 SYNOPSIS + +Watch a directory, tell us when things change in it: + + inotify-hookable --watch-directories /tmp/watch-this + +Watch a git tree, some configs, and a repository of static assets, +restart the webserver or compress those assets if anything changes: + + inotify-hookable \ + --watch-directories /etc/uwsgi \ + --watch-directories /git_tree/central \ + --watch-directories /etc/app-config \ + --watch-directories /git_tree/static_assets \ + --on-modify-path-command "^(/etc/uwsgi|/git_tree/central|/etc/app-config)=sudo /etc/init.d/uwsgi restart" \ + --on-modify-path-command "^/git_tree/static_assets=(cd /git_tree/static_assets && compress_static_assets)" + +Or watch specific files: + + inotify-hookable \ + --watch-files /var/www/cgi-bin/mod_perl_handler \ + --on-modify-command "apachectl restart" + +=head1 DESCRIPTION + +This simple command-line program is my replacement for the +functionality offered by L's L. I +found that on very large git trees Plack would spend an inordinate +amount watching the filesystem for changes. + +This program uses L, so the kernel will notify it +B when something changes (actually it's so fast that we +have to work around how fast it sends us events). + +The result is that you can run this e.g. in a screen session and have +it watch your development environment, and your webserver will have +begun restarting before your finger leaves the I button. + +vim and emacs temporary files are ignored by default (see C<--ignore-paths>.) +so you can edit your files without your server restarting unnecessarily. + +Currently the command-line interface for this is the only one that +really makes sense, this module is entirely blocking (although it +could probably run in another process via L or +something). Patches welcome. + +=head1 OPTIONS + +Note that boolean options can be negated with C<--no-OPTION>, +e.g. C<--no-r> or C<--no-recursive> to turn off the C<--recursive> +option which is on by default. + +=head2 C<-w> or C<--watch-directories> + +Specify this to watch a directory, you can give this however many +times you like to watch lots of directories. + +=head2 C<-f> or C<--watch-files> + +Watch a file, specify multiple times for multiple files. +You can watch files and directories in the same command. + +=head2 C<-r> or C<--recursive> + +If you supply this any directory you give will be recursively +watched. This is on by default. + +=head2 C<-c> or C<--on-modify-command> + +A command that will be run when something is modified. + +=head2 C<-C> or C<--on-modify-path-command> + +A key-value pair where the key is a regex that'll be matched against a +modified path, and the value is a command that'll be run. See the +L for an example. + +Useful for e.g. restarting a webserver if you modify directory F +but compressing some static assets if you modify directory F. + +=head2 C<-t> or C<--buffer-time> + +Linux will send you inotify events B fast, so fast that if you +run something like: + + touch foo bar + +You might get an event for F in one batch, followed by an event +for F later on. + +To deal with this we enter a loop when we start getting events and sleep for a +default of 100 microseconds, as long as we keep getting events we keep sleeping +for 100 microseconds, but as soon as we haven't received anything new we fire +off our event handlers. + +=head2 C<-i> or C<--ignore-paths> + +Regexes for files/directories to ignore events for. By default this is set to +regexes for vim and emacs temporary files, C and +C respectively. + +The regexes match after any C in the path or the beginning of the string. + +=head2 C<-d> or C<--debug> + +Spew out some verbose debug output while running. + +=head1 ACKNOWLEDGMENT + +This module was originally developed at and for Booking.com. With +approval from Booking.com, this module was generalized and put on +CPAN, for which the authors would like to express their gratitude. + +=head1 AUTHOR + +Ævar Arnfjörð Bjarmason + +=cut -- cgit v1.2.3 From 6f5a294702da7beb30385c61592d2707975966ad Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Mon, 15 Feb 2016 20:17:28 +0000 Subject: Remove trailing whitespace --- lib/App/Inotify/Hookable.pm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/App/Inotify/Hookable.pm b/lib/App/Inotify/Hookable.pm index b383f50..d7faf78 100755 --- a/lib/App/Inotify/Hookable.pm +++ b/lib/App/Inotify/Hookable.pm @@ -132,7 +132,7 @@ sub log { my $dumper_squashed = sub { my $val = shift; - + my $dd = Data::Dumper->new([]); $dd->Terse(1)->Indent(1)->Useqq(1)->Deparse(1)->Quotekeys(0)->Sortkeys(1)->Indent(0); return $dd->Values([ $val ])->Dump; @@ -141,7 +141,7 @@ my $dumper_squashed = sub { sub run { my ($self) = @_; - # Catch sigint so DEMOLISH can run + # Catch sigint so DEMOLISH can run local $SIG{INT} = sub { exit 1 }; my @watch_dirs = $self->watch_directories; @@ -363,7 +363,7 @@ sub setup_watch { $path, ( # Is this is a directory? - ($type eq 'directory' ? + ($type eq 'directory' ? # Modifications I care about IN_MODIFY | -- cgit v1.2.3 From 46d640a617eec96fbf602ec1f991896cb6cb5e18 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Mon, 15 Feb 2016 20:28:53 +0000 Subject: Handle not ENOENT errors while adding watches There's a race condition here that sometimes causes us to die and exit the whole program, if a directory we're intending to watch is removed between us running the find and running this code we'll hard die. Just handle that gracefully and log it and continue. Arguably this should be handling the case of: perl -Ilib bin/inotify-hookable --no-r -w $PWD/lib -w $PWD/bin -w $PWD/t -d Where all the $PWD directories except t exist differently than the more common case of some subdirectory of lib being created and then deleted, i.e. maybe we should still die if a top-level watch goes missing. --- lib/App/Inotify/Hookable.pm | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/App/Inotify/Hookable.pm b/lib/App/Inotify/Hookable.pm index d7faf78..6781b43 100755 --- a/lib/App/Inotify/Hookable.pm +++ b/lib/App/Inotify/Hookable.pm @@ -410,6 +410,13 @@ The original error was: $error DIE + } elsif ($error == ENOENT) { + # Don't hard die on the common race condition where a + # file/directory we found with our "find" call has + # since gone away (e.g. due to a different "git + # checkout" removing it). + $self->log("Couldn't watch $type '$path': $error"); + next WATCH; } else { die $error; } -- cgit v1.2.3 From 81634c8783812e2a2e39aa72075b670f38acec00 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Mon, 15 Feb 2016 20:29:49 +0000 Subject: v0.08 --- Changes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changes b/Changes index 7ed8bc9..5df79aa 100644 --- a/Changes +++ b/Changes @@ -2,6 +2,8 @@ Revision history for App::Inotify::Hookable {{$NEXT}} +0.08 2016-02-15 20:29:38 + 0.07 2012-12-21 16:18:36 - Use find(1) again instead of File::Find::Rule, it's noticably -- cgit v1.2.3 From 63b723e7d8789cbefc500428809286ec90ae8637 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Mon, 15 Feb 2016 20:33:32 +0000 Subject: Write the changelog entry for 0.09 which I forgot to do... --- Changes | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/Changes b/Changes index 5df79aa..1928a02 100644 --- a/Changes +++ b/Changes @@ -2,6 +2,16 @@ Revision history for App::Inotify::Hookable {{$NEXT}} + - I forgot to write a changelog for 0.08, this release is + identical to it except I'm now updating the Changes file: + + - Fixed a common race condition bug where inotify-hookable would + just plain die if a directory it tried to watch had been deleted + (happens e.g. in the middle of "git checkout"). + + Now we just ignore that and move on, before the whola watcher + would just stop. + 0.08 2016-02-15 20:29:38 0.07 2012-12-21 16:18:36 -- cgit v1.2.3 From 7b91b99f8ae844eace028af0b479a48fcea907b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=C3=86var=20Arnfj=C3=B6r=C3=B0=20Bjarmason?= Date: Mon, 15 Feb 2016 20:33:55 +0000 Subject: v0.09 - I forgot to write a changelog for 0.08, this release is identical to it except I'm now updating the Changes file: - Fixed a common race condition bug where inotify-hookable would just plain die if a directory it tried to watch had been deleted (happens e.g. in the middle of "git checkout"). Now we just ignore that and move on, before the whola watcher would just stop. --- Changes | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Changes b/Changes index 1928a02..f9b0189 100644 --- a/Changes +++ b/Changes @@ -2,6 +2,8 @@ Revision history for App::Inotify::Hookable {{$NEXT}} +0.09 2016-02-15 20:33:43 + - I forgot to write a changelog for 0.08, this release is identical to it except I'm now updating the Changes file: -- cgit v1.2.3