fsmonitor-watchman: fix variable reference and remove redundant code

The is_work_tree_watched() function in fsmonitor-watchman.sample has two bugs: 1. Wrong variable in error check: After calling watchman_clock(), the result is stored in $o, but the code checks $output->{error} instead of $o->{error}. This means errors from the clock command are silently ignored. 2. Double output violates protocol: When the retry path triggers (the directory wasn't initially watched), output_result() is called with the "/" flag, then launch_watchman() is called recursively which calls output_result() again. This outputs two clock tokens to stdout, but git's fsmonitor v2 protocol expects exactly one response. Fix #1 by checking $o->{error} after watchman_clock(). Fix #2 by removing the recursive launch_watchman() call. The "/" "everything is dirty" flag already tells git to do a full scan, and git will call the hook again on the next invocation with a valid clock token. With the recursive call removed, the $retry guard is no longer needed since it only existed to prevent infinite recursion. Remove it. Apply the same fixes to the test helper scripts in t/t7519/. Signed-off-by: Paul Tarjan <github@paulisageek.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Paul Tarjan committed Feb 28, 2026 at 17:37 UTC 41366e46779865164e3e8af2bac6eb95ea6f6586
3 files changed +5 -21
t/t7519/fsmonitor-watchman
+1 -5
@@ -38,8 +38,6 @@ if ($^O =~ 'msys' || $^O =~ 'cygwin') {
38 $git_work_tree = Cwd::cwd();
39 }
40
41 -my $retry = 1;
42 -
41 launch_watchman();
42
43 sub launch_watchman {
@@ -92,9 +90,8 @@ sub launch_watchman {
90
91 my $o = $json_pkg->new->utf8->decode($response);
92
95 - if ($retry > 0 and $o->{error} and $o->{error} =~ m/unable to resolve root .* directory (.*) is not watched/) {
93 + if ($o->{error} and $o->{error} =~ m/unable to resolve root .* directory (.*) is not watched/) {
94 print STDERR "Adding '$git_work_tree' to watchman's watch list.\n";
97 - $retry--;
95 qx/watchman watch "$git_work_tree"/;
96 die "Failed to make watchman watch '$git_work_tree'.\n" .
97 "Falling back to scanning...\n" if $? != 0;
@@ -109,7 +106,6 @@ sub launch_watchman {
106 close $fh;
107
108 print "/\0";
112 - eval { launch_watchman() };
109 exit 0;
110 }
111
t/t7519/fsmonitor-watchman-v2
+2 -8
@@ -29,8 +29,6 @@ if ($version ne 2) {
29
30 my $git_work_tree = get_working_dir();
31
32 -my $retry = 1;
33 -
32 my $json_pkg;
33 eval {
34 require JSON::XS;
@@ -122,8 +120,7 @@ sub watchman_query {
120 sub is_work_tree_watched {
121 my ($output) = @_;
122 my $error = $output->{error};
125 - if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) {
126 - $retry--;
123 + if ($error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) {
124 my $response = qx/watchman watch "$git_work_tree"/;
125 die "Failed to make watchman watch '$git_work_tree'.\n" .
126 "Falling back to scanning...\n" if $? != 0;
@@ -141,15 +138,12 @@ sub is_work_tree_watched {
138 # Watchman query just to get it over with now so we won't pay
139 # the cost in git to look up each individual file.
140 my $o = watchman_clock();
144 - $error = $output->{error};
141 + $error = $o->{error};
142
143 die "Watchman: $error.\n" .
144 "Falling back to scanning...\n" if $error;
145
146 output_result($o->{clock}, ("/"));
150 - $last_update_token = $o->{clock};
151 -
152 - eval { launch_watchman() };
147 return 0;
148 }
149
templates/hooks/fsmonitor-watchman.sample
+2 -8
@@ -29,8 +29,6 @@ if ($version ne 2) {
29
30 my $git_work_tree = get_working_dir();
31
32 -my $retry = 1;
33 -
32 my $json_pkg;
33 eval {
34 require JSON::XS;
@@ -123,8 +121,7 @@ sub watchman_query {
121 sub is_work_tree_watched {
122 my ($output) = @_;
123 my $error = $output->{error};
126 - if ($retry > 0 and $error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) {
127 - $retry--;
124 + if ($error and $error =~ m/unable to resolve root .* directory (.*) is not watched/) {
125 my $response = qx/watchman watch "$git_work_tree"/;
126 die "Failed to make watchman watch '$git_work_tree'.\n" .
127 "Falling back to scanning...\n" if $? != 0;
@@ -142,15 +139,12 @@ sub is_work_tree_watched {
139 # Watchman query just to get it over with now so we won't pay
140 # the cost in git to look up each individual file.
141 my $o = watchman_clock();
145 - $error = $output->{error};
142 + $error = $o->{error};
143
144 die "Watchman: $error.\n" .
145 "Falling back to scanning...\n" if $error;
146
147 output_result($o->{clock}, ("/"));
151 - $last_update_token = $o->{clock};
152 -
153 - eval { launch_watchman() };
148 return 0;
149 }
150