aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorEllie Huxtable <e@elm.sh>2021-04-21 18:13:51 +0100
committerEllie Huxtable <e@elm.sh>2021-04-21 21:26:44 +0100
commit4a50ce366639ca9dac7324d6a47d6a0e6c7fccdf (patch)
tree7ffd8848f675e1377f750cc0757768d074a5ac05
parentBump rusqlite from 0.25.0 to 0.25.1 (#35) (diff)
downloadatuin-4a50ce366639ca9dac7324d6a47d6a0e6c7fccdf.zip
Bugfixes, show time ago, perf improvements
Also allow unique listing and more ergonomic cwd usage
-rw-r--r--Cargo.lock382
-rw-r--r--Cargo.toml3
-rw-r--r--Dockerfile6
-rw-r--r--atuin-client/Cargo.toml2
-rw-r--r--atuin-client/src/api_client.rs120
-rw-r--r--atuin-client/src/database.rs113
-rw-r--r--atuin-client/src/encryption.rs39
-rw-r--r--atuin-client/src/history.rs2
-rw-r--r--atuin-client/src/settings.rs5
-rw-r--r--atuin-client/src/sync.rs20
-rw-r--r--atuin-server/Cargo.toml3
-rw-r--r--atuin-server/src/settings.rs5
-rw-r--r--src/command/history.rs38
-rw-r--r--src/command/login.rs26
-rw-r--r--src/command/mod.rs20
-rw-r--r--src/command/register.rs31
-rw-r--r--src/command/search.rs91
-rw-r--r--src/command/stats.rs2
-rw-r--r--src/main.rs19
-rw-r--r--src/shell/atuin.zsh6
20 files changed, 586 insertions, 347 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 47864054..a8d6b948 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -51,9 +51,9 @@ checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b"
[[package]]
name = "async-trait"
-version = "0.1.49"
+version = "0.1.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "589652ce7ccb335d1e7ecb3be145425702b290dbcb7029bbeaae263fc1d87b48"
+checksum = "0b98e84bbb4cbcdd97da190ba0c58a1bb0de2c1fdf67d159e192ed766aeca722"
dependencies = [
"proc-macro2",
"quote",
@@ -94,13 +94,12 @@ dependencies = [
"cli-table",
"directories",
"eyre",
- "fern",
"fork",
- "humantime",
+ "humantime 2.1.0",
"indicatif",
"itertools",
"log",
- "reqwest",
+ "pretty_env_logger",
"rusqlite",
"serde 1.0.125",
"serde_derive",
@@ -125,7 +124,7 @@ dependencies = [
"directories",
"eyre",
"fern",
- "humantime",
+ "humantime 2.1.0",
"indicatif",
"itertools",
"log",
@@ -180,7 +179,6 @@ dependencies = [
"log",
"parse_duration",
"rand 0.8.3",
- "reqwest",
"rmp-serde",
"rust-crypto",
"serde 1.0.125",
@@ -270,9 +268,9 @@ dependencies = [
[[package]]
name = "build_const"
-version = "0.2.1"
+version = "0.2.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "39092a32794787acd8525ee150305ff051b0aa6cc2abaf193924f5ab05425f39"
+checksum = "b4ae4235e6dac0694637c763029ecea1a2ec9e4e06ec2729bd21ba4d9c863eb7"
[[package]]
name = "bumpalo"
@@ -282,9 +280,9 @@ checksum = "63396b8a4b9de3f4fdfb320ab6080762242f66a8ef174c49d8e19b674db4cdbe"
[[package]]
name = "byteorder"
-version = "1.4.2"
+version = "1.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b"
+checksum = "14c189c53d098945499cdfa7ecc63567cf3886b3332b312a5b4585d8d3a6a610"
[[package]]
name = "bytes"
@@ -300,9 +298,9 @@ checksum = "df8670b8c7b9dae1793364eafadf7239c40d669904660c5960d74cfd80b46a53"
[[package]]
name = "cc"
-version = "1.0.66"
+version = "1.0.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4c0496836a84f8d0495758516b8621a622beb77c0fed418570e50764093ced48"
+checksum = "e3c69b077ad434294d3ce9f1f6143a2a4b89a8a2d54ef813d85003a4fd1137fd"
[[package]]
name = "cfg-if"
@@ -326,9 +324,9 @@ dependencies = [
[[package]]
name = "chrono-english"
-version = "0.1.4"
+version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4233ee19352739cfdcb5d7c2085005b166f6170ef2845ed9eef27a8fa5f95206"
+checksum = "0be5180df5f7c41fc2416bc038bc8d78d44db8136c415b94ccbc95f523dc38e9"
dependencies = [
"chrono",
"scanlex",
@@ -402,9 +400,9 @@ dependencies = [
[[package]]
name = "console"
-version = "0.14.0"
+version = "0.14.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "7cc80946b3480f421c2f17ed1cb841753a371c7c5104f51d507e13f532c856aa"
+checksum = "3993e6445baa160675931ec041a5e03ca84b9c6e32a056150d3aa2bdda0a1f45"
dependencies = [
"encode_unicode",
"lazy_static",
@@ -422,22 +420,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "cpuid-bool"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -474,9 +456,9 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
-version = "0.8.1"
+version = "0.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d"
+checksum = "e7e9d99fa91428effe99c5c6d4634cdeba32b8cf784fc428a2a687f61a952c49"
dependencies = [
"autocfg",
"cfg-if",
@@ -602,6 +584,19 @@ dependencies = [
]
[[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]]
name = "eyre"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -640,21 +635,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "fork"
version = "0.1.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -935,9 +915,9 @@ dependencies = [
[[package]]
name = "httparse"
-version = "1.3.5"
+version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "615caabe2c3160b313d52ccc905335f4ed5f10881dd63dc5699d47e90be85691"
+checksum = "4a1ce40d6fc9764887c2fdc7305c3dcc429ba11ff981c1509416afd5697e4437"
[[package]]
name = "httpdate"
@@ -947,6 +927,15 @@ 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]]
+name = "humantime"
version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4"
@@ -976,23 +965,25 @@ dependencies = [
]
[[package]]
-name = "hyper-tls"
-version = "0.5.0"
+name = "hyper-rustls"
+version = "0.22.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
+checksum = "5f9f7a97316d44c0af9b0301e65010573a853a9fc97046d7331d7f6bc0fd5a64"
dependencies = [
- "bytes",
+ "futures-util",
"hyper",
- "native-tls",
+ "log",
+ "rustls",
"tokio",
- "tokio-native-tls",
+ "tokio-rustls",
+ "webpki",
]
[[package]]
name = "idna"
-version = "0.2.2"
+version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "89829a5d69c23d348314a7ac337fe39173b61149a9864deabd260983aed48c21"
+checksum = "418a0a6fab821475f634efe3ccc45c013f742efe03d853e8d3355d5cb850ecf8"
dependencies = [
"matches",
"unicode-bidi",
@@ -1001,15 +992,15 @@ dependencies = [
[[package]]
name = "indenter"
-version = "0.3.2"
+version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f4d5eb2e114fec2b7fe0fadc22888ad2658789bb7acac4dbee9cf8389f971ec8"
+checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683"
[[package]]
name = "indexmap"
-version = "1.6.1"
+version = "1.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "4fb1fa934250de4de8aef298d81c729a7d33d8c239daa3a7575e6b92bfc7313b"
+checksum = "824845a0bf897a9042383849b02c1bc219c2383772efcd5c6f9766fa4b81aef3"
dependencies = [
"autocfg",
"hashbrown",
@@ -1096,9 +1087,9 @@ dependencies = [
[[package]]
name = "libc"
-version = "0.2.86"
+version = "0.2.93"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b7282d924be3275cec7f6756ff4121987bc6481325397dde6ba3e7802b1a8b1c"
+checksum = "9385f66bf6105b241aa65a61cb923ef20efc665cb9f9bb50ac2f0c4b7f378d41"
[[package]]
name = "libsodium-sys"
@@ -1130,9 +1121,9 @@ checksum = "7fb9b38af92608140b86b693604b9ffcc5824240a484d1ecd4795bacb2fe88f3"
[[package]]
name = "lock_api"
-version = "0.4.2"
+version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "dd96ffd135b2fd7b973ac026d28085defbe8983df057ced3eb4f2130b0831312"
+checksum = "5a3c91c24eae6777794bb1997ad98bbb87daf92890acab859f7eaa4320333176"
dependencies = [
"scopeguard",
]
@@ -1232,24 +1223,6 @@ dependencies = [
]
[[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]]
name = "nom"
version = "5.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1392,9 +1365,9 @@ checksum = "b8f8bdf33df195859076e54ab11ee78a1b208382d3a26ec40d142ffc1ecc49ef"
[[package]]
name = "once_cell"
-version = "1.5.2"
+version = "1.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "13bd41f508810a131401606d54ac32a467c97172d74ba7662562ebba5ad07fa0"
+checksum = "af8b08b04175473088b46763e51ee54da5f9a164bc162f615b91bc179dbf15a3"
[[package]]
name = "opaque-debug"
@@ -1403,39 +1376,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "parking_lot"
version = "0.11.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1455,7 +1395,7 @@ dependencies = [
"cfg-if",
"instant",
"libc",
- "redox_syscall 0.2.4",
+ "redox_syscall 0.2.6",
"smallvec",
"winapi",
]
@@ -1479,18 +1419,18 @@ checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e"
[[package]]
name = "pin-project"
-version = "1.0.6"
+version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "bc174859768806e91ae575187ada95c91a29e96a98dc5d2cd9a1fed039501ba6"
+checksum = "c7509cc106041c40a4518d2af7a61530e1eed0e6285296a3d8c5472806ccc4a4"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
-version = "1.0.6"
+version = "1.0.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "a490329918e856ed1b083f244e3bfe2d8c4f336407e4ea9e1a9f479ff09049e5"
+checksum = "48c950132583b500556b1efd71d45b319029f2b71518d979fcc208e16b42426f"
dependencies = [
"proc-macro2",
"quote",
@@ -1522,6 +1462,16 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1723,9 +1673,9 @@ checksum = "41cc0f7e4d5d4544e8861606a285bb08d3e70712ccc7d2b84d7c0ccfaf4b05ce"
[[package]]
name = "redox_syscall"
-version = "0.2.4"
+version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05ec8ca9416c5ea37062b502703cd7fcb207736bc294f6e0cf367ac6fc234570"
+checksum = "8270314b5ccceb518e7e578952f0b72b88222d02e8f77f5ecf7abbb673539041"
dependencies = [
"bitflags",
]
@@ -1736,7 +1686,7 @@ version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8440d8acb4fd3d277125b4bd01a6f38aee8d814b3b5fc09b3f2b825d37d3fe8f"
dependencies = [
- "redox_syscall 0.2.4",
+ "redox_syscall 0.2.6",
]
[[package]]
@@ -1757,19 +1707,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "528532f3d801c87aec9def2add9ca802fe569e44a544afe633765267840abe64"
dependencies = [
"getrandom 0.2.2",
- "redox_syscall 0.2.4",
+ "redox_syscall 0.2.6",
]
[[package]]
name = "regex"
-version = "1.4.3"
+version = "1.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "d9251239e129e16308e70d853559389de218ac275b515068abc96829d05b948a"
+checksum = "957056ecddbeba1b26965114e191d2e8589ce74db242b6ea25fc4062427a5c19"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
- "thread_local",
]
[[package]]
@@ -1783,9 +1732,9 @@ dependencies = [
[[package]]
name = "regex-syntax"
-version = "0.6.22"
+version = "0.6.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5eb417147ba9860a96cfe72a0b93bf88fee1744b5636ec99ab20c1aa9376581"
+checksum = "24d5f089152e60f62d28b835fbff2cd2e8dc0baf1ac13343bef92ab7eed84548"
[[package]]
name = "remove_dir_all"
@@ -1810,28 +1759,44 @@ dependencies = [
"http",
"http-body",
"hyper",
- "hyper-tls",
+ "hyper-rustls",
"ipnet",
"js-sys",
"lazy_static",
"log",
"mime",
- "native-tls",
"percent-encoding",
"pin-project-lite",
+ "rustls",
"serde 1.0.125",
"serde_json",
"serde_urlencoded",
"tokio",
- "tokio-native-tls",
+ "tokio-rustls",
"url",
"wasm-bindgen",
"wasm-bindgen-futures",
"web-sys",
+ "webpki-roots",
"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]]
name = "rmp"
version = "0.8.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1905,6 +1870,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "ryu"
version = "1.0.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1923,16 +1901,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
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]]
name = "scoped-tls"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -1945,26 +1913,13 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
-name = "security-framework"
-version = "2.2.0"
+name = "sct"
+version = "0.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "3670b1d2fdf6084d192bc71ead7aabe6c06aa2ea3fbd9cc3ac111fa5c2b1bd84"
+checksum = "b362b83898e0e69f38515b82ee15aa80636befe47c3b6d3d89a911e78fc228ce"
dependencies = [
- "bitflags",
- "core-foundation",
- "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",
+ "ring",
+ "untrusted",
]
[[package]]
@@ -2074,9 +2029,9 @@ dependencies = [
[[package]]
name = "slab"
-version = "0.4.2"
+version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "c111b5bd5695e56cffe5129854aa230b39c93a305372fdbb2668ca2394eea9f8"
+checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527"
[[package]]
name = "smallvec"
@@ -2106,6 +2061,12 @@ dependencies = [
]
[[package]]
+name = "spin"
+version = "0.5.2"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
+
+[[package]]
name = "sqlformat"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2162,6 +2123,7 @@ dependencies = [
"parking_lot",
"percent-encoding",
"rand 0.8.3",
+ "rustls",
"serde 1.0.125",
"serde_json",
"sha-1",
@@ -2174,6 +2136,8 @@ dependencies = [
"tokio-stream",
"url",
"uuid",
+ "webpki",
+ "webpki-roots",
"whoami",
]
@@ -2202,10 +2166,9 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6ae97ab05063ed515cdc23d90253213aa24dda0a288c5ec079af3d10f9771bc"
dependencies = [
- "native-tls",
"once_cell",
"tokio",
- "tokio-native-tls",
+ "tokio-rustls",
]
[[package]]
@@ -2286,7 +2249,7 @@ dependencies = [
"cfg-if",
"libc",
"rand 0.8.3",
- "redox_syscall 0.2.4",
+ "redox_syscall 0.2.6",
"remove_dir_all",
"winapi",
]
@@ -2318,7 +2281,7 @@ checksum = "077185e2eac69c3f8379a4298e1e07cd36beb962290d4a51199acf0fdc10607e"
dependencies = [
"libc",
"numtoa",
- "redox_syscall 0.2.4",
+ "redox_syscall 0.2.6",
"redox_termios",
]
@@ -2352,15 +2315,6 @@ dependencies = [
]
[[package]]
-name = "thread_local"
-version = "1.1.3"
-source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "8018d24e04c95ac8790716a5987d0fec4f8b27249ffa0f7d33f1369bdfb88cbd"
-dependencies = [
- "once_cell",
-]
-
-[[package]]
name = "time"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2372,9 +2326,9 @@ dependencies = [
[[package]]
name = "tinyvec"
-version = "1.1.1"
+version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "317cca572a0e89c3ce0ca1f1bdc9369547fe318a683418e42ac8f59d14701023"
+checksum = "5b5220f05bb7de7f3f53c7c065e1199b3172696fe2db9f9c4d8ad9b4ee74c342"
dependencies = [
"tinyvec_macros",
]
@@ -2387,9 +2341,9 @@ checksum = "cda74da7e1a664f795bb1f8a87ec406fb89a02522cf6e50620d016add6dbbf5c"
[[package]]
name = "tokio"
-version = "1.4.0"
+version = "1.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "134af885d758d645f0f0505c9a8b3f9bf8a348fd822e112ab5248138348f1722"
+checksum = "83f0c8e7c0addab50b663055baf787d0af7f413a46e6e7fb9559a4e4db7137a5"
dependencies = [
"autocfg",
"bytes",
@@ -2417,13 +2371,14 @@ dependencies = [
]
[[package]]
-name = "tokio-native-tls"
-version = "0.3.0"
+name = "tokio-rustls"
+version = "0.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "f7d995660bd2b7f8c1568414c1126076c13fbb725c40112dc0120b78eb9b717b"
+checksum = "bc6844de72e57df1980054b38be3a9f4702aba4858be64dd700181a8a6d0e1b6"
dependencies = [
- "native-tls",
+ "rustls",
"tokio",
+ "webpki",
]
[[package]]
@@ -2452,9 +2407,9 @@ dependencies = [
[[package]]
name = "tokio-util"
-version = "0.6.5"
+version = "0.6.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "5143d049e85af7fbc36f5454d990e62c2df705b3589f123b71f441b6b59f443f"
+checksum = "940a12c99365c31ea8dd9ba04ec1be183ffe4920102bb7122c2f515437601e8e"
dependencies = [
"bytes",
"futures-core",
@@ -2549,9 +2504,9 @@ dependencies = [
[[package]]
name = "typenum"
-version = "1.12.0"
+version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
+checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06"
[[package]]
name = "unicase"
@@ -2564,9 +2519,9 @@ dependencies = [
[[package]]
name = "unicode-bidi"
-version = "0.3.4"
+version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5"
+checksum = "eeb8be209bb1c96b7c177c7420d26e04eccacb0eeae6b980e35fcb74678107e0"
dependencies = [
"matches",
]
@@ -2605,6 +2560,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39ec24b3121d976906ece63c9daad25b85969647682eee313cb5779fdd69e14e"
[[package]]
+name = "untrusted"
+version = "0.7.1"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "a156c684c91ea7d62626509bce3cb4e1d9ed5c4d978f7b4352658f96a4c26b4a"
+
+[[package]]
name = "url"
version = "2.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
@@ -2624,9 +2585,9 @@ checksum = "c9232eb53352b4442e40d7900465dfc534e8cb2dc8f18656fcb2ac16112b5593"
[[package]]
name = "utf-8"
-version = "0.7.5"
+version = "0.7.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7"
+checksum = "09cc8ee72d2a9becf2f2febe0205bbed8fc6615b7cb429ad062dc7b7ddd036a9"
[[package]]
name = "uuid"
@@ -2639,9 +2600,9 @@ dependencies = [
[[package]]
name = "vcpkg"
-version = "0.2.11"
+version = "0.2.12"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b00bca6106a5e23f3eee943593759b7fcddb00554332e856d990c893966879fb"
+checksum = "cbdbff6266a24120518560b5dc983096efb98462e51d0d68169895b237be3e5d"
[[package]]
name = "vec_map"
@@ -2651,9 +2612,9 @@ checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
-version = "0.9.2"
+version = "0.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
-checksum = "b5a972e5669d67ba988ce3dc826706fb0a8b01471c088cb0b6110b805cc36aed"
+checksum = "5fecdca9a5291cc2b8dcf7dc02453fee791a280f3743cb0905f8822ae463b3fe"
[[package]]
name = "want"
@@ -2785,6 +2746,25 @@ dependencies = [
]
[[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]]
name = "whoami"
version = "1.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
diff --git a/Cargo.toml b/Cargo.toml
index 0ee82caa..8995a75b 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -15,7 +15,7 @@ atuin-client = { path = "atuin-client", version = "0.6.0" }
atuin-common = { path = "atuin-common", version = "0.6.0" }
log = "0.4"
-fern = {version = "0.6.0", features = ["colored"] }
+pretty_env_logger = "0.4"
chrono = { version = "0.4", features = ["serde"] }
eyre = "0.6"
structopt = "0.3"
@@ -33,7 +33,6 @@ tokio = { version = "1", features = ["full"] }
async-trait = "0.1.49"
chrono-english = "0.1.4"
cli-table = "0.4"
-reqwest = { version = "0.11", features = ["blocking", "json"] }
base64 = "0.13.0"
humantime = "2.1.0"
diff --git a/Dockerfile b/Dockerfile
index e7125414..4f0d615b 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -17,6 +17,12 @@ COPY --from=cacher $CARGO_HOME $CARGO_HOME
RUN cargo build --release --bin atuin
FROM debian:buster-slim as runtime
+
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
ENTRYPOINT ["/usr/local/bin/atuin"]
diff --git a/atuin-client/Cargo.toml b/atuin-client/Cargo.toml
index 9d639d18..09cf9c47 100644
--- a/atuin-client/Cargo.toml
+++ b/atuin-client/Cargo.toml
@@ -26,7 +26,7 @@ serde = "1.0.125"
serde_json = "1.0.64"
rmp-serde = "0.15.4"
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"
parse_duration = "2.1.1"
rand = "0.8.3"
diff --git a/atuin-client/src/api_client.rs b/atuin-client/src/api_client.rs
index db2802c3..a8ce7b27 100644
--- a/atuin-client/src/api_client.rs
+++ b/atuin-client/src/api_client.rs
@@ -1,15 +1,24 @@
+use std::collections::HashMap;
+
use chrono::Utc;
-use eyre::Result;
-use reqwest::header::{HeaderMap, AUTHORIZATION};
-use reqwest::Url;
+use eyre::{eyre, Result};
+use reqwest::header::{HeaderMap, AUTHORIZATION, USER_AGENT};
+use reqwest::{StatusCode, Url};
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 crate::encryption::decrypt;
+use crate::encryption::{decode_key, decrypt};
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> {
sync_addr: &'a str,
token: &'a str,
@@ -17,14 +26,70 @@ pub struct Client<'a> {
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> {
- pub fn new(sync_addr: &'a str, token: &'a str, key: secretbox::Key) -> Self {
- Client {
+ pub fn new(sync_addr: &'a str, token: &'a str, key: String) -> Result<Self> {
+ Ok(Client {
sync_addr,
token,
- key,
+ key: decode_key(key)?,
client: reqwest::Client::new(),
- }
+ })
}
pub async fn count(&self) -> Result<i64> {
@@ -36,7 +101,17 @@ impl<'a> Client<'a> {
let mut headers = HeaderMap::new();
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?;
@@ -66,6 +141,7 @@ impl<'a> Client<'a> {
.client
.get(url)
.header(AUTHORIZATION, format!("Token {}", self.token))
+ .header(USER_AGENT, format!("atuin/{}", VERSION))
.send()
.await?;
@@ -88,9 +164,33 @@ impl<'a> Client<'a> {
.post(url)
.json(history)
.header(AUTHORIZATION, format!("Token {}", self.token))
+ .header(USER_AGENT, format!("atuin/{}", VERSION))
.send()
.await?;
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)
+ }
}
diff --git a/atuin-client/src/database.rs b/atuin-client/src/database.rs
index abc22bb8..0855359b 100644
--- a/atuin-client/src/database.rs
+++ b/atuin-client/src/database.rs
@@ -2,7 +2,7 @@ use chrono::prelude::*;
use chrono::Utc;
use std::path::Path;
-use eyre::Result;
+use eyre::{eyre, Result};
use rusqlite::{params, Connection};
use rusqlite::{Params, Transaction};
@@ -14,7 +14,7 @@ pub trait Database {
fn save_bulk(&mut self, h: &[History]) -> Result<()>;
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>)
-> Result<Vec<History>>;
@@ -27,6 +27,8 @@ pub trait Database {
fn before(&self, timestamp: chrono::DateTime<Utc>, count: i64) -> 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.
@@ -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(())
}
@@ -136,16 +148,19 @@ impl Database for Sqlite {
}
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
- where id = ?1",
+ where id = ?1 limit 1",
+ &[id],
)?;
- let history = stmt.query_row(params![id], |row| {
- history_from_sqlite_row(Some(id.to_string()), row)
- })?;
+ if history.is_empty() {
+ return Err(eyre!("could not find history with id {}", id));
+ }
+
+ let history = history[0].clone();
Ok(history)
}
@@ -163,16 +178,39 @@ impl Database for Sqlite {
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");
- let mut stmt = self
- .conn
- .prepare("SELECT * FROM history order by timestamp asc")?;
+ // very likely vulnerable to SQL injection
+ // however, this is client side, and only used by the client, on their
+ // 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(
@@ -207,7 +245,7 @@ impl Database for Sqlite {
fn last(&self) -> Result<History> {
let mut stmt = self
.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))?;
@@ -235,9 +273,17 @@ impl Database for Sqlite {
}
fn prefix_search(&self, query: &str) -> Result<Vec<History>> {
+ let query = query.to_string().replace("*", "%"); // allow wildcard char
+
self.query(
- "select * from history where command like ?1 || '%' order by timestamp asc limit 1000",
- &[query],
+ "select * from history h
+ 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)
}
+
+ 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(
diff --git a/atuin-client/src/encryption.rs b/atuin-client/src/encryption.rs
index 37153f94..19b773ab 100644
--- a/atuin-client/src/encryption.rs
+++ b/atuin-client/src/encryption.rs
@@ -29,20 +29,51 @@ pub fn load_key(settings: &Settings) -> Result<secretbox::Key> {
let path = settings.key_path.as_str();
if PathBuf::from(path).exists() {
- let bytes = std::fs::read(path)?;
- let key: secretbox::Key = rmp_serde::from_read_ref(&bytes)?;
+ let key = std::fs::read_to_string(path)?;
+ let key = decode_key(key)?;
Ok(key)
} else {
let key = secretbox::gen_key();
- let buf = rmp_serde::to_vec(&key)?;
+ let encoded = encode_key(key.clone())?;
let mut file = File::create(path)?;
- file.write_all(&buf)?;
+ file.write_all(encoded.as_bytes())?;
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> {
// serialize with msgpack
let buf = rmp_serde::to_vec(history)?;
diff --git a/atuin-client/src/history.rs b/atuin-client/src/history.rs
index 7f607784..8dd161db 100644
--- a/atuin-client/src/history.rs
+++ b/atuin-client/src/history.rs
@@ -6,7 +6,7 @@ use chrono::Utc;
use atuin_common::utils::uuid_v4;
// Any new fields MUST be Optional<>!
-#[derive(Debug, Clone, Serialize, Deserialize)]
+#[derive(Debug, Clone, Serialize, Deserialize, Ord, PartialOrd)]
pub struct History {
pub id: String,
pub timestamp: chrono::DateTime<Utc>,
diff --git a/atuin-client/src/settings.rs b/atuin-client/src/settings.rs
index e28963c0..254bca6d 100644
--- a/atuin-client/src/settings.rs
+++ b/atuin-client/src/settings.rs
@@ -78,15 +78,16 @@ impl Settings {
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)
} else {
let mut config_file = PathBuf::new();
config_file.push(config_dir);
- config_file.push("config.toml");
config_file
};
+ config_file.push("config.toml");
+
let mut s = Config::new();
let db_path = ProjectDirs::from("com", "elliehuxtable", "atuin")
diff --git a/atuin-client/src/sync.rs b/atuin-client/src/sync.rs
index 0ca8d3a6..5d81a5e6 100644
--- a/atuin-client/src/sync.rs
+++ b/atuin-client/src/sync.rs
@@ -7,7 +7,7 @@ use atuin_common::{api::AddHistoryRequest, utils::hash_str};
use crate::api_client;
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};
// Currently sync is kinda naive, and basically just pages backwards through
@@ -26,6 +26,8 @@ async fn sync_download(
client: &api_client::Client<'_>,
db: &mut (impl Database + Send),
) -> Result<(i64, i64)> {
+ debug!("starting sync download");
+
let remote_count = client.count().await?;
let initial_local = db.history_count()?;
@@ -46,14 +48,14 @@ async fn sync_download(
.get_history(last_sync, last_timestamp, host.clone())
.await?;
- if page.len() < HISTORY_PAGE_SIZE.try_into().unwrap() {
- break;
- }
-
db.save_bulk(&page)?;
local_count = db.history_count()?;
+ if page.len() < HISTORY_PAGE_SIZE.try_into().unwrap() {
+ break;
+ }
+
let page_last = page
.last()
.expect("could not get last element of page")
@@ -80,11 +82,15 @@ async fn sync_upload(
client: &api_client::Client<'_>,
db: &mut (impl Database + Send),
) -> Result<()> {
+ debug!("starting sync upload");
+
let initial_remote_count = client.count().await?;
let mut remote_count = initial_remote_count;
let local_count = db.history_count()?;
+ debug!("remote has {}, we have {}", remote_count, local_count);
+
let key = load_key(settings)?; // encryption key
// 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(
settings.sync_address.as_str(),
settings.session_token.as_str(),
- load_key(settings)?,
- );
+ load_encoded_key(settings)?,
+ )?;
sync_upload(settings, force, &client, db).await?;
diff --git a/atuin-server/Cargo.toml b/atuin-server/Cargo.toml
index 548a97ca..d4db38cd 100644
--- a/atuin-server/Cargo.toml
+++ b/atuin-server/Cargo.toml
@@ -25,7 +25,6 @@ serde_json = "1.0.64"
rmp-serde = "0.15.4"
unicode-width = "0.1"
sodiumoxide = "0.2.6"
-reqwest = { version = "0.11", features = ["blocking", "json"] }
base64 = "0.13.0"
fork = "0.1.18"
parse_duration = "2.1.1"
@@ -33,6 +32,6 @@ rand = "0.8.3"
rust-crypto = "^0.2"
tokio = { version = "1", features = ["full"] }
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"
urlencoding = "1.1.1"
diff --git a/atuin-server/src/settings.rs b/atuin-server/src/settings.rs
index 596b9018..e51b6b2a 100644
--- a/atuin-server/src/settings.rs
+++ b/atuin-server/src/settings.rs
@@ -23,15 +23,16 @@ impl Settings {
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)
} else {
let mut config_file = PathBuf::new();
config_file.push(config_dir);
- config_file.push("server.toml");
config_file
};
+ config_file.push("server.toml");
+
// create the config file if it does not exist
let mut s = Config::new();
diff --git a/src/command/history.rs b/src/command/history.rs
index 2b691bac..a88aeae2 100644
--- a/src/command/history.rs
+++ b/src/command/history.rs
@@ -1,7 +1,6 @@
use std::env;
use eyre::Result;
-use fork::{fork, Fork};
use structopt::StructOpt;
use atuin_client::database::Database;
@@ -44,6 +43,12 @@ pub enum Cmd {
aliases=&["se", "sea", "sear", "searc"],
)]
Search { query: Vec<String> },
+
+ #[structopt(
+ about="get the last command ran",
+ aliases=&["la", "las"],
+ )]
+ Last {},
}
fn print_list(h: &[History]) {
@@ -74,22 +79,24 @@ impl Cmd {
}
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.duration = chrono::Utc::now().timestamp_nanos() - h.timestamp.timestamp_nanos();
db.update(&h)?;
if settings.should_sync()? {
- match fork() {
- Ok(Fork::Parent(child)) => {
- debug!("launched sync background process with PID {}", child);
- }
- Ok(Fork::Child) => {
- debug!("running periodic background sync");
- sync::sync(settings, false, db).await?;
- }
- Err(_) => println!("Fork failed"),
- }
+ debug!("running periodic background sync");
+ sync::sync(settings, false, db).await?;
+ } else {
+ debug!("sync disabled! not syncing");
}
Ok(())
@@ -107,7 +114,7 @@ impl Cmd {
let session = env::var("ATUIN_SESSION")?;
let history = match params {
- (false, false) => db.list()?,
+ (false, false) => db.list(None, false)?,
(true, false) => db.query(QUERY_SESSION, &[session.as_str()])?,
(false, true) => db.query(QUERY_DIR, &[cwd.as_str()])?,
(true, true) => {
@@ -126,6 +133,13 @@ impl Cmd {
Ok(())
}
+
+ Self::Last {} => {
+ let last = db.last()?;
+ print_list(&[last]);
+
+ Ok(())
+ }
}
}
}
diff --git a/src/command/login.rs b/src/command/login.rs
index eaeb297f..eacb2105 100644
--- a/src/command/login.rs
+++ b/src/command/login.rs
@@ -1,10 +1,10 @@
-use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
-use eyre::{eyre, Result};
+use eyre::Result;
use structopt::StructOpt;
+use atuin_client::api_client;
use atuin_client::settings::Settings;
#[derive(StructOpt)]
@@ -22,25 +22,15 @@ pub struct Cmd {
impl Cmd {
pub fn run(&self, settings: &Settings) -> Result<()> {
- let mut map = HashMap::new();
- map.insert("username", self.username.clone());
- map.insert("password", self.password.clone());
-
- 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 = api_client::login(
+ settings.sync_address.as_str(),
+ self.username.as_str(),
+ self.password.as_str(),
+ )?;
let session_path = settings.session_path.as_str();
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 mut file = File::create(key_path)?;
diff --git a/src/command/mod.rs b/src/command/mod.rs
index 6fd52613..805ad9f0 100644
--- a/src/command/mod.rs
+++ b/src/command/mod.rs
@@ -43,7 +43,18 @@ pub enum AtuinCmd {
Uuid,
#[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")]
Sync {
@@ -76,7 +87,12 @@ impl AtuinCmd {
Self::Server(server) => server.run(&server_settings).await,
Self::Stats(stats) => stats.run(&mut db, &client_settings),
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::Login(l) => l.run(&client_settings),
diff --git a/src/command/register.rs b/src/command/register.rs
index 1126645a..acf9b1a3 100644
--- a/src/command/register.rs
+++ b/src/command/register.rs
@@ -1,10 +1,10 @@
-use std::collections::HashMap;
use std::fs::File;
use std::io::prelude::*;
-use eyre::{eyre, Result};
+use eyre::Result;
use structopt::StructOpt;
+use atuin_client::api_client;
use atuin_client::settings::Settings;
#[derive(StructOpt)]
@@ -21,34 +21,11 @@ pub struct Cmd {
}
pub fn run(settings: &Settings, username: &str, email: &str, password: &str) -> Result<()> {
- let mut map = HashMap::new();
- 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 session = api_client::register(settings.sync_address.as_str(), username, email, password)?;
let path = settings.session_path.as_str();
let mut file = File::create(path)?;
- file.write_all(session.as_bytes())?;
+ file.write_all(session.session.as_bytes())?;
Ok(())
}
diff --git a/src/command/search.rs b/src/command/search.rs
index 773c112f..b074371e 100644
--- a/src/command/search.rs
+++ b/src/command/search.rs
@@ -1,7 +1,6 @@
use eyre::Result;
-use itertools::Itertools;
-use std::io::stdout;
use std::time::Duration;
+use std::{io::stdout, ops::Sub};
use termion::{event::Key, input::MouseTerminal, raw::IntoRawMode, screen::AlternateScreen};
use tui::{
@@ -31,7 +30,7 @@ struct State {
#[allow(clippy::clippy::cast_sign_loss)]
impl State {
- fn durations(&self) -> Vec<String> {
+ fn durations(&self) -> Vec<(String, String)> {
self.results
.iter()
.map(|h| {
@@ -40,7 +39,33 @@ impl State {
let duration = humantime::format_duration(duration).to_string();
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()
}
@@ -51,9 +76,9 @@ impl State {
r: tui::layout::Rect,
) {
let durations = self.durations();
- let max_length = durations
- .iter()
- .fold(0, |largest, i| std::cmp::max(largest, i.len()));
+ let max_length = durations.iter().fold(0, |largest, i| {
+ std::cmp::max(largest, i.0.len() + i.1.len())
+ });
let results: Vec<ListItem> = self
.results
@@ -64,10 +89,10 @@ impl State {
let mut command = Span::raw(command);
- let mut duration = durations[i].clone();
+ let (duration, mut ago) = durations[i].clone();
- while duration.len() < max_length {
- duration.push(' ');
+ while (duration.len() + ago.len()) < max_length {
+ ago = " ".to_owned() + ago.as_str();
}
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 selected == i {
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)
})
@@ -103,12 +131,12 @@ impl State {
fn query_results(app: &mut State, db: &mut impl Database) {
let results = match app.input.as_str() {
- "" => db.list(),
+ "" => db.list(Some(200), true),
i => db.prefix_search(i),
};
if let Ok(results) = results {
- app.results = results.into_iter().rev().unique().collect();
+ app.results = results;
}
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> {
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);
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<()> {
- let item = select_history(query, db)?;
- eprintln!("{}", item);
+pub fn run(
+ cwd: Option<String>,
+ 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(())
}
diff --git a/src/command/stats.rs b/src/command/stats.rs
index 0da303d7..5c9a9dbb 100644
--- a/src/command/stats.rs
+++ b/src/command/stats.rs
@@ -94,7 +94,7 @@ impl Cmd {
}
Self::All => {
- let history = db.list()?;
+ let history = db.list(None, false)?;
compute_stats(&history)?;
diff --git a/src/main.rs b/src/main.rs
index c116d1f3..184c0323 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -2,7 +2,6 @@
#![allow(clippy::use_self)] // not 100% reliable
use eyre::Result;
-use fern::colors::{Color, ColoredLevelConfig};
use structopt::{clap::AppSettings, StructOpt};
#[macro_use]
@@ -32,23 +31,7 @@ impl Atuin {
#[tokio::main]
async fn main() -> Result<()> {
- let colors = ColoredLevelConfig::new()
- .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()?;
+ pretty_env_logger::init();
Atuin::from_args().run().await
}
diff --git a/src/shell/atuin.zsh b/src/shell/atuin.zsh
index d6d58f53..cdef5e54 100644
--- a/src/shell/atuin.zsh
+++ b/src/shell/atuin.zsh
@@ -15,8 +15,8 @@ _atuin_precmd(){
[[ -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(){
@@ -27,7 +27,7 @@ _atuin_search(){
echoti rmkx
# swap stderr and stdout, so that the tui stuff works
# 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
if [[ -n $output ]] ; then