]>
Commit | Line | Data |
---|---|---|
def43767 BP |
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 | # | |
e4e1e834 KW |
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. | |
def43767 BP |
15 | # |
16 | # To enable this hook, rename this file to "query-watchman" and set | |
17 | # 'git config core.fsmonitor .git/hooks/query-watchman' | |
18 | # | |
e4e1e834 | 19 | my ($version, $last_update_token) = @ARGV; |
def43767 | 20 | |
e4e1e834 KW |
21 | # Uncomment for debugging |
22 | # print STDERR "$0 $version $last_update_token\n"; | |
def43767 | 23 | |
e4e1e834 KW |
24 | # Check the hook interface version |
25 | if ($version ne 2) { | |
def43767 BP |
26 | die "Unsupported query-fsmonitor hook version '$version'.\n" . |
27 | "Falling back to scanning...\n"; | |
28 | } | |
29 | ||
e4e1e834 | 30 | my $git_work_tree = get_working_dir(); |
def43767 BP |
31 | |
32 | my $retry = 1; | |
33 | ||
e4e1e834 KW |
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 | ||
def43767 BP |
44 | launch_watchman(); |
45 | ||
46 | sub launch_watchman { | |
e4e1e834 KW |
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) = @_; | |
def43767 | 55 | |
e4e1e834 KW |
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 { | |
c87fbcf7 | 78 | my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') |
e4e1e834 KW |
79 | or die "open2() failed: $!\n" . |
80 | "Falling back to scanning...\n"; | |
def43767 BP |
81 | |
82 | # In the query expression below we're asking for names of files that | |
e4e1e834 | 83 | # changed since $last_update_token but not from the .git folder. |
def43767 BP |
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 | |
e4e1e834 KW |
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 | } | |
def43767 BP |
92 | my $query = <<" END"; |
93 | ["query", "$git_work_tree", { | |
e4e1e834 KW |
94 | "since": $last_update_token, |
95 | "fields": ["name"], | |
96 | "expression": ["not", ["dirname", ".git"]] | |
def43767 BP |
97 | }] |
98 | END | |
99 | ||
e4e1e834 KW |
100 | # Uncomment for debugging the watchman query |
101 | # open (my $fh, ">", ".git/watchman-query.json"); | |
102 | # print $fh $query; | |
103 | # close $fh; | |
104 | ||
def43767 | 105 | print CHLD_IN $query; |
2a387b17 AV |
106 | close CHLD_IN; |
107 | my $response = do {local $/; <CHLD_OUT>}; | |
def43767 | 108 | |
e4e1e834 KW |
109 | # Uncomment for debugging the watch response |
110 | # open ($fh, ">", ".git/watchman-response.json"); | |
111 | # print $fh $response; | |
112 | # close $fh; | |
113 | ||
def43767 | 114 | die "Watchman: command returned no output.\n" . |
e4e1e834 | 115 | "Falling back to scanning...\n" if $response eq ""; |
def43767 | 116 | die "Watchman: command returned invalid output: $response\n" . |
e4e1e834 KW |
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/) { | |
def43767 | 126 | $retry--; |
e4e1e834 | 127 | my $response = qx/watchman watch "$git_work_tree"/; |
def43767 BP |
128 | die "Failed to make watchman watch '$git_work_tree'.\n" . |
129 | "Falling back to scanning...\n" if $? != 0; | |
e4e1e834 KW |
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; | |
def43767 BP |
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. | |
e4e1e834 KW |
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 | ||
def43767 | 152 | eval { launch_watchman() }; |
e4e1e834 | 153 | return 0; |
def43767 BP |
154 | } |
155 | ||
e4e1e834 KW |
156 | die "Watchman: $error.\n" . |
157 | "Falling back to scanning...\n" if $error; | |
def43767 | 158 | |
e4e1e834 KW |
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; | |
def43767 | 173 | } |