Compare commits

...

22 Commits
v2.0.0 ... main

Author SHA1 Message Date
Félix Saparelli 9f1f2e9d04
chore: Release 2024-05-16 14:20:58 +12:00
Félix Saparelli 0e393c25cf
update changelog 2024-05-16 14:20:36 +12:00
Luca Barbato 2026c52abd
feat: Add git-describe support (#832) 2024-05-15 13:02:25 +00:00
Félix Saparelli 72f069a847
chore: Release 2024-04-30 20:41:43 +12:00
Adit 4affed6fff
fix(cli): recursive paths provided by user getting treated non-recursively (#828) 2024-04-30 07:10:28 +00:00
Félix Saparelli e0084e69f8
fix ci again 2024-04-28 19:14:21 +12:00
Félix Saparelli 592b712c95
chore: Release 2024-04-28 18:55:23 +12:00
Félix Saparelli c9a3b9df00
chore: Release 2024-04-28 18:53:42 +12:00
Félix Saparelli e63d37f601
chore: Release 2024-04-28 18:52:50 +12:00
Félix Saparelli 14e6294f5a
chore: Release 2024-04-28 18:51:48 +12:00
Félix Saparelli 234d606563
chore: Release 2024-04-28 18:50:18 +12:00
Félix Saparelli 77405c8ce1
chore: Release 2024-04-28 18:48:50 +12:00
Félix Saparelli 6c23afe839
feat: make it possible to watch non-recursively (#827)
Fixes #227
Fixes #174

docs(cli): be more precise in print-events advice to use `-v`
docs(cli): improve jaq error help
feat(cli): add `-W` for non-recursive watches
feat(cli): use non-blocking logging
feat(globset): hide `fmt::Debug` spew from ignore crate
feat(ignore-files): hide `fmt::Debug` spew from ignore crate
feat(lib): make it possible to watch non-recursively
fix(lib): inserting `WatchedPath`s directly should be possible
refactor(lib): move `WatchedPath` out of `fs` mod
2024-04-28 06:33:07 +00:00
dependabot[bot] ee3795d776
Bump softprops/action-gh-release from 2.0.3 to 2.0.4 (#823)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-04-28 18:19:34 +12:00
Félix Saparelli eff96c7324
feat(project-origins): add support for out-of-tree git repos (#826) 2024-04-28 14:26:07 +12:00
Félix Saparelli a4df258735
doc: fix --on-busy-update help text (#825) 2024-04-23 14:44:59 +12:00
Félix Saparelli d388a280f0
ci: more build improvements (for next time) 2024-04-21 02:11:37 +12:00
Félix Saparelli bb97f71c8c
gha: probably the most frustrating syntax in the world 2024-04-21 02:04:56 +12:00
Félix Saparelli 953fa89dd9
even better 2024-04-21 02:02:57 +12:00
Félix Saparelli 0ef87821f2
Run manpage and completions in release when we've already built in releases 2024-04-21 01:57:09 +12:00
Félix Saparelli 62af5dd868
Fix dist manifest 2024-04-21 01:52:11 +12:00
Félix Saparelli 4497aaf515
Fix release builder 2024-04-21 01:38:11 +12:00
47 changed files with 796 additions and 402 deletions

View File

@ -4,7 +4,6 @@
app_name: "watchexec",
app_version: $version,
changelog_title: "CLI \($version)",
changelog_body: $changelog,
artifacts: [ $files | split("\n") | .[] | {
name: .,
kind: (if (. | test("[.](deb|rpm)$")) then "installer" else "executable-zip" end),

View File

@ -196,19 +196,29 @@ jobs:
with:
tool: cross
- name: Build (cargo)
if: "!matrix.cross"
run: cargo build --package watchexec-cli --release --locked --target ${{ matrix.target }}
- name: Build (cross)
if: matrix.cross
run: cross build --package watchexec-cli --release --locked --target ${{ matrix.target }}
- name: Build
shell: bash
run: |
${{ matrix.cross && 'cross' || 'cargo' }} build \
-p watchexec-cli \
--release --locked \
--target ${{ matrix.target }}
- name: Make manpage
run: cargo run -p watchexec-cli -- --manual > doc/watchexec.1
shell: bash
run: |
cargo run -p watchexec-cli \
${{ (!matrix.cross) && '--release --target' || '' }} \
${{ (!matrix.cross) && matrix.target || '' }} \
--locked -- --manual > doc/watchexec.1
- name: Make completions
run: bin/completions
shell: bash
run: |
bin/completions \
${{ (!matrix.cross) && '--release --target' || '' }} \
${{ (!matrix.cross) && matrix.target || '' }} \
--locked
- name: Package
shell: bash
@ -248,7 +258,7 @@ jobs:
- uses: actions/upload-artifact@v4
with:
name: builds
name: ${{ matrix.name }}
retention-days: 1
path: |
watchexec-*.tar.xz
@ -273,7 +283,7 @@ jobs:
- uses: actions/download-artifact@v4
with:
name: builds
merge-multiple: true
- name: Dist manifest
run: |
@ -296,7 +306,7 @@ jobs:
sha512sum $file | cut -d ' ' -f1 > "$file.sha512"
done
- uses: softprops/action-gh-release@3198ee18f814cdf787321b4a32a26ddbf37acc52
- uses: softprops/action-gh-release@9d7c94cfd0a1f3ed45544c887983e9fa900f0564
with:
tag_name: v${{ needs.info.outputs.cli_version }}
name: CLI v${{ needs.info.outputs.cli_version }}

View File

@ -3,8 +3,8 @@ message: |
If you use this software, please cite it using these metadata.
title: "Watchexec: a tool to react to filesystem changes, and a crate ecosystem to power it"
version: "2.0.0"
date-released: 2024-04-20
version: "2.1.1"
date-released: 2024-04-30
repository-code: https://github.com/watchexec/watchexec
license: Apache-2.0

64
Cargo.lock generated
View File

@ -488,7 +488,7 @@ dependencies = [
[[package]]
name = "bosion"
version = "1.0.3"
version = "1.1.0"
dependencies = [
"gix",
"time",
@ -1317,6 +1317,7 @@ dependencies = [
"gix-glob",
"gix-hash",
"gix-hashtable",
"gix-index",
"gix-lock",
"gix-macros",
"gix-object",
@ -1354,6 +1355,15 @@ dependencies = [
"winnow 0.6.6",
]
[[package]]
name = "gix-bitmap"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a371db66cbd4e13f0ed9dc4c0fea712d7276805fccc877f77e96374d317e87ae"
dependencies = [
"thiserror",
]
[[package]]
name = "gix-chunk"
version = "0.4.8"
@ -1513,6 +1523,33 @@ dependencies = [
"parking_lot",
]
[[package]]
name = "gix-index"
version = "0.32.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "881ab3b1fa57f497601a5add8289e72a7ae09471fc0b9bbe483b628ae8e418a1"
dependencies = [
"bitflags 2.5.0",
"bstr",
"filetime",
"fnv",
"gix-bitmap",
"gix-features",
"gix-fs",
"gix-hash",
"gix-lock",
"gix-object",
"gix-traverse",
"gix-utils",
"hashbrown 0.14.3",
"itoa",
"libc",
"memmap2",
"rustix",
"smallvec",
"thiserror",
]
[[package]]
name = "gix-lock"
version = "13.1.1"
@ -1988,7 +2025,7 @@ dependencies = [
[[package]]
name = "ignore-files"
version = "3.0.0"
version = "3.0.1"
dependencies = [
"dunce",
"futures",
@ -2833,7 +2870,7 @@ checksum = "744a264d26b88a6a7e37cbad97953fa233b94d585236310bcbc88474b4092d79"
[[package]]
name = "project-origins"
version = "1.3.0"
version = "1.4.0"
dependencies = [
"futures",
"miette",
@ -3685,6 +3722,18 @@ dependencies = [
"tracing-core",
]
[[package]]
name = "tracing-appender"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3566e8ce28cc0a3fe42519fc80e6b4c943cc4c8cef275620eb8dac2d3d4e06cf"
dependencies = [
"crossbeam-channel",
"thiserror",
"time",
"tracing-subscriber",
]
[[package]]
name = "tracing-attributes"
version = "0.1.27"
@ -3995,7 +4044,7 @@ checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96"
[[package]]
name = "watchexec"
version = "4.0.0"
version = "4.1.0"
dependencies = [
"async-priority-channel",
"async-recursion",
@ -4020,7 +4069,7 @@ dependencies = [
[[package]]
name = "watchexec-cli"
version = "2.0.0"
version = "2.1.1"
dependencies = [
"ahash",
"argfile",
@ -4060,6 +4109,7 @@ dependencies = [
"termcolor",
"tokio",
"tracing",
"tracing-appender",
"tracing-subscriber",
"tracing-test",
"uuid",
@ -4084,7 +4134,7 @@ dependencies = [
[[package]]
name = "watchexec-filterer-globset"
version = "4.0.0"
version = "4.0.1"
dependencies = [
"ignore",
"ignore-files",
@ -4098,7 +4148,7 @@ dependencies = [
[[package]]
name = "watchexec-filterer-ignore"
version = "4.0.0"
version = "4.0.1"
dependencies = [
"dunce",
"ignore",

View File

@ -1,7 +1,7 @@
#!/bin/sh
cargo run -p watchexec-cli -- --completions bash > completions/bash
cargo run -p watchexec-cli -- --completions elvish > completions/elvish
cargo run -p watchexec-cli -- --completions fish > completions/fish
cargo run -p watchexec-cli -- --completions nu > completions/nu
cargo run -p watchexec-cli -- --completions powershell > completions/powershell
cargo run -p watchexec-cli -- --completions zsh > completions/zsh
cargo run -p watchexec-cli $* -- --completions bash > completions/bash
cargo run -p watchexec-cli $* -- --completions elvish > completions/elvish
cargo run -p watchexec-cli $* -- --completions fish > completions/fish
cargo run -p watchexec-cli $* -- --completions nu > completions/nu
cargo run -p watchexec-cli $* -- --completions powershell > completions/powershell
cargo run -p watchexec-cli $* -- --completions zsh > completions/zsh

View File

@ -19,7 +19,7 @@ _watchexec() {
case "${cmd}" in
watchexec)
opts="-w -c -o -r -s -d -p -n -E -1 -N -q -e -f -j -i -v -h -V --watch --clear --on-busy-update --restart --signal --stop-signal --stop-timeout --map-signal --debounce --stdin-quit --no-vcs-ignore --no-project-ignore --no-global-ignore --no-default-ignore --no-discover-ignore --ignore-nothing --postpone --delay-run --poll --shell --no-environment --emit-events-to --only-emit-events --env --no-process-group --wrap-process --notify --color --timings --quiet --bell --project-origin --workdir --exts --filter --filter-file --filter-prog --ignore --ignore-file --fs-events --no-meta --print-events --verbose --log-file --manual --completions --help --version [COMMAND]..."
opts="-w -W -c -o -r -s -d -p -n -E -1 -N -q -e -f -j -i -v -h -V --watch --watch-non-recursive --clear --on-busy-update --restart --signal --stop-signal --stop-timeout --map-signal --debounce --stdin-quit --no-vcs-ignore --no-project-ignore --no-global-ignore --no-default-ignore --no-discover-ignore --ignore-nothing --postpone --delay-run --poll --shell --no-environment --emit-events-to --only-emit-events --env --no-process-group --wrap-process --notify --color --timings --quiet --bell --project-origin --workdir --exts --filter --filter-file --filter-prog --ignore --ignore-file --fs-events --no-meta --print-events --manual --completions --verbose --log-file --help --version [COMMAND]..."
if [[ ${cur} == -* || ${COMP_CWORD} -eq 1 ]] ; then
COMPREPLY=( $(compgen -W "${opts}" -- "${cur}") )
return 0
@ -33,6 +33,14 @@ _watchexec() {
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--watch-non-recursive)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
-W)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--clear)
COMPREPLY=($(compgen -W "clear reset" -- "${cur}"))
return 0
@ -189,14 +197,14 @@ _watchexec() {
COMPREPLY=($(compgen -W "access create remove rename modify metadata" -- "${cur}"))
return 0
;;
--log-file)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
--completions)
COMPREPLY=($(compgen -W "bash elvish fish nu powershell zsh" -- "${cur}"))
return 0
;;
--log-file)
COMPREPLY=($(compgen -f "${cur}"))
return 0
;;
*)
COMPREPLY=()
;;

View File

@ -20,6 +20,8 @@ set edit:completion:arg-completer[watchexec] = {|@words|
&'watchexec'= {
cand -w 'Watch a specific file or directory'
cand --watch 'Watch a specific file or directory'
cand -W 'Watch a specific directory, non-recursively'
cand --watch-non-recursive 'Watch a specific directory, non-recursively'
cand -c 'Clear screen before running command'
cand --clear 'Clear screen before running command'
cand -o 'What to do when receiving events while the command is running'
@ -52,8 +54,8 @@ set edit:completion:arg-completer[watchexec] = {|@words|
cand --ignore 'Filename patterns to filter out'
cand --ignore-file 'Files to load ignores from'
cand --fs-events 'Filesystem events to filter to'
cand --log-file 'Write diagnostic logs to a file'
cand --completions 'Generate a shell completions script'
cand --log-file 'Write diagnostic logs to a file'
cand -r 'Restart the process if it''s still running'
cand --restart 'Restart the process if it''s still running'
cand --stdin-quit 'Exit when stdin closes'
@ -78,9 +80,9 @@ set edit:completion:arg-completer[watchexec] = {|@words|
cand --bell 'Ring the terminal bell on command completion'
cand --no-meta 'Don''t emit fs events for metadata changes'
cand --print-events 'Print events that trigger actions'
cand --manual 'Show the manual page'
cand -v 'Set diagnostic log level'
cand --verbose 'Set diagnostic log level'
cand --manual 'Show the manual page'
cand -h 'Print help (see more with ''--help'')'
cand --help 'Print help (see more with ''--help'')'
cand -V 'Print version'

View File

@ -1,4 +1,5 @@
complete -c watchexec -s w -l watch -d 'Watch a specific file or directory' -r -F
complete -c watchexec -s W -l watch-non-recursive -d 'Watch a specific directory, non-recursively' -r -F
complete -c watchexec -s c -l clear -d 'Clear screen before running command' -r -f -a "{clear '',reset ''}"
complete -c watchexec -s o -l on-busy-update -d 'What to do when receiving events while the command is running' -r -f -a "{queue '',do-nothing '',restart '',signal ''}"
complete -c watchexec -s s -l signal -d 'Send a signal to the process when it\'s still running' -r
@ -22,8 +23,8 @@ complete -c watchexec -s j -l filter-prog -d '[experimental] Filter programs' -r
complete -c watchexec -s i -l ignore -d 'Filename patterns to filter out' -r
complete -c watchexec -l ignore-file -d 'Files to load ignores from' -r -F
complete -c watchexec -l fs-events -d 'Filesystem events to filter to' -r -f -a "{access '',create '',remove '',rename '',modify '',metadata ''}"
complete -c watchexec -l log-file -d 'Write diagnostic logs to a file' -r -F
complete -c watchexec -l completions -d 'Generate a shell completions script' -r -f -a "{bash '',elvish '',fish '',nu '',powershell '',zsh ''}"
complete -c watchexec -l log-file -d 'Write diagnostic logs to a file' -r -F
complete -c watchexec -s r -l restart -d 'Restart the process if it\'s still running'
complete -c watchexec -l stdin-quit -d 'Exit when stdin closes'
complete -c watchexec -l no-vcs-ignore -d 'Don\'t load gitignores'
@ -44,7 +45,7 @@ complete -c watchexec -s q -l quiet -d 'Don\'t print starting and stopping messa
complete -c watchexec -l bell -d 'Ring the terminal bell on command completion'
complete -c watchexec -l no-meta -d 'Don\'t emit fs events for metadata changes'
complete -c watchexec -l print-events -d 'Print events that trigger actions'
complete -c watchexec -s v -l verbose -d 'Set diagnostic log level'
complete -c watchexec -l manual -d 'Show the manual page'
complete -c watchexec -s v -l verbose -d 'Set diagnostic log level'
complete -c watchexec -s h -l help -d 'Print help (see more with \'--help\')'
complete -c watchexec -s V -l version -d 'Print version'

View File

@ -32,6 +32,7 @@ module completions {
export extern watchexec [
...command: string # Command to run on changes
--watch(-w): string # Watch a specific file or directory
--watch-non-recursive(-W): string # Watch a specific directory, non-recursively
--clear(-c): string@"nu-complete watchexec screen_clear" # Clear screen before running command
--on-busy-update(-o): string@"nu-complete watchexec on_busy_update" # What to do when receiving events while the command is running
--restart(-r) # Restart the process if it's still running
@ -75,10 +76,10 @@ module completions {
--fs-events: string@"nu-complete watchexec filter_fs_events" # Filesystem events to filter to
--no-meta # Don't emit fs events for metadata changes
--print-events # Print events that trigger actions
--verbose(-v) # Set diagnostic log level
--log-file: string # Write diagnostic logs to a file
--manual # Show the manual page
--completions: string@"nu-complete watchexec completions" # Generate a shell completions script
--verbose(-v) # Set diagnostic log level
--log-file: string # Write diagnostic logs to a file
--help(-h) # Print help (see more with '--help')
--version(-V) # Print version
]

View File

@ -23,6 +23,8 @@ Register-ArgumentCompleter -Native -CommandName 'watchexec' -ScriptBlock {
'watchexec' {
[CompletionResult]::new('-w', 'w', [CompletionResultType]::ParameterName, 'Watch a specific file or directory')
[CompletionResult]::new('--watch', 'watch', [CompletionResultType]::ParameterName, 'Watch a specific file or directory')
[CompletionResult]::new('-W', 'W ', [CompletionResultType]::ParameterName, 'Watch a specific directory, non-recursively')
[CompletionResult]::new('--watch-non-recursive', 'watch-non-recursive', [CompletionResultType]::ParameterName, 'Watch a specific directory, non-recursively')
[CompletionResult]::new('-c', 'c', [CompletionResultType]::ParameterName, 'Clear screen before running command')
[CompletionResult]::new('--clear', 'clear', [CompletionResultType]::ParameterName, 'Clear screen before running command')
[CompletionResult]::new('-o', 'o', [CompletionResultType]::ParameterName, 'What to do when receiving events while the command is running')
@ -55,8 +57,8 @@ Register-ArgumentCompleter -Native -CommandName 'watchexec' -ScriptBlock {
[CompletionResult]::new('--ignore', 'ignore', [CompletionResultType]::ParameterName, 'Filename patterns to filter out')
[CompletionResult]::new('--ignore-file', 'ignore-file', [CompletionResultType]::ParameterName, 'Files to load ignores from')
[CompletionResult]::new('--fs-events', 'fs-events', [CompletionResultType]::ParameterName, 'Filesystem events to filter to')
[CompletionResult]::new('--log-file', 'log-file', [CompletionResultType]::ParameterName, 'Write diagnostic logs to a file')
[CompletionResult]::new('--completions', 'completions', [CompletionResultType]::ParameterName, 'Generate a shell completions script')
[CompletionResult]::new('--log-file', 'log-file', [CompletionResultType]::ParameterName, 'Write diagnostic logs to a file')
[CompletionResult]::new('-r', 'r', [CompletionResultType]::ParameterName, 'Restart the process if it''s still running')
[CompletionResult]::new('--restart', 'restart', [CompletionResultType]::ParameterName, 'Restart the process if it''s still running')
[CompletionResult]::new('--stdin-quit', 'stdin-quit', [CompletionResultType]::ParameterName, 'Exit when stdin closes')
@ -81,9 +83,9 @@ Register-ArgumentCompleter -Native -CommandName 'watchexec' -ScriptBlock {
[CompletionResult]::new('--bell', 'bell', [CompletionResultType]::ParameterName, 'Ring the terminal bell on command completion')
[CompletionResult]::new('--no-meta', 'no-meta', [CompletionResultType]::ParameterName, 'Don''t emit fs events for metadata changes')
[CompletionResult]::new('--print-events', 'print-events', [CompletionResultType]::ParameterName, 'Print events that trigger actions')
[CompletionResult]::new('--manual', 'manual', [CompletionResultType]::ParameterName, 'Show the manual page')
[CompletionResult]::new('-v', 'v', [CompletionResultType]::ParameterName, 'Set diagnostic log level')
[CompletionResult]::new('--verbose', 'verbose', [CompletionResultType]::ParameterName, 'Set diagnostic log level')
[CompletionResult]::new('--manual', 'manual', [CompletionResultType]::ParameterName, 'Show the manual page')
[CompletionResult]::new('-h', 'h', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')')
[CompletionResult]::new('--help', 'help', [CompletionResultType]::ParameterName, 'Print help (see more with ''--help'')')
[CompletionResult]::new('-V', 'V ', [CompletionResultType]::ParameterName, 'Print version')

View File

@ -17,6 +17,8 @@ _watchexec() {
_arguments "${_arguments_options[@]}" \
'*-w+[Watch a specific file or directory]:PATH:_files' \
'*--watch=[Watch a specific file or directory]:PATH:_files' \
'*-W+[Watch a specific directory, non-recursively]:PATH:_files' \
'*--watch-non-recursive=[Watch a specific directory, non-recursively]:PATH:_files' \
'-c+[Clear screen before running command]' \
'--clear=[Clear screen before running command]' \
'-o+[What to do when receiving events while the command is running]:MODE:(queue do-nothing restart signal)' \
@ -49,8 +51,8 @@ _watchexec() {
'*--ignore=[Filename patterns to filter out]:PATTERN: ' \
'*--ignore-file=[Files to load ignores from]:PATH:_files' \
'*--fs-events=[Filesystem events to filter to]:EVENTS:(access create remove rename modify metadata)' \
'--log-file=[Write diagnostic logs to a file]' \
'(--manual)--completions=[Generate a shell completions script]:COMPLETIONS:(bash elvish fish nu powershell zsh)' \
'--log-file=[Write diagnostic logs to a file]' \
'(-o --on-busy-update)-r[Restart the process if it'\''s still running]' \
'(-o --on-busy-update)--restart[Restart the process if it'\''s still running]' \
'--stdin-quit[Exit when stdin closes]' \
@ -75,9 +77,9 @@ _watchexec() {
'--bell[Ring the terminal bell on command completion]' \
'(--fs-events)--no-meta[Don'\''t emit fs events for metadata changes]' \
'--print-events[Print events that trigger actions]' \
'(--completions)--manual[Show the manual page]' \
'*-v[Set diagnostic log level]' \
'*--verbose[Set diagnostic log level]' \
'(--completions)--manual[Show the manual page]' \
'-h[Print help (see more with '\''--help'\'')]' \
'--help[Print help (see more with '\''--help'\'')]' \
'-V[Print version]' \

View File

@ -2,6 +2,10 @@
## Next (YYYY-MM-DD)
## v1.1.0 (2024-05-16)
- Add `git-describe` support (#832, by @lu-zero)
## v1.0.3 (2024-04-20)
- Deps: gix 0.62

View File

@ -1,6 +1,6 @@
[package]
name = "bosion"
version = "1.0.3"
version = "1.1.0"
authors = ["Félix Saparelli <felix@passcod.name>"]
license = "Apache-2.0 OR MIT"
@ -22,6 +22,7 @@ features = ["macros", "formatting"]
version = "0.62.0"
optional = true
default-features = false
features = ["revision"]
[features]
default = ["git", "reproducible", "std"]

View File

@ -15,7 +15,7 @@ In your `Cargo.toml`:
```toml
[build-dependencies]
bosion = "1.0.3"
bosion = "1.1.0"
```
In your `build.rs`:

View File

@ -8,6 +8,24 @@ version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "ahash"
version = "0.8.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e89da841a80418a9b391ebaea17f5c112ffaaa96f621d2c285b5174da76b9011"
dependencies = [
"cfg-if",
"once_cell",
"version_check",
"zerocopy",
]
[[package]]
name = "allocator-api2"
version = "0.2.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c6cb57a04249c6480766f7f7cef5467412af1490f8d1e243141daddada3264f"
[[package]]
name = "anstream"
version = "0.6.13"
@ -82,7 +100,7 @@ checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1"
[[package]]
name = "bosion"
version = "1.0.2"
version = "1.0.3"
dependencies = [
"gix",
"time",
@ -211,6 +229,18 @@ version = "2.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984"
[[package]]
name = "filetime"
version = "0.2.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd"
dependencies = [
"cfg-if",
"libc",
"redox_syscall",
"windows-sys",
]
[[package]]
name = "flate2"
version = "1.0.28"
@ -221,6 +251,12 @@ dependencies = [
"miniz_oxide",
]
[[package]]
name = "fnv"
version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "form_urlencoded"
version = "1.2.1"
@ -247,6 +283,7 @@ dependencies = [
"gix-glob",
"gix-hash",
"gix-hashtable",
"gix-index",
"gix-lock",
"gix-macros",
"gix-object",
@ -284,6 +321,15 @@ dependencies = [
"winnow",
]
[[package]]
name = "gix-bitmap"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a371db66cbd4e13f0ed9dc4c0fea712d7276805fccc877f77e96374d317e87ae"
dependencies = [
"thiserror",
]
[[package]]
name = "gix-chunk"
version = "0.4.8"
@ -443,6 +489,33 @@ dependencies = [
"parking_lot",
]
[[package]]
name = "gix-index"
version = "0.32.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "881ab3b1fa57f497601a5add8289e72a7ae09471fc0b9bbe483b628ae8e418a1"
dependencies = [
"bitflags 2.5.0",
"bstr",
"filetime",
"fnv",
"gix-bitmap",
"gix-features",
"gix-fs",
"gix-hash",
"gix-lock",
"gix-object",
"gix-traverse",
"gix-utils",
"hashbrown",
"itoa",
"libc",
"memmap2",
"rustix",
"smallvec",
"thiserror",
]
[[package]]
name = "gix-lock"
version = "13.1.1"
@ -702,6 +775,10 @@ name = "hashbrown"
version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
dependencies = [
"ahash",
"allocator-api2",
]
[[package]]
name = "heck"
@ -1076,6 +1153,12 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
version = "2.5.0"
@ -1255,3 +1338,23 @@ checksum = "f0c976aaaa0e1f90dbb21e9587cdaf1d9679a1cde8875c0d6bd83ab96a208352"
dependencies = [
"memchr",
]
[[package]]
name = "zerocopy"
version = "0.7.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae87e3fcd617500e5d106f0380cf7b77f3c6092aae37191433159dda23cfb087"
dependencies = [
"zerocopy-derive",
]
[[package]]
name = "zerocopy-derive"
version = "0.7.34"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "15e934569e47891f7d9411f1a451d947a60e000ab3bd24fbb970f000387d1b3b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]

View File

@ -13,6 +13,9 @@ struct Args {
#[clap(long)]
dates: bool,
#[clap(long)]
describe: bool,
}
fn main() {
@ -23,17 +26,15 @@ fn main() {
"{}",
Bosion::long_version_with(&[("extra", "field"), ("custom", "1.2.3"),])
);
} else
if args.features {
} else if args.features {
println!("Features: {}", Bosion::CRATE_FEATURE_STRING);
} else
if args.dates {
} else if args.dates {
println!("commit date: {}", Bosion::GIT_COMMIT_DATE);
println!("commit datetime: {}", Bosion::GIT_COMMIT_DATETIME);
println!("build date: {}", Bosion::BUILD_DATE);
println!("build datetime: {}", Bosion::BUILD_DATETIME);
} else if args.describe {
println!("commit description: {}", Bosion::GIT_COMMIT_DESCRIPTION);
} else {
println!("{}", Bosion::LONG_VERSION);
}

View File

@ -145,6 +145,9 @@ pub struct GitInfo {
/// The datetime of the current commit, in the format `YYYY-MM-DD HH:MM:SS`, at UTC.
pub git_datetime: String,
/// The `git describe` equivalent output
pub git_description: String,
}
#[cfg(feature = "git")]
@ -163,6 +166,7 @@ impl GitInfo {
git_shorthash: head.short_id().err_string()?.to_string(),
git_date: timestamp.format(DATE_FORMAT).err_string()?,
git_datetime: timestamp.format(DATETIME_FORMAT).err_string()?,
git_description: head.describe().format().err_string()?.to_string(),
})
}
}

View File

@ -74,6 +74,7 @@ pub fn gather_to(filename: &str, structname: &str, public: bool) {
git_shorthash,
git_date,
git_datetime,
git_description,
..
}) = git
{
@ -104,6 +105,11 @@ pub fn gather_to(filename: &str, structname: &str, public: bool) {
/// This is the date and time (`YYYY-MM-DD HH:MM:SS`) of the commit that was built. Same
/// caveats as with `GIT_COMMIT_HASH` apply.
pub const GIT_COMMIT_DATETIME: &'static str = {git_datetime:?};
/// The git description
///
/// This is the string equivalent to what `git describe` would output
pub const GIT_COMMIT_DESCRIPTION: &'static str = {git_description:?};
"
), format!("{crate_version} ({git_shorthash} {git_date}) {crate_feature_string}\ncommit-hash: {git_hash}\ncommit-date: {git_date}\nbuild-date: {build_date}\nrelease: {crate_version}\nfeatures: {crate_feature_list}"))
} else {
@ -244,6 +250,7 @@ pub fn gather_to_env_with_prefix(prefix: &str) {
git_shorthash,
git_date,
git_datetime,
git_description,
..
}) = git
{
@ -251,5 +258,6 @@ pub fn gather_to_env_with_prefix(prefix: &str) {
println!("cargo:rustc-env={prefix}GIT_COMMIT_SHORTHASH={git_shorthash}");
println!("cargo:rustc-env={prefix}GIT_COMMIT_DATE={git_date}");
println!("cargo:rustc-env={prefix}GIT_COMMIT_DATETIME={git_datetime}");
println!("cargo:rustc-env={prefix}GIT_COMMIT_DESCRIPTION={git_description}");
}
}

View File

@ -1,6 +1,6 @@
[package]
name = "watchexec-cli"
version = "2.0.0"
version = "2.1.1"
authors = ["Félix Saparelli <felix@passcod.name>", "Matt Green <mattgreenrocks@gmail.com>"]
license = "Apache-2.0"
@ -44,6 +44,7 @@ serde_json = "1.0.107"
tempfile = "3.8.1"
termcolor = "1.4.0"
tracing = "0.1.40"
tracing-appender = "0.2.3"
which = "6.0.1"
[dependencies.blake3]
@ -68,7 +69,7 @@ features = ["log", "env_logger"]
optional = true
[dependencies.ignore-files]
version = "3.0.0"
version = "3.0.1"
path = "../ignore-files"
[dependencies.miette]
@ -80,11 +81,11 @@ version = "0.1.1"
optional = true
[dependencies.project-origins]
version = "1.3.0"
version = "1.4.0"
path = "../project-origins"
[dependencies.watchexec]
version = "4.0.0"
version = "4.1.0"
path = "../lib"
[dependencies.watchexec-events]
@ -97,7 +98,7 @@ version = "3.0.0"
path = "../signals"
[dependencies.watchexec-filterer-globset]
version = "4.0.0"
version = "4.0.1"
path = "../filterer/globset"
[dependencies.tokio]
@ -129,7 +130,7 @@ mimalloc = "0.1.39"
embed-resource = "2.4.0"
[build-dependencies.bosion]
version = "1.0.3"
version = "1.1.0"
path = "../bosion"
[dev-dependencies]

View File

@ -2,6 +2,8 @@ pre-release-commit-message = "release: cli v{{version}}"
tag-prefix = ""
tag-message = "watchexec {{version}}"
pre-release-hook = ["sh", "-c", "cd ../.. && bin/completions && bin/manpage"]
[[pre-release-replacements]]
file = "watchexec.exe.manifest"
search = "^ version=\"[\\d.]+[.]0\""

View File

@ -1,21 +1,28 @@
use std::{
collections::BTreeSet,
ffi::{OsStr, OsString},
path::PathBuf,
fs::canonicalize,
mem::take,
path::{Path, PathBuf},
str::FromStr,
time::Duration,
};
use clap::{
builder::TypedValueParser, error::ErrorKind, Arg, ArgAction, Command, CommandFactory, Parser,
ValueEnum, ValueHint,
builder::TypedValueParser, error::ErrorKind, Arg, Command, CommandFactory, Parser, ValueEnum,
ValueHint,
};
use miette::{IntoDiagnostic, Result};
use tokio::{fs::File, io::AsyncReadExt};
use watchexec::paths::PATH_SEPARATOR;
use tracing::{debug, info, trace, warn};
use tracing_appender::non_blocking::WorkerGuard;
use watchexec::{paths::PATH_SEPARATOR, sources::fs::WatchedPath};
use watchexec_signals::Signal;
use crate::filterer::parse::parse_filter_program;
mod logging;
const OPTSET_FILTERING: &str = "Filtering";
const OPTSET_COMMAND: &str = "Command";
const OPTSET_DEBUGGING: &str = "Debugging";
@ -128,7 +135,25 @@ pub struct Args {
value_hint = ValueHint::AnyPath,
value_name = "PATH",
)]
pub paths: Vec<PathBuf>,
pub recursive_paths: Vec<PathBuf>,
/// Watch a specific directory, non-recursively
///
/// Unlike '-w', folders watched with this option are not recursed into.
///
/// This option can be specified multiple times to watch multiple directories non-recursively.
#[arg(
short = 'W',
long = "watch-non-recursive",
help_heading = OPTSET_FILTERING,
value_hint = ValueHint::AnyPath,
value_name = "PATH",
)]
pub non_recursive_paths: Vec<PathBuf>,
#[doc(hidden)]
#[arg(skip)]
pub paths: Vec<WatchedPath>,
/// Clear screen before running command
///
@ -145,17 +170,14 @@ pub struct Args {
/// What to do when receiving events while the command is running
///
/// Default is to 'queue' up events and run the command once again when the previous run has
/// finished. You can also use 'do-nothing', which ignores events while the command is running
/// and may be useful to avoid spurious changes made by that command, or 'restart', which
/// terminates the running command and starts a new one. Finally, there's 'signal', which only
/// sends a signal; this can be useful with programs that can reload their configuration without
/// a full restart.
/// Default is to 'do-nothing', which ignores events while the command is running, so that
/// changes that occur due to the command are ignored, like compilation outputs. You can also
/// use 'queue' which will run the command once again when the current run has finished if any
/// events occur while it's running, or 'restart', which terminates the running command and starts
/// a new one. Finally, there's 'signal', which only sends a signal; this can be useful with
/// programs that can reload their configuration without a full restart.
///
/// The signal can be specified with the '--signal' option.
///
/// Note that this option is scheduled to change its default to 'do-nothing' in the next major
/// release. File an issue if you have any concerns.
#[arg(
short,
long,
@ -794,6 +816,7 @@ pub struct Args {
///
/// Provide your own custom filter programs in jaq (similar to jq) syntax. Programs are given
/// an event in the same format as described in '--emit-events-to' and must return a boolean.
/// Invalid programs will make watchexec fail to start; use '-v' to see program runtime errors.
///
/// In addition to the jaq stdlib, watchexec adds some custom filter definitions:
///
@ -924,54 +947,13 @@ pub struct Args {
/// This prints the events that triggered the action when handling it (after debouncing), in a
/// human readable form. This is useful for debugging filters.
///
/// Use '-v' when you need more diagnostic information.
/// Use '-vvv' instead when you need more diagnostic information.
#[arg(
long,
help_heading = OPTSET_DEBUGGING,
)]
pub print_events: bool,
/// Set diagnostic log level
///
/// This enables diagnostic logging, which is useful for investigating bugs or gaining more
/// insight into faulty filters or "missing" events. Use multiple times to increase verbosity.
///
/// Goes up to '-vvvv'. When submitting bug reports, default to a '-vvv' log level.
///
/// You may want to use with '--log-file' to avoid polluting your terminal.
///
/// Setting $RUST_LOG also works, and takes precedence, but is not recommended. However, using
/// $RUST_LOG is the only way to get logs from before these options are parsed.
#[arg(
long,
short,
help_heading = OPTSET_DEBUGGING,
action = ArgAction::Count,
num_args = 0,
)]
pub verbose: Option<u8>,
/// Write diagnostic logs to a file
///
/// This writes diagnostic logs to a file, instead of the terminal, in JSON format. If a log
/// level was not already specified, this will set it to '-vvv'.
///
/// If a path is not provided, the default is the working directory. Note that with
/// '--ignore-nothing', the write events to the log will likely get picked up by Watchexec,
/// causing a loop; prefer setting a path outside of the watched directory.
///
/// If the path provided is a directory, a file will be created in that directory. The file name
/// will be the current date and time, in the format 'watchexec.YYYY-MM-DDTHH-MM-SSZ.log'.
#[arg(
long,
help_heading = OPTSET_DEBUGGING,
num_args = 0..=1,
default_missing_value = ".",
value_hint = ValueHint::AnyPath,
value_name = "PATH",
)]
pub log_file: Option<PathBuf>,
/// Show the manual page
///
/// This shows the manual page for Watchexec, if the output is a terminal and the 'man' program
@ -996,6 +978,9 @@ pub struct Args {
conflicts_with_all = ["command", "manual"],
)]
pub completions: Option<ShellCompletion>,
#[command(flatten)]
pub logging: logging::LoggingArgs,
}
#[derive(Clone, Copy, Debug, Default, ValueEnum)]
@ -1162,11 +1147,10 @@ fn expand_args_up_to_doubledash() -> Result<Vec<OsString>, std::io::Error> {
}
#[inline]
pub async fn get_args() -> Result<Args> {
use tracing::{debug, trace, warn};
if std::env::var("RUST_LOG").is_ok() {
warn!("⚠ RUST_LOG environment variable set, logging options have no effect");
pub async fn get_args() -> Result<(Args, Option<WorkerGuard>)> {
let prearg_logs = logging::preargs();
if prearg_logs {
warn!("⚠ RUST_LOG environment variable set or hardcoded, logging options have no effect");
}
debug!("expanding @argfile arguments if any");
@ -1175,6 +1159,12 @@ pub async fn get_args() -> Result<Args> {
debug!("parsing arguments");
let mut args = Args::parse_from(args);
let log_guard = if !prearg_logs {
logging::postargs(&args.logging).await?
} else {
None
};
// https://no-color.org/
if args.color == ColourMode::Auto && std::env::var("NO_COLOR").is_ok() {
args.color = ColourMode::Never;
@ -1195,10 +1185,12 @@ pub async fn get_args() -> Result<Args> {
}
if args.no_environment {
warn!("--no-environment is deprecated");
args.emit_events_to = EmitEvents::None;
}
if args.no_process_group {
warn!("--no-process-group is deprecated");
args.wrap_process = WrapMode::None;
}
@ -1224,6 +1216,63 @@ pub async fn get_args() -> Result<Args> {
.exit();
}
let workdir = if let Some(w) = take(&mut args.workdir) {
w
} else {
let curdir = std::env::current_dir().into_diagnostic()?;
canonicalize(curdir).into_diagnostic()?
};
info!(path=?workdir, "effective working directory");
args.workdir = Some(workdir.clone());
let project_origin = if let Some(p) = take(&mut args.project_origin) {
p
} else {
crate::dirs::project_origin(&args).await?
};
info!(path=?project_origin, "effective project origin");
args.project_origin = Some(project_origin.clone());
args.paths = take(&mut args.recursive_paths)
.into_iter()
.map(|path| {
{
if path.is_absolute() {
Ok(path)
} else {
canonicalize(project_origin.join(path)).into_diagnostic()
}
}
.map(WatchedPath::recursive)
})
.chain(take(&mut args.non_recursive_paths).into_iter().map(|path| {
{
if path.is_absolute() {
Ok(path)
} else {
canonicalize(project_origin.join(path)).into_diagnostic()
}
}
.map(WatchedPath::non_recursive)
}))
.collect::<Result<BTreeSet<_>>>()?
.into_iter()
.collect();
if args.paths.len() == 1
&& args
.paths
.first()
.map_or(false, |p| p.as_ref() == Path::new("/dev/null"))
{
info!("only path is /dev/null, not watching anything");
args.paths = Vec::new();
} else if args.paths.is_empty() {
info!("no paths, using current directory");
args.paths.push(args.workdir.clone().unwrap().into());
}
info!(paths=?args.paths, "effective watched paths");
for (n, prog) in args.filter_programs.iter_mut().enumerate() {
if let Some(progpath) = prog.strip_prefix('@') {
trace!(?n, path=?progpath, "reading filter program from file");
@ -1236,12 +1285,14 @@ pub async fn get_args() -> Result<Args> {
}
}
args.filter_programs_parsed = std::mem::take(&mut args.filter_programs)
args.filter_programs_parsed = take(&mut args.filter_programs)
.into_iter()
.enumerate()
.map(parse_filter_program)
.collect::<Result<_, _>>()?;
debug!(?args, "got arguments");
Ok(args)
debug_assert!(args.workdir.is_some());
debug_assert!(args.project_origin.is_some());
info!(?args, "got arguments");
Ok((args, log_guard))
}

View File

@ -0,0 +1,132 @@
use std::{env::var, io::stderr, path::PathBuf};
use clap::{ArgAction, Parser, ValueHint};
use miette::{bail, Result};
use tokio::fs::metadata;
use tracing::{info, warn};
use tracing_appender::{non_blocking, non_blocking::WorkerGuard, rolling};
#[derive(Debug, Clone, Parser)]
pub struct LoggingArgs {
/// Set diagnostic log level
///
/// This enables diagnostic logging, which is useful for investigating bugs or gaining more
/// insight into faulty filters or "missing" events. Use multiple times to increase verbosity.
///
/// Goes up to '-vvvv'. When submitting bug reports, default to a '-vvv' log level.
///
/// You may want to use with '--log-file' to avoid polluting your terminal.
///
/// Setting $RUST_LOG also works, and takes precedence, but is not recommended. However, using
/// $RUST_LOG is the only way to get logs from before these options are parsed.
#[arg(
long,
short,
help_heading = super::OPTSET_DEBUGGING,
action = ArgAction::Count,
default_value = "0",
num_args = 0,
)]
pub verbose: u8,
/// Write diagnostic logs to a file
///
/// This writes diagnostic logs to a file, instead of the terminal, in JSON format. If a log
/// level was not already specified, this will set it to '-vvv'.
///
/// If a path is not provided, the default is the working directory. Note that with
/// '--ignore-nothing', the write events to the log will likely get picked up by Watchexec,
/// causing a loop; prefer setting a path outside of the watched directory.
///
/// If the path provided is a directory, a file will be created in that directory. The file name
/// will be the current date and time, in the format 'watchexec.YYYY-MM-DDTHH-MM-SSZ.log'.
#[arg(
long,
help_heading = super::OPTSET_DEBUGGING,
num_args = 0..=1,
default_missing_value = ".",
value_hint = ValueHint::AnyPath,
value_name = "PATH",
)]
pub log_file: Option<PathBuf>,
}
pub fn preargs() -> bool {
let mut log_on = false;
#[cfg(feature = "dev-console")]
match console_subscriber::try_init() {
Ok(_) => {
warn!("dev-console enabled");
log_on = true;
}
Err(e) => {
eprintln!("Failed to initialise tokio console, falling back to normal logging\n{e}")
}
}
if !log_on && var("RUST_LOG").is_ok() {
match tracing_subscriber::fmt::try_init() {
Ok(()) => {
warn!(RUST_LOG=%var("RUST_LOG").unwrap(), "logging configured from RUST_LOG");
log_on = true;
}
Err(e) => eprintln!("Failed to initialise logging with RUST_LOG, falling back\n{e}"),
}
}
log_on
}
pub async fn postargs(args: &LoggingArgs) -> Result<Option<WorkerGuard>> {
if args.verbose == 0 {
return Ok(None);
}
let (log_writer, guard) = if let Some(file) = &args.log_file {
let is_dir = metadata(&file).await.map_or(false, |info| info.is_dir());
let (dir, filename) = if is_dir {
(
file.to_owned(),
PathBuf::from(format!(
"watchexec.{}.log",
chrono::Utc::now().format("%Y-%m-%dT%H-%M-%SZ")
)),
)
} else if let (Some(parent), Some(file_name)) = (file.parent(), file.file_name()) {
(parent.into(), PathBuf::from(file_name))
} else {
bail!("Failed to determine log file name");
};
non_blocking(rolling::never(dir, filename))
} else {
non_blocking(stderr())
};
let mut builder = tracing_subscriber::fmt().with_env_filter(match args.verbose {
0 => unreachable!("checked by if earlier"),
1 => "warn",
2 => "info",
3 => "debug",
_ => "trace",
});
if args.verbose > 2 {
use tracing_subscriber::fmt::format::FmtSpan;
builder = builder.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE);
}
match if args.log_file.is_some() {
builder.json().with_writer(log_writer).try_init()
} else if args.verbose > 3 {
builder.pretty().with_writer(log_writer).try_init()
} else {
builder.with_writer(log_writer).try_init()
} {
Ok(()) => info!("logging initialised"),
Err(e) => eprintln!("Failed to initialise logging, continuing with none\n{e}"),
}
Ok(Some(guard))
}

View File

@ -1,11 +1,10 @@
use std::{
borrow::Cow,
collections::HashMap,
env::{current_dir, var},
env::var,
ffi::{OsStr, OsString},
fs::File,
io::{IsTerminal, Write},
path::Path,
process::Stdio,
sync::{
atomic::{AtomicBool, AtomicU8, Ordering},
@ -68,19 +67,7 @@ pub fn make_config(args: &Args, state: &State) -> Result<Config> {
eprintln!("[[Error (not fatal)]]\n{}", Report::new(err.error));
});
config.pathset(if args.paths.is_empty() {
vec![current_dir().into_diagnostic()?]
} else if args.paths.len() == 1
&& args
.paths
.first()
.map_or(false, |p| p == Path::new("/dev/null"))
{
// special case: /dev/null means "don't start the fs event source"
Vec::new()
} else {
args.paths.clone()
});
config.pathset(args.paths.clone());
config.throttle(args.debounce.0);
config.keyboard_events(args.stdin_quit);

View File

@ -1,7 +1,5 @@
use std::{
borrow::Cow,
collections::HashSet,
env,
path::{Path, PathBuf},
};
@ -14,16 +12,7 @@ use watchexec::paths::common_prefix;
use crate::args::Args;
type ProjectOriginPath = PathBuf;
type WorkDirPath = PathBuf;
/// Extract relevant directories (in particular the project origin and work directory)
/// given the command line arguments that were provided
pub async fn dirs(args: &Args) -> Result<(ProjectOriginPath, WorkDirPath)> {
let curdir = env::current_dir().into_diagnostic()?;
let curdir = canonicalize(curdir).await.into_diagnostic()?;
debug!(?curdir, "current directory");
pub async fn project_origin(args: &Args) -> Result<PathBuf> {
let project_origin = if let Some(origin) = &args.project_origin {
debug!(?origin, "project origin override");
canonicalize(origin).await.into_diagnostic()?
@ -34,27 +23,19 @@ pub async fn dirs(args: &Args) -> Result<(ProjectOriginPath, WorkDirPath)> {
};
debug!(?homedir, "home directory");
let mut paths = HashSet::new();
for path in &args.paths {
paths.insert(canonicalize(path).await.into_diagnostic()?);
}
let homedir_requested = homedir.as_ref().map_or(false, |home| paths.contains(home));
let homedir_requested = homedir.as_ref().map_or(false, |home| {
args.paths
.binary_search_by_key(home, |w| PathBuf::from(w.clone()))
.is_ok()
});
debug!(
?homedir_requested,
"resolved whether the homedir is explicitly requested"
);
if paths.is_empty() {
debug!("no paths, using current directory");
paths.insert(curdir.clone());
}
debug!(?paths, "resolved all watched paths");
let mut origins = HashSet::new();
for path in paths {
origins.extend(project_origins::origins(&path).await);
for path in &args.paths {
origins.extend(project_origins::origins(path).await);
}
match (homedir, homedir_requested) {
@ -67,7 +48,7 @@ pub async fn dirs(args: &Args) -> Result<(ProjectOriginPath, WorkDirPath)> {
if origins.is_empty() {
debug!("no origins, using current directory");
origins.insert(curdir.clone());
origins.insert(args.workdir.clone().unwrap());
}
debug!(?origins, "resolved all project origins");
@ -80,12 +61,9 @@ pub async fn dirs(args: &Args) -> Result<(ProjectOriginPath, WorkDirPath)> {
.await
.into_diagnostic()?
};
info!(?project_origin, "resolved common/project origin");
debug!(?project_origin, "resolved common/project origin");
let workdir = curdir;
info!(?workdir, "resolved working directory");
Ok((project_origin, workdir))
Ok(project_origin)
}
pub async fn vcs_types(origin: &Path) -> Vec<ProjectType> {
@ -94,41 +72,34 @@ pub async fn vcs_types(origin: &Path) -> Vec<ProjectType> {
.into_iter()
.filter(|pt| pt.is_vcs())
.collect::<Vec<_>>();
info!(?vcs_types, "resolved vcs types");
info!(?vcs_types, "effective vcs types");
vcs_types
}
pub async fn ignores(
args: &Args,
vcs_types: &[ProjectType],
origin: &Path,
) -> Result<Vec<IgnoreFile>> {
fn higher_make_absolute_if_needed<'a>(
origin: &'a Path,
) -> impl 'a + Fn(&'a PathBuf) -> Cow<'a, Path> {
|path| {
if path.is_absolute() {
Cow::Borrowed(path)
} else {
Cow::Owned(origin.join(path))
}
}
}
pub async fn ignores(args: &Args, vcs_types: &[ProjectType]) -> Result<Vec<IgnoreFile>> {
let origin = args.project_origin.clone().unwrap();
let mut skip_git_global_excludes = false;
let mut ignores = if args.no_project_ignore {
Vec::new()
} else {
let make_absolute_if_needed = higher_make_absolute_if_needed(origin);
let include_paths = args.paths.iter().map(&make_absolute_if_needed);
let ignore_files = args.ignore_files.iter().map(&make_absolute_if_needed);
let ignore_files = args.ignore_files.iter().map(|path| {
if path.is_absolute() {
path.into()
} else {
origin.join(path)
}
});
let (mut ignores, errors) = ignore_files::from_origin(
IgnoreFilesFromOriginArgs::new_unchecked(origin, include_paths, ignore_files)
.canonicalise()
.await
.into_diagnostic()?,
IgnoreFilesFromOriginArgs::new_unchecked(
&origin,
args.paths.iter().map(PathBuf::from),
ignore_files,
)
.canonicalise()
.await
.into_diagnostic()?,
)
.await;
@ -221,7 +192,7 @@ pub async fn ignores(
.filter(|ig| {
!ig.applies_in
.as_ref()
.map_or(false, |p| p.starts_with(origin))
.map_or(false, |p| p.starts_with(&origin))
})
.collect::<Vec<_>>();
debug!(

View File

@ -16,7 +16,6 @@ use watchexec_filterer_globset::GlobsetFilterer;
use crate::args::{Args, FsEvent};
mod dirs;
pub(crate) mod parse;
mod proglib;
mod progs;
@ -71,13 +70,14 @@ impl Filterer for WatchexecFilterer {
impl WatchexecFilterer {
/// Create a new filterer from the given arguments
pub async fn new(args: &Args) -> Result<Arc<Self>> {
let (project_origin, workdir) = dirs::dirs(args).await?;
let project_origin = args.project_origin.clone().unwrap();
let workdir = args.workdir.clone().unwrap();
let ignore_files = if args.no_discover_ignore {
Vec::new()
} else {
let vcs_types = dirs::vcs_types(&project_origin).await;
dirs::ignores(args, &vcs_types, &project_origin).await?
let vcs_types = crate::dirs::vcs_types(&project_origin).await;
crate::dirs::ignores(args, &vcs_types).await?
};
let mut ignores = Vec::new();

View File

@ -10,7 +10,7 @@ pub fn parse_filter_program((n, prog): (usize, String)) -> Result<jaq_syn::Main>
.map(|err| err.to_string())
.collect::<Vec<_>>()
.join("\n");
return Err(miette!("failed to load filter program #{}: {:?}", n, errs));
return Err(miette!("{}", errs).wrap_err(format!("failed to load filter program #{}", n)));
}
main.ok_or_else(|| miette!("failed to load filter program #{} (no reason given)", n))

View File

@ -1,7 +1,7 @@
#![deny(rust_2018_idioms)]
#![allow(clippy::missing_const_for_fn, clippy::future_not_send)]
use std::{env::var, fs::File, io::Write, process::Stdio, sync::Mutex};
use std::{io::Write, process::Stdio};
use args::{Args, ShellCompletion};
use clap::CommandFactory;
@ -9,8 +9,8 @@ use clap_complete::{Generator, Shell};
use clap_mangen::Man;
use is_terminal::IsTerminal;
use miette::{IntoDiagnostic, Result};
use tokio::{fs::metadata, io::AsyncWriteExt, process::Command};
use tracing::{debug, info, warn};
use tokio::{io::AsyncWriteExt, process::Command};
use tracing::{debug, info};
use watchexec::Watchexec;
use watchexec_events::{Event, Priority};
@ -18,86 +18,11 @@ use crate::filterer::WatchexecFilterer;
pub mod args;
mod config;
mod dirs;
mod emits;
mod filterer;
mod state;
async fn init() -> Result<Args> {
let mut log_on = false;
#[cfg(feature = "dev-console")]
match console_subscriber::try_init() {
Ok(_) => {
warn!("dev-console enabled");
log_on = true;
}
Err(e) => {
eprintln!("Failed to initialise tokio console, falling back to normal logging\n{e}")
}
}
if !log_on && var("RUST_LOG").is_ok() {
match tracing_subscriber::fmt::try_init() {
Ok(()) => {
warn!(RUST_LOG=%var("RUST_LOG").unwrap(), "logging configured from RUST_LOG");
log_on = true;
}
Err(e) => eprintln!("Failed to initialise logging with RUST_LOG, falling back\n{e}"),
}
}
let args = args::get_args().await?;
let verbosity = args.verbose.unwrap_or(0);
if log_on {
warn!("ignoring logging options from args");
} else if verbosity > 0 {
let log_file = if let Some(file) = &args.log_file {
let is_dir = metadata(&file).await.map_or(false, |info| info.is_dir());
let path = if is_dir {
let filename = format!(
"watchexec.{}.log",
chrono::Utc::now().format("%Y-%m-%dT%H-%M-%SZ")
);
file.join(filename)
} else {
file.to_owned()
};
// TODO: use tracing-appender instead
Some(File::create(path).into_diagnostic()?)
} else {
None
};
let mut builder = tracing_subscriber::fmt().with_env_filter(match verbosity {
0 => unreachable!("checked by if earlier"),
1 => "warn",
2 => "info",
3 => "debug",
_ => "trace",
});
if verbosity > 2 {
use tracing_subscriber::fmt::format::FmtSpan;
builder = builder.with_span_events(FmtSpan::NEW | FmtSpan::CLOSE);
}
match if let Some(writer) = log_file {
builder.json().with_writer(Mutex::new(writer)).try_init()
} else if verbosity > 3 {
builder.pretty().try_init()
} else {
builder.try_init()
} {
Ok(()) => info!("logging initialised"),
Err(e) => eprintln!("Failed to initialise logging, continuing with none\n{e}"),
}
}
Ok(args)
}
async fn run_watchexec(args: Args) -> Result<()> {
info!(version=%env!("CARGO_PKG_VERSION"), "constructing Watchexec from CLI");
@ -191,8 +116,7 @@ async fn run_completions(shell: ShellCompletion) -> Result<()> {
}
pub async fn run() -> Result<()> {
let args = init().await?;
debug!(?args, "arguments");
let (args, _log_guard) = args::get_args().await?;
if args.manual {
run_manpage(args).await

View File

@ -3,7 +3,7 @@
<assemblyIdentity
type="win32"
name="Watchexec.Cli.watchexec"
version="2.0.0.0"
version="2.1.1.0"
/>
<trustInfo>

View File

@ -2,6 +2,10 @@
## Next (YYYY-MM-DD)
## v4.0.1 (2024-04-28)
- Hide fmt::Debug spew from ignore crate, use `full_debug` feature to restore.
## v4.0.0 (2024-04-20)
- Deps: watchexec 4

View File

@ -1,6 +1,6 @@
[package]
name = "watchexec-filterer-globset"
version = "4.0.0"
version = "4.0.1"
authors = ["Matt Green <mattgreenrocks@gmail.com>", "Félix Saparelli <felix@passcod.name>"]
license = "Apache-2.0"
@ -20,11 +20,11 @@ ignore = "0.4.18"
tracing = "0.1.40"
[dependencies.ignore-files]
version = "3.0.0"
version = "3.0.1"
path = "../../ignore-files"
[dependencies.watchexec]
version = "4.0.0"
version = "4.1.0"
path = "../../lib"
[dependencies.watchexec-events]
@ -32,7 +32,7 @@ version = "3.0.0"
path = "../../events"
[dependencies.watchexec-filterer-ignore]
version = "4.0.0"
version = "4.0.1"
path = "../ignore"
[dev-dependencies]
@ -47,3 +47,9 @@ features = [
"rt-multi-thread",
"macros",
]
[features]
default = []
## Don't hide ignore::gitignore::Gitignore Debug impl
full_debug = []

View File

@ -10,6 +10,7 @@
use std::{
ffi::OsString,
fmt,
path::{Path, PathBuf},
};
@ -21,7 +22,7 @@ use watchexec_events::{Event, FileType, Priority};
use watchexec_filterer_ignore::IgnoreFilterer;
/// A simple filterer in the style of the watchexec v1.17 filter.
#[derive(Debug)]
#[cfg_attr(feature = "full_debug", derive(Debug))]
pub struct GlobsetFilterer {
#[cfg_attr(not(unix), allow(dead_code))]
origin: PathBuf,
@ -31,6 +32,19 @@ pub struct GlobsetFilterer {
extensions: Vec<OsString>,
}
#[cfg(not(feature = "full_debug"))]
impl fmt::Debug for GlobsetFilterer {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("GlobsetFilterer")
.field("origin", &self.origin)
.field("filters", &"ignore::gitignore::Gitignore{...}")
.field("ignores", &"ignore::gitignore::Gitignore{...}")
.field("ignore_files", &self.ignore_files)
.field("extensions", &self.extensions)
.finish()
}
}
impl GlobsetFilterer {
/// Create a new `GlobsetFilterer` from a project origin, allowed extensions, and lists of globs.
///

View File

@ -2,6 +2,8 @@
## Next (YYYY-MM-DD)
## v4.0.1 (2024-04-28)
## v4.0.0 (2024-04-20)
- Deps: watchexec 4

View File

@ -1,6 +1,6 @@
[package]
name = "watchexec-filterer-ignore"
version = "4.0.0"
version = "4.0.1"
authors = ["Félix Saparelli <felix@passcod.name>"]
license = "Apache-2.0"
@ -22,11 +22,11 @@ normalize-path = "0.2.1"
tracing = "0.1.40"
[dependencies.ignore-files]
version = "3.0.0"
version = "3.0.1"
path = "../../ignore-files"
[dependencies.watchexec]
version = "4.0.0"
version = "4.1.0"
path = "../../lib"
[dependencies.watchexec-events]
@ -41,7 +41,7 @@ path = "../../signals"
tracing-subscriber = "0.3.6"
[dev-dependencies.project-origins]
version = "1.3.0"
version = "1.4.0"
path = "../../project-origins"
[dev-dependencies.tokio]

View File

@ -2,6 +2,10 @@
## Next (YYYY-MM-DD)
## v3.0.1 (2024-04-28)
- Hide fmt::Debug spew from ignore crate, use `full_debug` feature to restore.
## v3.0.0 (2024-04-20)
- Deps: gix-config 0.36

View File

@ -1,6 +1,6 @@
[package]
name = "ignore-files"
version = "3.0.0"
version = "3.0.1"
authors = ["Félix Saparelli <felix@passcod.name>"]
license = "Apache-2.0"
@ -35,8 +35,14 @@ features = [
]
[dependencies.project-origins]
version = "1.3.0"
version = "1.4.0"
path = "../project-origins"
[dev-dependencies]
tracing-subscriber = "0.3.6"
[features]
default = []
## Don't hide ignore::gitignore::Gitignore Debug impl
full_debug = []

View File

@ -1,3 +1,4 @@
use std::fmt;
use std::path::{Path, PathBuf};
use futures::stream::{FuturesUnordered, StreamExt};
@ -11,12 +12,23 @@ use tracing::{trace, trace_span};
use crate::{simplify_path, Error, IgnoreFile};
#[derive(Clone, Debug)]
#[derive(Clone)]
#[cfg_attr(feature = "full_debug", derive(Debug))]
struct Ignore {
gitignore: Gitignore,
builder: Option<GitignoreBuilder>,
}
#[cfg(not(feature = "full_debug"))]
impl fmt::Debug for Ignore {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Ignore")
.field("gitignore", &"ignore::gitignore::Gitignore{...}")
.field("builder", &"ignore::gitignore::GitignoreBuilder{...}")
.finish()
}
}
/// A mutable filter dedicated to ignore files and trees of ignore files.
///
/// This reads and compiles ignore files, and should be used for handling ignore files. It's created

View File

@ -2,6 +2,12 @@
## Next (YYYY-MM-DD)
## v4.1.0 (2024-04-28)
- Feature: non-recursive watches with `WatchedPath::non_recursive()`
- Fix: `config.pathset()` now preserves `WatchedPath` attributes
- Refactor: move `WatchedPath` to the root of the crate (old path remains as re-export for now)
## v4.0.0 (2024-04-20)
- Deps: replace command-group with process-wrap (in supervisor, but has flow-on effects)

View File

@ -1,6 +1,6 @@
[package]
name = "watchexec"
version = "4.0.0"
version = "4.1.0"
authors = ["Félix Saparelli <felix@passcod.name>", "Matt Green <mattgreenrocks@gmail.com>"]
license = "Apache-2.0"
@ -43,11 +43,11 @@ version = "2.0.0"
path = "../supervisor"
[dependencies.ignore-files]
version = "3.0.0"
version = "3.0.1"
path = "../ignore-files"
[dependencies.project-origins]
version = "1.3.0"
version = "1.4.0"
path = "../project-origins"
[dependencies.tokio]

View File

@ -1,6 +1,6 @@
//! Configuration and builders for [`crate::Watchexec`].
use std::{future::Future, path::Path, pin::pin, sync::Arc, time::Duration};
use std::{future::Future, pin::pin, sync::Arc, time::Duration};
use tokio::sync::Notify;
use tracing::{debug, trace};
@ -195,9 +195,9 @@ impl Config {
pub fn pathset<I, P>(&self, pathset: I) -> &Self
where
I: IntoIterator<Item = P>,
P: AsRef<Path>,
P: Into<WatchedPath>,
{
let pathset = pathset.into_iter().map(|p| p.as_ref().into()).collect();
let pathset = pathset.into_iter().map(|p| p.into()).collect();
debug!(?pathset, "Config: pathset");
self.pathset.replace(pathset);
self.signal_change()

View File

@ -68,12 +68,14 @@ pub mod config;
mod id;
mod late_join_set;
mod watched_path;
mod watchexec;
#[doc(inline)]
pub use crate::{
id::Id,
watchexec::{ErrorHook, Watchexec},
watched_path::WatchedPath,
};
#[doc(no_inline)]

View File

@ -4,7 +4,6 @@ use std::{
collections::{HashMap, HashSet},
fs::metadata,
mem::take,
path::{Path, PathBuf},
sync::Arc,
time::Duration,
};
@ -20,6 +19,9 @@ use crate::{
Config,
};
// re-export for compatibility, until next major version
pub use crate::WatchedPath;
/// What kind of filesystem watcher to use.
///
/// For now only native and poll watchers are supported. In the future there may be additional
@ -72,42 +74,6 @@ impl Watcher {
}
}
/// A path to watch.
///
/// This is currently only a wrapper around a [`PathBuf`], but may be augmented in the future.
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WatchedPath(PathBuf);
impl From<PathBuf> for WatchedPath {
fn from(path: PathBuf) -> Self {
Self(path)
}
}
impl From<&str> for WatchedPath {
fn from(path: &str) -> Self {
Self(path.into())
}
}
impl From<&Path> for WatchedPath {
fn from(path: &Path) -> Self {
Self(path.into())
}
}
impl From<WatchedPath> for PathBuf {
fn from(path: WatchedPath) -> Self {
path.0
}
}
impl AsRef<Path> for WatchedPath {
fn as_ref(&self) -> &Path {
self.0.as_ref()
}
}
/// Launch the filesystem event worker.
///
/// While you can run several, you should only have one.
@ -190,6 +156,7 @@ pub async fn worker(
// now let's calculate which paths we should add to the watch, and which we should drop:
let config_pathset = config.pathset.get();
tracing::info!(?config_pathset, "obtaining pathset");
let (to_watch, to_drop) = if pathset.is_empty() {
// if the current pathset is empty, we can take a shortcut
(config_pathset, Vec::new())
@ -222,7 +189,7 @@ pub async fn worker(
for path in to_drop {
trace!(?path, "removing path from the watcher");
if let Err(err) = watcher.unwatch(path.as_ref()) {
if let Err(err) = watcher.unwatch(path.path.as_ref()) {
error!(?err, "notify unwatch() error");
for e in notify_multi_path_errors(watcher_type, path, err, true) {
errors.send(e).await?;
@ -234,13 +201,18 @@ pub async fn worker(
for path in to_watch {
trace!(?path, "adding path to the watcher");
if let Err(err) = watcher.watch(path.as_ref(), notify::RecursiveMode::Recursive) {
if let Err(err) = watcher.watch(
path.path.as_ref(),
if path.recursive {
notify::RecursiveMode::Recursive
} else {
notify::RecursiveMode::NonRecursive
},
) {
error!(?err, "notify watch() error");
for e in notify_multi_path_errors(watcher_type, path, err, false) {
errors.send(e).await?;
}
// TODO: unwatch and re-watch manually while ignoring all the erroring paths
// See https://github.com/watchexec/watchexec/issues/218
} else {
pathset.insert(path);
}
@ -250,13 +222,13 @@ pub async fn worker(
fn notify_multi_path_errors(
kind: Watcher,
path: WatchedPath,
watched_path: WatchedPath,
mut err: notify::Error,
rm: bool,
) -> Vec<RuntimeError> {
let mut paths = take(&mut err.paths);
if paths.is_empty() {
paths.push(path.into());
paths.push(watched_path.into());
}
let generic = err.to_string();

View File

@ -0,0 +1,82 @@
use std::path::{Path, PathBuf};
/// A path to watch.
///
/// Can be a recursive or non-recursive watch.
#[derive(Clone, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct WatchedPath {
pub(crate) path: PathBuf,
pub(crate) recursive: bool,
}
impl From<PathBuf> for WatchedPath {
fn from(path: PathBuf) -> Self {
Self {
path,
recursive: true,
}
}
}
impl From<&str> for WatchedPath {
fn from(path: &str) -> Self {
Self {
path: path.into(),
recursive: true,
}
}
}
impl From<String> for WatchedPath {
fn from(path: String) -> Self {
Self {
path: path.into(),
recursive: true,
}
}
}
impl From<&Path> for WatchedPath {
fn from(path: &Path) -> Self {
Self {
path: path.into(),
recursive: true,
}
}
}
impl From<WatchedPath> for PathBuf {
fn from(path: WatchedPath) -> Self {
path.path
}
}
impl From<&WatchedPath> for PathBuf {
fn from(path: &WatchedPath) -> Self {
path.path.clone()
}
}
impl AsRef<Path> for WatchedPath {
fn as_ref(&self) -> &Path {
self.path.as_ref()
}
}
impl WatchedPath {
/// Create a new watched path, recursively descending into subdirectories.
pub fn recursive(path: impl Into<PathBuf>) -> Self {
Self {
path: path.into(),
recursive: true,
}
}
/// Create a new watched path, not descending into subdirectories.
pub fn non_recursive(path: impl Into<PathBuf>) -> Self {
Self {
path: path.into(),
recursive: false,
}
}
}

View File

@ -2,6 +2,10 @@
## Next (YYYY-MM-DD)
## v1.4.0 (2024-04-28)
- Add out-of-tree Git repositories (`.git` file instead of folder).
## v1.3.0 (2024-01-01)
- Remove `README.md` files from detection; those were causing too many false positives and were a weak signal anyway.

View File

@ -1,6 +1,6 @@
[package]
name = "project-origins"
version = "1.3.0"
version = "1.4.0"
authors = ["Félix Saparelli <felix@passcod.name>"]
license = "Apache-2.0"

View File

@ -49,7 +49,7 @@ pub enum ProjectType {
/// VCS: [Git](https://git-scm.com/).
///
/// Detects when a `.git` folder is present, or any of the files `.gitattributes` or
/// Detects when a `.git` file or folder is present, or any of the files `.gitattributes` or
/// `.gitmodules`. Does _not_ check or return from the presence of `.gitignore` files, as Git
/// supports nested ignores, and that would result in false-positives.
Git,
@ -208,6 +208,7 @@ pub async fn origins(path: impl AsRef<Path> + Send) -> HashSet<PathBuf> {
list.has_file(".codecov.yml"),
list.has_file(".ctags"),
list.has_file(".editorconfig"),
list.has_file(".git"),
list.has_file(".gitattributes"),
list.has_file(".gitmodules"),
list.has_file(".hgignore"),
@ -293,6 +294,7 @@ pub async fn types(path: impl AsRef<Path> + Send) -> HashSet<ProjectType> {
list.if_has_dir(".svn", ProjectType::Subversion),
list.if_has_file(".bzrignore", ProjectType::Bazaar),
list.if_has_file(".ctags", ProjectType::C),
list.if_has_file(".git", ProjectType::Git),
list.if_has_file(".gitattributes", ProjectType::Git),
list.if_has_file(".gitmodules", ProjectType::Git),
list.if_has_file(".hgignore", ProjectType::Mercurial),

View File

@ -1,10 +1,10 @@
.ie \n(.g .ds Aq \(aq
.el .ds Aq '
.TH watchexec 1 "watchexec 1.25.1"
.TH watchexec 1 "watchexec 2.1.1"
.SH NAME
watchexec \- Execute commands when watched files change
.SH SYNOPSIS
\fBwatchexec\fR [\fB\-w\fR|\fB\-\-watch\fR] [\fB\-c\fR|\fB\-\-clear\fR] [\fB\-o\fR|\fB\-\-on\-busy\-update\fR] [\fB\-r\fR|\fB\-\-restart\fR] [\fB\-s\fR|\fB\-\-signal\fR] [\fB\-\-stop\-signal\fR] [\fB\-\-stop\-timeout\fR] [\fB\-\-map\-signal\fR] [\fB\-d\fR|\fB\-\-debounce\fR] [\fB\-\-stdin\-quit\fR] [\fB\-\-no\-vcs\-ignore\fR] [\fB\-\-no\-project\-ignore\fR] [\fB\-\-no\-global\-ignore\fR] [\fB\-\-no\-default\-ignore\fR] [\fB\-\-no\-discover\-ignore\fR] [\fB\-\-ignore\-nothing\fR] [\fB\-p\fR|\fB\-\-postpone\fR] [\fB\-\-delay\-run\fR] [\fB\-\-poll\fR] [\fB\-\-shell\fR] [\fB\-n \fR] [\fB\-\-emit\-events\-to\fR] [\fB\-\-only\-emit\-events\fR] [\fB\-E\fR|\fB\-\-env\fR] [\fB\-\-no\-process\-group\fR] [\fB\-\-wrap\-process\fR] [\fB\-N\fR|\fB\-\-notify\fR] [\fB\-\-color\fR] [\fB\-\-timings\fR] [\fB\-q\fR|\fB\-\-quiet\fR] [\fB\-\-bell\fR] [\fB\-\-project\-origin\fR] [\fB\-\-workdir\fR] [\fB\-e\fR|\fB\-\-exts\fR] [\fB\-f\fR|\fB\-\-filter\fR] [\fB\-\-filter\-file\fR] [\fB\-j\fR|\fB\-\-filter\-prog\fR] [\fB\-i\fR|\fB\-\-ignore\fR] [\fB\-\-ignore\-file\fR] [\fB\-\-fs\-events\fR] [\fB\-\-no\-meta\fR] [\fB\-\-print\-events\fR] [\fB\-v\fR|\fB\-\-verbose\fR]... [\fB\-\-log\-file\fR] [\fB\-\-manual\fR] [\fB\-\-completions\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fICOMMAND\fR]
\fBwatchexec\fR [\fB\-w\fR|\fB\-\-watch\fR] [\fB\-W\fR|\fB\-\-watch\-non\-recursive\fR] [\fB\-c\fR|\fB\-\-clear\fR] [\fB\-o\fR|\fB\-\-on\-busy\-update\fR] [\fB\-r\fR|\fB\-\-restart\fR] [\fB\-s\fR|\fB\-\-signal\fR] [\fB\-\-stop\-signal\fR] [\fB\-\-stop\-timeout\fR] [\fB\-\-map\-signal\fR] [\fB\-d\fR|\fB\-\-debounce\fR] [\fB\-\-stdin\-quit\fR] [\fB\-\-no\-vcs\-ignore\fR] [\fB\-\-no\-project\-ignore\fR] [\fB\-\-no\-global\-ignore\fR] [\fB\-\-no\-default\-ignore\fR] [\fB\-\-no\-discover\-ignore\fR] [\fB\-\-ignore\-nothing\fR] [\fB\-p\fR|\fB\-\-postpone\fR] [\fB\-\-delay\-run\fR] [\fB\-\-poll\fR] [\fB\-\-shell\fR] [\fB\-n \fR] [\fB\-\-emit\-events\-to\fR] [\fB\-\-only\-emit\-events\fR] [\fB\-E\fR|\fB\-\-env\fR] [\fB\-\-no\-process\-group\fR] [\fB\-\-wrap\-process\fR] [\fB\-N\fR|\fB\-\-notify\fR] [\fB\-\-color\fR] [\fB\-\-timings\fR] [\fB\-q\fR|\fB\-\-quiet\fR] [\fB\-\-bell\fR] [\fB\-\-project\-origin\fR] [\fB\-\-workdir\fR] [\fB\-e\fR|\fB\-\-exts\fR] [\fB\-f\fR|\fB\-\-filter\fR] [\fB\-\-filter\-file\fR] [\fB\-j\fR|\fB\-\-filter\-prog\fR] [\fB\-i\fR|\fB\-\-ignore\fR] [\fB\-\-ignore\-file\fR] [\fB\-\-fs\-events\fR] [\fB\-\-no\-meta\fR] [\fB\-\-print\-events\fR] [\fB\-\-manual\fR] [\fB\-\-completions\fR] [\fB\-v\fR|\fB\-\-verbose\fR]... [\fB\-\-log\-file\fR] [\fB\-h\fR|\fB\-\-help\fR] [\fB\-V\fR|\fB\-\-version\fR] [\fICOMMAND\fR]
.SH DESCRIPTION
Execute commands when watched files change.
.PP
@ -48,6 +48,13 @@ This option can be specified multiple times to watch multiple files or directori
The special value \*(Aq/dev/null\*(Aq, provided as the only path watched, will cause Watchexec to not watch any paths. Other event sources (like signals or key events) may still be used.
.TP
\fB\-W\fR, \fB\-\-watch\-non\-recursive\fR=\fIPATH\fR
Watch a specific directory, non\-recursively
Unlike \*(Aq\-w\*(Aq, folders watched with this option are not recursed into.
This option can be specified multiple times to watch multiple directories non\-recursively.
.TP
\fB\-c\fR, \fB\-\-clear\fR=\fIMODE\fR
Clear screen before running command
@ -56,11 +63,9 @@ If this doesn\*(Aqt completely clear the screen, try \*(Aq\-\-clear=reset\*(Aq.
\fB\-o\fR, \fB\-\-on\-busy\-update\fR=\fIMODE\fR
What to do when receiving events while the command is running
Default is to \*(Aqqueue\*(Aq up events and run the command once again when the previous run has finished. You can also use \*(Aqdo\-nothing\*(Aq, which ignores events while the command is running and may be useful to avoid spurious changes made by that command, or \*(Aqrestart\*(Aq, which terminates the running command and starts a new one. Finally, there\*(Aqs \*(Aqsignal\*(Aq, which only sends a signal; this can be useful with programs that can reload their configuration without a full restart.
Default is to \*(Aqdo\-nothing\*(Aq, which ignores events while the command is running, so that changes that occur due to the command are ignored, like compilation outputs. You can also use \*(Aqqueue\*(Aq which will run the command once again when the current run has finished if any events occur while it\*(Aqs running, or \*(Aqrestart\*(Aq, which terminates the running command and starts a new one. Finally, there\*(Aqs \*(Aqsignal\*(Aq, which only sends a signal; this can be useful with programs that can reload their configuration without a full restart.
The signal can be specified with the \*(Aq\-\-signal\*(Aq option.
Note that this option is scheduled to change its default to \*(Aqdo\-nothing\*(Aq in the next major release. File an issue if you have any concerns.
.TP
\fB\-r\fR, \fB\-\-restart\fR
Restart the process if it\*(Aqs still running
@ -441,7 +446,7 @@ This can also be used via the $WATCHEXEC_FILTER_FILES environment variable.
/!\\ This option is EXPERIMENTAL and may change and/or vanish without notice.
Provide your own custom filter programs in jaq (similar to jq) syntax. Programs are given an event in the same format as described in \*(Aq\-\-emit\-events\-to\*(Aq and must return a boolean.
Provide your own custom filter programs in jaq (similar to jq) syntax. Programs are given an event in the same format as described in \*(Aq\-\-emit\-events\-to\*(Aq and must return a boolean. Invalid programs will make watchexec fail to start; use \*(Aq\-v\*(Aq to see program runtime errors.
In addition to the jaq stdlib, watchexec adds some custom filter definitions:
@ -512,7 +517,19 @@ Print events that trigger actions
This prints the events that triggered the action when handling it (after debouncing), in a human readable form. This is useful for debugging filters.
Use \*(Aq\-v\*(Aq when you need more diagnostic information.
Use \*(Aq\-vvv\*(Aq instead when you need more diagnostic information.
.TP
\fB\-\-manual\fR
Show the manual page
This shows the manual page for Watchexec, if the output is a terminal and the \*(Aqman\*(Aq program is available. If not, the manual page is printed to stdout in ROFF format (suitable for writing to a watchexec.1 file).
.TP
\fB\-\-completions\fR=\fICOMPLETIONS\fR
Generate a shell completions script
Provides a completions script or configuration for the given shell. If Watchexec is not distributed with pre\-generated completions, you can use this to generate them yourself.
Supported shells: bash, elvish, fish, nu, powershell, zsh.
.TP
\fB\-v\fR, \fB\-\-verbose\fR
Set diagnostic log level
@ -534,18 +551,6 @@ If a path is not provided, the default is the working directory. Note that with
If the path provided is a directory, a file will be created in that directory. The file name will be the current date and time, in the format \*(Aqwatchexec.YYYY\-MM\-DDTHH\-MM\-SSZ.log\*(Aq.
.TP
\fB\-\-manual\fR
Show the manual page
This shows the manual page for Watchexec, if the output is a terminal and the \*(Aqman\*(Aq program is available. If not, the manual page is printed to stdout in ROFF format (suitable for writing to a watchexec.1 file).
.TP
\fB\-\-completions\fR=\fICOMPLETIONS\fR
Generate a shell completions script
Provides a completions script or configuration for the given shell. If Watchexec is not distributed with pre\-generated completions, you can use this to generate them yourself.
Supported shells: bash, elvish, fish, nu, powershell, zsh.
.TP
\fB\-h\fR, \fB\-\-help\fR
Print help (see a summary with \*(Aq\-h\*(Aq)
.TP
@ -577,6 +582,6 @@ Use @argfile as first argument to load arguments from the file \*(Aqargfile\*(Aq
Didn\*(Aqt expect this much output? Use the short \*(Aq\-h\*(Aq flag to get short help.
.SH VERSION
v1.25.1
v2.1.1
.SH AUTHORS
Félix Saparelli <felix@passcod.name>, Matt Green <mattgreenrocks@gmail.com>

View File

@ -4,7 +4,8 @@ watchexec - Execute commands when watched files change
# SYNOPSIS
**watchexec** \[**-w**\|**\--watch**\] \[**-c**\|**\--clear**\]
**watchexec** \[**-w**\|**\--watch**\]
\[**-W**\|**\--watch-non-recursive**\] \[**-c**\|**\--clear**\]
\[**-o**\|**\--on-busy-update**\] \[**-r**\|**\--restart**\]
\[**-s**\|**\--signal**\] \[**\--stop-signal**\] \[**\--stop-timeout**\]
\[**\--map-signal**\] \[**-d**\|**\--debounce**\] \[**\--stdin-quit**\]
@ -20,10 +21,10 @@ watchexec - Execute commands when watched files change
\[**\--workdir**\] \[**-e**\|**\--exts**\] \[**-f**\|**\--filter**\]
\[**\--filter-file**\] \[**-j**\|**\--filter-prog**\]
\[**-i**\|**\--ignore**\] \[**\--ignore-file**\] \[**\--fs-events**\]
\[**\--no-meta**\] \[**\--print-events**\]
\[**-v**\|**\--verbose**\]\... \[**\--log-file**\] \[**\--manual**\]
\[**\--completions**\] \[**-h**\|**\--help**\]
\[**-V**\|**\--version**\] \[*COMMAND*\]
\[**\--no-meta**\] \[**\--print-events**\] \[**\--manual**\]
\[**\--completions**\] \[**-v**\|**\--verbose**\]\...
\[**\--log-file**\] \[**-h**\|**\--help**\] \[**-V**\|**\--version**\]
\[*COMMAND*\]
# DESCRIPTION
@ -82,6 +83,15 @@ The special value /dev/null, provided as the only path watched, will
cause Watchexec to not watch any paths. Other event sources (like
signals or key events) may still be used.
**-W**, **\--watch-non-recursive**=*PATH*
: Watch a specific directory, non-recursively
Unlike -w, folders watched with this option are not recursed into.
This option can be specified multiple times to watch multiple
directories non-recursively.
**-c**, **\--clear**=*MODE*
: Clear screen before running command
@ -92,19 +102,17 @@ If this doesnt completely clear the screen, try \--clear=reset.
: What to do when receiving events while the command is running
Default is to queue up events and run the command once again when the
previous run has finished. You can also use do-nothing, which ignores
events while the command is running and may be useful to avoid spurious
changes made by that command, or restart, which terminates the running
command and starts a new one. Finally, theres signal, which only sends a
signal; this can be useful with programs that can reload their
configuration without a full restart.
Default is to do-nothing, which ignores events while the command is
running, so that changes that occur due to the command are ignored, like
compilation outputs. You can also use queue which will run the command
once again when the current run has finished if any events occur while
its running, or restart, which terminates the running command and starts
a new one. Finally, theres signal, which only sends a signal; this can
be useful with programs that can reload their configuration without a
full restart.
The signal can be specified with the \--signal option.
Note that this option is scheduled to change its default to do-nothing
in the next major release. File an issue if you have any concerns.
**-r**, **\--restart**
: Restart the process if its still running
@ -632,7 +640,8 @@ notice.
Provide your own custom filter programs in jaq (similar to jq) syntax.
Programs are given an event in the same format as described in
\--emit-events-to and must return a boolean.
\--emit-events-to and must return a boolean. Invalid programs will make
watchexec fail to start; use -v to see program runtime errors.
In addition to the jaq stdlib, watchexec adds some custom filter
definitions:
@ -748,7 +757,25 @@ This prints the events that triggered the action when handling it (after
debouncing), in a human readable form. This is useful for debugging
filters.
Use -v when you need more diagnostic information.
Use -vvv instead when you need more diagnostic information.
**\--manual**
: Show the manual page
This shows the manual page for Watchexec, if the output is a terminal
and the man program is available. If not, the manual page is printed to
stdout in ROFF format (suitable for writing to a watchexec.1 file).
**\--completions**=*COMPLETIONS*
: Generate a shell completions script
Provides a completions script or configuration for the given shell. If
Watchexec is not distributed with pre-generated completions, you can use
this to generate them yourself.
Supported shells: bash, elvish, fish, nu, powershell, zsh.
**-v**, **\--verbose**
@ -784,24 +811,6 @@ If the path provided is a directory, a file will be created in that
directory. The file name will be the current date and time, in the
format watchexec.YYYY-MM-DDTHH-MM-SSZ.log.
**\--manual**
: Show the manual page
This shows the manual page for Watchexec, if the output is a terminal
and the man program is available. If not, the manual page is printed to
stdout in ROFF format (suitable for writing to a watchexec.1 file).
**\--completions**=*COMPLETIONS*
: Generate a shell completions script
Provides a completions script or configuration for the given shell. If
Watchexec is not distributed with pre-generated completions, you can use
this to generate them yourself.
Supported shells: bash, elvish, fish, nu, powershell, zsh.
**-h**, **\--help**
: Print help (see a summary with -h)
@ -852,7 +861,7 @@ Didnt expect this much output? Use the short -h flag to get short help.
# VERSION
v1.25.1
v2.1.1
# AUTHORS