From d6623cf2da1bdb49dc9019818ab7593f1fbd1f80 Mon Sep 17 00:00:00 2001 From: Todd Derr Date: Fri, 17 Jun 2022 18:25:55 -0400 Subject: [PATCH] feat: use watchman's `watch-project` if available If watchman is bersion 3.3 or later, use `watch-project` rather than the deprecated `watch`. The main advantage is eliminating redundant nested watches (i.e. where one is a subtree of another), which reduces RAM and CPU usage in the `watchman` daemon and eliminates the indexing delay when invoking Command-T after moving down the directory hierarchy. Although `watch-project` was introduced in 3.1 it is awkward/inefficient to use without the `relative_root` query which was introduced in 3.3. 3.3 was released in June 2015, so most users should have a newer version but backward compatibility with older versions is maintained. Fixes #389 Tested: * Tested the new code under various scenarios by invoking Command-T in my homedir and various subdirectories, as well as other paths (/tmp). * Manually tested the fallback path to `watch`, which is unchanged from existing code. * Tested various version numbers by manually simulating the output of `--version` * If the version number is missing or malformed, it will end up as version 0.0 * test cases: nil, "", "foo.bar", "-5.2", "1", "3.2", 3.3.449", "4", "2022.06.13.00" --- .../file_scanner/watchman_file_scanner.rb | 36 ++++++++++++++----- 1 file changed, 27 insertions(+), 9 deletions(-) diff --git a/ruby/command-t/lib/command-t/scanner/file_scanner/watchman_file_scanner.rb b/ruby/command-t/lib/command-t/scanner/file_scanner/watchman_file_scanner.rb index 60f412a7..330bc3c9 100644 --- a/ruby/command-t/lib/command-t/scanner/file_scanner/watchman_file_scanner.rb +++ b/ruby/command-t/lib/command-t/scanner/file_scanner/watchman_file_scanner.rb @@ -25,20 +25,29 @@ def paths! UNIXSocket.open(sockname) do |socket| root = Pathname.new(@path).realpath.to_s - roots = Watchman::Utils.query(['watch-list'], socket)['roots'] - if !roots.include?(root) - # this path isn't being watched yet; try to set up watch - result = Watchman::Utils.query(['watch', root], socket) + # use `watch-project` for efficiency if it's available + if use_watch_project? + result = Watchman::Utils.query(['watch-project', root], socket) + root = extract_value(result, 'watch') + relative_root = extract_value(result, 'relative_path') if result.has_key?('relative_path') + else + roots = Watchman::Utils.query(['watch-list'], socket)['roots'] + if !roots.include?(root) + # this path isn't being watched yet; try to set up watch + result = Watchman::Utils.query(['watch', root], socket) - # root_restrict_files setting may prevent Watchman from working - # or enforce_root_files/root_files (>= version 3.1) - extract_value(result) + # root_restrict_files setting may prevent Watchman from working + # or enforce_root_files/root_files (>= version 3.1) + extract_value(result) + end end - query = ['query', root, { + query_params = { 'expression' => ['type', 'f'], 'fields' => ['name'], - }] + } + query_params['relative_root'] = relative_root if relative_root; + query = ['query', root, query_params] paths = Watchman::Utils.query(query, socket) # could return error if watch is removed @@ -68,6 +77,15 @@ def get_raw_sockname end raw_sockname end + + # watch_project is available in 3.1+ but it's awkward to use without + # relative_root (3.3+), so use the latter as our minimum version. + def use_watch_project? + return @use_watch_project if defined?(@use_watch_project) + version = %x{watchman --version 2>/dev/null} + major, minor = version.split('.')[0..1] if !$?.exitstatus.nil? && $?.exitstatus.zero? && version + @use_watch_project = major.to_i > 3 || (major.to_i == 3 && minor.to_i >= 3) + end end end end