Bugfixes, show time ago, perf improvements

Also allow unique listing and more ergonomic cwd usage
This commit is contained in:
Ellie Huxtable 2021-04-21 18:13:51 +01:00
parent a9b117aad7
commit 4a50ce3666
20 changed files with 586 additions and 347 deletions

382
Cargo.lock generated
View file

@ -51,9 +51,9 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]] [[package]]
name = "async-trait" name = "async-trait"
version = "0.1.49" version = "0.1.50"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "589652ce7ccb335d1e7ecb3be145425702b290dbcb7029bbeaae263fc1d87b48" checksum = "0b98e84bbb4cbcdd97da190ba0c58a1bb0de2c1fdf67d159e192ed766aeca722"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -94,13 +94,12 @@ dependencies = [
"cli-table", "cli-table",
"directories", "directories",
"eyre", "eyre",
"fern",
"fork", "fork",
"humantime", "humantime 2.1.0",
"indicatif", "indicatif",
"itertools", "itertools",
"log", "log",
"reqwest", "pretty_env_logger",
"rusqlite", "rusqlite",
"serde 1.0.125", "serde 1.0.125",
"serde_derive", "serde_derive",
@ -125,7 +124,7 @@ dependencies = [
"directories", "directories",
"eyre", "eyre",
"fern", "fern",
"humantime", "humantime 2.1.0",
"indicatif", "indicatif",
"itertools", "itertools",
"log", "log",
@ -180,7 +179,6 @@ dependencies = [
"log", "log",
"parse_duration", "parse_duration",
"rand 0.8.3", "rand 0.8.3",
"reqwest",
"rmp-serde", "rmp-serde",
"rust-crypto", "rust-crypto",
"serde 1.0.125", "serde 1.0.125",
@ -270,9 +268,9 @@ dependencies = [
[[package]] [[package]]
name = "build_const" name = "build_const"
version = "0.2.1" version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39" checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7"
[[package]] [[package]]
name = "bumpalo" name = "bumpalo"
@ -282,9 +280,9 @@ checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe"
[[package]] [[package]]
name = "byteorder" name = "byteorder"
version = "1.4.2" version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]] [[package]]
name = "bytes" name = "bytes"
@ -300,9 +298,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.66" version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48" checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@ -326,9 +324,9 @@ dependencies = [
[[package]] [[package]]
name = "chrono-english" name = "chrono-english"
version = "0.1.4" version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4233ee19352739cfdcb5d7c2085005b166f6170ef2845ed9eef27a8fa5f95206" checksum = "0be5180df5f7c41fc2416bc038bc8d78d44db8136c415b94ccbc95f523dc38e9"
dependencies = [ dependencies = [
"chrono", "chrono",
"scanlex", "scanlex",
@ -402,9 +400,9 @@ dependencies = [
[[package]] [[package]]
name = "console" name = "console"
version = "0.14.0" version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7cc80946b3480f421c2f17ed1cb841753a371c7c5104f51d507e13f532c856aa" checksum = "3993e6445baa160675931ec041a5e03ca84b9c6e32a056150d3aa2bdda0a1f45"
dependencies = [ dependencies = [
"encode_unicode", "encode_unicode",
"lazy_static", "lazy_static",
@ -421,22 +419,6 @@ version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc" checksum = "245097e9a4535ee1e3e3931fcfcd55a796a44c643e8596ff6566d68f09b87bbc"
[[package]]
name = "core-foundation"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0a89e2ae426ea83155dccf10c0fa6b1463ef6d5fcb44cee0b224a408fa640a62"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea221b5284a47e40033bf9b66f35f984ec0ea2931eb03505246cd27a963f981b"
[[package]] [[package]]
name = "cpuid-bool" name = "cpuid-bool"
version = "0.1.2" version = "0.1.2"
@ -474,9 +456,9 @@ dependencies = [
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.1" version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"cfg-if", "cfg-if",
@ -601,6 +583,19 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "env_logger"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "44533bbbb3bb3c1fa17d9f2e4e38bbbaf8396ba82193c4cb1b6445d711445d36"
dependencies = [
"atty",
"humantime 1.3.0",
"log",
"regex",
"termcolor",
]
[[package]] [[package]]
name = "eyre" name = "eyre"
version = "0.6.5" version = "0.6.5"
@ -639,21 +634,6 @@ version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
[[package]]
name = "foreign-types"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1"
dependencies = [
"foreign-types-shared",
]
[[package]]
name = "foreign-types-shared"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b"
[[package]] [[package]]
name = "fork" name = "fork"
version = "0.1.18" version = "0.1.18"
@ -935,9 +915,9 @@ dependencies = [
[[package]] [[package]]
name = "httparse" name = "httparse"
version = "1.3.5" version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "615caabe2c3160b313d52ccc905335f4ed5f10881dd63dc5699d47e90be85691" checksum = "4a1ce40d6fc9764887c2fdc7305c3dcc429ba11ff981c1509416afd5697e4437"
[[package]] [[package]]
name = "httpdate" name = "httpdate"
@ -945,6 +925,15 @@ version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47" checksum = "494b4d60369511e7dea41cf646832512a94e542f68bb9c49e54518e0f468eb47"
[[package]]
name = "humantime"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "df004cfca50ef23c36850aaaa59ad52cc70d0e90243c3c7737a4dd32dc7a3c4f"
dependencies = [
"quick-error",
]
[[package]] [[package]]
name = "humantime" name = "humantime"
version = "2.1.0" version = "2.1.0"
@ -976,23 +965,25 @@ dependencies = [
] ]
[[package]] [[package]]
name = "hyper-tls" name = "hyper-rustls"
version = "0.5.0" version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905" checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64"
dependencies = [ dependencies = [
"bytes", "futures-util",
"hyper", "hyper",
"native-tls", "log",
"rustls",
"tokio", "tokio",
"tokio-native-tls", "tokio-rustls",
"webpki",
] ]
[[package]] [[package]]
name = "idna" name = "idna"
version = "0.2.2" version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89829a5d69c23d348314a7ac337fe39173b61149a9864deabd260983aed48c21" checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
dependencies = [ dependencies = [
"matches", "matches",
"unicode-bidi", "unicode-bidi",
@ -1001,15 +992,15 @@ dependencies = [
[[package]] [[package]]
name = "indenter" name = "indenter"
version = "0.3.2" version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f4d5eb2e114fec2b7fe0fadc22888ad2658789bb7acac4dbee9cf8389f971ec8" checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "1.6.1" version = "1.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb1fa934250de4de8aef298d81c729a7d33d8c239daa3a7575e6b92bfc7313b" checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"hashbrown", "hashbrown",
@ -1096,9 +1087,9 @@ dependencies = [
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.86" version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c" checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41"
[[package]] [[package]]
name = "libsodium-sys" name = "libsodium-sys"
@ -1130,9 +1121,9 @@ checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.2" version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312" checksum = "5a3c91c24eae6777794bb1997ad98bbb87daf92890acab859f7eaa4320333176"
dependencies = [ dependencies = [
"scopeguard", "scopeguard",
] ]
@ -1231,24 +1222,6 @@ dependencies = [
"twoway", "twoway",
] ]
[[package]]
name = "native-tls"
version = "0.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8d96b2e1c8da3957d58100b09f102c6d9cfdfced01b7ec5a8974044bb09dbd4"
dependencies = [
"lazy_static",
"libc",
"log",
"openssl",
"openssl-probe",
"openssl-sys",
"schannel",
"security-framework",
"security-framework-sys",
"tempfile",
]
[[package]] [[package]]
name = "nom" name = "nom"
version = "5.1.2" version = "5.1.2"
@ -1392,9 +1365,9 @@ checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.5.2" version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0" checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
[[package]] [[package]]
name = "opaque-debug" name = "opaque-debug"
@ -1402,39 +1375,6 @@ version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5" checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "openssl"
version = "0.10.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a61075b62a23fef5a29815de7536d940aa35ce96d18ce0cc5076272db678a577"
dependencies = [
"bitflags",
"cfg-if",
"foreign-types",
"libc",
"once_cell",
"openssl-sys",
]
[[package]]
name = "openssl-probe"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77af24da69f9d9341038eba93a073b1fdaaa1b788221b00a69bce9e762cb32de"
[[package]]
name = "openssl-sys"
version = "0.9.61"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "313752393519e876837e09e1fa183ddef0be7735868dced3196f4472d536277f"
dependencies = [
"autocfg",
"cc",
"libc",
"pkg-config",
"vcpkg",
]
[[package]] [[package]]
name = "parking_lot" name = "parking_lot"
version = "0.11.1" version = "0.11.1"
@ -1455,7 +1395,7 @@ dependencies = [
"cfg-if", "cfg-if",
"instant", "instant",
"libc", "libc",
"redox_syscall 0.2.4", "redox_syscall 0.2.6",
"smallvec", "smallvec",
"winapi", "winapi",
] ]
@ -1479,18 +1419,18 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]] [[package]]
name = "pin-project" name = "pin-project"
version = "1.0.6" version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc174859768806e91ae575187ada95c91a29e96a98dc5d2cd9a1fed039501ba6" checksum = "c7509cc106041c40a4518d2af7a61530e1eed0e6285296a3d8c5472806ccc4a4"
dependencies = [ dependencies = [
"pin-project-internal", "pin-project-internal",
] ]
[[package]] [[package]]
name = "pin-project-internal" name = "pin-project-internal"
version = "1.0.6" version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a490329918e856ed1b083f244e3bfe2d8c4f336407e4ea9e1a9f479ff09049e5" checksum = "48c950132583b500556b1efd71d45b319029f2b71518d979fcc208e16b42426f"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@ -1521,6 +1461,16 @@ version = "0.2.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
[[package]]
name = "pretty_env_logger"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "926d36b9553851b8b0005f1275891b392ee4d2d833852c417ed025477350fb9d"
dependencies = [
"env_logger",
"log",
]
[[package]] [[package]]
name = "proc-macro-error" name = "proc-macro-error"
version = "1.0.4" version = "1.0.4"
@ -1723,9 +1673,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
[[package]] [[package]]
name = "redox_syscall" name = "redox_syscall"
version = "0.2.4" version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05ec8ca9416c5ea37062b502703cd7fcb207736bc294f6e0cf367ac6fc234570" checksum = "8270314b5ccceb518e7e578952f0b72b88222d02e8f77f5ecf7abbb673539041"
dependencies = [ dependencies = [
"bitflags", "bitflags",
] ]
@ -1736,7 +1686,7 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f" checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f"
dependencies = [ dependencies = [
"redox_syscall 0.2.4", "redox_syscall 0.2.6",
] ]
[[package]] [[package]]
@ -1757,19 +1707,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64" checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
dependencies = [ dependencies = [
"getrandom 0.2.2", "getrandom 0.2.2",
"redox_syscall 0.2.4", "redox_syscall 0.2.6",
] ]
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.4.3" version = "1.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a" checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
"regex-syntax", "regex-syntax",
"thread_local",
] ]
[[package]] [[package]]
@ -1783,9 +1732,9 @@ dependencies = [
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.6.22" version = "0.6.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581" checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548"
[[package]] [[package]]
name = "remove_dir_all" name = "remove_dir_all"
@ -1810,27 +1759,43 @@ dependencies = [
"http", "http",
"http-body", "http-body",
"hyper", "hyper",
"hyper-tls", "hyper-rustls",
"ipnet", "ipnet",
"js-sys", "js-sys",
"lazy_static", "lazy_static",
"log", "log",
"mime", "mime",
"native-tls",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"rustls",
"serde 1.0.125", "serde 1.0.125",
"serde_json", "serde_json",
"serde_urlencoded", "serde_urlencoded",
"tokio", "tokio",
"tokio-native-tls", "tokio-rustls",
"url", "url",
"wasm-bindgen", "wasm-bindgen",
"wasm-bindgen-futures", "wasm-bindgen-futures",
"web-sys", "web-sys",
"webpki-roots",
"winreg", "winreg",
] ]
[[package]]
name = "ring"
version = "0.16.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3053cf52e236a3ed746dfc745aa9cacf1b791d846bdaf412f60a8d7d6e17c8fc"
dependencies = [
"cc",
"libc",
"once_cell",
"spin",
"untrusted",
"web-sys",
"winapi",
]
[[package]] [[package]]
name = "rmp" name = "rmp"
version = "0.8.10" version = "0.8.10"
@ -1904,6 +1869,19 @@ version = "0.3.24"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda" checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
[[package]]
name = "rustls"
version = "0.19.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "35edb675feee39aec9c99fa5ff985081995a06d594114ae14cbe797ad7b7a6d7"
dependencies = [
"base64",
"log",
"ring",
"sct",
"webpki",
]
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.5" version = "1.0.5"
@ -1922,16 +1900,6 @@ version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "088c5d71572124929ea7549a8ce98e1a6fd33d0a38367b09027b382e67c033db" checksum = "088c5d71572124929ea7549a8ce98e1a6fd33d0a38367b09027b382e67c033db"
[[package]]
name = "schannel"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f05ba609c234e60bee0d547fe94a4c7e9da733d1c962cf6e59efa4cd9c8bc75"
dependencies = [
"lazy_static",
"winapi",
]
[[package]] [[package]]
name = "scoped-tls" name = "scoped-tls"
version = "1.0.0" version = "1.0.0"
@ -1945,26 +1913,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]] [[package]]
name = "security-framework" name = "sct"
version = "2.2.0" version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3670b1d2fdf6084d192bc71ead7aabe6c06aa2ea3fbd9cc3ac111fa5c2b1bd84" checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce"
dependencies = [ dependencies = [
"bitflags", "ring",
"core-foundation", "untrusted",
"core-foundation-sys",
"libc",
"security-framework-sys",
]
[[package]]
name = "security-framework-sys"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3676258fd3cfe2c9a0ec99ce3038798d847ce3e4bb17746373eb9f0f1ac16339"
dependencies = [
"core-foundation-sys",
"libc",
] ]
[[package]] [[package]]
@ -2074,9 +2029,9 @@ dependencies = [
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.2" version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8" checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527"
[[package]] [[package]]
name = "smallvec" name = "smallvec"
@ -2105,6 +2060,12 @@ dependencies = [
"serde 1.0.125", "serde 1.0.125",
] ]
[[package]]
name = "spin"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]] [[package]]
name = "sqlformat" name = "sqlformat"
version = "0.1.6" version = "0.1.6"
@ -2162,6 +2123,7 @@ dependencies = [
"parking_lot", "parking_lot",
"percent-encoding", "percent-encoding",
"rand 0.8.3", "rand 0.8.3",
"rustls",
"serde 1.0.125", "serde 1.0.125",
"serde_json", "serde_json",
"sha-1", "sha-1",
@ -2174,6 +2136,8 @@ dependencies = [
"tokio-stream", "tokio-stream",
"url", "url",
"uuid", "uuid",
"webpki",
"webpki-roots",
"whoami", "whoami",
] ]
@ -2202,10 +2166,9 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6ae97ab05063ed515cdc23d90253213aa24dda0a288c5ec079af3d10f9771bc" checksum = "b6ae97ab05063ed515cdc23d90253213aa24dda0a288c5ec079af3d10f9771bc"
dependencies = [ dependencies = [
"native-tls",
"once_cell", "once_cell",
"tokio", "tokio",
"tokio-native-tls", "tokio-rustls",
] ]
[[package]] [[package]]
@ -2286,7 +2249,7 @@ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
"rand 0.8.3", "rand 0.8.3",
"redox_syscall 0.2.4", "redox_syscall 0.2.6",
"remove_dir_all", "remove_dir_all",
"winapi", "winapi",
] ]
@ -2318,7 +2281,7 @@ checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e"
dependencies = [ dependencies = [
"libc", "libc",
"numtoa", "numtoa",
"redox_syscall 0.2.4", "redox_syscall 0.2.6",
"redox_termios", "redox_termios",
] ]
@ -2351,15 +2314,6 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "thread_local"
version = "1.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
dependencies = [
"once_cell",
]
[[package]] [[package]]
name = "time" name = "time"
version = "0.1.43" version = "0.1.43"
@ -2372,9 +2326,9 @@ dependencies = [
[[package]] [[package]]
name = "tinyvec" name = "tinyvec"
version = "1.1.1" version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "317cca572a0e89c3ce0ca1f1bdc9369547fe318a683418e42ac8f59d14701023" checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342"
dependencies = [ dependencies = [
"tinyvec_macros", "tinyvec_macros",
] ]
@ -2387,9 +2341,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.4.0" version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "134af885d758d645f0f0505c9a8b3f9bf8a348fd822e112ab5248138348f1722" checksum = "83f0c8e7c0addab50b663055baf787d0af7f413a46e6e7fb9559a4e4db7137a5"
dependencies = [ dependencies = [
"autocfg", "autocfg",
"bytes", "bytes",
@ -2417,13 +2371,14 @@ dependencies = [
] ]
[[package]] [[package]]
name = "tokio-native-tls" name = "tokio-rustls"
version = "0.3.0" version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b" checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6"
dependencies = [ dependencies = [
"native-tls", "rustls",
"tokio", "tokio",
"webpki",
] ]
[[package]] [[package]]
@ -2452,9 +2407,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-util" name = "tokio-util"
version = "0.6.5" version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5143d049e85af7fbc36f5454d990e62c2df705b3589f123b71f441b6b59f443f" checksum = "940a12c99365c31ea8dd9ba04ec1be183ffe4920102bb7122c2f515437601e8e"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-core", "futures-core",
@ -2549,9 +2504,9 @@ dependencies = [
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.12.0" version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
[[package]] [[package]]
name = "unicase" name = "unicase"
@ -2564,9 +2519,9 @@ dependencies = [
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.4" version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0"
dependencies = [ dependencies = [
"matches", "matches",
] ]
@ -2604,6 +2559,12 @@ version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e" checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
[[package]]
name = "untrusted"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
[[package]] [[package]]
name = "url" name = "url"
version = "2.2.1" version = "2.2.1"
@ -2624,9 +2585,9 @@ checksum = "c9232eb53352b4442e40d7900465dfc534e8cb2dc8f18656fcb2ac16112b5593"
[[package]] [[package]]
name = "utf-8" name = "utf-8"
version = "0.7.5" version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]] [[package]]
name = "uuid" name = "uuid"
@ -2639,9 +2600,9 @@ dependencies = [
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.11" version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb" checksum = "cbdbff6266a24120518560b5dc983096efb98462e51d0d68169895b237be3e5d"
[[package]] [[package]]
name = "vec_map" name = "vec_map"
@ -2651,9 +2612,9 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.2" version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed" checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]] [[package]]
name = "want" name = "want"
@ -2784,6 +2745,25 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "webpki"
version = "0.21.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8e38c0608262c46d4a56202ebabdeb094cef7e560ca7a226c6bf055188aa4ea"
dependencies = [
"ring",
"untrusted",
]
[[package]]
name = "webpki-roots"
version = "0.21.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aabe153544e473b775453675851ecc86863d2a81d786d741f6b76778f2a48940"
dependencies = [
"webpki",
]
[[package]] [[package]]
name = "whoami" name = "whoami"
version = "1.1.2" version = "1.1.2"

View file

@ -15,7 +15,7 @@ atuin-client = { path = "atuin-client", version = "0.6.0" }
atuin-common = { path = "atuin-common", version = "0.6.0" } atuin-common = { path = "atuin-common", version = "0.6.0" }
log = "0.4" log = "0.4"
fern = {version = "0.6.0", features = ["colored"] } pretty_env_logger = "0.4"
chrono = { version = "0.4", features = ["serde"] } chrono = { version = "0.4", features = ["serde"] }
eyre = "0.6" eyre = "0.6"
structopt = "0.3" structopt = "0.3"
@ -33,7 +33,6 @@ tokio = { version = "1", features = ["full"] }
async-trait = "0.1.49" async-trait = "0.1.49"
chrono-english = "0.1.4" chrono-english = "0.1.4"
cli-table = "0.4" cli-table = "0.4"
reqwest = { version = "0.11", features = ["blocking", "json"] }
base64 = "0.13.0" base64 = "0.13.0"
humantime = "2.1.0" humantime = "2.1.0"

View file

@ -17,6 +17,12 @@ COPY --from=cacher $CARGO_HOME $CARGO_HOME
RUN cargo build --release --bin atuin RUN cargo build --release --bin atuin
FROM debian:buster-slim as runtime FROM debian:buster-slim as runtime
WORKDIR app WORKDIR app
ENV TZ=Etc/UTC
ENV RUST_LOG=info
ENV ATUIN_CONFIG_DIR=/config
COPY --from=builder /app/target/release/atuin /usr/local/bin COPY --from=builder /app/target/release/atuin /usr/local/bin
ENTRYPOINT ["/usr/local/bin/atuin"] ENTRYPOINT ["/usr/local/bin/atuin"]

View file

@ -26,7 +26,7 @@ serde = "1.0.125"
serde_json = "1.0.64" serde_json = "1.0.64"
rmp-serde = "0.15.4" rmp-serde = "0.15.4"
sodiumoxide = "0.2.6" sodiumoxide = "0.2.6"
reqwest = { version = "0.11", features = ["blocking", "json"] } reqwest = { version = "0.11", features = ["blocking", "json", "rustls-tls"], default-features = false }
base64 = "0.13.0" base64 = "0.13.0"
parse_duration = "2.1.1" parse_duration = "2.1.1"
rand = "0.8.3" rand = "0.8.3"

View file

@ -1,15 +1,24 @@
use std::collections::HashMap;
use chrono::Utc; use chrono::Utc;
use eyre::Result; use eyre::{eyre, Result};
use reqwest::header::{HeaderMap, AUTHORIZATION}; use reqwest::header::{HeaderMap, AUTHORIZATION, USER_AGENT};
use reqwest::Url; use reqwest::{StatusCode, Url};
use sodiumoxide::crypto::secretbox; use sodiumoxide::crypto::secretbox;
use atuin_common::api::{AddHistoryRequest, CountResponse, SyncHistoryResponse}; use atuin_common::api::{
AddHistoryRequest, CountResponse, LoginResponse, RegisterResponse, SyncHistoryResponse,
};
use atuin_common::utils::hash_str; use atuin_common::utils::hash_str;
use crate::encryption::decrypt; use crate::encryption::{decode_key, decrypt};
use crate::history::History; use crate::history::History;
const VERSION: &str = env!("CARGO_PKG_VERSION");
// TODO: remove all references to the encryption key from this
// It should be handled *elsewhere*
pub struct Client<'a> { pub struct Client<'a> {
sync_addr: &'a str, sync_addr: &'a str,
token: &'a str, token: &'a str,
@ -17,14 +26,70 @@ pub struct Client<'a> {
client: reqwest::Client, client: reqwest::Client,
} }
pub fn register(
address: &str,
username: &str,
email: &str,
password: &str,
) -> Result<RegisterResponse> {
let mut map = HashMap::new();
map.insert("username", username);
map.insert("email", email);
map.insert("password", password);
let url = format!("{}/user/{}", address, username);
let resp = reqwest::blocking::get(url)?;
if resp.status().is_success() {
return Err(eyre!("username already in use"));
}
let url = format!("{}/register", address);
let client = reqwest::blocking::Client::new();
let resp = client
.post(url)
.header(USER_AGENT, format!("atuin/{}", VERSION))
.json(&map)
.send()?;
if !resp.status().is_success() {
return Err(eyre!("failed to register user"));
}
let session = resp.json::<RegisterResponse>()?;
Ok(session)
}
pub fn login(address: &str, username: &str, password: &str) -> Result<LoginResponse> {
let mut map = HashMap::new();
map.insert("username", username);
map.insert("password", password);
let url = format!("{}/login", address);
let client = reqwest::blocking::Client::new();
let resp = client
.post(url)
.header(USER_AGENT, format!("atuin/{}", VERSION))
.json(&map)
.send()?;
if resp.status() != reqwest::StatusCode::OK {
return Err(eyre!("invalid login details"));
}
let session = resp.json::<LoginResponse>()?;
Ok(session)
}
impl<'a> Client<'a> { impl<'a> Client<'a> {
pub fn new(sync_addr: &'a str, token: &'a str, key: secretbox::Key) -> Self { pub fn new(sync_addr: &'a str, token: &'a str, key: String) -> Result<Self> {
Client { Ok(Client {
sync_addr, sync_addr,
token, token,
key, key: decode_key(key)?,
client: reqwest::Client::new(), client: reqwest::Client::new(),
} })
} }
pub async fn count(&self) -> Result<i64> { pub async fn count(&self) -> Result<i64> {
@ -36,7 +101,17 @@ impl<'a> Client<'a> {
let mut headers = HeaderMap::new(); let mut headers = HeaderMap::new();
headers.insert(AUTHORIZATION, token); headers.insert(AUTHORIZATION, token);
let resp = self.client.get(url).headers(headers).send().await?; let resp = self
.client
.get(url)
.header(USER_AGENT, format!("atuin/{}", VERSION))
.headers(headers)
.send()
.await?;
if resp.status() != StatusCode::OK {
return Err(eyre!("failed to get count (are you logged in?)"));
}
let count = resp.json::<CountResponse>().await?; let count = resp.json::<CountResponse>().await?;
@ -66,6 +141,7 @@ impl<'a> Client<'a> {
.client .client
.get(url) .get(url)
.header(AUTHORIZATION, format!("Token {}", self.token)) .header(AUTHORIZATION, format!("Token {}", self.token))
.header(USER_AGENT, format!("atuin/{}", VERSION))
.send() .send()
.await?; .await?;
@ -88,9 +164,33 @@ impl<'a> Client<'a> {
.post(url) .post(url)
.json(history) .json(history)
.header(AUTHORIZATION, format!("Token {}", self.token)) .header(AUTHORIZATION, format!("Token {}", self.token))
.header(USER_AGENT, format!("atuin/{}", VERSION))
.send() .send()
.await?; .await?;
Ok(()) Ok(())
} }
pub async fn login(&self, username: &str, password: &str) -> Result<LoginResponse> {
let mut map = HashMap::new();
map.insert("username", username);
map.insert("password", password);
let url = format!("{}/login", self.sync_addr);
let resp = self
.client
.post(url)
.json(&map)
.header(USER_AGENT, format!("atuin/{}", VERSION))
.send()
.await?;
if resp.status() != reqwest::StatusCode::OK {
return Err(eyre!("invalid login details"));
}
let session = resp.json::<LoginResponse>().await?;
Ok(session)
}
} }

View file

@ -2,7 +2,7 @@ use chrono::prelude::*;
use chrono::Utc; use chrono::Utc;
use std::path::Path; use std::path::Path;
use eyre::Result; use eyre::{eyre, Result};
use rusqlite::{params, Connection}; use rusqlite::{params, Connection};
use rusqlite::{Params, Transaction}; use rusqlite::{Params, Transaction};
@ -14,7 +14,7 @@ pub trait Database {
fn save_bulk(&mut self, h: &[History]) -> Result<()>; fn save_bulk(&mut self, h: &[History]) -> Result<()>;
fn load(&self, id: &str) -> Result<History>; fn load(&self, id: &str) -> Result<History>;
fn list(&self) -> Result<Vec<History>>; fn list(&self, max: Option<usize>, unique: bool) -> Result<Vec<History>>;
fn range(&self, from: chrono::DateTime<Utc>, to: chrono::DateTime<Utc>) fn range(&self, from: chrono::DateTime<Utc>, to: chrono::DateTime<Utc>)
-> Result<Vec<History>>; -> Result<Vec<History>>;
@ -27,6 +27,8 @@ pub trait Database {
fn before(&self, timestamp: chrono::DateTime<Utc>, count: i64) -> Result<Vec<History>>; fn before(&self, timestamp: chrono::DateTime<Utc>, count: i64) -> Result<Vec<History>>;
fn prefix_search(&self, query: &str) -> Result<Vec<History>>; fn prefix_search(&self, query: &str) -> Result<Vec<History>>;
fn search(&self, cwd: Option<String>, exit: Option<i64>, query: &str) -> Result<Vec<History>>;
} }
// Intended for use on a developer machine and not a sync server. // Intended for use on a developer machine and not a sync server.
@ -81,6 +83,16 @@ impl Sqlite {
[], [],
)?; )?;
conn.execute(
"create index if not exists idx_history_timestamp on history(timestamp)",
[],
)?;
conn.execute(
"create index if not exists idx_history_command on history(command)",
[],
)?;
Ok(()) Ok(())
} }
@ -136,16 +148,19 @@ impl Database for Sqlite {
} }
fn load(&self, id: &str) -> Result<History> { fn load(&self, id: &str) -> Result<History> {
debug!("loading history item"); debug!("loading history item {}", id);
let mut stmt = self.conn.prepare( let history = self.query(
"select id, timestamp, duration, exit, command, cwd, session, hostname from history "select id, timestamp, duration, exit, command, cwd, session, hostname from history
where id = ?1", where id = ?1 limit 1",
&[id],
)?; )?;
let history = stmt.query_row(params![id], |row| { if history.is_empty() {
history_from_sqlite_row(Some(id.to_string()), row) return Err(eyre!("could not find history with id {}", id));
})?; }
let history = history[0].clone();
Ok(history) Ok(history)
} }
@ -163,16 +178,39 @@ impl Database for Sqlite {
Ok(()) Ok(())
} }
fn list(&self) -> Result<Vec<History>> { // make a unique list, that only shows the *newest* version of things
fn list(&self, max: Option<usize>, unique: bool) -> Result<Vec<History>> {
debug!("listing history"); debug!("listing history");
let mut stmt = self // very likely vulnerable to SQL injection
.conn // however, this is client side, and only used by the client, on their
.prepare("SELECT * FROM history order by timestamp asc")?; // own data. They can just open the db file...
// otherwise building the query is awkward
let query = format!(
"select * from history h
{}
order by timestamp desc
{}",
// inject the unique check
if unique {
"where timestamp = (
select max(timestamp) from history
where h.command = history.command
)"
} else {
""
},
// inject the limit
if let Some(max) = max {
format!("limit {}", max)
} else {
"".to_string()
}
);
let history_iter = stmt.query_map(params![], |row| history_from_sqlite_row(None, row))?; let history = self.query(query.as_str(), params![])?;
Ok(history_iter.filter_map(Result::ok).collect()) Ok(history)
} }
fn range( fn range(
@ -207,7 +245,7 @@ impl Database for Sqlite {
fn last(&self) -> Result<History> { fn last(&self) -> Result<History> {
let mut stmt = self let mut stmt = self
.conn .conn
.prepare("SELECT * FROM history order by timestamp desc limit 1")?; .prepare("SELECT * FROM history where duration >= 0 order by timestamp desc limit 1")?;
let history = stmt.query_row(params![], |row| history_from_sqlite_row(None, row))?; let history = stmt.query_row(params![], |row| history_from_sqlite_row(None, row))?;
@ -235,9 +273,17 @@ impl Database for Sqlite {
} }
fn prefix_search(&self, query: &str) -> Result<Vec<History>> { fn prefix_search(&self, query: &str) -> Result<Vec<History>> {
let query = query.to_string().replace("*", "%"); // allow wildcard char
self.query( self.query(
"select * from history where command like ?1 || '%' order by timestamp asc limit 1000", "select * from history h
&[query], where command like ?1 || '%'
and timestamp = (
select max(timestamp) from history
where h.command = history.command
)
order by timestamp desc limit 200",
&[query.as_str()],
) )
} }
@ -248,6 +294,39 @@ impl Database for Sqlite {
Ok(res) Ok(res)
} }
fn search(&self, cwd: Option<String>, exit: Option<i64>, query: &str) -> Result<Vec<History>> {
match (cwd, exit) {
(Some(cwd), Some(exit)) => self.query(
"select * from history
where command like ?1 || '%'
and cwd = ?2
and exit = ?3
order by timestamp asc limit 1000",
&[query, cwd.as_str(), exit.to_string().as_str()],
),
(Some(cwd), None) => self.query(
"select * from history
where command like ?1 || '%'
and cwd = ?2
order by timestamp asc limit 1000",
&[query, cwd.as_str()],
),
(None, Some(exit)) => self.query(
"select * from history
where command like ?1 || '%'
and exit = ?2
order by timestamp asc limit 1000",
&[query, exit.to_string().as_str()],
),
(None, None) => self.query(
"select * from history
where command like ?1 || '%'
order by timestamp asc limit 1000",
&[query],
),
}
}
} }
fn history_from_sqlite_row( fn history_from_sqlite_row(

View file

@ -29,20 +29,51 @@ pub fn load_key(settings: &Settings) -> Result<secretbox::Key> {
let path = settings.key_path.as_str(); let path = settings.key_path.as_str();
if PathBuf::from(path).exists() { if PathBuf::from(path).exists() {
let bytes = std::fs::read(path)?; let key = std::fs::read_to_string(path)?;
let key: secretbox::Key = rmp_serde::from_read_ref(&bytes)?; let key = decode_key(key)?;
Ok(key) Ok(key)
} else { } else {
let key = secretbox::gen_key(); let key = secretbox::gen_key();
let buf = rmp_serde::to_vec(&key)?; let encoded = encode_key(key.clone())?;
let mut file = File::create(path)?; let mut file = File::create(path)?;
file.write_all(&buf)?; file.write_all(encoded.as_bytes())?;
Ok(key) Ok(key)
} }
} }
pub fn load_encoded_key(settings: &Settings) -> Result<String> {
let path = settings.key_path.as_str();
if PathBuf::from(path).exists() {
let key = std::fs::read_to_string(path)?;
Ok(key)
} else {
let key = secretbox::gen_key();
let encoded = encode_key(key)?;
let mut file = File::create(path)?;
file.write_all(encoded.as_bytes())?;
Ok(encoded)
}
}
pub fn encode_key(key: secretbox::Key) -> Result<String> {
let buf = rmp_serde::to_vec(&key)?;
let buf = base64::encode(buf);
Ok(buf)
}
pub fn decode_key(key: String) -> Result<secretbox::Key> {
let buf = base64::decode(key)?;
let buf: secretbox::Key = rmp_serde::from_read_ref(&buf)?;
Ok(buf)
}
pub fn encrypt(history: &History, key: &secretbox::Key) -> Result<EncryptedHistory> { pub fn encrypt(history: &History, key: &secretbox::Key) -> Result<EncryptedHistory> {
// serialize with msgpack // serialize with msgpack
let buf = rmp_serde::to_vec(history)?; let buf = rmp_serde::to_vec(history)?;

View file

@ -6,7 +6,7 @@ use chrono::Utc;
use atuin_common::utils::uuid_v4; use atuin_common::utils::uuid_v4;
// Any new fields MUST be Optional<>! // Any new fields MUST be Optional<>!
#[derive(Debug, Clone, Serialize, Deserialize)] #[derive(Debug, Clone, Serialize, Deserialize, Ord, PartialOrd)]
pub struct History { pub struct History {
pub id: String, pub id: String,
pub timestamp: chrono::DateTime<Utc>, pub timestamp: chrono::DateTime<Utc>,

View file

@ -78,15 +78,16 @@ impl Settings {
create_dir_all(config_dir)?; create_dir_all(config_dir)?;
let config_file = if let Ok(p) = std::env::var("ATUIN_CONFIG") { let mut config_file = if let Ok(p) = std::env::var("ATUIN_CONFIG_DIR") {
PathBuf::from(p) PathBuf::from(p)
} else { } else {
let mut config_file = PathBuf::new(); let mut config_file = PathBuf::new();
config_file.push(config_dir); config_file.push(config_dir);
config_file.push("config.toml");
config_file config_file
}; };
config_file.push("config.toml");
let mut s = Config::new(); let mut s = Config::new();
let db_path = ProjectDirs::from("com", "elliehuxtable", "atuin") let db_path = ProjectDirs::from("com", "elliehuxtable", "atuin")

View file

@ -7,7 +7,7 @@ use atuin_common::{api::AddHistoryRequest, utils::hash_str};
use crate::api_client; use crate::api_client;
use crate::database::Database; use crate::database::Database;
use crate::encryption::{encrypt, load_key}; use crate::encryption::{encrypt, load_encoded_key, load_key};
use crate::settings::{Settings, HISTORY_PAGE_SIZE}; use crate::settings::{Settings, HISTORY_PAGE_SIZE};
// Currently sync is kinda naive, and basically just pages backwards through // Currently sync is kinda naive, and basically just pages backwards through
@ -26,6 +26,8 @@ async fn sync_download(
client: &api_client::Client<'_>, client: &api_client::Client<'_>,
db: &mut (impl Database + Send), db: &mut (impl Database + Send),
) -> Result<(i64, i64)> { ) -> Result<(i64, i64)> {
debug!("starting sync download");
let remote_count = client.count().await?; let remote_count = client.count().await?;
let initial_local = db.history_count()?; let initial_local = db.history_count()?;
@ -46,14 +48,14 @@ async fn sync_download(
.get_history(last_sync, last_timestamp, host.clone()) .get_history(last_sync, last_timestamp, host.clone())
.await?; .await?;
if page.len() < HISTORY_PAGE_SIZE.try_into().unwrap() {
break;
}
db.save_bulk(&page)?; db.save_bulk(&page)?;
local_count = db.history_count()?; local_count = db.history_count()?;
if page.len() < HISTORY_PAGE_SIZE.try_into().unwrap() {
break;
}
let page_last = page let page_last = page
.last() .last()
.expect("could not get last element of page") .expect("could not get last element of page")
@ -80,11 +82,15 @@ async fn sync_upload(
client: &api_client::Client<'_>, client: &api_client::Client<'_>,
db: &mut (impl Database + Send), db: &mut (impl Database + Send),
) -> Result<()> { ) -> Result<()> {
debug!("starting sync upload");
let initial_remote_count = client.count().await?; let initial_remote_count = client.count().await?;
let mut remote_count = initial_remote_count; let mut remote_count = initial_remote_count;
let local_count = db.history_count()?; let local_count = db.history_count()?;
debug!("remote has {}, we have {}", remote_count, local_count);
let key = load_key(settings)?; // encryption key let key = load_key(settings)?; // encryption key
// first just try the most recent set // first just try the most recent set
@ -127,8 +133,8 @@ pub async fn sync(settings: &Settings, force: bool, db: &mut (impl Database + Se
let client = api_client::Client::new( let client = api_client::Client::new(
settings.sync_address.as_str(), settings.sync_address.as_str(),
settings.session_token.as_str(), settings.session_token.as_str(),
load_key(settings)?, load_encoded_key(settings)?,
); )?;
sync_upload(settings, force, &client, db).await?; sync_upload(settings, force, &client, db).await?;

View file

@ -25,7 +25,6 @@ serde_json = "1.0.64"
rmp-serde = "0.15.4" rmp-serde = "0.15.4"
unicode-width = "0.1" unicode-width = "0.1"
sodiumoxide = "0.2.6" sodiumoxide = "0.2.6"
reqwest = { version = "0.11", features = ["blocking", "json"] }
base64 = "0.13.0" base64 = "0.13.0"
fork = "0.1.18" fork = "0.1.18"
parse_duration = "2.1.1" parse_duration = "2.1.1"
@ -33,6 +32,6 @@ rand = "0.8.3"
rust-crypto = "^0.2" rust-crypto = "^0.2"
tokio = { version = "1", features = ["full"] } tokio = { version = "1", features = ["full"] }
warp = "0.3" warp = "0.3"
sqlx = { version = "0.5", features = [ "runtime-tokio-native-tls", "uuid", "chrono", "postgres" ] } sqlx = { version = "0.5", features = [ "runtime-tokio-rustls", "uuid", "chrono", "postgres" ] }
async-trait = "0.1.49" async-trait = "0.1.49"
urlencoding = "1.1.1" urlencoding = "1.1.1"

View file

@ -23,15 +23,16 @@ impl Settings {
create_dir_all(config_dir)?; create_dir_all(config_dir)?;
let config_file = if let Ok(p) = std::env::var("ATUIN_CONFIG") { let mut config_file = if let Ok(p) = std::env::var("ATUIN_CONFIG_DIR") {
PathBuf::from(p) PathBuf::from(p)
} else { } else {
let mut config_file = PathBuf::new(); let mut config_file = PathBuf::new();
config_file.push(config_dir); config_file.push(config_dir);
config_file.push("server.toml");
config_file config_file
}; };
config_file.push("server.toml");
// create the config file if it does not exist // create the config file if it does not exist
let mut s = Config::new(); let mut s = Config::new();

View file

@ -1,7 +1,6 @@
use std::env; use std::env;
use eyre::Result; use eyre::Result;
use fork::{fork, Fork};
use structopt::StructOpt; use structopt::StructOpt;
use atuin_client::database::Database; use atuin_client::database::Database;
@ -44,6 +43,12 @@ pub enum Cmd {
aliases=&["se", "sea", "sear", "searc"], aliases=&["se", "sea", "sear", "searc"],
)] )]
Search { query: Vec<String> }, Search { query: Vec<String> },
#[structopt(
about="get the last command ran",
aliases=&["la", "las"],
)]
Last {},
} }
fn print_list(h: &[History]) { fn print_list(h: &[History]) {
@ -74,22 +79,24 @@ impl Cmd {
} }
let mut h = db.load(id)?; let mut h = db.load(id)?;
if h.duration > 0 {
debug!("cannot end history - already has duration");
// returning OK as this can occur if someone Ctrl-c a prompt
return Ok(());
}
h.exit = *exit; h.exit = *exit;
h.duration = chrono::Utc::now().timestamp_nanos() - h.timestamp.timestamp_nanos(); h.duration = chrono::Utc::now().timestamp_nanos() - h.timestamp.timestamp_nanos();
db.update(&h)?; db.update(&h)?;
if settings.should_sync()? { if settings.should_sync()? {
match fork() { debug!("running periodic background sync");
Ok(Fork::Parent(child)) => { sync::sync(settings, false, db).await?;
debug!("launched sync background process with PID {}", child); } else {
} debug!("sync disabled! not syncing");
Ok(Fork::Child) => {
debug!("running periodic background sync");
sync::sync(settings, false, db).await?;
}
Err(_) => println!("Fork failed"),
}
} }
Ok(()) Ok(())
@ -107,7 +114,7 @@ impl Cmd {
let session = env::var("ATUIN_SESSION")?; let session = env::var("ATUIN_SESSION")?;
let history = match params { let history = match params {
(false, false) => db.list()?, (false, false) => db.list(None, false)?,
(true, false) => db.query(QUERY_SESSION, &[session.as_str()])?, (true, false) => db.query(QUERY_SESSION, &[session.as_str()])?,
(false, true) => db.query(QUERY_DIR, &[cwd.as_str()])?, (false, true) => db.query(QUERY_DIR, &[cwd.as_str()])?,
(true, true) => { (true, true) => {
@ -126,6 +133,13 @@ impl Cmd {
Ok(()) Ok(())
} }
Self::Last {} => {
let last = db.last()?;
print_list(&[last]);
Ok(())
}
} }
} }
} }

View file

@ -1,10 +1,10 @@
use std::collections::HashMap;
use std::fs::File; use std::fs::File;
use std::io::prelude::*; use std::io::prelude::*;
use eyre::{eyre, Result}; use eyre::Result;
use structopt::StructOpt; use structopt::StructOpt;
use atuin_client::api_client;
use atuin_client::settings::Settings; use atuin_client::settings::Settings;
#[derive(StructOpt)] #[derive(StructOpt)]
@ -22,25 +22,15 @@ pub struct Cmd {
impl Cmd { impl Cmd {
pub fn run(&self, settings: &Settings) -> Result<()> { pub fn run(&self, settings: &Settings) -> Result<()> {
let mut map = HashMap::new(); let session = api_client::login(
map.insert("username", self.username.clone()); settings.sync_address.as_str(),
map.insert("password", self.password.clone()); self.username.as_str(),
self.password.as_str(),
let url = format!("{}/login", settings.sync_address); )?;
let client = reqwest::blocking::Client::new();
let resp = client.post(url).json(&map).send()?;
if resp.status() != reqwest::StatusCode::OK {
return Err(eyre!("invalid login details"));
}
let session = resp.json::<HashMap<String, String>>()?;
let session = session["session"].clone();
let session_path = settings.session_path.as_str(); let session_path = settings.session_path.as_str();
let mut file = File::create(session_path)?; let mut file = File::create(session_path)?;
file.write_all(session.as_bytes())?; file.write_all(session.session.as_bytes())?;
let key_path = settings.key_path.as_str(); let key_path = settings.key_path.as_str();
let mut file = File::create(key_path)?; let mut file = File::create(key_path)?;

View file

@ -43,7 +43,18 @@ pub enum AtuinCmd {
Uuid, Uuid,
#[structopt(about = "interactive history search")] #[structopt(about = "interactive history search")]
Search { query: Vec<String> }, Search {
#[structopt(long, short, about = "filter search result by directory")]
cwd: Option<String>,
#[structopt(long, short, about = "filter search result by exit code")]
exit: Option<i64>,
#[structopt(long, short, about = "open interactive search UI")]
interactive: bool,
query: Vec<String>,
},
#[structopt(about = "sync with the configured server")] #[structopt(about = "sync with the configured server")]
Sync { Sync {
@ -76,7 +87,12 @@ impl AtuinCmd {
Self::Server(server) => server.run(&server_settings).await, Self::Server(server) => server.run(&server_settings).await,
Self::Stats(stats) => stats.run(&mut db, &client_settings), Self::Stats(stats) => stats.run(&mut db, &client_settings),
Self::Init => init::init(), Self::Init => init::init(),
Self::Search { query } => search::run(&query, &mut db), Self::Search {
cwd,
exit,
interactive,
query,
} => search::run(cwd, exit, interactive, &query, &mut db),
Self::Sync { force } => sync::run(&client_settings, force, &mut db).await, Self::Sync { force } => sync::run(&client_settings, force, &mut db).await,
Self::Login(l) => l.run(&client_settings), Self::Login(l) => l.run(&client_settings),

View file

@ -1,10 +1,10 @@
use std::collections::HashMap;
use std::fs::File; use std::fs::File;
use std::io::prelude::*; use std::io::prelude::*;
use eyre::{eyre, Result}; use eyre::Result;
use structopt::StructOpt; use structopt::StructOpt;
use atuin_client::api_client;
use atuin_client::settings::Settings; use atuin_client::settings::Settings;
#[derive(StructOpt)] #[derive(StructOpt)]
@ -21,34 +21,11 @@ pub struct Cmd {
} }
pub fn run(settings: &Settings, username: &str, email: &str, password: &str) -> Result<()> { pub fn run(settings: &Settings, username: &str, email: &str, password: &str) -> Result<()> {
let mut map = HashMap::new(); let session = api_client::register(settings.sync_address.as_str(), username, email, password)?;
map.insert("username", username);
map.insert("email", email);
map.insert("password", password);
let url = format!("{}/user/{}", settings.sync_address, username);
let resp = reqwest::blocking::get(url)?;
if resp.status().is_success() {
println!("Username is already in use! Please try another.");
return Ok(());
}
let url = format!("{}/register", settings.sync_address);
let client = reqwest::blocking::Client::new();
let resp = client.post(url).json(&map).send()?;
if !resp.status().is_success() {
println!("Failed to register user - please check your details and try again");
return Err(eyre!("failed to register user"));
}
let session = resp.json::<HashMap<String, String>>()?;
let session = session["session"].clone();
let path = settings.session_path.as_str(); let path = settings.session_path.as_str();
let mut file = File::create(path)?; let mut file = File::create(path)?;
file.write_all(session.as_bytes())?; file.write_all(session.session.as_bytes())?;
Ok(()) Ok(())
} }

View file

@ -1,7 +1,6 @@
use eyre::Result; use eyre::Result;
use itertools::Itertools;
use std::io::stdout;
use std::time::Duration; use std::time::Duration;
use std::{io::stdout, ops::Sub};
use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen}; use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen};
use tui::{ use tui::{
@ -31,7 +30,7 @@ struct State {
#[allow(clippy::clippy::cast_sign_loss)] #[allow(clippy::clippy::cast_sign_loss)]
impl State { impl State {
fn durations(&self) -> Vec<String> { fn durations(&self) -> Vec<(String, String)> {
self.results self.results
.iter() .iter()
.map(|h| { .map(|h| {
@ -40,7 +39,33 @@ impl State {
let duration = humantime::format_duration(duration).to_string(); let duration = humantime::format_duration(duration).to_string();
let duration: Vec<&str> = duration.split(' ').collect(); let duration: Vec<&str> = duration.split(' ').collect();
duration[0].to_string() let ago = chrono::Utc::now().sub(h.timestamp);
let ago = humantime::format_duration(ago.to_std().unwrap()).to_string();
let ago: Vec<&str> = ago.split(' ').collect();
(
duration[0]
.to_string()
.replace("days", "d")
.replace("day", "d")
.replace("weeks", "w")
.replace("week", "w")
.replace("months", "mo")
.replace("month", "mo")
.replace("years", "y")
.replace("year", "y"),
ago[0]
.to_string()
.replace("days", "d")
.replace("day", "d")
.replace("weeks", "w")
.replace("week", "w")
.replace("months", "mo")
.replace("month", "mo")
.replace("years", "y")
.replace("year", "y")
+ " ago",
)
}) })
.collect() .collect()
} }
@ -51,9 +76,9 @@ impl State {
r: tui::layout::Rect, r: tui::layout::Rect,
) { ) {
let durations = self.durations(); let durations = self.durations();
let max_length = durations let max_length = durations.iter().fold(0, |largest, i| {
.iter() std::cmp::max(largest, i.0.len() + i.1.len())
.fold(0, |largest, i| std::cmp::max(largest, i.len())); });
let results: Vec<ListItem> = self let results: Vec<ListItem> = self
.results .results
@ -64,10 +89,10 @@ impl State {
let mut command = Span::raw(command); let mut command = Span::raw(command);
let mut duration = durations[i].clone(); let (duration, mut ago) = durations[i].clone();
while duration.len() < max_length { while (duration.len() + ago.len()) < max_length {
duration.push(' '); ago = " ".to_owned() + ago.as_str();
} }
let duration = Span::styled( let duration = Span::styled(
@ -79,6 +104,8 @@ impl State {
}), }),
); );
let ago = Span::styled(ago, Style::default().fg(Color::Blue));
if let Some(selected) = self.results_state.selected() { if let Some(selected) = self.results_state.selected() {
if selected == i { if selected == i {
command.style = command.style =
@ -86,7 +113,8 @@ impl State {
} }
} }
let spans = Spans::from(vec![duration, Span::raw(" "), command]); let spans =
Spans::from(vec![duration, Span::raw(" "), ago, Span::raw(" "), command]);
ListItem::new(spans) ListItem::new(spans)
}) })
@ -103,12 +131,12 @@ impl State {
fn query_results(app: &mut State, db: &mut impl Database) { fn query_results(app: &mut State, db: &mut impl Database) {
let results = match app.input.as_str() { let results = match app.input.as_str() {
"" => db.list(), "" => db.list(Some(200), true),
i => db.prefix_search(i), i => db.prefix_search(i),
}; };
if let Ok(results) = results { if let Ok(results) = results {
app.results = results.into_iter().rev().unique().collect(); app.results = results;
} }
if app.results.is_empty() { if app.results.is_empty() {
@ -120,7 +148,8 @@ fn query_results(app: &mut State, db: &mut impl Database) {
fn key_handler(input: Key, db: &mut impl Database, app: &mut State) -> Option<String> { fn key_handler(input: Key, db: &mut impl Database, app: &mut State) -> Option<String> {
match input { match input {
Key::Esc | Key::Char('\n') => { Key::Esc => return Some(String::from("")),
Key::Char('\n') => {
let i = app.results_state.selected().unwrap_or(0); let i = app.results_state.selected().unwrap_or(0);
return Some( return Some(
@ -268,9 +297,37 @@ fn select_history(query: &[String], db: &mut impl Database) -> Result<String> {
} }
} }
pub fn run(query: &[String], db: &mut impl Database) -> Result<()> { pub fn run(
let item = select_history(query, db)?; cwd: Option<String>,
eprintln!("{}", item); exit: Option<i64>,
interactive: bool,
query: &[String],
db: &mut impl Database,
) -> Result<()> {
let dir = if let Some(cwd) = cwd {
if cwd == "." {
let current = std::env::current_dir()?;
let current = current.as_os_str();
let current = current.to_str().unwrap();
Some(current.to_owned())
} else {
Some(cwd)
}
} else {
None
};
if interactive {
let item = select_history(query, db)?;
eprintln!("{}", item);
} else {
let results = db.search(dir, exit, query.join(" ").as_str())?;
for i in &results {
println!("{}", i.command);
}
}
Ok(()) Ok(())
} }

View file

@ -94,7 +94,7 @@ impl Cmd {
} }
Self::All => { Self::All => {
let history = db.list()?; let history = db.list(None, false)?;
compute_stats(&history)?; compute_stats(&history)?;

View file

@ -2,7 +2,6 @@
#![allow(clippy::use_self)] // not 100% reliable #![allow(clippy::use_self)] // not 100% reliable
use eyre::Result; use eyre::Result;
use fern::colors::{Color, ColoredLevelConfig};
use structopt::{clap::AppSettings, StructOpt}; use structopt::{clap::AppSettings, StructOpt};
#[macro_use] #[macro_use]
@ -32,23 +31,7 @@ impl Atuin {
#[tokio::main] #[tokio::main]
async fn main() -> Result<()> { async fn main() -> Result<()> {
let colors = ColoredLevelConfig::new() pretty_env_logger::init();
.warn(Color::Yellow)
.error(Color::Red);
fern::Dispatch::new()
.format(move |out, message, record| {
out.finish(format_args!(
"{} [{}] {}",
chrono::Local::now().to_rfc3339(),
colors.color(record.level()),
message
))
})
.level(log::LevelFilter::Info)
.level_for("sqlx", log::LevelFilter::Warn)
.chain(std::io::stdout())
.apply()?;
Atuin::from_args().run().await Atuin::from_args().run().await
} }

View file

@ -15,8 +15,8 @@ _atuin_precmd(){
[[ -z "${ATUIN_HISTORY_ID}" ]] && return [[ -z "${ATUIN_HISTORY_ID}" ]] && return
atuin history end $ATUIN_HISTORY_ID --exit $EXIT
export ATUIN_HISTORY_ID="" (RUST_LOG=error atuin history end $ATUIN_HISTORY_ID --exit $EXIT &) > /dev/null 2>&1
} }
_atuin_search(){ _atuin_search(){
@ -27,7 +27,7 @@ _atuin_search(){
echoti rmkx echoti rmkx
# swap stderr and stdout, so that the tui stuff works # swap stderr and stdout, so that the tui stuff works
# TODO: not this # TODO: not this
output=$(atuin search $BUFFER 3>&1 1>&2 2>&3) output=$(RUST_LOG=error atuin search -i $BUFFER 3>&1 1>&2 2>&3)
echoti smkx echoti smkx
if [[ -n $output ]] ; then if [[ -n $output ]] ; then