1#!/usr/bin/perl 2 3use strict; 4use warnings; 5use IPC::Open2; 6 7# An example hook script to integrate Watchman 8# (https://facebook.github.io/watchman/) with git to speed up detecting 9# new and modified files. 10# 11# The hook is passed a version (currently 1) and a time in nanoseconds 12# formatted as a string and outputs to stdout all files that have been 13# modified since the given time. Paths must be relative to the root of 14# the working tree and separated by a single NUL. 15# 16# To enable this hook, rename this file to "query-watchman" and set 17# 'git config core.fsmonitor .git/hooks/query-watchman' 18# 19my ($version, $time) = @ARGV; 20 21# Check the hook interface version 22 23if ($version == 1) { 24 # convert nanoseconds to seconds 25 # subtract one second to make sure watchman will return all changes 26 $time = int ($time / 1000000000) - 1; 27} else { 28 die "Unsupported query-fsmonitor hook version '$version'.\n" . 29 "Falling back to scanning...\n"; 30} 31 32my $git_work_tree; 33if ($^O =~ 'msys' || $^O =~ 'cygwin') { 34 $git_work_tree = Win32::GetCwd(); 35 $git_work_tree =~ tr/\\/\//; 36} else { 37 require Cwd; 38 $git_work_tree = Cwd::cwd(); 39} 40 41my $retry = 1; 42 43launch_watchman(); 44 45sub launch_watchman { 46 47 my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') 48 or die "open2() failed: $!\n" . 49 "Falling back to scanning...\n"; 50 51 # In the query expression below we're asking for names of files that 52 # changed since $time but were not transient (ie created after 53 # $time but no longer exist). 54 # 55 # To accomplish this, we're using the "since" generator to use the 56 # recency index to select candidate nodes and "fields" to limit the 57 # output to file names only. 58 59 my $query = <<" END"; 60 ["query", "$git_work_tree", { 61 "since": $time, 62 "fields": ["name"] 63 }] 64 END 65 66 print CHLD_IN $query; 67 close CHLD_IN; 68 my $response = do {local $/; <CHLD_OUT>}; 69 70 die "Watchman: command returned no output.\n" . 71 "Falling back to scanning...\n" if $response eq ""; 72 die "Watchman: command returned invalid output: $response\n" . 73 "Falling back to scanning...\n" unless $response =~ /^\{/; 74 75 my $json_pkg; 76 eval { 77 require JSON::XS; 78 $json_pkg = "JSON::XS"; 79 1; 80 } or do { 81 require JSON::PP; 82 $json_pkg = "JSON::PP"; 83 }; 84 85 my $o = $json_pkg->new->utf8->decode($response); 86 87 if ($retry > 0 and $o->{error} and $o->{error} =~ m/unable to resolve root .* directory (.*) is not watched/) { 88 print STDERR "Adding '$git_work_tree' to watchman's watch list.\n"; 89 $retry--; 90 qx/watchman watch "$git_work_tree"/; 91 die "Failed to make watchman watch '$git_work_tree'.\n" . 92 "Falling back to scanning...\n" if $? != 0; 93 94 # Watchman will always return all files on the first query so 95 # return the fast "everything is dirty" flag to git and do the 96 # Watchman query just to get it over with now so we won't pay 97 # the cost in git to look up each individual file. 98 print "/\0"; 99 eval { launch_watchman() }; 100 exit 0; 101 } 102 103 die "Watchman: $o->{error}.\n" . 104 "Falling back to scanning...\n" if $o->{error}; 105 106 binmode STDOUT, ":utf8"; 107 local $, = "\0"; 108 print @{$o->{files}}; 109} 110