mirror of
https://github.com/NotAShelf/stash.git
synced 2026-04-12 14:07:42 +00:00
Merge pull request #12 from NotAShelf/notashelf/push-lrzrznnxzlpw
stash: allow confirming destructive operations; add filter
This commit is contained in:
commit
40f4c1196d
6 changed files with 475 additions and 76 deletions
274
Cargo.lock
generated
274
Cargo.lock
generated
|
|
@ -198,6 +198,12 @@ version = "0.22.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "2.9.1"
|
||||
|
|
@ -309,6 +315,31 @@ version = "0.8.21"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
|
||||
|
||||
[[package]]
|
||||
name = "crossterm"
|
||||
version = "0.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"crossterm_winapi",
|
||||
"libc",
|
||||
"mio",
|
||||
"parking_lot",
|
||||
"signal-hook",
|
||||
"signal-hook-mio",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossterm_winapi"
|
||||
version = "0.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "acdd7c62a3665c7f6830a51635d9ac9b23ed385797f70a83bb8bafe9c572ab2b"
|
||||
dependencies = [
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dirs"
|
||||
version = "6.0.0"
|
||||
|
|
@ -336,6 +367,12 @@ version = "1.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2"
|
||||
|
||||
[[package]]
|
||||
name = "dyn-clone"
|
||||
version = "1.0.20"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d0881ea181b1df73ff77ffaaf9c7544ecc11e82fba9b5f27b262a3c73a332555"
|
||||
|
||||
[[package]]
|
||||
name = "env_filter"
|
||||
version = "0.1.3"
|
||||
|
|
@ -451,6 +488,15 @@ dependencies = [
|
|||
"pin-project-lite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fxhash"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c31b6d751ae2c7f11320402d34e41349dd1016f8d5d45e48c4312bc8625af50c"
|
||||
dependencies = [
|
||||
"byteorder",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.16"
|
||||
|
|
@ -520,6 +566,22 @@ dependencies = [
|
|||
"hashbrown",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "inquire"
|
||||
version = "0.7.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fddf93031af70e75410a2511ec04d49e758ed2f26dad3404a934e0fb45cc12a"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
"crossterm",
|
||||
"dyn-clone",
|
||||
"fxhash",
|
||||
"newline-converter",
|
||||
"once_cell",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "is_terminal_polyfill"
|
||||
version = "1.70.1"
|
||||
|
|
@ -568,7 +630,7 @@ version = "0.1.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "391290121bad3d37fbddad76d8f5d1c1c314cfc646d143d7e07a3086ddff0ce3"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.9.1",
|
||||
"libc",
|
||||
]
|
||||
|
||||
|
|
@ -595,6 +657,16 @@ version = "0.9.4"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12"
|
||||
|
||||
[[package]]
|
||||
name = "lock_api"
|
||||
version = "0.4.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "log"
|
||||
version = "0.4.27"
|
||||
|
|
@ -613,6 +685,27 @@ version = "0.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
|
||||
|
||||
[[package]]
|
||||
name = "mio"
|
||||
version = "0.8.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"wasi 0.11.1+wasi-snapshot-preview1",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "newline-converter"
|
||||
version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47b6b097ecb1cbfed438542d16e84fd7ad9b0c76c8a65b7f9039212a3d14dc7f"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nom"
|
||||
version = "7.1.3"
|
||||
|
|
@ -666,6 +759,29 @@ version = "2.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f38d5652c16fde515bb1ecef450ab0f6a219d619a7274976324d5e377f7dceba"
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.12.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
"parking_lot_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot_core"
|
||||
version = "0.9.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "paste"
|
||||
version = "1.0.15"
|
||||
|
|
@ -767,6 +883,15 @@ version = "5.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f"
|
||||
|
||||
[[package]]
|
||||
name = "redox_syscall"
|
||||
version = "0.5.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5407465600fb0548f1442edf71dd20683c6ed326200ace4b1ef0763521bb3b77"
|
||||
dependencies = [
|
||||
"bitflags 2.9.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "redox_users"
|
||||
version = "0.5.2"
|
||||
|
|
@ -835,7 +960,7 @@ version = "0.37.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "165ca6e57b20e1351573e3729b958bc62f0e48025386970b6e4d29e7a7e71f3f"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.9.1",
|
||||
"fallible-iterator",
|
||||
"fallible-streaming-iterator",
|
||||
"hashlink",
|
||||
|
|
@ -849,7 +974,7 @@ version = "0.38.44"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.9.1",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.4.15",
|
||||
|
|
@ -862,7 +987,7 @@ version = "1.0.8"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "11181fbabf243db407ef8df94a6ce0b2f9a733bd8be4ad02b4eda9602296cac8"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.9.1",
|
||||
"errno",
|
||||
"libc",
|
||||
"linux-raw-sys 0.9.4",
|
||||
|
|
@ -875,6 +1000,12 @@ version = "1.0.20"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f"
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49"
|
||||
|
||||
[[package]]
|
||||
name = "serde"
|
||||
version = "1.0.219"
|
||||
|
|
@ -913,6 +1044,27 @@ version = "1.3.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64"
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"signal-hook-registry",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-mio"
|
||||
version = "0.2.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34db1a06d485c9142248b7a054f034b349b212551f3dfd19c94d45a754a217cd"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"mio",
|
||||
"signal-hook",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "signal-hook-registry"
|
||||
version = "1.4.6"
|
||||
|
|
@ -961,7 +1113,9 @@ dependencies = [
|
|||
"dirs",
|
||||
"env_logger",
|
||||
"imagesize",
|
||||
"inquire",
|
||||
"log",
|
||||
"regex",
|
||||
"rmp-serde",
|
||||
"rusqlite",
|
||||
"serde",
|
||||
|
|
@ -979,9 +1133,9 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
|||
|
||||
[[package]]
|
||||
name = "syn"
|
||||
version = "2.0.104"
|
||||
version = "2.0.105"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40"
|
||||
checksum = "7bc3fcb250e53458e712715cf74285c1f889686520d79294a9ef3bd7aa1fc619"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
|
|
@ -1039,6 +1193,18 @@ version = "1.0.18"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-segmentation"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6ccf251212114b54433ec949fd6a7841275f9ada20dddd2f29e9ceea4501493"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||
|
||||
[[package]]
|
||||
name = "utf8parse"
|
||||
version = "0.2.2"
|
||||
|
|
@ -1085,7 +1251,7 @@ version = "0.31.11"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c66a47e840dc20793f2264eb4b3e4ecb4b75d91c0dd4af04b456128e0bdd449d"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.9.1",
|
||||
"rustix 1.0.8",
|
||||
"wayland-backend",
|
||||
"wayland-scanner",
|
||||
|
|
@ -1097,7 +1263,7 @@ version = "0.32.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efa790ed75fbfd71283bd2521a1cfdc022aabcc28bdcff00851f9e4ae88d9901"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.9.1",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-scanner",
|
||||
|
|
@ -1109,7 +1275,7 @@ version = "0.3.9"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "efd94963ed43cf9938a090ca4f7da58eb55325ec8200c3848963e98dc25b78ec"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.9.1",
|
||||
"wayland-backend",
|
||||
"wayland-client",
|
||||
"wayland-protocols",
|
||||
|
|
@ -1136,12 +1302,43 @@ dependencies = [
|
|||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
|
||||
dependencies = [
|
||||
"winapi-i686-pc-windows-gnu",
|
||||
"winapi-x86_64-pc-windows-gnu",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi-i686-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
|
||||
|
||||
[[package]]
|
||||
name = "winapi-x86_64-pc-windows-gnu"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
|
||||
|
||||
[[package]]
|
||||
name = "windows-link"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a"
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.59.0"
|
||||
|
|
@ -1160,6 +1357,21 @@ dependencies = [
|
|||
"windows-targets 0.53.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.52.6"
|
||||
|
|
@ -1193,6 +1405,12 @@ dependencies = [
|
|||
"windows_x86_64_msvc 0.53.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
|
|
@ -1205,6 +1423,12 @@ version = "0.53.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "86b8d5f90ddd19cb4a147a5fa63ca848db3df085e25fee3cc10b39b6eebae764"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
|
|
@ -1217,6 +1441,12 @@ version = "0.53.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c7651a1f62a11b8cbd5e0d42526e55f2c99886c77e007179efff86c2b137e66c"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
|
|
@ -1241,6 +1471,12 @@ version = "0.53.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9ce6ccbdedbf6d6354471319e781c0dfef054c81fbc7cf83f338a4296c0cae11"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
|
|
@ -1253,6 +1489,12 @@ version = "0.53.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "581fee95406bb13382d2f65cd4a908ca7b1e4c2f1917f143ba16efe98a589b5d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
|
|
@ -1265,6 +1507,12 @@ version = "0.53.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e55b5ac9ea33f2fc1716d1742db15574fd6fc8dadc51caab1c16a3d3b4190ba"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
|
|
@ -1277,6 +1525,12 @@ version = "0.53.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0a6e035dd0599267ce1ee132e51c27dd29437f63325753051e71dd9e42406c57"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
|
|
@ -1295,7 +1549,7 @@ version = "0.39.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6f42320e61fe2cfd34354ecb597f86f413484a798ba44a8ca1165c58d42da6c1"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"bitflags 2.9.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
|
|
|||
12
Cargo.toml
12
Cargo.toml
|
|
@ -9,20 +9,24 @@ repository = "https://github.com/notashelf/stash"
|
|||
rust-version = "1.85"
|
||||
|
||||
[dependencies]
|
||||
clap = { version = "4.5.44", features = ["derive"] }
|
||||
clap = { version = "4.5.45", features = ["derive"] }
|
||||
clap-verbosity-flag = "3.0.3"
|
||||
dirs = "6.0.0"
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
rmp-serde = "1.3.0"
|
||||
imagesize = "0.14"
|
||||
imagesize = "0.14.0"
|
||||
inquire = { default-features = false, version = "0.7.5", features = [
|
||||
"crossterm",
|
||||
] }
|
||||
log = "0.4.27"
|
||||
env_logger = "0.11.8"
|
||||
clap-verbosity-flag = "3.0.3"
|
||||
thiserror = "2.0.14"
|
||||
wl-clipboard-rs = "0.9.2"
|
||||
rusqlite = { version = "0.37.0", features = ["bundled"] }
|
||||
smol = "2.0.2"
|
||||
serde = { version = "1.0.219", features = ["derive"] }
|
||||
serde_json = "1.0.142"
|
||||
base64 = "0.22.1"
|
||||
regex = "1.11.1"
|
||||
|
||||
|
||||
[profile.release]
|
||||
|
|
|
|||
61
README.md
61
README.md
|
|
@ -4,6 +4,18 @@ Wayland clipboard "manager" with fast persistent history and multi-media
|
|||
support. Stores and previews clipboard entries (text, images) on the command
|
||||
line.
|
||||
|
||||
## Features
|
||||
|
||||
- Stores clipboard entries with automatic MIME detection
|
||||
- Fast persistent storage using SQLite
|
||||
- List, search, decode, delete, and wipe clipboard history
|
||||
- Backwards compatible with Cliphist TSV format
|
||||
- Import clipboard history from TSV (e.g., from `cliphist list`)
|
||||
- Image preview (shows dimensions and format)
|
||||
- Deduplication and entry limit control
|
||||
- Text previews with customizable width
|
||||
- Sensitive clipboard filtering via regex (see below)
|
||||
|
||||
## Installation
|
||||
|
||||
### With Nix
|
||||
|
|
@ -59,23 +71,18 @@ releases are made when a version gets tagged, and are available under
|
|||
cargo install --git https://github.com/notashelf/stash
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
- Stores clipboard entries with automatic MIME detection
|
||||
- Fast persistent storage using SQLite
|
||||
- List, search, decode, delete, and wipe clipboard history
|
||||
- Backwards compatible with Cliphist TSV format
|
||||
- Import clipboard history from TSV (e.g., from `cliphist list`)
|
||||
- Image preview (shows dimensions and format)
|
||||
- Deduplication and entry limit control
|
||||
- Text previews with customizable width
|
||||
|
||||
## Usage
|
||||
|
||||
Command interface is only slightly different from Cliphist. In most cases, it
|
||||
will be as simple as replacing `cliphist` with `stash` in your commands, aliases
|
||||
or scripts.
|
||||
|
||||
> [!NOTE]
|
||||
> It is not a priority to provide 1:1 backwards compatibility with Cliphist.
|
||||
> While the interface is _almost_ identical, Stash chooses to build upon
|
||||
> Cliphist's design and extend existing design choices. See
|
||||
> [Migrating from Cliphist](#migrating-from-cliphist) for more details.
|
||||
|
||||
### Store an entry
|
||||
|
||||
```bash
|
||||
|
|
@ -132,11 +139,41 @@ commands `--help` text for more details. The following are generally standard:
|
|||
- `--preview-width <N>`: Text preview max width for `list`
|
||||
- `--version`: Print the current version and exit
|
||||
|
||||
#### Sensitive Clipboard Filtering
|
||||
|
||||
Stash can be configured to avoid storing clipboard entries that match a
|
||||
sensitive pattern, using a regular expression. This is useful for preventing
|
||||
accidental storage of secrets, passwords, or other sensitive data. You don't
|
||||
want sensitive data ending up in your persistent clipboard, right?
|
||||
|
||||
The filter can be configured in one of two ways:
|
||||
|
||||
- **Environment variable**: Set `STASH_SENSITIVE_REGEX` to a valid regex
|
||||
pattern. If clipboard text matches, it will not be stored.
|
||||
- **Systemd LoadCredential**: If running as a service, you can provide a regex
|
||||
pattern via a credential file. For example, add to your `stash.service`:
|
||||
|
||||
```ini
|
||||
LoadCredential=clipboard_filter:/etc/stash/clipboard_filter
|
||||
```
|
||||
|
||||
The file `/etc/stash/clipboard_filter` should contain your regex pattern (no
|
||||
quotes). This is done automatically in the vendored Systemd service. Remember
|
||||
to set the appropriate file permissions if using this option.
|
||||
|
||||
The service will check the credential file first, then the environment variable.
|
||||
If a clipboard entry matches the regex, it will be skipped and a warning will be
|
||||
logged.
|
||||
|
||||
**Example regex to block common password patterns**:
|
||||
|
||||
- `(password|secret|api[_-]?key|token)[=: ]+[^\s]+`
|
||||
|
||||
## Tips & Tricks
|
||||
|
||||
### Migrating from Cliphist
|
||||
|
||||
Stash is designed to be a drop-in replacement for Cliphist, with only minor
|
||||
Stash was designed to be a drop-in replacement for Cliphist, with only minor
|
||||
improvements. If you are migrating from Cliphist, here are a few things you
|
||||
should know.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,9 +1,12 @@
|
|||
use std::env;
|
||||
use std::fmt;
|
||||
use std::fs;
|
||||
use std::io::{BufRead, BufReader, Read, Write};
|
||||
use std::str;
|
||||
|
||||
use imagesize::{ImageSize, ImageType};
|
||||
use log::{error, info};
|
||||
use log::{error, info, warn};
|
||||
use regex::Regex;
|
||||
|
||||
use rusqlite::{Connection, OptionalExtension, params};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
|
@ -176,6 +179,18 @@ impl ClipboardDb for SqliteClipboardDb {
|
|||
other => other,
|
||||
};
|
||||
|
||||
// Try to load regex from systemd credential file, then env var
|
||||
let regex = load_sensitive_regex();
|
||||
if let Some(re) = regex {
|
||||
// Only check text data
|
||||
if let Ok(s) = std::str::from_utf8(&buf) {
|
||||
if re.is_match(s) {
|
||||
warn!("Clipboard entry matches sensitive regex, skipping store.");
|
||||
return Err(StashError::Store("Filtered by sensitive regex".to_string()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.deduplicate(&buf, max_dedupe_search)?;
|
||||
|
||||
self.conn
|
||||
|
|
@ -375,6 +390,31 @@ impl ClipboardDb for SqliteClipboardDb {
|
|||
}
|
||||
|
||||
// Helper functions
|
||||
|
||||
/// Try to load a sensitive regex from systemd credential or env.
|
||||
///
|
||||
/// # Returns
|
||||
/// `Some(Regex)` if present and valid, `None` otherwise.
|
||||
fn load_sensitive_regex() -> Option<Regex> {
|
||||
if let Ok(regex_path) = env::var("CREDENTIALS_DIRECTORY") {
|
||||
let file = format!("{regex_path}/clipboard_filter");
|
||||
if let Ok(contents) = fs::read_to_string(&file) {
|
||||
if let Ok(re) = Regex::new(contents.trim()) {
|
||||
return Some(re);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to an environment variable
|
||||
if let Ok(pattern) = env::var("STASH_SENSITIVE_REGEX") {
|
||||
if let Ok(re) = Regex::new(&pattern) {
|
||||
return Some(re);
|
||||
}
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
pub fn extract_id(input: &str) -> Result<u64, &'static str> {
|
||||
let id_str = input.split('\t').next().unwrap_or("");
|
||||
id_str.parse().map_err(|_| "invalid id")
|
||||
|
|
|
|||
161
src/main.rs
161
src/main.rs
|
|
@ -6,6 +6,7 @@ use std::{
|
|||
};
|
||||
|
||||
use clap::{CommandFactory, Parser, Subcommand};
|
||||
use inquire::Confirm;
|
||||
|
||||
mod commands;
|
||||
mod db;
|
||||
|
|
@ -39,6 +40,10 @@ struct Cli {
|
|||
#[arg(long)]
|
||||
db_path: Option<PathBuf>,
|
||||
|
||||
/// Ask for confirmation before destructive operations
|
||||
#[arg(long)]
|
||||
ask: bool,
|
||||
|
||||
#[command(flatten)]
|
||||
verbosity: clap_verbosity_flag::Verbosity,
|
||||
}
|
||||
|
|
@ -67,16 +72,28 @@ enum Command {
|
|||
/// Explicitly specify type: "id" or "query"
|
||||
#[arg(long, value_parser = ["id", "query"])]
|
||||
r#type: Option<String>,
|
||||
|
||||
/// Ask for confirmation before deleting
|
||||
#[arg(long)]
|
||||
ask: bool,
|
||||
},
|
||||
|
||||
/// Wipe all clipboard history
|
||||
Wipe,
|
||||
Wipe {
|
||||
/// Ask for confirmation before wiping
|
||||
#[arg(long)]
|
||||
ask: bool,
|
||||
},
|
||||
|
||||
/// Import clipboard data from stdin (default: TSV format)
|
||||
Import {
|
||||
/// Explicitly specify format: "tsv" (default)
|
||||
#[arg(long, value_parser = ["tsv"])]
|
||||
r#type: Option<String>,
|
||||
|
||||
/// Ask for confirmation before importing
|
||||
#[arg(long)]
|
||||
ask: bool,
|
||||
},
|
||||
|
||||
/// Watch clipboard for changes and store automatically
|
||||
|
|
@ -93,6 +110,7 @@ fn report_error<T>(result: Result<T, impl std::fmt::Display>, context: &str) ->
|
|||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)] // whatever
|
||||
fn main() {
|
||||
smol::block_on(async {
|
||||
let cli = Cli::parse();
|
||||
|
|
@ -144,17 +162,16 @@ fn main() {
|
|||
"Failed to list entries",
|
||||
);
|
||||
}
|
||||
"json" => {
|
||||
// Implement JSON output
|
||||
match db.list_json() {
|
||||
Ok(json) => {
|
||||
println!("{json}");
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to list entries as JSON: {e}");
|
||||
}
|
||||
|
||||
"json" => match db.list_json() {
|
||||
Ok(json) => {
|
||||
println!("{json}");
|
||||
}
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to list entries as JSON: {e}");
|
||||
}
|
||||
},
|
||||
|
||||
_ => {
|
||||
log::error!("Unsupported format: {format}");
|
||||
}
|
||||
|
|
@ -166,52 +183,98 @@ fn main() {
|
|||
"Failed to decode entry",
|
||||
);
|
||||
}
|
||||
Some(Command::Delete { arg, r#type }) => match (arg, r#type.as_deref()) {
|
||||
(Some(s), Some("id")) => {
|
||||
if let Ok(id) = s.parse::<u64>() {
|
||||
use std::io::Cursor;
|
||||
report_error(
|
||||
db.delete(Cursor::new(format!("{id}\n"))),
|
||||
"Failed to delete entry by id",
|
||||
);
|
||||
} else {
|
||||
log::error!("Argument is not a valid id");
|
||||
Some(Command::Delete { arg, r#type, ask }) => {
|
||||
let mut should_proceed = true;
|
||||
if ask {
|
||||
should_proceed =
|
||||
Confirm::new("Are you sure you want to delete clipboard entries?")
|
||||
.with_default(false)
|
||||
.prompt()
|
||||
.unwrap_or(false);
|
||||
|
||||
if !should_proceed {
|
||||
log::info!("Aborted by user.");
|
||||
}
|
||||
}
|
||||
(Some(s), Some("query")) => {
|
||||
report_error(db.query_delete(&s), "Failed to delete entry by query");
|
||||
}
|
||||
(Some(s), None) => {
|
||||
if let Ok(id) = s.parse::<u64>() {
|
||||
use std::io::Cursor;
|
||||
report_error(
|
||||
db.delete(Cursor::new(format!("{id}\n"))),
|
||||
"Failed to delete entry by id",
|
||||
);
|
||||
} else {
|
||||
report_error(db.query_delete(&s), "Failed to delete entry by query");
|
||||
if should_proceed {
|
||||
match (arg, r#type.as_deref()) {
|
||||
(Some(s), Some("id")) => {
|
||||
if let Ok(id) = s.parse::<u64>() {
|
||||
use std::io::Cursor;
|
||||
report_error(
|
||||
db.delete(Cursor::new(format!("{id}\n"))),
|
||||
"Failed to delete entry by id",
|
||||
);
|
||||
} else {
|
||||
log::error!("Argument is not a valid id");
|
||||
}
|
||||
}
|
||||
(Some(s), Some("query")) => {
|
||||
report_error(db.query_delete(&s), "Failed to delete entry by query");
|
||||
}
|
||||
(Some(s), None) => {
|
||||
if let Ok(id) = s.parse::<u64>() {
|
||||
use std::io::Cursor;
|
||||
report_error(
|
||||
db.delete(Cursor::new(format!("{id}\n"))),
|
||||
"Failed to delete entry by id",
|
||||
);
|
||||
} else {
|
||||
report_error(
|
||||
db.query_delete(&s),
|
||||
"Failed to delete entry by query",
|
||||
);
|
||||
}
|
||||
}
|
||||
(None, _) => {
|
||||
report_error(
|
||||
db.delete(io::stdin()),
|
||||
"Failed to delete entry from stdin",
|
||||
);
|
||||
}
|
||||
(_, Some(_)) => {
|
||||
log::error!("Unknown type for --type. Use \"id\" or \"query\".");
|
||||
}
|
||||
}
|
||||
}
|
||||
(None, _) => {
|
||||
report_error(db.delete(io::stdin()), "Failed to delete entry from stdin");
|
||||
}
|
||||
Some(Command::Wipe { ask }) => {
|
||||
let mut should_proceed = true;
|
||||
if ask {
|
||||
should_proceed =
|
||||
Confirm::new("Are you sure you want to wipe all clipboard history?")
|
||||
.with_default(false)
|
||||
.prompt()
|
||||
.unwrap_or(false);
|
||||
if !should_proceed {
|
||||
log::info!("Aborted by user.");
|
||||
}
|
||||
}
|
||||
(_, Some(_)) => {
|
||||
log::error!("Unknown type for --type. Use \"id\" or \"query\".");
|
||||
if should_proceed {
|
||||
report_error(db.wipe(), "Failed to wipe database");
|
||||
}
|
||||
},
|
||||
Some(Command::Wipe) => {
|
||||
report_error(db.wipe(), "Failed to wipe database");
|
||||
}
|
||||
|
||||
Some(Command::Import { r#type }) => {
|
||||
// Default format is TSV (Cliphist compatible)
|
||||
let format = r#type.as_deref().unwrap_or("tsv");
|
||||
match format {
|
||||
"tsv" => {
|
||||
db.import_tsv(io::stdin());
|
||||
Some(Command::Import { r#type, ask }) => {
|
||||
let mut should_proceed = true;
|
||||
if ask {
|
||||
should_proceed = Confirm::new("Are you sure you want to import clipboard data? This may overwrite existing entries.")
|
||||
.with_default(false)
|
||||
.prompt()
|
||||
.unwrap_or(false);
|
||||
if !should_proceed {
|
||||
log::info!("Aborted by user.");
|
||||
}
|
||||
_ => {
|
||||
log::error!("Unsupported import format: {format}");
|
||||
}
|
||||
if should_proceed {
|
||||
let format = r#type.as_deref().unwrap_or("tsv");
|
||||
match format {
|
||||
"tsv" => {
|
||||
db.import_tsv(io::stdin());
|
||||
}
|
||||
_ => {
|
||||
log::error!("Unsupported import format: {format}");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -220,7 +283,7 @@ fn main() {
|
|||
}
|
||||
None => {
|
||||
if let Err(e) = Cli::command().print_help() {
|
||||
eprintln!("Failed to print help: {e}");
|
||||
log::error!("Failed to print help: {e}");
|
||||
}
|
||||
println!();
|
||||
}
|
||||
|
|
|
|||
1
vendor/stash.service
vendored
1
vendor/stash.service
vendored
|
|
@ -8,6 +8,7 @@ Requisite=graphical-session.target
|
|||
Type=simple
|
||||
ExecStart=stash watch
|
||||
Restart=on-failure
|
||||
LoadCredential=clipboard_filter:/etc/stash/clipboard_filter
|
||||
|
||||
[Install]
|
||||
WantedBy=graphical-session.target
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue