github.com/shravanasati/hydra@v1.0.1-0.20240122045627-1082d2ed50d2/hydra/hooks/fsmonitor-watchman.sample (about) 1 #!/usr/bin/perl 2 3 use strict; 4 use warnings; 5 use 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 2) and last update token 12 # formatted as a string and outputs to stdout a new update token and 13 # all files that have been modified since the update token. Paths must 14 # be relative to the root of 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 # 19 my ($version, $last_update_token) = @ARGV; 20 21 # Uncomment for debugging 22 # print STDERR "$0 $version $last_update_token\n"; 23 24 # Check the hook interface version 25 if ($version ne 2) { 26 die "Unsupported query-fsmonitor hook version '$version'.\n" . 27 "Falling back to scanning...\n"; 28 } 29 30 my $git_work_tree = get_working_dir(); 31 32 my $retry = 1; 33 34 my $json_pkg; 35 eval { 36 require JSON::XS; 37 $json_pkg = "JSON::XS"; 38 1; 39 } or do { 40 require JSON::PP; 41 $json_pkg = "JSON::PP"; 42 }; 43 44 launch_watchman(); 45 46 sub launch_watchman { 47 my $o = watchman_query(); 48 if (is_work_tree_watched($o)) { 49 output_result($o->{clock}, @{$o->{files}}); 50 } 51 } 52 53 sub output_result { 54 my ($clockid, @files) = @_; 55 56 # Uncomment for debugging watchman output 57 # open (my $fh, ">", ".git/watchman-output.out"); 58 # binmode $fh, ":utf8"; 59 # print $fh "$clockid\n@files\n"; 60 # close $fh; 61 62 binmode STDOUT, ":utf8"; 63 print $clockid; 64 print "\0"; 65 local $, = "\0"; 66 print @files; 67 } 68 69 sub watchman_clock { 70 my $response = qx/watchman clock "$git_work_tree"/; 71 die "Failed to get clock id on '$git_work_tree'.\n" . 72 "Falling back to scanning...\n" if $? != 0; 73 74 return $json_pkg->new->utf8->decode($response); 75 } 76 77 sub watchman_query { 78 my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') 79 or die "open2() failed: $!\n" . 80 "Falling back to scanning...\n"; 81 82 # In the query expression below we're asking for names of files that 83 # changed since $last_update_token but not from the .git folder. 84 # 85 # To accomplish this, we're using the "since" generator to use the 86 # recency index to select candidate nodes and "fields" to limit the 87 # output to file names only. Then we're using the "expression" term to 88 # further constrain the results. 89 if (substr($last_update_token, 0, 1) eq "c") { 90 $last_update_token = "\"$last_update_token\""; 91 } 92 my $query = <<" END"; 93 ["query", "$git_work_tree", { 94 "since": $last_update_token, 95 "fields": ["name"], 96 "expression": ["not", ["dirname", ".git"]] 97 }] 98 END 99 100 # Uncomment for debugging the watchman query 101 # open (my $fh, ">", ".git/watchman-query.json"); 102 # print $fh $query; 103 # close $fh; 104 105 print CHLD_IN $query; 106 close CHLD_IN; 107 my $response = do {local $/; <CHLD_OUT>}; 108 109 # Uncomment for debugging the watch response 110 # open ($fh, ">", ".git/watchman-response.json"); 111 # print $fh $response; 112 # close $fh; 113 114 die "Watchman: command returned no output.\n" . 115 "Falling back to scanning...\n" if $response eq ""; 116 die "Watchman: command returned invalid output: $response\n" . 117 "Falling back to scanning...\n" unless $response =~ /^\{/; 118 119 return $json_pkg->new->utf8->decode($response); 120 } 121 122 sub is_work_tree_watched { 123 my ($output) = @_; 124 my $error = $output->{error}; 125 if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) { 126 $retry--; 127 my $response = qx/watchman watch "$git_work_tree"/; 128 die "Failed to make watchman watch '$git_work_tree'.\n" . 129 "Falling back to scanning...\n" if $? != 0; 130 $output = $json_pkg->new->utf8->decode($response); 131 $error = $output->{error}; 132 die "Watchman: $error.\n" . 133 "Falling back to scanning...\n" if $error; 134 135 # Uncomment for debugging watchman output 136 # open (my $fh, ">", ".git/watchman-output.out"); 137 # close $fh; 138 139 # Watchman will always return all files on the first query so 140 # return the fast "everything is dirty" flag to git and do the 141 # Watchman query just to get it over with now so we won't pay 142 # the cost in git to look up each individual file. 143 my $o = watchman_clock(); 144 $error = $output->{error}; 145 146 die "Watchman: $error.\n" . 147 "Falling back to scanning...\n" if $error; 148 149 output_result($o->{clock}, ("/")); 150 $last_update_token = $o->{clock}; 151 152 eval { launch_watchman() }; 153 return 0; 154 } 155 156 die "Watchman: $error.\n" . 157 "Falling back to scanning...\n" if $error; 158 159 return 1; 160 } 161 162 sub get_working_dir { 163 my $working_dir; 164 if ($^O =~ 'msys' || $^O =~ 'cygwin') { 165 $working_dir = Win32::GetCwd(); 166 $working_dir =~ tr/\\/\//; 167 } else { 168 require Cwd; 169 $working_dir = Cwd::cwd(); 170 } 171 172 return $working_dir; 173 }