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