Git fork

fsmonitor: add fsmonitor hook scripts for version 2

Version 2 of the fsmonitor hooks is passed the version and an update
token and must pass back a last update token to use for subsequent calls
to the hook.

Signed-off-by: Kevin Willford <Kevin.Willford@microsoft.com>
Signed-off-by: Junio C Hamano <gitster@pobox.com>

authored by

Kevin Willford and committed by
Junio C Hamano
e4e1e834 8da2c576

+308 -50
+21
t/t7519/fsmonitor-all-v2
··· 1 + #!/usr/bin/perl 2 + 3 + use strict; 4 + use warnings; 5 + # 6 + # An test hook script to integrate with git to test fsmonitor. 7 + # 8 + # The hook is passed a version (currently 2) and since token 9 + # formatted as a string and outputs to stdout all files that have been 10 + # modified since the given time. Paths must be relative to the root of 11 + # the working tree and separated by a single NUL. 12 + # 13 + #echo "$0 $*" >&2 14 + my ($version, $last_update_token) = @ARGV; 15 + 16 + if ($version ne 2) { 17 + print "Unsupported query-fsmonitor hook version '$version'.\n"; 18 + exit 1; 19 + } 20 + 21 + print "last_update_token\0/\0"
+173
t/t7519/fsmonitor-watchman-v2
··· 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 + }
+114 -50
templates/hooks--fsmonitor-watchman.sample
··· 8 8 # (https://facebook.github.io/watchman/) with git to speed up detecting 9 9 # new and modified files. 10 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. 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 15 # 16 16 # To enable this hook, rename this file to "query-watchman" and set 17 17 # 'git config core.fsmonitor .git/hooks/query-watchman' 18 18 # 19 - my ($version, $time) = @ARGV; 19 + my ($version, $last_update_token) = @ARGV; 20 + 21 + # Uncomment for debugging 22 + # print STDERR "$0 $version $last_update_token\n"; 20 23 21 24 # Check the hook interface version 22 - 23 - if ($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 { 25 + if ($version ne 2) { 28 26 die "Unsupported query-fsmonitor hook version '$version'.\n" . 29 27 "Falling back to scanning...\n"; 30 28 } 31 29 32 - my $git_work_tree; 33 - if ($^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 - } 30 + my $git_work_tree = get_working_dir(); 40 31 41 32 my $retry = 1; 42 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 + 43 44 launch_watchman(); 44 45 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 + } 46 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 { 47 78 my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') 48 - or die "open2() failed: $!\n" . 49 - "Falling back to scanning...\n"; 79 + or die "open2() failed: $!\n" . 80 + "Falling back to scanning...\n"; 50 81 51 82 # 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). 83 + # changed since $last_update_token but not from the .git folder. 54 84 # 55 85 # To accomplish this, we're using the "since" generator to use the 56 86 # recency index to select candidate nodes and "fields" to limit the 57 - # output to file names only. 58 - 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 + } 59 92 my $query = <<" END"; 60 93 ["query", "$git_work_tree", { 61 - "since": $time, 62 - "fields": ["name"] 94 + "since": $last_update_token, 95 + "fields": ["name"], 96 + "expression": ["not", ["dirname", ".git"]] 63 97 }] 64 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; 65 104 66 105 print CHLD_IN $query; 67 106 close CHLD_IN; 68 107 my $response = do {local $/; <CHLD_OUT>}; 69 108 109 + # Uncomment for debugging the watch response 110 + # open ($fh, ">", ".git/watchman-response.json"); 111 + # print $fh $response; 112 + # close $fh; 113 + 70 114 die "Watchman: command returned no output.\n" . 71 - "Falling back to scanning...\n" if $response eq ""; 115 + "Falling back to scanning...\n" if $response eq ""; 72 116 die "Watchman: command returned invalid output: $response\n" . 73 - "Falling back to scanning...\n" unless $response =~ /^\{/; 117 + "Falling back to scanning...\n" unless $response =~ /^\{/; 74 118 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); 119 + return $json_pkg->new->utf8->decode($response); 120 + } 86 121 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"; 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/) { 89 126 $retry--; 90 - qx/watchman watch "$git_work_tree"/; 127 + my $response = qx/watchman watch "$git_work_tree"/; 91 128 die "Failed to make watchman watch '$git_work_tree'.\n" . 92 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; 93 138 94 139 # Watchman will always return all files on the first query so 95 140 # return the fast "everything is dirty" flag to git and do the 96 141 # Watchman query just to get it over with now so we won't pay 97 142 # the cost in git to look up each individual file. 98 - print "/\0"; 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 + 99 152 eval { launch_watchman() }; 100 - exit 0; 153 + return 0; 101 154 } 102 155 103 - die "Watchman: $o->{error}.\n" . 104 - "Falling back to scanning...\n" if $o->{error}; 156 + die "Watchman: $error.\n" . 157 + "Falling back to scanning...\n" if $error; 105 158 106 - binmode STDOUT, ":utf8"; 107 - local $, = "\0"; 108 - print @{$o->{files}}; 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; 109 173 }