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 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 | # |
---|
19 | my ($version, $time) = @ARGV; |
---|
20 | |
---|
21 | # 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 { |
---|
28 | die "Unsupported query-fsmonitor hook version '$version'.\n" . |
---|
29 | "Falling back to scanning...\n"; |
---|
30 | } |
---|
31 | |
---|
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 | } |
---|
40 | |
---|
41 | my $retry = 1; |
---|
42 | |
---|
43 | launch_watchman(); |
---|
44 | |
---|
45 | sub launch_watchman { |
---|
46 | |
---|
47 | my $pid = open2(\*CHLD_OUT, \*CHLD_IN, 'watchman -j --no-pretty') |
---|
48 | or die "open2() failed: $!\n" . |
---|
49 | "Falling back to scanning...\n"; |
---|
50 | |
---|
51 | # 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). |
---|
54 | # |
---|
55 | # To accomplish this, we're using the "since" generator to use the |
---|
56 | # recency index to select candidate nodes and "fields" to limit the |
---|
57 | # output to file names only. |
---|
58 | |
---|
59 | my $query = <<" END"; |
---|
60 | ["query", "$git_work_tree", { |
---|
61 | "since": $time, |
---|
62 | "fields": ["name"] |
---|
63 | }] |
---|
64 | END |
---|
65 | |
---|
66 | print CHLD_IN $query; |
---|
67 | close CHLD_IN; |
---|
68 | my $response = do {local $/; <CHLD_OUT>}; |
---|
69 | |
---|
70 | die "Watchman: command returned no output.\n" . |
---|
71 | "Falling back to scanning...\n" if $response eq ""; |
---|
72 | die "Watchman: command returned invalid output: $response\n" . |
---|
73 | "Falling back to scanning...\n" unless $response =~ /^\{/; |
---|
74 | |
---|
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); |
---|
86 | |
---|
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"; |
---|
89 | $retry--; |
---|
90 | qx/watchman watch "$git_work_tree"/; |
---|
91 | die "Failed to make watchman watch '$git_work_tree'.\n" . |
---|
92 | "Falling back to scanning...\n" if $? != 0; |
---|
93 | |
---|
94 | # Watchman will always return all files on the first query so |
---|
95 | # return the fast "everything is dirty" flag to git and do the |
---|
96 | # Watchman query just to get it over with now so we won't pay |
---|
97 | # the cost in git to look up each individual file. |
---|
98 | print "/\0"; |
---|
99 | eval { launch_watchman() }; |
---|
100 | exit 0; |
---|
101 | } |
---|
102 | |
---|
103 | die "Watchman: $o->{error}.\n" . |
---|
104 | "Falling back to scanning...\n" if $o->{error}; |
---|
105 | |
---|
106 | binmode STDOUT, ":utf8"; |
---|
107 | local $, = "\0"; |
---|
108 | print @{$o->{files}}; |
---|
109 | } |
---|