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$time=int$time/1000000000; 26}else{ 27die"Unsupported query-fsmonitor hook version '$version'.\n". 28"Falling back to scanning...\n"; 29} 30 31# Convert unix style paths to escaped Windows style paths when running 32# in Windows command prompt 33 34my$system=`uname -s`; 35$system=~s/[\r\n]+//g; 36my$git_work_tree; 37 38if($system=~m/^MSYS_NT/) { 39$git_work_tree=`cygpath -aw "\$PWD"`; 40$git_work_tree=~s/[\r\n]+//g; 41$git_work_tree=~ s,\\,/,g; 42}else{ 43$git_work_tree=$ENV{'PWD'}; 44} 45 46my$retry=1; 47 48launch_watchman(); 49 50sub launch_watchman { 51 52# Set input record separator 53local$/=0666; 54 55my$pid= open2(\*CHLD_OUT, \*CHLD_IN,'watchman -j') 56or die"open2() failed:$!\n". 57"Falling back to scanning...\n"; 58 59# In the query expression below we're asking for names of files that 60# changed since $time but were not transient (ie created after 61# $time but no longer exist). 62# 63# To accomplish this, we're using the "since" generator to use the 64# recency index to select candidate nodes and "fields" to limit the 65# output to file names only. Then we're using the "expression" term to 66# further constrain the results. 67# 68# The category of transient files that we want to ignore will have a 69# creation clock (cclock) newer than $time_t value and will also not 70# currently exist. 71 72my$query= <<" END"; 73["query","$git_work_tree", { 74"since":$time, 75"fields": ["name"], 76"expression": ["not", ["allof", ["since",$time,"cclock"], ["not","exists"]]] 77}] 78END 79 80print CHLD_IN $query; 81my$response= <CHLD_OUT>; 82 83die"Watchman: command returned no output.\n". 84"Falling back to scanning...\n"if$responseeq""; 85die"Watchman: command returned invalid output:$response\n". 86"Falling back to scanning...\n"unless$response=~/^\{/; 87 88my$json_pkg; 89eval{ 90require JSON::XS; 91$json_pkg="JSON::XS"; 921; 93}ordo{ 94require JSON::PP; 95$json_pkg="JSON::PP"; 96}; 97 98my$o=$json_pkg->new->utf8->decode($response); 99 100if($retry>0and$o->{error}and$o->{error} =~m/unable to resolve root .* directory (.*) is not watched/) { 101print STDERR "Adding '$git_work_tree' to watchman's watch list.\n"; 102$retry--; 103 qx/watchman watch "$git_work_tree"/; 104die"Failed to make watchman watch '$git_work_tree'.\n". 105"Falling back to scanning...\n"if$?!=0; 106 107# Watchman will always return all files on the first query so 108# return the fast "everything is dirty" flag to git and do the 109# Watchman query just to get it over with now so we won't pay 110# the cost in git to look up each individual file. 111print"/\0"; 112eval{ launch_watchman() }; 113exit0; 114} 115 116die"Watchman:$o->{error}.\n". 117"Falling back to scanning...\n"if$o->{error}; 118 119binmode STDOUT,":utf8"; 120local$, ="\0"; 121print@{$o->{files}}; 122}