Compare commits

..

8 Commits

Author SHA1 Message Date
lol
79f83b71de test: create first real test 2024-02-21 16:55:56 +01:00
lol
639f18426f fix(token): allow to get and store token in local config 2024-02-21 16:50:54 +01:00
lol
9859f26604 fix(config): add option to last category 2024-02-21 16:50:34 +01:00
lol
1217fe0480 fix: minor warnings 2024-02-20 21:28:18 +01:00
lol
cc916c0ed6 feat(config): create a proper config file with proper settings manipulation 2024-02-20 21:25:21 +01:00
lol
628c26ef81 feat(credential): allow to add credential 2024-02-20 21:24:22 +01:00
lol
cd0eee8b20 refactor(clone): set remote in config 2024-02-20 15:47:51 +01:00
lol
ef986305c0 feat(remote): add new remote 2024-02-20 15:45:01 +01:00
56 changed files with 1391 additions and 2191 deletions

524
Cargo.lock generated
View File

@@ -2,21 +2,6 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 3 version = 3
[[package]]
name = "addr2line"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a30b2e23b9e17a9f90641c7ab1549cd9b44f296d3ccbf309d2863cfe398a0cb"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]] [[package]]
name = "aho-corasick" name = "aho-corasick"
version = "1.0.2" version = "1.0.2"
@@ -42,51 +27,23 @@ dependencies = [
] ]
[[package]] [[package]]
name = "anstream" name = "ansi_term"
version = "0.6.13" version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d96bd03f33fe50a863e394ee9718a706f988b9079b20c3784fb726e7678b62fb" checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [ dependencies = [
"anstyle", "winapi",
"anstyle-parse",
"anstyle-query",
"anstyle-wincon",
"colorchoice",
"utf8parse",
] ]
[[package]] [[package]]
name = "anstyle" name = "atty"
version = "1.0.6" version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8901269c6307e8d93993578286ac0edf7f195079ffff5ebdeea6a59ffb7e36bc" checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
[[package]]
name = "anstyle-parse"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c75ac65da39e5fe5ab759307499ddad880d724eed2f6ce5b5e8a26f4f387928c"
dependencies = [ dependencies = [
"utf8parse", "hermit-abi 0.1.19",
] "libc",
"winapi",
[[package]]
name = "anstyle-query"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e28923312444cdd728e4738b3f9c9cac739500909bb3d3c94b43551b16517648"
dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "anstyle-wincon"
version = "3.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cd54b81ec8d6180e24654d0b371ad22fc3dd083b6ff8ba325b72e00c87660a7"
dependencies = [
"anstyle",
"windows-sys 0.52.0",
] ]
[[package]] [[package]]
@@ -95,21 +52,6 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "backtrace"
version = "0.3.71"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26b05800d2e817c8b3b4b54abd461726265fa9789ae34330622f2db9ee696f9d"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]] [[package]]
name = "base64" name = "base64"
version = "0.21.2" version = "0.21.2"
@@ -142,9 +84,9 @@ checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.90" version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8cd6604a82acf3039f1144f54b8eb34e91ffba622051189e71b781822d5ee1f5" checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
@@ -154,59 +96,43 @@ checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]] [[package]]
name = "chrono" name = "chrono"
version = "0.4.37" version = "0.4.26"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a0d04d43504c61aa6c7531f1871dd0d418d91130162063b789da00fd7057a5e" checksum = "ec837a71355b28f6556dbd569b37b3f363091c0bd4b2e735674521b4c5fd9bc5"
dependencies = [ dependencies = [
"android-tzdata", "android-tzdata",
"iana-time-zone", "iana-time-zone",
"js-sys", "js-sys",
"num-traits", "num-traits",
"time",
"wasm-bindgen", "wasm-bindgen",
"windows-targets 0.52.0", "winapi",
] ]
[[package]] [[package]]
name = "clap" name = "clap"
version = "4.5.4" version = "2.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "90bc066a67923782aa8515dbaea16946c5bcc5addbd668bb80af688e53e548a0" checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
dependencies = [ dependencies = [
"clap_builder", "ansi_term",
] "atty",
"bitflags 1.3.2",
[[package]]
name = "clap_builder"
version = "4.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae129e2e766ae0ec03484e609954119f123cc1fe650337e155d03b022f24f7b4"
dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim", "strsim",
"textwrap 0.11.0",
"unicode-width",
"vec_map",
] ]
[[package]]
name = "clap_lex"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "98cc8fbded0c607b7ba9dd60cd98df59af97e84d24e49c8557331cfc26d301ce"
[[package]]
name = "colorchoice"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acbf1af155f9b9ef647e42cdc158db4b64a1b61f743629225fde6f3e0be2a7c7"
[[package]] [[package]]
name = "colored" name = "colored"
version = "2.1.0" version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" checksum = "b3616f750b84d8f0de8a58bda93e08e2a81ad3f523089b05f1dffecab48c6cbd"
dependencies = [ dependencies = [
"atty",
"lazy_static", "lazy_static",
"windows-sys 0.48.0", "winapi",
] ]
[[package]] [[package]]
@@ -259,12 +185,6 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "equivalent"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5"
[[package]] [[package]]
name = "errno" name = "errno"
version = "0.3.8" version = "0.3.8"
@@ -328,21 +248,21 @@ dependencies = [
[[package]] [[package]]
name = "futures-core" name = "futures-core"
version = "0.3.30" version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
[[package]] [[package]]
name = "futures-io" name = "futures-io"
version = "0.3.30" version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" checksum = "4fff74096e71ed47f8e023204cfd0aa1289cd54ae5430a9523be060cdb849964"
[[package]] [[package]]
name = "futures-macro" name = "futures-macro"
version = "0.3.30" version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "87750cf4b7a4c0625b1529e4c543c2182106e4dedc60a2a6455e00d212c489ac" checksum = "89ca545a94061b6365f2c7355b4b32bd20df3ff95f02da9329b34ccc3bd6ee72"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -351,21 +271,21 @@ dependencies = [
[[package]] [[package]]
name = "futures-sink" name = "futures-sink"
version = "0.3.30" version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" checksum = "f43be4fe21a13b9781a69afa4985b0f6ee0e1afab2c6f454a8cf30e2b2237b6e"
[[package]] [[package]]
name = "futures-task" name = "futures-task"
version = "0.3.30" version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" checksum = "76d3d132be6c0e6aa1534069c705a74a5997a356c0dc2f86a47765e5617c5b65"
[[package]] [[package]]
name = "futures-util" name = "futures-util"
version = "0.3.30" version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" checksum = "26b01e40b772d54cf6c6d721c1d1abd0647a0106a12ecaa1c186273392a69533"
dependencies = [ dependencies = [
"futures-core", "futures-core",
"futures-io", "futures-io",
@@ -395,12 +315,6 @@ dependencies = [
"wasi 0.11.0+wasi-snapshot-preview1", "wasi 0.11.0+wasi-snapshot-preview1",
] ]
[[package]]
name = "gimli"
version = "0.28.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4271d37baee1b8c7e4b708028c57d816cf9d2434acb33a549475f78c181f6253"
[[package]] [[package]]
name = "glob" name = "glob"
version = "0.3.1" version = "0.3.1"
@@ -409,9 +323,9 @@ checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b"
[[package]] [[package]]
name = "h2" name = "h2"
version = "0.4.3" version = "0.3.19"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "51ee2dd2e4f378392eeff5d51618cd9a63166a2513846bbc55f21cfacd9199d4" checksum = "d357c7ae988e7d2182f7d7871d0b963962420b0678b0997ce7de72001aeab782"
dependencies = [ dependencies = [
"bytes", "bytes",
"fnv", "fnv",
@@ -428,9 +342,18 @@ dependencies = [
[[package]] [[package]]
name = "hashbrown" name = "hashbrown"
version = "0.14.3" version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]] [[package]]
name = "hermit-abi" name = "hermit-abi"
@@ -443,9 +366,9 @@ dependencies = [
[[package]] [[package]]
name = "http" name = "http"
version = "1.1.0" version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" checksum = "bd6effc99afb63425aff9b05836f029929e345a6148a14b7ecd5ab67af944482"
dependencies = [ dependencies = [
"bytes", "bytes",
"fnv", "fnv",
@@ -454,24 +377,12 @@ dependencies = [
[[package]] [[package]]
name = "http-body" name = "http-body"
version = "1.0.0" version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1cac85db508abc24a2e48553ba12a996e87244a0395ce011e62b37158745d643" checksum = "d5f38f16d184e36f2408a55281cd658ecbd3ca05cce6d6510a176eca393e26d1"
dependencies = [ dependencies = [
"bytes", "bytes",
"http", "http",
]
[[package]]
name = "http-body-util"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0475f8b2ac86659c21b64320d5d653f9efe42acd2a4e560073ec61a155a34f1d"
dependencies = [
"bytes",
"futures-core",
"http",
"http-body",
"pin-project-lite", "pin-project-lite",
] ]
@@ -482,59 +393,46 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904" checksum = "d897f394bad6a705d5f4104762e116a75639e470d80901eed05a860a95cb1904"
[[package]] [[package]]
name = "hyper" name = "httpdate"
version = "1.2.0" version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "186548d73ac615b32a73aafe38fb4f56c0d340e110e5a200bcadbaf2e199263a" checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
[[package]]
name = "hyper"
version = "0.14.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab302d72a6f11a3b910431ff93aae7e773078c769f0a3ef15fb9ec692ed147d4"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures-channel", "futures-channel",
"futures-core",
"futures-util", "futures-util",
"h2", "h2",
"http", "http",
"http-body", "http-body",
"httparse", "httparse",
"httpdate",
"itoa", "itoa",
"pin-project-lite", "pin-project-lite",
"smallvec", "socket2",
"tokio", "tokio",
"tower-service",
"tracing",
"want", "want",
] ]
[[package]] [[package]]
name = "hyper-tls" name = "hyper-tls"
version = "0.6.0" version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" checksum = "d6183ddfa99b85da61a140bea0efc93fdf56ceaa041b37d553518030827f9905"
dependencies = [ dependencies = [
"bytes", "bytes",
"http-body-util",
"hyper", "hyper",
"hyper-util",
"native-tls", "native-tls",
"tokio", "tokio",
"tokio-native-tls", "tokio-native-tls",
"tower-service",
]
[[package]]
name = "hyper-util"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca38ef113da30126bbff9cd1705f9273e15d45498615d138b0c20279ac7a76aa"
dependencies = [
"bytes",
"futures-channel",
"futures-util",
"http",
"http-body",
"hyper",
"pin-project-lite",
"socket2",
"tokio",
"tower",
"tower-service",
"tracing",
] ]
[[package]] [[package]]
@@ -572,19 +470,19 @@ dependencies = [
[[package]] [[package]]
name = "indexmap" name = "indexmap"
version = "2.2.6" version = "1.9.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99"
dependencies = [ dependencies = [
"equivalent", "autocfg",
"hashbrown", "hashbrown",
] ]
[[package]] [[package]]
name = "indicatif" name = "indicatif"
version = "0.17.8" version = "0.17.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "763a5a8f45087d6bcea4222e7b72c291a054edf80e4ef6efd2a4979878c7bea3" checksum = "8ff8cc23a7393a397ed1d7f56e6365cba772aba9f9912ab968b03043c395d057"
dependencies = [ dependencies = [
"console", "console",
"instant", "instant",
@@ -616,9 +514,9 @@ checksum = "453ad9f582a441959e5f0d088b02ce04cfe8d51a8eaf077f12ac6d3e94164ca6"
[[package]] [[package]]
name = "js-sys" name = "js-sys"
version = "0.3.69" version = "0.3.63"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" checksum = "2f37a4a5928311ac501dee68b3c7613a1037d0edb30c8e5427bd832d55d1b790"
dependencies = [ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
@@ -665,9 +563,9 @@ checksum = "490cc448043f947bae3cbee9c203358d62dbee0db12107a74be5c30ccfd09771"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.2" version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6c8640c5d730cb13ebd907d8d04b52f55ac9a2eec55b440c8892f40d56c76c1d" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]] [[package]]
name = "mime" name = "mime"
@@ -685,20 +583,11 @@ dependencies = [
"unicase", "unicase",
] ]
[[package]]
name = "miniz_oxide"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9d811f3e15f28568be3407c8e7fdb6514c1cda3cb30683f15b6a1a1dc4ea14a7"
dependencies = [
"adler",
]
[[package]] [[package]]
name = "mio" name = "mio"
version = "0.8.11" version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" checksum = "eebffdb73fe72e917997fad08bdbf31ac50b0fa91cec93e69a0662e4264d454c"
dependencies = [ dependencies = [
"libc", "libc",
"wasi 0.11.0+wasi-snapshot-preview1", "wasi 0.11.0+wasi-snapshot-preview1",
@@ -741,9 +630,8 @@ dependencies = [
"reqwest", "reqwest",
"rpassword", "rpassword",
"rust-crypto", "rust-crypto",
"rustc-serialize",
"tempfile", "tempfile",
"textwrap", "textwrap 0.13.4",
"tokio", "tokio",
"xml-rs", "xml-rs",
] ]
@@ -763,7 +651,7 @@ version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b" checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [ dependencies = [
"hermit-abi", "hermit-abi 0.2.6",
"libc", "libc",
] ]
@@ -773,15 +661,6 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3"
[[package]]
name = "object"
version = "0.32.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6a622008b6e321afc04970976f62ee297fdbaa6f95318ca343e3eebb9648441"
dependencies = [
"memchr",
]
[[package]] [[package]]
name = "once_cell" name = "once_cell"
version = "1.17.2" version = "1.17.2"
@@ -861,31 +740,11 @@ version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e" checksum = "478c572c3d73181ff3c2539045f6eb99e5491218eae919370993b890cdbdd98e"
[[package]]
name = "pin-project"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "pin-project-lite" name = "pin-project-lite"
version = "0.2.14" version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]] [[package]]
name = "pin-utils" name = "pin-utils"
@@ -1017,21 +876,9 @@ dependencies = [
[[package]] [[package]]
name = "regex" name = "regex"
version = "1.10.4" version = "1.8.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c117dbdfde9c8308975b6a18d71f3f385c89461f7b3fb054288ecf2a2058ba4c" checksum = "81ca098a9821bd52d6b24fd8b10bd081f47d39c22778cafaa75a2857a62c6390"
dependencies = [
"aho-corasick",
"memchr",
"regex-automata",
"regex-syntax",
]
[[package]]
name = "regex-automata"
version = "0.4.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86b83b8b9847f9bf95ef68afb0b8e6cdb80f498442f5179a29fad448fcc1eaea"
dependencies = [ dependencies = [
"aho-corasick", "aho-corasick",
"memchr", "memchr",
@@ -1040,15 +887,15 @@ dependencies = [
[[package]] [[package]]
name = "regex-syntax" name = "regex-syntax"
version = "0.8.3" version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "adad44e29e4c806119491a7f06f03de4d1af22c3a680dd47f1e6e179439d1f56" checksum = "436b050e76ed2903236f032a59761c1eb99e1b0aead2c257922771dab1fc8c78"
[[package]] [[package]]
name = "reqwest" name = "reqwest"
version = "0.12.2" version = "0.11.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2d66674f2b6fb864665eea7a3c1ac4e3dfacd2fda83cf6f935a612e01b0e3338" checksum = "cde824a14b7c14f85caff81225f411faacc04a2013f41670f41443742b1c1c55"
dependencies = [ dependencies = [
"base64", "base64",
"bytes", "bytes",
@@ -1058,10 +905,8 @@ dependencies = [
"h2", "h2",
"http", "http",
"http-body", "http-body",
"http-body-util",
"hyper", "hyper",
"hyper-tls", "hyper-tls",
"hyper-util",
"ipnet", "ipnet",
"js-sys", "js-sys",
"log", "log",
@@ -1071,12 +916,9 @@ dependencies = [
"once_cell", "once_cell",
"percent-encoding", "percent-encoding",
"pin-project-lite", "pin-project-lite",
"rustls-pemfile",
"serde", "serde",
"serde_json", "serde_json",
"serde_urlencoded", "serde_urlencoded",
"sync_wrapper",
"system-configuration",
"tokio", "tokio",
"tokio-native-tls", "tokio-native-tls",
"tokio-util", "tokio-util",
@@ -1091,13 +933,13 @@ dependencies = [
[[package]] [[package]]
name = "rpassword" name = "rpassword"
version = "7.3.1" version = "7.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "80472be3c897911d0137b2d2b9055faf6eeac5b14e324073d83bc17b191d7e3f" checksum = "6678cf63ab3491898c0d021b493c94c9b221d91295294a2a5746eacbe5928322"
dependencies = [ dependencies = [
"libc", "libc",
"rtoolbox", "rtoolbox",
"windows-sys 0.48.0", "winapi",
] ]
[[package]] [[package]]
@@ -1123,17 +965,11 @@ dependencies = [
"time", "time",
] ]
[[package]]
name = "rustc-demangle"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76"
[[package]] [[package]]
name = "rustc-serialize" name = "rustc-serialize"
version = "0.3.25" 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 = "fe834bc780604f4674073badbad26d7219cadfb4a2275802db12cbae17498401" checksum = "dcf128d1287d2ea9d80910b5f1120d0b8eede3fbf1abe91c40d39ea7d51e6fda"
[[package]] [[package]]
name = "rustix" name = "rustix"
@@ -1148,15 +984,6 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "rustls-pemfile"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1c74cae0a4cf6ccbbf5f359f08efdf8ee7e1dc532573bf0db71968cb56b1448c"
dependencies = [
"base64",
]
[[package]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.13" version = "1.0.13"
@@ -1250,9 +1077,9 @@ dependencies = [
[[package]] [[package]]
name = "smallvec" name = "smallvec"
version = "1.13.2" version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]] [[package]]
name = "smawk" name = "smawk"
@@ -1262,19 +1089,19 @@ checksum = "f67ad224767faa3c7d8b6d91985b78e70a1324408abcb1cfcc2be4c06bc06043"
[[package]] [[package]]
name = "socket2" name = "socket2"
version = "0.5.6" version = "0.4.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "05ffd9c0a93b7543e062e759284fcf5f5e3b098501104bfbdde4d404db792871" checksum = "64a4a911eed85daf18834cfaa86a79b7d266ff93ff5ba14005426219480ed662"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.52.0", "winapi",
] ]
[[package]] [[package]]
name = "strsim" name = "strsim"
version = "0.11.0" version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5ee073c9e4cd00e28217186dbe12796d692868f432bf2e97ee73bed0c56dfa01" checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]] [[package]]
name = "syn" name = "syn"
@@ -1287,38 +1114,11 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "sync_wrapper"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2047c6ded9c721764247e62cd3b03c09ffc529b2ba5b10ec482ae507a4a70160"
[[package]]
name = "system-configuration"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7"
dependencies = [
"bitflags 1.3.2",
"core-foundation",
"system-configuration-sys",
]
[[package]]
name = "system-configuration-sys"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9"
dependencies = [
"core-foundation-sys",
"libc",
]
[[package]] [[package]]
name = "tempfile" name = "tempfile"
version = "3.10.1" version = "3.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" checksum = "a365e8cd18e44762ef95d87f284f4b5cd04107fec2ff3052bd6a3e6069669e67"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"fastrand", "fastrand",
@@ -1328,12 +1128,20 @@ dependencies = [
[[package]] [[package]]
name = "textwrap" name = "textwrap"
version = "0.16.1" version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "textwrap"
version = "0.13.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd05616119e612a8041ef58f2b578906cc2531a6069047ae092cfb86a325d835"
dependencies = [ dependencies = [
"smawk", "smawk",
"unicode-linebreak",
"unicode-width", "unicode-width",
] ]
@@ -1365,11 +1173,11 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20"
[[package]] [[package]]
name = "tokio" name = "tokio"
version = "1.37.0" version = "1.28.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1adbebffeca75fcfd058afa480fb6c0b81e165a0323f9c9d39c9697e37c46787" checksum = "94d7b1cfd2aa4011f2de74c2c4c63665e27a71006b0a192dcd2710272e73dfa2"
dependencies = [ dependencies = [
"backtrace", "autocfg",
"bytes", "bytes",
"libc", "libc",
"mio", "mio",
@@ -1384,9 +1192,9 @@ dependencies = [
[[package]] [[package]]
name = "tokio-macros" name = "tokio-macros"
version = "2.2.0" version = "2.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b8a1e28f2deaa14e508979454cb3a223b10b938b45af148bc0986de36f1923b" checksum = "630bdcf245f78637c13ec01ffae6187cca34625e8c63150d424b59e55af2675e"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1417,28 +1225,6 @@ dependencies = [
"tracing", "tracing",
] ]
[[package]]
name = "tower"
version = "0.4.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c"
dependencies = [
"futures-core",
"futures-util",
"pin-project",
"pin-project-lite",
"tokio",
"tower-layer",
"tower-service",
"tracing",
]
[[package]]
name = "tower-layer"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0"
[[package]] [[package]]
name = "tower-service" name = "tower-service"
version = "0.3.2" version = "0.3.2"
@@ -1452,7 +1238,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8" checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"log",
"pin-project-lite", "pin-project-lite",
"tracing-core", "tracing-core",
] ]
@@ -1493,12 +1278,6 @@ version = "1.0.9"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0" checksum = "b15811caf2415fb889178633e7724bad2509101cde276048e013b9def5e51fa0"
[[package]]
name = "unicode-linebreak"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f"
[[package]] [[package]]
name = "unicode-normalization" name = "unicode-normalization"
version = "0.1.22" version = "0.1.22"
@@ -1525,18 +1304,18 @@ dependencies = [
"percent-encoding", "percent-encoding",
] ]
[[package]]
name = "utf8parse"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]] [[package]]
name = "vcpkg" name = "vcpkg"
version = "0.2.15" version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]] [[package]]
name = "version_check" name = "version_check"
version = "0.9.4" version = "0.9.4"
@@ -1567,9 +1346,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]] [[package]]
name = "wasm-bindgen" name = "wasm-bindgen"
version = "0.2.92" version = "0.2.86"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" checksum = "5bba0e8cb82ba49ff4e229459ff22a191bbe9a1cb3a341610c9c33efc27ddf73"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"wasm-bindgen-macro", "wasm-bindgen-macro",
@@ -1577,9 +1356,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-backend" name = "wasm-bindgen-backend"
version = "0.2.92" version = "0.2.86"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" checksum = "19b04bc93f9d6bdee709f6bd2118f57dd6679cf1176a1af464fca3ab0d66d8fb"
dependencies = [ dependencies = [
"bumpalo", "bumpalo",
"log", "log",
@@ -1592,9 +1371,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-futures" name = "wasm-bindgen-futures"
version = "0.4.42" version = "0.4.36"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" checksum = "2d1985d03709c53167ce907ff394f5316aa22cb4e12761295c5dc57dacb6297e"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"js-sys", "js-sys",
@@ -1604,9 +1383,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro" name = "wasm-bindgen-macro"
version = "0.2.92" version = "0.2.86"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" checksum = "14d6b024f1a526bb0234f52840389927257beb670610081360e5a03c5df9c258"
dependencies = [ dependencies = [
"quote", "quote",
"wasm-bindgen-macro-support", "wasm-bindgen-macro-support",
@@ -1614,9 +1393,9 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-macro-support" name = "wasm-bindgen-macro-support"
version = "0.2.92" version = "0.2.86"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" checksum = "e128beba882dd1eb6200e1dc92ae6c5dbaa4311aa7bb211ca035779e5efc39f8"
dependencies = [ dependencies = [
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -1627,15 +1406,15 @@ dependencies = [
[[package]] [[package]]
name = "wasm-bindgen-shared" name = "wasm-bindgen-shared"
version = "0.2.92" version = "0.2.86"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" checksum = "ed9d5b4305409d1fc9482fee2d7f9bcbf24b3972bf59817ef757e23982242a93"
[[package]] [[package]]
name = "wasm-streams" name = "wasm-streams"
version = "0.4.0" 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 = "b65dc4c90b63b118468cf747d8bf3566c1913ef60be765b5730ead9e0a3ba129" checksum = "6bbae3363c08332cadccd13b67db371814cd214c2524020932f0804b8cf7c078"
dependencies = [ dependencies = [
"futures-util", "futures-util",
"js-sys", "js-sys",
@@ -1646,9 +1425,9 @@ dependencies = [
[[package]] [[package]]
name = "web-sys" name = "web-sys"
version = "0.3.69" version = "0.3.63"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" checksum = "3bdd9ef4e984da1187bf8110c5cf5b845fbc87a23602cdf912386a76fcd3a7c2"
dependencies = [ dependencies = [
"js-sys", "js-sys",
"wasm-bindgen", "wasm-bindgen",
@@ -1900,16 +1679,15 @@ checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]] [[package]]
name = "winreg" name = "winreg"
version = "0.50.0" version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "524e57b2c537c0f9b1e69f1965311ec12182b4122e45035b1508cd24d2adadb1" checksum = "80d0f4e272c85def139476380b12f9ac60926689dd2e01d4923222f40580869d"
dependencies = [ dependencies = [
"cfg-if", "winapi",
"windows-sys 0.48.0",
] ]
[[package]] [[package]]
name = "xml-rs" name = "xml-rs"
version = "0.8.19" version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fcb9cbac069e033553e8bb871be2fbdffcab578eb25bd0f7c508cedc6dcd75a" checksum = "52839dc911083a8ef63efa4d039d1f58b5e409f923e44c80828f206f66e5541c"

View File

@@ -6,25 +6,24 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
rustc-serialize="0.3.25" reqwest = { version = "0.11", features = ["stream", "json", "multipart"] }
reqwest = { version = "0.12", features = ["stream", "json", "multipart"] } tokio = { version = "1", features = ["full"] }
tokio = { version = "1.37", features = ["full"] }
dotenv ="0.15.0" dotenv ="0.15.0"
clap = "4.5.4" clap = "2.33"
rust-crypto = "0.2.36" rust-crypto = "0.2.36"
colored = "2.1.0" colored = "2.0.0"
xml-rs = "0.8.19" xml-rs = "0.8.0"
regex = "1.10.4" regex = "1.8.3"
lazy_static = "1.4.0" lazy_static = "1.4.0"
glob = "0.3.1" glob = "0.3.1"
textwrap = "0.16.1" textwrap = "0.13"
chrono = "0.4.37" chrono = "0.4.26"
indicatif = "0.17.8" indicatif = "0.17.5"
md5 = "0.7.0" md5 = "0.7.0"
futures-util = "0.3.30" futures-util = "0.3.28"
rpassword = "7.3.1" rpassword = "7.2"
rand = "0.8.5" rand = "0.8"
tempfile = "3.10.1" tempfile = "3.10.0"
[profile.release] [profile.release]
debug = true debug = true

View File

@@ -1,14 +1,14 @@
use std::io::Write; use std::io::Write;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use clap::Values;
use glob::glob; use glob::glob;
use crate::store::{self, object::Object}; use crate::store::{self, object::Object};
use crate::utils::{self, path}; use crate::utils::{self, path};
use crate::store::object::object::{Obj, ObjMethods};
use crate::utils::nextsyncignore::{self, ignore_file}; use crate::utils::nextsyncignore::{self, ignore_file};
use crate::utils::path::{normalize_relative, repo_root, path_buf_to_string}; use crate::utils::path::{normalize_relative, repo_root, path_buf_to_string};
pub struct AddArgs { pub struct AddArgs<'a> {
pub files: Vec<String>, pub files: Option<Values<'a>>,
pub force: bool, pub force: bool,
pub all: bool, pub all: bool,
} }
@@ -17,13 +17,13 @@ pub struct AddArgs {
pub fn add(args: AddArgs) { pub fn add(args: AddArgs) {
let mut pattern: String; let mut pattern: String;
let file_vec: Vec<String> = match args.all { let file_vec: Vec<&str> = match args.all {
true => { true => {
pattern = path_buf_to_string(repo_root()); pattern = path_buf_to_string(repo_root());
pattern.push_str("/*"); pattern.push_str("/*");
vec![pattern] vec![&pattern]
}, },
false => args.files, false => args.files.unwrap().collect(),
}; };
let mut added_files: Vec<String> = vec![]; let mut added_files: Vec<String> = vec![];
@@ -31,7 +31,7 @@ pub fn add(args: AddArgs) {
let rules = nextsyncignore::get_rules(); let rules = nextsyncignore::get_rules();
for file in file_vec { for file in file_vec {
let f = match normalize_relative(&file) { let f = match normalize_relative(file) {
Ok(f) => f, Ok(f) => f,
Err(err) => { Err(err) => {
eprintln!("err: {} {}", file, err); eprintln!("err: {} {}", file, err);
@@ -39,19 +39,35 @@ pub fn add(args: AddArgs) {
} }
}; };
// check if the file must be ignored
if !args.force && ignore_file(&f, rules.clone(), &mut ignored_f) {
continue;
}
let path = repo_root().join(Path::new(&f)); let path = repo_root().join(Path::new(&f));
match path.exists() { match path.exists() {
true => { true => {
add_entry(path, args.force, &mut added_files, rules.clone(), &mut ignored_f); if path.is_dir() {
add_folder_content(path.to_path_buf(), &mut added_files);
}
added_files.push(f);
}, },
false => { false => {
if Object::new(path.to_str().unwrap()).exists() { if Object::new(path.to_str().unwrap()).exists() {
// object is deleted so not present but can still be added for deletion // object is deleted so not a present file but can still be added
added_files.push(String::from(f)); added_files.push(String::from(f));
} else { } else {
// try globbing if nothing has been found
for entry in try_globbing(path) { for entry in try_globbing(path) {
add_entry(entry, args.force, &mut added_files, rules.clone(), &mut ignored_f); if path::is_nextsync_config(entry.clone()) {
continue;
}
if !args.force && ignore_file(&path_buf_to_string(entry.clone()), rules.clone(), &mut ignored_f) {
continue;
}
if entry.is_dir() {
add_folder_content(entry.to_path_buf(), &mut added_files);
}
added_files.push(path_buf_to_string(entry.strip_prefix(repo_root()).unwrap().to_path_buf()));
} }
} }
} }
@@ -62,41 +78,6 @@ pub fn add(args: AddArgs) {
write_added_files(added_files); write_added_files(added_files);
} }
fn add_entry(entry: PathBuf, force: bool, added_files: &mut Vec<String>, rules: Vec<String>, ignored_f: &mut Vec<String>) {
// ignore nextsync config files
if path::is_nextsync_config(entry.clone()) {
return;
}
// check if the file must be ignored
if !force && ignore_file(&path_buf_to_string(entry.clone()), rules, ignored_f) {
return;
}
// add the parent if there is one and it is not already created
add_parent(entry.clone(), added_files);
added_files.push(path_buf_to_string(entry.strip_prefix(repo_root()).unwrap().to_path_buf()));
if entry.is_dir() {
add_folder_content(entry.to_path_buf(), added_files);
}
}
fn add_parent(entry: PathBuf, added_files: &mut Vec<String>) {
let test_parent = entry.strip_prefix(repo_root()).unwrap().parent();
if test_parent.is_none() || test_parent.unwrap() == PathBuf::new() {
return;
}
let parent = entry.parent().unwrap();
if !Obj::from_path(parent).exists_on_remote() {
add_parent(parent.to_path_buf(), added_files);
added_files.push(path_buf_to_string(parent.strip_prefix(repo_root()).unwrap().to_path_buf()));
}
}
fn print_ignored_files(ignored_files: Vec<String>) { fn print_ignored_files(ignored_files: Vec<String>) {
if ignored_files.len() > 0 { if ignored_files.len() > 0 {
// todo multiple nextsyncignore // todo multiple nextsyncignore

View File

@@ -2,6 +2,7 @@ use std::io;
use std::io::prelude::*; use std::io::prelude::*;
use std::fs::DirBuilder; use std::fs::DirBuilder;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use clap::Values;
use regex::Regex; use regex::Regex;
use crate::services::downloader::Downloader; use crate::services::downloader::Downloader;
use crate::utils::api::ApiProps; use crate::utils::api::ApiProps;
@@ -11,22 +12,22 @@ use crate::global::global::{DIR_PATH, set_dir_path};
use crate::services::api::ApiError; use crate::services::api::ApiError;
use crate::services::api_call::ApiCall; use crate::services::api_call::ApiCall;
use crate::services::req_props::{ReqProps, ObjProps}; use crate::services::req_props::{ReqProps, ObjProps};
use crate::store::object::{tree::Tree, blob::Blob}; use crate::store::object::{tree, blob::Blob};
use crate::commands::config; use crate::commands::config;
use crate::commands::init; use crate::commands::init;
pub const DEPTH: &str = "3"; pub const DEPTH: &str = "3";
pub struct CloneArgs { pub struct CloneArgs<'a> {
pub remote: String, pub remote: Values<'a>,
pub depth: Option<String>, pub depth: Option<String>,
} }
pub fn clone(args: CloneArgs) { pub fn clone(args: CloneArgs) {
let d = DIR_PATH.lock().unwrap().clone(); let d = DIR_PATH.lock().unwrap().clone();
let url = args.remote.clone(); let url = args.remote.clone().next().unwrap();
let (host, tmp_user, dist_path_str) = get_url_props(&url); let (host, tmp_user, dist_path_str) = get_url_props(url);
let username = match tmp_user { let username = match tmp_user {
Some(u) => u.to_string(), Some(u) => u.to_string(),
None => { None => {
@@ -90,7 +91,7 @@ pub fn clone(args: CloneArgs) {
// add tree // add tree
let path_folder = p.strip_prefix(ref_path.clone()).unwrap(); let path_folder = p.strip_prefix(ref_path.clone()).unwrap();
let lastmodified = folder.lastmodified.unwrap().timestamp_millis(); let lastmodified = folder.lastmodified.unwrap().timestamp_millis();
if let Err(err) = Tree::from_path(path_folder.to_path_buf()).create(&lastmodified.to_string(), false) { if let Err(err) = tree::add(path_folder.to_path_buf(), &lastmodified.to_string(), false) {
eprintln!("err: saving ref of {} ({})", path_folder.display(), err); eprintln!("err: saving ref of {} ({})", path_folder.display(), err);
} }
} }
@@ -106,7 +107,7 @@ fn save_blob(obj: ObjProps) {
let relative_s = &obj.clone().relative_s.unwrap(); let relative_s = &obj.clone().relative_s.unwrap();
let relative_p = PathBuf::from(&relative_s); let relative_p = PathBuf::from(&relative_s);
let lastmodified = obj.clone().lastmodified.unwrap().timestamp_millis(); let lastmodified = obj.clone().lastmodified.unwrap().timestamp_millis();
if let Err(err) = Blob::from_path(relative_p).create(&lastmodified.to_string(), false) { if let Err(err) = Blob::new(relative_p).create(&lastmodified.to_string(), false) {
eprintln!("err: saving ref of {} ({})", relative_s.clone(), err); eprintln!("err: saving ref of {} ({})", relative_s.clone(), err);
} }
} }
@@ -177,7 +178,7 @@ mod tests {
fn test_get_url_props() { fn test_get_url_props() {
let p = "/foo/bar"; let p = "/foo/bar";
let u = Some("user"); let u = Some("user");
// let d = String::from("http://nextcloud.com"); let d = String::from("http://nextcloud.com");
let sd = String::from("https://nextcloud.com"); let sd = String::from("https://nextcloud.com");
let sld = String::from("https://nextcloud.example.com"); let sld = String::from("https://nextcloud.example.com");
let ld = String::from("http://nextcloud.example.com"); let ld = String::from("http://nextcloud.example.com");

View File

@@ -1,11 +1,12 @@
use std::fs::OpenOptions; use std::fs::OpenOptions;
use clap::Values;
use std::io::{self, Write, BufRead, Seek, SeekFrom}; use std::io::{self, Write, BufRead, Seek, SeekFrom};
use crate::utils::{path, read}; use crate::utils::{path, read};
use std::collections::HashMap; use std::collections::HashMap;
pub struct ConfigSetArgs { pub struct ConfigSetArgs<'a> {
pub name: String, pub name: Option<Values<'a>>,
pub value: String, pub value: Option<Values<'a>>,
} }
pub fn config_set(args: ConfigSetArgs) { pub fn config_set(args: ConfigSetArgs) {
@@ -14,25 +15,29 @@ pub fn config_set(args: ConfigSetArgs) {
option_categories.insert("force_insecure", "core"); option_categories.insert("force_insecure", "core");
option_categories.insert("token", "core"); option_categories.insert("token", "core");
let name = args.name.unwrap().next().unwrap();
let value = args.value.unwrap().next().unwrap();
// get category of option // get category of option
let category = option_categories.get(args.name.as_str()); let category = option_categories.get(name);
if category.is_none() { if category.is_none() {
eprintln!("fatal: '{}' is not a valid option.", args.name.clone()); eprintln!("fatal: '{}' is not a valid option.", name);
std::process::exit(1); std::process::exit(1);
} }
let _ = write_option_in_cat(category.unwrap(), &args.name, &args.value); write_option_in_cat(category.unwrap(), name, value);
} }
pub fn find_option_in_cat(category: &str, option: &str) -> Option<String> { pub fn find_option_in_cat(category: &str, option: &str) -> Option<String> {
let mut config = path::nextsync(); let mut root = path::nextsync();
config.push("config"); root.push("config");
let mut in_target_category = false; let mut in_target_category = false;
if let Ok(lines) = read::read_lines(config) { if let Ok(mut lines) = read::read_lines(root) {
for line in lines { for line in lines {
if let Ok(line) = line { if let Ok(line) = line {
let trimmed_line = line.trim(); let trimmed_line = line.trim();
@@ -53,14 +58,14 @@ pub fn find_option_in_cat(category: &str, option: &str) -> Option<String> {
} }
pub fn write_option_in_cat(category: &str, option: &str, value: &str) -> io::Result<()> { pub fn write_option_in_cat(category: &str, option: &str, value: &str) -> io::Result<()> {
let mut config = path::nextsync(); let mut root = path::nextsync();
config.push("config"); root.push("config");
let mut file = OpenOptions::new() let mut file = OpenOptions::new()
.read(true) .read(true)
.write(true) .write(true)
.create(true) .create(true)
.open(&config)?; .open(&root)?;
let mut in_target_category = false; let mut in_target_category = false;
let mut option_found = false; let mut option_found = false;
@@ -124,7 +129,8 @@ pub fn write_option_in_cat(category: &str, option: &str, value: &str) -> io::Res
} }
pub fn add_remote(name: &str, url: &str) -> io::Result<()> { pub fn add_remote(name: &str, url: &str) -> io::Result<()> {
let config = path::config(); let mut root = path::nextsync();
root.push("config");
// check if there is already a remote with this name // check if there is already a remote with this name
if get_remote(name).is_some() if get_remote(name).is_some()
@@ -138,7 +144,7 @@ pub fn add_remote(name: &str, url: &str) -> io::Result<()> {
.write(true) .write(true)
.create(true) .create(true)
.append(true) .append(true)
.open(config)?; .open(root)?;
writeln!(file, "[remote \"{}\"]", name)?; writeln!(file, "[remote \"{}\"]", name)?;
writeln!(file, "\turl = {}", url)?; writeln!(file, "\turl = {}", url)?;
@@ -150,41 +156,42 @@ pub fn get_remote(name: &str) -> Option<String> {
find_option_in_cat(&format!("remote \"{}\"", name), "url") find_option_in_cat(&format!("remote \"{}\"", name), "url")
} }
/// return a vector of remote found in config file (e.g: ("origin", "https://example.com"))
pub fn get_all_remote() -> Vec<(String, String)> {
let config = path::config();
let mut remotes: Vec<(String, String)> = vec![];
let mut in_remote = false;
let mut remote_name = String::new();
if let Ok(lines) = read::read_lines(config) {
for line in lines {
if let Ok(line) = line {
let trimmed_line = line.trim();
if trimmed_line.starts_with("[remote ") {
in_remote = true;
remote_name = trimmed_line.strip_prefix("[remote \"").unwrap().strip_suffix("\"]").unwrap().to_string();
}
else if trimmed_line.starts_with('[')
{
in_remote = false;
}
else if in_remote {
let parts: Vec<&str> = trimmed_line.splitn(2, '=').collect();
if parts.len() == 2 {
remotes.push((remote_name.to_string(), parts[1].trim().to_string()))
}
}
}
}
}
remotes
}
pub fn get_core(name: &str) -> Option<String> { pub fn get_core(name: &str) -> Option<String> {
find_option_in_cat("core", name) find_option_in_cat("core", name)
} }
pub fn add_core(name: &str, value: &str) -> io::Result<()> {
let mut root = path::nextsync();
root.push("config");
let mut file = OpenOptions::new()
.read(true)
.write(true)
.create(true)
.append(true)
.open(root)?;
writeln!(file, "[core]")?;
writeln!(file, "\t{} = {}", name, value)?;
Ok(())
}
pub fn get(var: &str) -> Option<String> {
let mut root = path::nextsync();
root.push("config");
if let Ok(lines) = read::read_lines(root) {
for line in lines {
if let Ok(l) = line {
if l.starts_with(var) {
let (_, val) = l.split_once(" ").unwrap();
return Some(val.to_owned());
}
}
}
}
None
}

View File

@@ -1,3 +1,4 @@
use clap::Values;
use crate::commands::clone::get_url_props; use crate::commands::clone::get_url_props;
use crate::services::api::ApiError::RequestError; use crate::services::api::ApiError::RequestError;
@@ -5,9 +6,9 @@ use crate::services::login::Login;
use crate::services::api_call::ApiCall; use crate::services::api_call::ApiCall;
use crate::commands::config; use crate::commands::config;
pub struct CredentialArgs { pub struct CredentialArgs<'a> {
pub username: String, pub username: Option<Values<'a>>,
pub password: Option<String>, pub password: Option<Values<'a>>,
} }
pub fn credential_add(args: CredentialArgs) { pub fn credential_add(args: CredentialArgs) {
@@ -22,9 +23,9 @@ pub fn credential_add(args: CredentialArgs) {
let (host, _, _) = get_url_props(&remote); let (host, _, _) = get_url_props(&remote);
// get username and password // get username and password
let username = args.username.to_owned(); let username = args.username.unwrap().next().unwrap();
let password = match args.password { let password = match args.password {
Some(mut pwd) => pwd.to_owned(), Some(mut pwd) => pwd.next().unwrap().to_owned(),
None => { None => {
println!("Please enter the password for {}: ", username); println!("Please enter the password for {}: ", username);
rpassword::read_password().unwrap() rpassword::read_password().unwrap()
@@ -33,7 +34,7 @@ pub fn credential_add(args: CredentialArgs) {
// get token // get token
let get_token = Login::new() let get_token = Login::new()
.set_auth(&username, &password) .set_auth(username, &password)
.set_host(Some(host)) .set_host(Some(host))
.send_login(); .send_login();

View File

@@ -1,6 +1,7 @@
use std::env; use std::env;
use std::fs::{DirBuilder, File}; use std::fs::{DirBuilder, File};
use std::path::PathBuf; use std::path::PathBuf;
use crate::utils::read::read_folder;
use crate::global::global::DIR_PATH; use crate::global::global::DIR_PATH;
pub fn init() { pub fn init() {
@@ -11,17 +12,16 @@ pub fn init() {
None => env::current_dir().unwrap(), None => env::current_dir().unwrap(),
}; };
// todo
// check if dir is empty // check if dir is empty
// if let Ok(entries) = read_folder(path.clone()) { if let Ok(entries) = read_folder(path.clone()) {
// if entries.len() != 0 { if entries.len() != 0 {
// eprintln!("fatal: destination path '{}' already exists and is not an empty directory.", path.display()); eprintln!("fatal: destination path '{}' already exists and is not an empty directory.", path.display());
// std::process::exit(1); std::process::exit(1);
// } }
// } else { } else {
// eprintln!("fatal: cannot open the destination directory"); eprintln!("fatal: cannot open the destination directory");
// std::process::exit(1); std::process::exit(1);
// } }
let builder = DirBuilder::new(); let builder = DirBuilder::new();
@@ -58,7 +58,6 @@ pub fn init() {
Err(_) => println!("Error: cannot create index"), Err(_) => println!("Error: cannot create index"),
} }
// todo
path.pop(); path.pop();
path.pop(); path.pop();
path.push(".nextsyncignore"); path.push(".nextsyncignore");

View File

@@ -4,7 +4,7 @@ use std::fs::DirBuilder;
use crate::services::downloader::Downloader; use crate::services::downloader::Downloader;
use crate::services::req_props::ObjProps; use crate::services::req_props::ObjProps;
use crate::store::object::blob::Blob; use crate::store::object::blob::Blob;
use crate::store::object::tree::Tree; use crate::store::object::tree;
use crate::utils::api::get_api_props; use crate::utils::api::get_api_props;
use crate::utils::path; use crate::utils::path;
use crate::commands::remote_diff::get_diff; use crate::commands::remote_diff::get_diff;
@@ -16,10 +16,10 @@ pub fn pull() {
.strip_prefix(path::repo_root()).unwrap().to_path_buf(); .strip_prefix(path::repo_root()).unwrap().to_path_buf();
let (folders, files) = get_diff(relative_p); let (folders, files) = get_diff(relative_p);
let root = path::repo_root(); let ref_p = path::nextsync();
for folder in folders { for folder in folders {
let p = root.clone().join(PathBuf::from(folder.relative_s.unwrap())); let p = ref_p.clone().join(PathBuf::from(folder.relative_s.unwrap()));
if !p.exists() { if !p.exists() {
// create folder // create folder
if let Err(err) = DirBuilder::new().recursive(true).create(p.clone()) { if let Err(err) = DirBuilder::new().recursive(true).create(p.clone()) {
@@ -27,9 +27,9 @@ pub fn pull() {
} }
// add tree // add tree
let path_folder = p.strip_prefix(root.clone()).unwrap(); let path_folder = p.strip_prefix(ref_p.clone()).unwrap();
let lastmodified = folder.lastmodified.unwrap().timestamp_millis(); let lastmodified = folder.lastmodified.unwrap().timestamp_millis();
if let Err(err) = Tree::from_path(path_folder).create(&lastmodified.to_string(), false) { if let Err(err) = tree::add(path_folder.to_path_buf(), &lastmodified.to_string(), false) {
eprintln!("err: saving ref of {} ({})", path_folder.display(), err); eprintln!("err: saving ref of {} ({})", path_folder.display(), err);
} }
} }
@@ -39,16 +39,18 @@ pub fn pull() {
.set_api_props(get_api_props()) .set_api_props(get_api_props())
.set_files(files) .set_files(files)
.should_log() .should_log()
.download(root, Some(&update_blob)); .download(ref_p.clone(), Some(&update_blob));
// todo look if need to download or update // todo look if need to download or update
} }
fn update_blob(obj: ObjProps) { fn update_blob(obj: ObjProps) {
// todo update blob
return;
let relative_s = &obj.clone().relative_s.unwrap(); let relative_s = &obj.clone().relative_s.unwrap();
let relative_p = PathBuf::from(&relative_s); let relative_p = PathBuf::from(&relative_s);
let lastmodified = obj.clone().lastmodified.unwrap().timestamp_millis(); let lastmodified = obj.clone().lastmodified.unwrap().timestamp_millis();
// todo update function // todo update function
if let Err(err) = Blob::from_path(relative_p).create(&lastmodified.to_string(), false) { if let Err(err) = Blob::new(relative_p).create(&lastmodified.to_string(), false) {
eprintln!("err: saving ref of {} ({})", relative_s.clone(), err); eprintln!("err: saving ref of {} ({})", relative_s.clone(), err);
} }
} }

View File

@@ -15,6 +15,7 @@ pub mod moved;
pub mod copied; pub mod copied;
pub fn push() { pub fn push() {
// todo err when pushing new folder
let _remote = match config::get_remote("origin") { let _remote = match config::get_remote("origin") {
Some(r) => r, Some(r) => r,
None => { None => {

View File

@@ -70,7 +70,7 @@ impl PushChange for Copied {
let lastmodified = prop.lastmodified.unwrap().timestamp_millis(); let lastmodified = prop.lastmodified.unwrap().timestamp_millis();
// create destination blob // create destination blob
if let Err(err) = Blob::from_path(obj.path.clone()).create(&lastmodified.to_string(), false) { if let Err(err) = Blob::new(obj.path.clone()).create(&lastmodified.to_string(), false) {
eprintln!("err: creating ref of {}: {}", obj.name.clone(), err); eprintln!("err: creating ref of {}: {}", obj.name.clone(), err);
} }

View File

@@ -7,7 +7,6 @@ use crate::store::index;
use crate::store::object::blob::Blob; use crate::store::object::blob::Blob;
use crate::commands::status::LocalObj; use crate::commands::status::LocalObj;
use crate::commands::push::push_factory::{PushState, PushChange, PushFlowState}; use crate::commands::push::push_factory::{PushState, PushChange, PushFlowState};
use crate::store::object::object::ObjMethods;
pub struct Deleted { pub struct Deleted {
pub obj: LocalObj pub obj: LocalObj
@@ -44,7 +43,7 @@ impl PushChange for Deleted {
// update tree // update tree
// todo date // todo date
Blob::from_path(obj.path.clone()).rm()?; Blob::new(obj.path.clone()).rm()?;
// remove index // remove index
index::rm_line(obj.path.to_str().unwrap())?; index::rm_line(obj.path.to_str().unwrap())?;

View File

@@ -68,7 +68,7 @@ impl PushChange for Modified {
let lastmodified = prop.lastmodified.unwrap().timestamp_millis(); let lastmodified = prop.lastmodified.unwrap().timestamp_millis();
// update blob // update blob
Blob::from_path(obj.path.clone()).update(&lastmodified.to_string())?; Blob::new(obj.path.clone()).update(&lastmodified.to_string())?;
Ok(()) Ok(())
} }

View File

@@ -8,7 +8,6 @@ use crate::commands::status::LocalObj;
use crate::commands::push::push_factory::{PushState, PushChange, PushFlowState}; use crate::commands::push::push_factory::{PushState, PushChange, PushFlowState};
use crate::store::object::blob::Blob; use crate::store::object::blob::Blob;
use crate::utils::path::path_buf_to_string; use crate::utils::path::path_buf_to_string;
use crate::store::object::object::ObjMethods;
pub struct Moved { pub struct Moved {
pub obj: LocalObj, pub obj: LocalObj,
@@ -71,10 +70,10 @@ impl PushChange for Moved {
let lastmodified = prop.lastmodified.unwrap().timestamp_millis(); let lastmodified = prop.lastmodified.unwrap().timestamp_millis();
// delete source and create destination blob // delete source and create destination blob
if let Err(err) = Blob::from_path(obj.path.clone()).create(&lastmodified.to_string(), false) { if let Err(err) = Blob::new(obj.path.clone()).create(&lastmodified.to_string(), false) {
eprintln!("err: creating ref of {}: {}", obj.name.clone(), err); eprintln!("err: creating ref of {}: {}", obj.name.clone(), err);
} }
if let Err(err) = Blob::from_path(obj.path_from.clone().unwrap()).rm() { if let Err(err) = Blob::new(obj.path_from.clone().unwrap()).rm() {
eprintln!("err: removing ref of {}: {}", obj.name.clone(), err); eprintln!("err: removing ref of {}: {}", obj.name.clone(), err);
} }

View File

@@ -32,7 +32,6 @@ impl PushChange for New {
match res { match res {
Err(ApiError::IncorrectRequest(err)) => { Err(ApiError::IncorrectRequest(err)) => {
dbg!(&err);
eprintln!("fatal: error pushing file '{}': {}", obj.name, err.status()); eprintln!("fatal: error pushing file '{}': {}", obj.name, err.status());
std::process::exit(1); std::process::exit(1);
}, },
@@ -69,7 +68,7 @@ impl PushChange for New {
let lastmodified = prop.lastmodified.unwrap().timestamp_millis(); let lastmodified = prop.lastmodified.unwrap().timestamp_millis();
// create new blob // create new blob
Blob::from_path(obj.path.clone()).create(&lastmodified.to_string(), false)?; Blob::new(obj.path.clone()).create(&lastmodified.to_string(), false)?;
Ok(()) Ok(())
} }

View File

@@ -5,7 +5,7 @@ use crate::services::api_call::ApiCall;
use crate::services::req_props::ReqProps; use crate::services::req_props::ReqProps;
use crate::services::create_folder::CreateFolder; use crate::services::create_folder::CreateFolder;
use crate::store::index; use crate::store::index;
use crate::store::object::tree::Tree; use crate::store::object::tree;
use crate::commands::status::LocalObj; use crate::commands::status::LocalObj;
use crate::commands::push::push_factory::{PushState, PushChange, PushFlowState}; use crate::commands::push::push_factory::{PushState, PushChange, PushFlowState};
@@ -75,7 +75,7 @@ impl PushChange for NewDir {
let lastmodified = prop.lastmodified.unwrap().timestamp_millis(); let lastmodified = prop.lastmodified.unwrap().timestamp_millis();
// update tree // update tree
Tree::from_path(obj.path.clone()).create(&lastmodified.to_string(), true)?; tree::add(obj.path.clone(), &lastmodified.to_string(), true)?;
// remove index // remove index
index::rm_line(obj.path.to_str().unwrap())?; index::rm_line(obj.path.to_str().unwrap())?;

View File

@@ -73,7 +73,7 @@ pub trait PushChange {
}; };
// check if remote is newest // check if remote is newest
let last_sync_ts = Blob::from_path(obj.path.clone()) let last_sync_ts = Blob::new(obj.path.clone())
.saved_remote_ts() .saved_remote_ts()
.parse::<i64>().unwrap(); .parse::<i64>().unwrap();
let remote_ts = obj_data.lastmodified.unwrap().timestamp_millis(); let remote_ts = obj_data.lastmodified.unwrap().timestamp_millis();

View File

@@ -4,10 +4,9 @@ use crate::services::api::ApiError;
use crate::services::api_call::ApiCall; use crate::services::api_call::ApiCall;
use crate::services::delete_path::DeletePath; use crate::services::delete_path::DeletePath;
use crate::store::index; use crate::store::index;
use crate::store::object::tree::Tree; use crate::store::object::tree;
use crate::commands::status::LocalObj; use crate::commands::status::LocalObj;
use crate::commands::push::push_factory::{PushState, PushChange, PushFlowState}; use crate::commands::push::push_factory::{PushState, PushChange, PushFlowState};
use crate::store::object::object::ObjMethods;
pub struct RmDir { pub struct RmDir {
pub obj: LocalObj pub obj: LocalObj
@@ -50,7 +49,7 @@ impl PushChange for RmDir {
// update tree // update tree
// todo update date // todo update date
Tree::from_path(obj.path.clone()).rm()?; tree::rm(obj.path.clone())?;
// remove index // remove index
index::rm_line(obj.path.to_str().unwrap())?; index::rm_line(obj.path.to_str().unwrap())?;

View File

@@ -1,27 +1,21 @@
use clap::Values;
use crate::commands::config; use crate::commands::config;
use super::config::get_all_remote; pub struct RemoteArgs<'a> {
pub name: Option<Values<'a>>,
pub struct RemoteArgs { pub url: Option<Values<'a>>,
pub name: String,
pub url: String,
} }
pub fn remote_add(args: RemoteArgs) { pub fn remote_add(args: RemoteArgs) {
let _ = config::add_remote(&args.name, &args.url); if args.name.is_none() || args.url.is_none() {
} eprintln!("Missing argument: remote add command need a name and an url");
return;
pub fn remote_list(verbose: bool) {
let remotes = get_all_remote();
for remote in remotes {
if verbose
{
println!("{} {}", remote.0, remote.1);
}
else
{
println!("{}", remote.0);
}
} }
let name = args.name.unwrap().next().unwrap();
let url = args.url.unwrap().next().unwrap();
let _ = config::add_remote(name, url);
} }

View File

@@ -24,7 +24,7 @@ pub fn remote_diff() {
pub fn get_diff(path: PathBuf) -> (Vec<ObjProps>, Vec<ObjProps>) { pub fn get_diff(path: PathBuf) -> (Vec<ObjProps>, Vec<ObjProps>) {
let depth = "6"; // todo opti let depth = "2"; // todo opti
let api_props = get_api_props(); let api_props = get_api_props();
enumerate_remote( enumerate_remote(
@@ -55,7 +55,6 @@ fn req(api_props: &ApiProps, depth: &str, relative_s: &str) -> Result<Vec<ObjPro
.set_request(relative_s, &api_props) .set_request(relative_s, &api_props)
.set_depth(depth) .set_depth(depth)
.gethref() .gethref()
.getcontentlength() // todo opti
.getlastmodified() .getlastmodified()
.send_req_multiple() .send_req_multiple()
} }

View File

@@ -1,15 +1,16 @@
use std::fs::File;
use std::path::PathBuf; use std::path::PathBuf;
use std::io::{Lines, BufReader};
use std::collections::HashMap; use std::collections::HashMap;
use crypto::digest::Digest; use crypto::digest::Digest;
use crypto::sha1::Sha1; use crypto::sha1::Sha1;
use colored::Colorize; use colored::Colorize;
use crate::utils::path::{self, path_buf_to_string}; use crate::utils::path::{self, path_buf_to_string};
use crate::store::head;
use crate::store::object::blob::Blob; use crate::store::object::blob::Blob;
use crate::store::object::object::Obj; use crate::utils::read::{read_folder, read_lines};
use crate::store::object::tree::Tree; use crate::store::object::tree;
use crate::utils::read::read_folder;
use crate::store::index; use crate::store::index;
use crate::store::object::object::ObjMethods;
pub struct StatusArgs { pub struct StatusArgs {
pub nostyle: bool, pub nostyle: bool,
@@ -96,7 +97,7 @@ fn should_retain(hasher: &mut Sha1, key: String, obj: LocalObj, move_copy_hashes
{ {
return true; return true;
} }
let mut blob = Blob::from_path(obj.path.clone()); let mut blob = Blob::new(obj.path.clone());
let mut flag = true; let mut flag = true;
let identical_blobs = blob.get_all_identical_blobs(); let identical_blobs = blob.get_all_identical_blobs();
@@ -160,17 +161,22 @@ pub struct LocalObj {
} }
pub fn get_all_staged() -> Vec<LocalObj> { pub fn get_all_staged() -> Vec<LocalObj> {
let mut staged_objs = vec![]; let mut lines: Vec<String> = vec![];
if let Ok(entries) = index::read_line() { if let Ok(entries) = index::read_line() {
for line in entries { for entry in entries {
lines.push(entry.unwrap());
}
}
let obj = Obj::from_path(line.unwrap()).get_local_obj(); let mut staged_objs = vec![];
for line in lines {
let obj = Blob::new(line).get_local_obj();
if obj.state != State::Default { if obj.state != State::Default {
staged_objs.push(obj); staged_objs.push(obj);
} }
} }
}
staged_objs staged_objs
} }
@@ -221,12 +227,6 @@ fn get_staged(hashes: &mut HashMap<String, LocalObj>) -> Vec<LocalObj> {
staged_objs staged_objs
} }
fn read_tree_to_hashmap(tree: &mut Tree, hashes: &mut HashMap<String, LocalObj>, path: PathBuf) {
while let Some(child) = tree.next() {
hashes.insert(String::from(child.get_hash_path()), child.get_local_obj());
};
}
fn get_diff() -> (HashMap<String, LocalObj>, HashMap<String, LocalObj>, Vec<String>) { fn get_diff() -> (HashMap<String, LocalObj>, HashMap<String, LocalObj>, Vec<String>) {
let mut hashes = HashMap::new(); let mut hashes = HashMap::new();
let mut objs: Vec<String> = vec![]; let mut objs: Vec<String> = vec![];
@@ -238,10 +238,9 @@ fn get_diff() -> (HashMap<String, LocalObj>, HashMap<String, LocalObj>, Vec<Stri
// todo use repo_root instead of current // todo use repo_root instead of current
let dist_path = current_p.strip_prefix(root.clone()).unwrap().to_path_buf(); let dist_path = current_p.strip_prefix(root.clone()).unwrap().to_path_buf();
read_tree_to_hashmap(&mut Tree::from_head(), &mut hashes, dist_path.clone()); if let Ok(lines) = read_lines(head::path()) {
//if let Ok(lines) = read_lines(head::path()) { add_to_hashmap(lines, &mut hashes, dist_path.clone());
// add_to_hashmap(lines, &mut hashes, dist_path.clone()); }
//}
if let Ok(entries) = read_folder(root.clone()) { if let Ok(entries) = read_folder(root.clone()) {
add_to_vec(entries, &mut objs, root.clone()); add_to_vec(entries, &mut objs, root.clone());
@@ -256,23 +255,18 @@ fn get_diff() -> (HashMap<String, LocalObj>, HashMap<String, LocalObj>, Vec<Stri
let obj_path = root.clone().join(cur_path.clone()); let obj_path = root.clone().join(cur_path.clone());
if obj_path.is_dir() { if obj_path.is_dir() {
// read virtual tree if let Some((_, lines)) = tree::read(cur_obj.clone()) {
read_tree_to_hashmap(&mut Tree::from_path(cur_obj.clone()), &mut hashes, dist_path.clone()); add_to_hashmap(lines, &mut hashes, cur_path.clone());
//let mut tree = Tree::from_path(cur_obj.clone()); }
//if let Some(lines) = tree.get_children() {
//add_to_hashmap(lines, &mut hashes, cur_path.clone());
//}
// read physical tree
if let Ok(entries) = read_folder(obj_path.clone()) { if let Ok(entries) = read_folder(obj_path.clone()) {
add_to_vec(entries, &mut objs, root.clone()); add_to_vec(entries, &mut objs, root.clone());
} }
// remove duplicate
let diff = remove_duplicate(&mut hashes, &mut objs, RemoveSide::Both); let diff = remove_duplicate(&mut hashes, &mut objs, RemoveSide::Both);
obj_to_analyse.append(&mut diff.clone()); obj_to_analyse.append(&mut diff.clone());
} else { } else {
if Blob::from_path(cur_path).has_changes() { if Blob::new(cur_path).has_change() {
objs_modified.push(cur_obj); objs_modified.push(cur_obj);
} }
} }
@@ -314,24 +308,24 @@ fn get_otype(p: PathBuf) -> String {
} }
} }
//fn add_to_hashmap(lines: Lines<BufReader<File>>, hashes: &mut HashMap<String, LocalObj>, path: PathBuf) { fn add_to_hashmap(lines: Lines<BufReader<File>>, hashes: &mut HashMap<String, LocalObj>, path: PathBuf) {
// for line in lines { for line in lines {
// if let Ok(ip) = line { if let Ok(ip) = line {
// if ip.clone().len() > 5 { if ip.clone().len() > 5 {
// let (ftype, hash, name) = tree::parse_line(ip); let (ftype, hash, name) = tree::parse_line(ip);
// let mut p = path.clone(); let mut p = path.clone();
// p.push(name.clone()); p.push(name.clone());
// hashes.insert(String::from(hash), LocalObj{ hashes.insert(String::from(hash), LocalObj{
// otype: String::from(ftype), otype: String::from(ftype),
// name: String::from(name), name: String::from(name),
// path: p, path: p,
// path_from: None, path_from: None,
// state: State::Default, state: State::Default,
// }); });
// } }
// } }
// } }
//} }
fn add_to_vec(entries: Vec<PathBuf>, objects: &mut Vec<String>, root: PathBuf) { fn add_to_vec(entries: Vec<PathBuf>, objects: &mut Vec<String>, root: PathBuf) {
for entry in entries { for entry in entries {
@@ -361,7 +355,7 @@ fn print_status(staged_objs: Vec<LocalObj>, objs: Vec<LocalObj>) {
// not staged files // not staged files
if objs.len() != 0 { if objs.len() != 0 {
println!("Changes not staged for push:"); println!("Changes not staged for push:");
println!(" (Use \"nextsync add <file>...\" to update what will be pushed)"); println!(" (Use\"nextsync add <file>...\" to update what will be pushed)");
for object in objs { for object in objs {
print_object(object); print_object(object);

View File

@@ -1,4 +1,4 @@
use clap::Command; use clap::{App, SubCommand};
mod subcommands; mod subcommands;
@@ -9,41 +9,42 @@ mod global;
mod store; mod store;
fn main() { fn main() {
let app = Command::new("Nextsync") let app = App::new("Nextsync")
.version("1.0") .version("1.0")
.author("grimhilt") .author("grimhilt")
.about("A git-line command line tool to interact with nextcloud") .about("A git-line command line tool to interact with nextcloud")
.subcommands([ .setting(clap::AppSettings::SubcommandRequiredElseHelp)
subcommands::clone::create(), .subcommand(subcommands::clone::create())
subcommands::init::create(), .subcommand(subcommands::init::create())
subcommands::status::create(), .subcommand(subcommands::status::create())
subcommands::add::create(), .subcommand(subcommands::add::create())
subcommands::push::create(), .subcommand(subcommands::push::create())
subcommands::reset::create(), .subcommand(subcommands::reset::create())
subcommands::remote::create(), .subcommand(subcommands::remote::create())
subcommands::config::create(), .subcommand(subcommands::config::create())
subcommands::remote_diff::create(), .subcommand(subcommands::remote_diff::create())
subcommands::pull::create(), .subcommand(subcommands::pull::create())
subcommands::credential::create(), .subcommand(subcommands::credential::create())
]); .subcommand(
// .setting(clap::AppSettings::SubcommandRequiredElseHelp); SubCommand::with_name("test")
);
let matches = app.get_matches(); let matches = app.get_matches();
match matches.subcommand() { match matches.subcommand() {
Some(("init", args)) => subcommands::init::handler(args), ("init", Some(args)) => subcommands::init::handler(args),
Some(("status", args)) => subcommands::status::handler(args), ("status", Some(args)) => subcommands::status::handler(args),
Some(("add", args)) => subcommands::add::handler(args), ("add", Some(args)) => subcommands::add::handler(args),
Some(("reset", _)) => commands::reset::reset(), ("reset", Some(_)) => commands::reset::reset(),
Some(("clone", args)) => subcommands::clone::handler(args), ("clone", Some(args)) => subcommands::clone::handler(args),
Some(("push", _)) => commands::push::push(), ("push", Some(_)) => commands::push::push(),
Some(("config", args)) => subcommands::config::handler(args), ("config", Some(args)) => subcommands::config::handler(args),
Some(("remote-diff", args)) => subcommands::remote_diff::handler(args), ("remote-diff", Some(args)) => subcommands::remote_diff::handler(args),
Some(("pull", args)) => subcommands::pull::handler(args), ("pull", Some(args)) => subcommands::pull::handler(args),
Some(("remote", args)) => subcommands::remote::handler(args), ("remote", Some(args)) => subcommands::remote::handler(args),
Some(("credential", args)) => subcommands::credential::handler(args), ("credential", Some(args)) => subcommands::credential::handler(args),
Some((_, _)) => {},
None => {}, (_, _) => {},
}; };
} }

View File

@@ -1,10 +1,9 @@
use std::error::Error;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use std::sync::Mutex; use std::sync::Mutex;
use reqwest::Client; use reqwest::Client;
use reqwest::RequestBuilder; use reqwest::RequestBuilder;
use reqwest::multipart::Form; use reqwest::multipart::Form;
use reqwest::{Response, Method}; use reqwest::{Response, Error, Method};
use reqwest::header::{HeaderValue, CONTENT_TYPE, HeaderMap, IntoHeaderName}; use reqwest::header::{HeaderValue, CONTENT_TYPE, HeaderMap, IntoHeaderName};
use crate::utils::api::ApiProps; use crate::utils::api::ApiProps;
use crate::commands::config; use crate::commands::config;
@@ -185,7 +184,7 @@ impl ApiBuilder {
self.set_request_manager(); self.set_request_manager();
} }
let res_req = tokio::runtime::Runtime::new().unwrap().block_on(async { let res = tokio::runtime::Runtime::new().unwrap().block_on(async {
match self.request.take() { match self.request.take() {
None => { None => {
eprintln!("fatal: incorrect request"); eprintln!("fatal: incorrect request");
@@ -200,16 +199,7 @@ impl ApiBuilder {
} }
}, },
} }
}); }).map_err(ApiError::RequestError)?;
// handle request error
let res = match res_req {
Err(err) => {
eprintln!("fatal: {}", err.source().unwrap());
std::process::exit(1);
},
Ok(res) => res,
};
if res.status().is_success() { if res.status().is_success() {
if need_text { if need_text {
@@ -225,7 +215,7 @@ impl ApiBuilder {
} }
} }
pub async fn old_send(&mut self) -> Result<Response, reqwest::Error> { pub async fn old_send(&mut self) -> Result<Response, Error> {
let mut request_manager = get_request_manager().lock().unwrap(); let mut request_manager = get_request_manager().lock().unwrap();
let request_manager = request_manager.as_mut().unwrap(); let request_manager = request_manager.as_mut().unwrap();
if !self.host.is_none() if !self.host.is_none()
@@ -247,9 +237,9 @@ impl ApiBuilder {
Some(req) => { Some(req) => {
if let Some(headers) = &self.headers { if let Some(headers) = &self.headers {
req.headers(headers.clone()) req.headers(headers.clone())
.send().await.map_err(reqwest::Error::from) .send().await.map_err(Error::from)
} else { } else {
req.send().await.map_err(reqwest::Error::from) req.send().await.map_err(Error::from)
} }
}, },
} }

View File

@@ -93,7 +93,6 @@ impl Downloader {
let mut total_size = 0; let mut total_size = 0;
let nb_objs = self.files.len(); let nb_objs = self.files.len();
// set the full size of the download
self.files self.files
.iter() .iter()
.for_each(|f| .for_each(|f|
@@ -112,7 +111,11 @@ impl Downloader {
let should_use_stream = { let should_use_stream = {
if let Some(size) = file.contentlength { if let Some(size) = file.contentlength {
size > SIZE_TO_STREAM if size > SIZE_TO_STREAM {
true
} else {
false
}
} else { } else {
false false
} }

View File

@@ -9,7 +9,6 @@ use crate::utils::{read, path};
pub mod tree; pub mod tree;
pub mod blob; pub mod blob;
pub mod object;
pub struct Object { pub struct Object {
path: PathBuf, path: PathBuf,
@@ -148,7 +147,7 @@ fn add_node(path: &Path, node: &str) -> io::Result<()> {
root.push(dir); root.push(dir);
if !root.exists() { if !root.exists() {
//todo!(); todo!();
} }
root.push(rest); root.push(rest);

View File

@@ -4,201 +4,76 @@ use std::io::Write;
use std::fs::OpenOptions; use std::fs::OpenOptions;
use std::path::PathBuf; use std::path::PathBuf;
use std::time::SystemTime; use std::time::SystemTime;
use crate::commands::status::State; use crypto::sha1::Sha1;
use crypto::digest::Digest;
use crate::commands::status::{LocalObj, State};
use crate::utils::into::IntoPathBuf; use crate::utils::into::IntoPathBuf;
use crate::utils::path::path_buf_to_string;
use crate::utils::{path, read}; use crate::utils::{path, read};
use crate::store::object::update_dates; use crate::store::head;
use crate::store::object::{update_dates, add_node, rm_node};
use crate::store::object::object::ObjMethods;
use crate::store::object::object::Obj;
const HASH_EMPTY: &str = "d41d8cd98f00b204e9800998ecf8427e"; const HASH_EMPTY: &str = "d41d8cd98f00b204e9800998ecf8427e";
pub struct Blob { pub struct Blob {
pub obj: Obj, r_path: PathBuf, // relative path
data: Vec<String>, // content of the ref file a_path: PathBuf, // absolute path
file_hash: Option<String>, // hash of the file's content hash: String, // hash of relative path
file_hash: Option<String>,
obj_p: PathBuf, // path of the object file
data: Vec<String>, // content of the blob
} }
//pub struct Blob {
// r_path: PathBuf, // relative path
// a_path: PathBuf, // absolute path
// hash: String, // hash of relative path
// file_hash: Option<String>,
// obj_p: PathBuf, // path of the object file
// data: Vec<String>, // content of the blob
//}
impl Blob { impl Blob {
pub fn new(obj: Obj) -> Self { pub fn new<S>(r_path: S) -> Blob where S: IntoPathBuf {
Self {
obj,
data: vec![],
file_hash: None,
}
}
pub fn from_path<S>(r_path: S) -> Blob where S: IntoPathBuf {
let r_path = r_path.into(); let r_path = r_path.into();
Self { if r_path.is_dir() {
obj: Obj::from_path(r_path), eprintln!("{}: is a directory not a blob", r_path.display());
data: vec![],
file_hash: None,
} }
let mut hasher = Sha1::new();
hasher.input_str(r_path.to_str().unwrap());
let hash = hasher.result_str();
let (dir, res) = hash.split_at(2);
let mut obj_p = path::objects();
obj_p.push(dir);
obj_p.push(res);
let root = path::repo_root();
let a_path = root.join(r_path.clone());
Blob {
r_path,
a_path,
hash,
file_hash: None,
obj_p,
data: vec![],
}
}
fn get_line_filename(&mut self) -> (String, String) {
let file_name = self.r_path.file_name().unwrap().to_str().unwrap().to_owned();
let mut line = String::from("blob");
line.push_str(" ");
line.push_str(&self.hash);
line.push_str(" ");
line.push_str(&file_name);
(line, file_name)
} }
fn get_file_hash(&mut self) -> String { fn get_file_hash(&mut self) -> String {
if self.file_hash.is_none() { if self.file_hash.is_none() {
let bytes = std::fs::read(self.get_file_path()).unwrap(); let bytes = std::fs::read(self.a_path.clone()).unwrap();
let hash = md5::compute(&bytes); let hash = md5::compute(&bytes);
self.file_hash = Some(format!("{:x}", hash)) self.file_hash = Some(format!("{:x}", hash))
} }
self.file_hash.clone().unwrap() self.file_hash.clone().unwrap()
} }
/// read line of blob to get all informations and store them in self.data fn create_blob_ref(&mut self, file_name: String, ts_remote: &str) -> io::Result<()> {
pub fn read_data(&mut self) { let metadata = fs::metadata(self.a_path.clone())?;
if self.data.len() == 0 {
if let Ok(mut file) = File::open(self.get_obj_path()) {
let mut buffer = String::new();
let _ = file.read_to_string(&mut buffer);
let data = buffer.rsplit(' ').collect::<Vec<_>>();
for e in data {
self.data.push(String::from(e));
}
self.data.reverse();
// remove \n of last element
if let Some(last) = self.data.last_mut() {
if last.ends_with("\n") {
last.pop();
}
}
}
}
}
fn get_data_index(&mut self, index: usize) -> String {
self.read_data();
if self.data.len() >= index + 1 {
self.data[index].clone()
} else {
String::new()
}
}
fn saved_filename(&mut self) -> String {
self.get_data_index(0)
}
pub fn saved_remote_ts(&mut self) -> String {
self.get_data_index(1)
}
fn saved_local_size(&mut self) -> String {
self.get_data_index(2)
}
fn saved_local_ts(&mut self) -> u64 {
match self.get_data_index(3).as_str() {
"" => 0,
str => str.parse::<u64>().unwrap()
}
}
fn saved_hash(&mut self) -> String {
self.get_data_index(4)
}
fn has_same_size(&mut self) -> bool {
let metadata = match fs::metadata(self.get_file_path()) {
Ok(m) => m,
Err(_) => return true,
};
if self.saved_local_size() == String::new() { return true; }
metadata.len().to_string() == self.saved_local_size()
}
fn is_newer(&mut self) -> bool {
let metadata = match fs::metadata(self.get_file_path()) {
Ok(m) => m,
Err(_) => return true,
};
let secs = metadata
.modified()
.unwrap()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
if self.saved_local_ts() == 0 { return true; }
secs > self.saved_local_ts()
}
fn has_same_hash(&mut self) -> bool {
if self.saved_hash() == String::new() { return false; }
let file_hash = self.get_file_hash().clone();
self.saved_hash() == file_hash
}
pub fn has_changes(&mut self) -> bool {
!self.has_same_size() || (self.is_newer() && !self.has_same_hash())
}
pub fn get_all_identical_blobs(&mut self) -> Vec<String> {
// an empty file is a new file not the copy of another empty file
if self.get_file_hash() == HASH_EMPTY {
return vec![];
}
let refs_p = self.get_obj_path();
let mut blobs: Vec<String> = vec![];
if let Ok(lines) = read::read_lines(refs_p) {
for line in lines {
if let Ok(l) = line {
blobs.push(l.clone());
}
}
}
blobs
}
pub fn status(&mut self, path_from: &mut Option<PathBuf>) -> State {
let has_obj_ref = self.get_obj_path().exists();
let blob_exists = self.get_file_path().exists();
if has_obj_ref && !blob_exists {
State::Deleted
} else if !has_obj_ref && blob_exists {
let identical_blobs = self.get_all_identical_blobs();
if identical_blobs.len() != 0 {
let identical_blob = Blob::from_path(identical_blobs[0].clone()).get_local_obj();
if identical_blob.state == State::Deleted {
*path_from = Some(identical_blob.path);
State::Moved
} else if identical_blob.state == State::Default {
*path_from = Some(identical_blob.path);
State::Copied
} else {
State::New
}
} else {
State::New
}
} else if !has_obj_ref && !blob_exists {
State::Default
} else if self.has_changes() {
State::Modified
} else {
State::Default
}
}
fn create_blob_ref(&mut self, ts_remote: &str) -> io::Result<()> {
let metadata = fs::metadata(self.get_file_path())?;
let secs = metadata let secs = metadata
.modified() .modified()
.unwrap() .unwrap()
@@ -206,28 +81,30 @@ impl Blob {
.unwrap() .unwrap()
.as_secs(); .as_secs();
// build line with all needed properties let mut content = file_name.clone();
let content = format!("{} {} {} {} {}", content.push_str(" ");
self.get_name(), content.push_str(ts_remote);
ts_remote, content.push_str(" ");
metadata.len().to_string(), content.push_str(&metadata.len().to_string());
secs.to_string(), content.push_str(" ");
self.get_file_hash()); content.push_str(&secs.to_string());
content.push_str(" ");
content.push_str(&self.get_file_hash());
// create parent dir if needed let binding = self.obj_p.clone();
let mut obj_path = self.get_obj_path(); let child = binding.file_name();
obj_path.pop(); self.obj_p.pop();
if !obj_path.exists() { if !self.obj_p.clone().exists() {
fs::create_dir_all(obj_path)?; fs::create_dir_all(self.obj_p.clone())?;
} }
self.obj_p.push(child.unwrap().to_str().unwrap());
// open ref file
let mut file = OpenOptions::new() let mut file = OpenOptions::new()
.create_new(true) .create_new(true)
.write(true) .write(true)
.open(self.get_obj_path())?; .open(self.obj_p.clone())?;
writeln!(file, "{}", content)?; writeln!(file, "{}", &content)?;
Ok(()) Ok(())
} }
@@ -257,74 +134,261 @@ impl Blob {
.open(refs_p)?; .open(refs_p)?;
// todo deal with duplicate content // todo deal with duplicate content
writeln!(file, "{}", self.get_relative_file_path().to_str().unwrap())?; writeln!(file, "{}", self.r_path.clone().to_str().unwrap())?;
Ok(()) Ok(())
} }
pub fn get_all_identical_blobs(&mut self) -> Vec<String> {
// an empty file is a new file not the copy of another empty file
if self.get_file_hash() == HASH_EMPTY {
return vec![];
}
let refs_p = self.get_file_ref();
let mut blobs: Vec<String> = vec![];
if let Ok(lines) = read::read_lines(refs_p) {
for line in lines {
if let Ok(l) = line {
blobs.push(l.clone());
}
}
}
blobs
}
pub fn create(&mut self, ts_remote: &str, up_parent: bool) -> io::Result<()> { pub fn create(&mut self, ts_remote: &str, up_parent: bool) -> io::Result<()> {
let (line, file_name) = self.get_line_filename();
// add blob reference to parent // add blob reference to parent
let _ = self.add_ref_to_parent(); if self.r_path.iter().count() == 1 {
head::add_line(line)?;
} else {
add_node(self.r_path.parent().unwrap(), &line)?;
}
if let Err(err) = self.create_blob_ref(ts_remote.clone()) { if let Err(err) = self.create_blob_ref(file_name.clone(), ts_remote.clone()) {
eprintln!("err: saving blob ref of {}: {}", self.get_relative_file_path().display(), err); eprintln!("err: saving blob ref of {}: {}", self.r_path.clone().display(), err);
} }
if let Err(err) = self.create_hash_ref() { if let Err(err) = self.create_hash_ref() {
eprintln!("err: saving hash ref of {}: {}", self.get_relative_file_path().display(), err); eprintln!("err: saving hash ref of {}: {}", self.r_path.clone().display(), err);
} }
// update date for all parent // update date for all parent
if up_parent { if up_parent {
if let Err(err) = update_dates(self.get_relative_file_path(), ts_remote) { if let Err(err) = update_dates(self.r_path.clone(), ts_remote) {
eprintln!("err: updating parent date of {}: {}", self.get_relative_file_path().display(), err); eprintln!("err: updating parent date of {}: {}", self.r_path.clone().display(), err);
} }
} }
Ok(()) Ok(())
} }
pub fn rm(&mut self) -> io::Result<()> {
let (line, _) = self.get_line_filename();
// remove blob reference to parent
if self.r_path.iter().count() == 1 {
head::rm_line(&line)?;
} else {
rm_node(self.r_path.parent().unwrap(), &line)?;
}
// remove blob object
fs::remove_file(self.obj_p.clone())?;
Ok(())
}
pub fn update(&mut self, ts_remote: &str) -> io::Result<()> { pub fn update(&mut self, ts_remote: &str) -> io::Result<()> {
// // remove old hash ref // remove old hash ref
// let mut refs_p = path::refs(); let mut refs_p = path::refs();
// let binding = self.saved_hash(); let binding = self.saved_hash();
// let (dir, res) = binding.split_at(2); let (dir, res) = binding.split_at(2);
// refs_p.push(dir); refs_p.push(dir);
// refs_p.push(res); refs_p.push(res);
// if let Err(err) = fs::remove_file(refs_p) { if let Err(err) = fs::remove_file(refs_p) {
// eprintln!("err: removing hash ref of {}: {}", self.r_path.clone().display(), err); eprintln!("err: removing hash ref of {}: {}", self.r_path.clone().display(), err);
// } }
//
// // creating new hash ref // creating new hash ref
// if let Err(err) = self.create_hash_ref() { if let Err(err) = self.create_hash_ref() {
// eprintln!("err: saving hash ref of {}: {}", self.r_path.clone().display(), err); eprintln!("err: saving hash ref of {}: {}", self.r_path.clone().display(), err);
// } }
//
// // updating content of blob's ref // updating content of blob's ref
// let metadata = fs::metadata(self.a_path.clone())?; let metadata = fs::metadata(self.a_path.clone())?;
// let secs = metadata let secs = metadata
// .modified() .modified()
// .unwrap() .unwrap()
// .duration_since(SystemTime::UNIX_EPOCH) .duration_since(SystemTime::UNIX_EPOCH)
// .unwrap() .unwrap()
// .as_secs(); .as_secs();
//
// let mut content = self.saved_filename(); let mut content = self.saved_filename();
// content.push_str(" "); content.push_str(" ");
// content.push_str(ts_remote); content.push_str(ts_remote);
// content.push_str(" "); content.push_str(" ");
// content.push_str(&metadata.len().to_string()); content.push_str(&metadata.len().to_string());
// content.push_str(" "); content.push_str(" ");
// content.push_str(&secs.to_string()); content.push_str(&secs.to_string());
// content.push_str(" "); content.push_str(" ");
// content.push_str(&self.get_file_hash()); content.push_str(&self.get_file_hash());
//
// let mut file = OpenOptions::new() let mut file = OpenOptions::new()
// .write(true) .write(true)
// .open(self.obj_p.clone())?; .open(self.obj_p.clone())?;
//
// writeln!(file, "{}", &content)?; writeln!(file, "{}", &content)?;
Ok(()) Ok(())
} }
pub fn read_data(&mut self) {
if self.data.len() == 0 {
if let Ok(mut file) = File::open(self.obj_p.clone()) {
let mut buffer = String::new();
let _ = file.read_to_string(&mut buffer);
let data = buffer.rsplit(' ').collect::<Vec<_>>();
for e in data {
self.data.push(String::from(e));
}
self.data.reverse();
// remove \n of last element
if let Some(last) = self.data.last_mut() {
if last.ends_with("\n") {
last.pop();
}
}
}
}
}
fn saved_filename(&mut self) -> String {
self.read_data();
if self.data.len() >= 1 {
self.data[0].clone()
} else {
String::new()
}
}
pub fn saved_remote_ts(&mut self) -> String {
self.read_data();
if self.data.len() >= 2 {
self.data[1].clone()
} else {
String::new()
}
}
fn saved_local_size(&mut self) -> String {
self.read_data();
if self.data.len() >= 3 {
self.data[2].clone()
} else {
String::new()
}
}
fn saved_local_ts(&mut self) -> u64 {
self.read_data();
if self.data.len() >= 4 {
self.data[3].parse::<u64>().unwrap()
} else {
0
}
}
fn saved_hash(&mut self) -> String {
self.read_data();
if self.data.len() >= 5 {
self.data[4].clone()
} else {
String::new()
}
}
fn has_same_size(&mut self) -> bool {
let metadata = match fs::metadata(self.a_path.clone()) {
Ok(m) => m,
Err(_) => return true,
};
if self.saved_local_size() == String::new() { return true; }
metadata.len().to_string() == self.saved_local_size()
}
fn is_newer(&mut self) -> bool {
let metadata = match fs::metadata(self.a_path.clone()) {
Ok(m) => m,
Err(_) => return true,
};
let secs = metadata
.modified()
.unwrap()
.duration_since(SystemTime::UNIX_EPOCH)
.unwrap()
.as_secs();
if self.saved_local_ts() == 0 { return true; }
secs > self.saved_local_ts()
}
fn has_same_hash(&mut self) -> bool {
if self.saved_hash() == String::new() { return false; }
let file_hash = self.get_file_hash().clone();
self.saved_hash() == file_hash
}
pub fn has_change(&mut self) -> bool {
!self.has_same_size() || (self.is_newer() && !self.has_same_hash())
}
pub fn status(&mut self, path_from: &mut Option<PathBuf>) -> State {
let has_obj_ref = self.obj_p.clone().exists();
let blob_exists = self.a_path.clone().exists();
if has_obj_ref && !blob_exists {
State::Deleted
} else if !has_obj_ref && blob_exists {
let identical_blobs = self.get_all_identical_blobs();
if identical_blobs.len() != 0 {
let identical_blob = Blob::new(identical_blobs[0].clone())
.get_local_obj();
if identical_blob.state == State::Deleted {
*path_from = Some(identical_blob.path);
State::Moved
} else if identical_blob.state == State::Default {
*path_from = Some(identical_blob.path);
State::Copied
} else {
State::New
}
} else {
State::New
}
} else if !has_obj_ref && !blob_exists {
State::Default
} else if self.has_change() {
State::Modified
} else {
State::Default
}
}
pub fn get_local_obj(&mut self) -> LocalObj {
let mut path_from = None;
let state = self.status(&mut path_from);
LocalObj {
otype: String::from("blob"),
name: path_buf_to_string(self.r_path.clone()),
path: self.r_path.clone(),
path_from,
state
}
}
} }

View File

@@ -1,380 +0,0 @@
use std::io;
use std::fs;
use std::path::PathBuf;
use crate::utils::path;
use crate::store::head;
use crate::store::object::{add_node, rm_node};
use crypto::sha1::Sha1;
use crypto::digest::Digest;
use crate::utils::into::IntoPathBuf;
use crate::store::object::{blob::Blob, tree::Tree};
use crate::commands::status::{State, LocalObj};
#[derive(Clone, Copy)]
pub enum ObjType {
TREE,
BLOB,
DEFAULT
}
pub trait ObjMethods {
fn get_type(&self) -> ObjType;
fn get_obj_path(&self) -> PathBuf;
fn get_file_path(&self) -> PathBuf;
fn get_relative_file_path(&self) -> PathBuf;
fn get_repo_file_path(&self) -> PathBuf;
fn get_name(&self) -> String;
fn get_hash_path(&self) -> String;
fn get_local_obj(&self) -> LocalObj;
fn get_line(&self) -> String;
fn add_ref_to_parent(&self) -> io::Result<()>;
fn rm(&mut self) -> io::Result<()>;
fn rm_node(&mut self) -> io::Result<()>;
fn rm_node_down(&mut self) -> io::Result<()>;
fn exists_on_remote(&mut self) -> bool;
}
pub struct Obj {
name: String,
obj_path: PathBuf,
obj_type: ObjType,
file_path: PathBuf, // file here is used as both file and directory
relative_file_path: PathBuf,
repo_file_path: PathBuf,
hash_path: String, // hash of the relative path of the file
}
impl ObjMethods for Obj {
fn get_type(&self) -> ObjType {
self.obj_type
}
fn get_obj_path(&self) -> PathBuf {
self.obj_path.clone()
}
fn get_file_path(&self) -> PathBuf {
self.file_path.clone()
}
fn get_relative_file_path(&self) -> PathBuf {
self.relative_file_path.clone()
}
fn get_repo_file_path(&self) -> PathBuf {
self.repo_file_path.clone()
}
fn get_local_obj(&self) -> LocalObj {
LocalObj {
otype: match self.obj_type {
ObjType::BLOB => String::from("blob"),
ObjType::TREE => String::from("tree"),
ObjType::DEFAULT => String::from("default"),
},
name: self.get_name(),
path: self.get_repo_file_path(),
path_from: None,
state: State::New
}
}
fn get_name(&self) -> String {
self.name.clone()
}
fn get_hash_path(&self) -> String {
self.hash_path.clone()
}
// build line for parent reference
fn get_line(&self) -> String {
format!("tree {} {}", self.get_hash_path(), self.get_name())
}
fn add_ref_to_parent(&self) -> io::Result<()> {
let line = self.get_line();
if self.get_relative_file_path().iter().count() == 1 {
head::add_line(line)?;
} else {
add_node(self.get_relative_file_path().parent().unwrap(), &line)?;
}
Ok(())
}
fn rm_node(&mut self) -> io::Result<()> {
// remove self object and children object
self.rm_node_down();
// remove parent reference to self
let line = self.get_line();
if self.get_relative_file_path().iter().count() == 1 {
head::rm_line(&line)?;
} else {
rm_node(self.get_relative_file_path().parent().unwrap(), &line)?;
}
Ok(())
}
fn rm_node_down(&mut self) -> io::Result<()> {
eprintln!("rm_node_down: tried to do this on Obj");
Ok(())
}
fn rm(&mut self) -> io::Result<()> {
eprintln!("rm: tried to do this on Obj");
Ok(())
}
fn exists_on_remote(&mut self) -> bool {
PathBuf::from(self.get_hash_path()).exists()
}
}
impl ObjMethods for Blob {
fn get_type(&self) -> ObjType {
self.obj.get_type()
}
fn get_obj_path(&self) -> PathBuf {
self.obj.get_obj_path()
}
fn get_file_path(&self) -> PathBuf {
self.obj.get_file_path()
}
fn get_relative_file_path(&self) -> PathBuf {
self.obj.get_relative_file_path()
}
fn get_repo_file_path(&self) -> PathBuf {
self.obj.get_repo_file_path()
}
fn get_local_obj(&self) -> LocalObj {
self.obj.get_local_obj()
}
fn get_name(&self) -> String {
self.obj.get_name()
}
fn get_hash_path(&self) -> String {
self.obj.get_hash_path()
}
fn get_line(&self) -> String {
self.obj.get_line()
}
fn add_ref_to_parent(&self) -> io::Result<()> {
self.obj.add_ref_to_parent()
}
fn rm_node(&mut self) -> io::Result<()> {
self.obj.rm_node()
}
fn rm_node_down(&mut self) -> io::Result<()> {
// remove reference to self
fs::remove_file(self.get_obj_path())?;
Ok(())
}
fn rm(&mut self) -> io::Result<()> {
// remove all references, including children's one
self.rm_node()?;
// remove file
fs::remove_file(self.get_file_path())?;
Ok(())
}
fn exists_on_remote(&mut self) -> bool {
self.obj.exists_on_remote()
}
}
impl ObjMethods for Tree {
fn get_type(&self) -> ObjType {
self.obj.get_type()
}
fn get_obj_path(&self) -> PathBuf {
self.obj.get_obj_path()
}
fn get_file_path(&self) -> PathBuf {
self.obj.get_file_path()
}
fn get_relative_file_path(&self) -> PathBuf {
self.obj.get_relative_file_path()
}
fn get_repo_file_path(&self) -> PathBuf {
self.obj.get_repo_file_path()
}
fn get_local_obj(&self) -> LocalObj {
self.obj.get_local_obj()
}
fn get_name(&self) -> String {
self.obj.get_name()
}
fn get_hash_path(&self) -> String {
self.obj.get_hash_path()
}
fn get_line(&self) -> String {
self.obj.get_line()
}
fn add_ref_to_parent(&self) -> io::Result<()> {
self.obj.add_ref_to_parent()
}
fn rm_node(&mut self) -> io::Result<()> {
self.obj.rm_node()
}
/// remove objects and children but not parent reference to self
fn rm_node_down(&mut self) -> io::Result<()> {
// remove children
while let Some(mut child) = self.next() {
match child.get_type() {
ObjType::TREE => child.rm_node_down(),
ObjType::BLOB => child.rm_node_down(),
_ => Ok(())
}?;
};
// remove reference to self
fs::remove_file(self.get_obj_path())?;
Ok(())
}
fn rm(&mut self) -> io::Result<()> {
// remove all references, including children's one
self.rm_node()?;
// remove directory and all subfiles
fs::remove_dir_all(self.get_file_path())?;
Ok(())
}
fn exists_on_remote(&mut self) -> bool {
self.obj.exists_on_remote()
}
}
impl Obj {
fn new() -> Self {
Obj {
name: String::new(),
obj_path: PathBuf::new(),
file_path: PathBuf::new(),
obj_type: ObjType::DEFAULT,
hash_path: String::new(),
relative_file_path: PathBuf::new(),
repo_file_path: PathBuf::new()
}
}
pub fn from_path<S>(path: S) -> Obj where S: IntoPathBuf {
let path = path.into();
let mut hasher = Sha1::new();
hasher.input_str(path.to_str().unwrap());
let hash = hasher.result_str();
let (dir, res) = hash.split_at(2);
let mut obj_path = path::objects();
obj_path.push(dir);
obj_path.push(res);
let root = path::repo_root();
let abs_path = root.join(path.clone());
Obj {
name: match abs_path.file_name() {
None => String::new(),
Some(name) => name.to_str().unwrap().to_owned()
},
obj_path,
obj_type: match path.exists() {
true => match path.is_dir() {
true => ObjType::TREE,
false => ObjType::BLOB
},
false => ObjType::DEFAULT
},
file_path: abs_path,
relative_file_path: path.clone(),
repo_file_path: path,
hash_path: hash,
}
}
/// load from the information line stored in the object
pub fn from_line(line: String, base_dir: Option<PathBuf>) -> Box<dyn ObjMethods> {
let mut split = line.rsplit(' ');
if split.clone().count() != 3 {
eprintln!("fatal: invalid object(s)");
std::process::exit(1);
}
let name = split.next().unwrap();
let hash_path = split.next().unwrap();
let obj_type = split.next().unwrap();
let (dir, res) = hash_path.split_at(2);
let mut obj_path = path::objects();
obj_path.push(dir);
obj_path.push(res);
let path = match base_dir {
Some(dir) => dir.join(name),
None => PathBuf::from(name),
};
let root = path::repo_root();
let abs_path = root.join(path.clone());
let obj = Obj {
name: String::from(name),
obj_path,
obj_type: match obj_type {
"tree" => ObjType::TREE,
"blob" => ObjType::BLOB,
_ => ObjType::DEFAULT
},
file_path: abs_path,
relative_file_path: path.clone(),
repo_file_path: path,
hash_path: String::from(hash_path),
};
match obj.obj_type {
ObjType::TREE => Box::new(Tree::new(obj)),
ObjType::BLOB => Box::new(Blob::new(obj)),
ObjType::DEFAULT => Box::new(Tree::new(obj))
}
}
pub fn from_head() -> Self {
Obj {
name: String::new(),
obj_path: head::path(),
obj_type: ObjType::TREE,
file_path: PathBuf::new(),
relative_file_path: PathBuf::new(),
repo_file_path: PathBuf::new(),
hash_path: String::new(),
}
}
}

View File

@@ -1,113 +1,106 @@
use crate::utils::into::IntoPathBuf; use std::fs::File;
use crate::store::object::object::Obj; use std::io;
use crate::store::object::update_dates; use std::path::PathBuf;
use crate::store::object::object::ObjMethods; use crate::utils::path::path_buf_to_string;
use std::fs::{self, File, OpenOptions}; use crate::utils::{read, path};
use std::io::{self, BufRead, BufReader, Write}; use crate::store::head;
use crate::store::object::{self, update_dates, parse_path, hash_obj, add_node, create_obj};
pub struct Tree {
pub obj: Obj,
pub buf_reader: Option<BufReader<File>>,
is_head: bool,
}
impl Tree {
pub fn new(obj: Obj) -> Self {
Tree {
obj,
buf_reader: None,
is_head: false,
}
}
pub fn from_head() -> Self { pub fn add(path: PathBuf, date: &str, up_parent: bool) -> io::Result<()> {
Tree { let (line, hash, name) = parse_path(path.clone(), false);
obj: Obj::from_head(),
buf_reader: None,
is_head: true,
}
}
pub fn from_path<S>(r_path: S) -> Tree where S: IntoPathBuf {
Tree {
obj: Obj::from_path(r_path.into()),
buf_reader: None,
is_head: false,
}
}
pub fn read(&mut self) {
if self.buf_reader.is_none() {
if let Ok(file) = File::open(self.get_obj_path()) {
self.buf_reader = Some(BufReader::new(file));
// skip first line if is head
if !self.is_head {
self.next();
}
}
}
}
pub fn next(&mut self) -> Option<Box<dyn ObjMethods>> {
self.read();
//if let Some(ref mut file) = self.buf_reader {
// let mut line = String::new();
// match file.read_line(&mut line) {
// Ok(0) => Ok(None), // End of file
// Ok(_) => Ok(Some(line.trim_end().len())), // Return length of line
// Err(e) => Err(e),
// }
//} else {
// Ok(None) // If file is None, return None
//}
match self.buf_reader {
Some(ref mut file) => {
let mut line = String::new();
match file.read_line(&mut line) {
Ok(0) => None,
Ok(_) => Some(Obj::from_line(line, Some(self.get_relative_file_path()))),
Err(e) => {
eprintln!("tree::next: failed to read next line: {}", e);
None
}
}
},
None => None
}
}
pub fn create(&self, date: &str, up_parent: bool) -> io::Result<()> {
// add tree reference to parent // add tree reference to parent
let _ = self.add_ref_to_parent(); if path.iter().count() == 1 {
head::add_line(line)?;
} else {
add_node(path.parent().unwrap(), &line)?;
}
// create tree object // create tree object
let content = format!("{} {}", self.get_name(), date); let mut content = name;
content.push_str(" ");
// create parent dir if needed content.push_str(date);
let mut obj_path = self.get_obj_path(); create_obj(hash, &content)?;
obj_path.pop();
if !obj_path.exists() {
fs::create_dir_all(obj_path)?;
}
// open ref file
let mut file = OpenOptions::new()
.create_new(true)
.write(true)
.open(self.get_obj_path())?;
// update date for all parent // update date for all parent
if up_parent { if up_parent {
if let Err(err) = update_dates(self.get_relative_file_path(), date) { update_dates(path, date)?;
eprintln!("err: updating parent date of {}: {}", self.get_relative_file_path().display(), err);
} }
}
writeln!(file, "{}", content)?;
Ok(()) Ok(())
}
pub fn rm(path: PathBuf) -> io::Result<()> {
let (_, lines) = read(path_buf_to_string(path.to_path_buf())).unwrap();
for line in lines {
let (ftype, hash, _) = parse_line(line.unwrap());
if ftype == String::from("blob") {
object::rm(&hash)?;
} else {
rm_hash(hash)?;
}
}
Ok(())
}
fn rm_hash(hash: String) -> io::Result<()> {
let mut obj_p = path::objects();
let (dir, res) = hash.split_at(2);
obj_p.push(dir);
obj_p.push(res);
match read::read_lines(obj_p) {
Ok(mut reader) => {
reader.next();
for line in reader {
let (ftype, hash, _) = parse_line(line.unwrap());
if ftype == String::from("blob") {
object::rm(&hash)?;
} else {
rm_hash(hash)?;
}
}
},
Err(err) => {
eprintln!("error reading tree: {}", err);
},
}
Ok(())
}
pub fn read(tree: String) -> Option<(String, io::Lines<io::BufReader<File>>)> {
let mut obj_p = path::objects();
let (dir, res) = hash_obj(&tree);
obj_p.push(dir);
obj_p.push(res);
match read::read_lines(obj_p) {
Ok(mut reader) => {
let name = match reader.next() {
Some(Ok(line)) => line,
_ => String::new(),
};
Some((name, reader))
},
Err(err) => {
eprintln!("error reading tree: {}", err);
None
},
}
}
pub fn parse_line(line: String) -> (String, String, String) {
let mut split = line.rsplit(' ');
if split.clone().count() != 3 {
eprintln!("fatal: invalid object(s)");
std::process::exit(1);
} }
let name = split.next().unwrap();
let hash = split.next().unwrap();
let ftype = split.next().unwrap();
(String::from(ftype), String::from(hash), String::from(name))
} }

View File

@@ -1,40 +1,38 @@
use clap::{Arg, ArgMatches, Command}; use clap::{App, Arg, SubCommand, ArgMatches};
use crate::commands; use crate::commands;
use crate::commands::add::AddArgs; use crate::commands::add::AddArgs;
pub fn create() -> Command { pub fn create() -> App<'static, 'static> {
Command::new("add") SubCommand::with_name("add")
.arg( .arg(
Arg::new("files") Arg::with_name("files")
.required(true) .required(true)
.conflicts_with("all") .conflicts_with("all")
.num_args(1..) .multiple(true)
.takes_value(true)
.value_name("FILE") .value_name("FILE")
.help("Files to add"), .help("Files to add"),
) )
.arg( .arg(
Arg::new("force") Arg::with_name("force")
.short('f') .short("f")
.long("force") .long("force")
.help("Allow adding otherwise ignored files."), .help("Allow adding otherwise ignored files."),
) )
.arg( .arg(
Arg::new("all") Arg::with_name("all")
.short('A') .short("A")
.long("all") .long("all")
.help("This adds, modifies, and removes index entries to match the working tree"), .help("This adds, modifies, and removes index entries to match the working tree"),
) )
.about("Add changes to the index") .about("Add changes to the index")
} }
pub fn handler(args: &ArgMatches) { pub fn handler(args: &ArgMatches<'_>) {
commands::add::add(AddArgs { commands::add::add(AddArgs {
files: match args.get_many::<String>("files") { files: args.values_of("files"),
None => vec![], force: args.is_present("force"),
Some(vals) => vals.map(|s| s.to_string()).collect(), all: args.is_present("all"),
},
force: args.contains_id("force"),
all: args.contains_id("all"),
}); });
} }

View File

@@ -1,52 +1,58 @@
use clap::{Arg, Command, ArgMatches}; use clap::{App, Arg, SubCommand, ArgMatches};
// use textwrap::{fill, Options}; use textwrap::{fill, Options};
use crate::commands::clone::CloneArgs; use crate::commands::clone::{self, CloneArgs};
use crate::global; use crate::global;
use crate::commands; use crate::commands;
// fn sized_str<'a>(content: &'a str) -> &'a str { fn sized_str<'a>(content: &'a str) -> &'a str {
// fill(content, Options::new(70).width).as_str(); fill(content, Options::new(70).width).as_str();
// "ok" "ok"
// } }
pub fn create() -> Command { fn test() -> String {
// let remote_desc = sized_str(&format!("The repository to clone from. See the NEXTSYNC URLS section below for more information on specifying repositories.")); String::from("ok")
// let depth_desc = sized_str(&format!("Depth of the recursive fetch of object properties. This value should be lower when there are a lot of files per directory and higher when there are a lot of subdirectories with fewer files. (Default: {})", clone::DEPTH)); }
Command::new("clone")
pub fn create() -> App<'static, 'static> {
let remote_desc = sized_str(&format!("The repository to clone from. See the NEXTSYNC URLS section below for more information on specifying repositories."));
let depth_desc = sized_str(&format!("Depth of the recursive fetch of object properties. This value should be lower when there are a lot of files per directory and higher when there are a lot of subdirectories with fewer files. (Default: {})", clone::DEPTH));
SubCommand::with_name("clone")
.arg( .arg(
Arg::new("remote") Arg::with_name("remote")
.required(true) .required(true)
.num_args(1) .takes_value(true)
.value_name("REMOTE") .value_name("REMOTE")
//.help(_desc) //.help(_desc)
) )
.arg( .arg(
Arg::new("depth") Arg::with_name("depth")
.short('d') .short("d")
.long("depth") .long("depth")
.required(false) .required(false)
.num_args(1) .takes_value(true)
//.help(&depth_desc) //.help(&depth_desc)
) )
.arg( .arg(
Arg::new("directory") Arg::with_name("directory")
.required(false) .required(false)
.num_args(1) .takes_value(true)
.value_name("DIRECTORY") .value_name("DIRECTORY")
) )
.about("Clone a repository into a new directory") .about("Clone a repository into a new directory")
.after_help("NEXTSYNC URLS\nThe following syntaxes may be used:\n\t- user@host.xz/path/to/repo\n\t- http[s]://host.xz/apps/files/?dir=/path/to/repo&fileid=111111\n\t- [http[s]://]host.xz/remote.php/dav/files/user/path/to/repo\n") .after_help("NEXTSYNC URLS\nThe following syntaxes may be used:\n\t- user@host.xz/path/to/repo\n\t- http[s]://host.xz/apps/files/?dir=/path/to/repo&fileid=111111\n\t- [http[s]://]host.xz/remote.php/dav/files/user/path/to/repo\n")
} }
pub fn handler(args: &ArgMatches) { pub fn handler(args: &ArgMatches<'_>) {
if let Some(val) = args.get_one::<String>("directory") { if let Some(val) = args.values_of("directory") {
global::global::set_dir_path(String::from(val.to_string())); global::global::set_dir_path(String::from(val.clone().next().unwrap()));
} }
if let Some(remote) = args.get_one::<String>("remote") { if let Some(remote) = args.values_of("remote") {
commands::clone::clone(CloneArgs { commands::clone::clone(CloneArgs {
remote: remote.to_string(), remote,
depth: args.get_one::<String>("depth").cloned(), depth: args.values_of("depth").map(
|mut val| val.next().unwrap().to_owned()
),
}); });
} }
} }

View File

@@ -1,32 +1,32 @@
use clap::{Arg, Command, ArgMatches}; use clap::{App, Arg, SubCommand, ArgMatches};
use crate::commands::config::ConfigSetArgs; use crate::commands::config::ConfigSetArgs;
use crate::commands; use crate::commands;
pub fn create() -> Command { pub fn create() -> App<'static, 'static> {
Command::new("config") SubCommand::with_name("config")
.about("Get and set repository or global options") .about("Get and set repository or global options")
.subcommand( .subcommand(
Command::new("get") SubCommand::with_name("get")
.about("Get the value of a configuration variable") .about("Get the value of a configuration variable")
.arg( .arg(
Arg::new("name") Arg::with_name("name")
.help("The name of the configuration variable") .help("The name of the configuration variable")
.required(true) .required(true)
.index(1) .index(1)
) )
) )
.subcommand( .subcommand(
Command::new("set") SubCommand::with_name("set")
.about("Set a configuration variable") .about("Set a configuration variable")
.arg( .arg(
Arg::new("name") Arg::with_name("name")
.help("The name of the configuration variable") .help("The name of the configuration variable")
.required(true) .required(true)
.index(1) .index(1)
) )
.arg( .arg(
Arg::new("value") Arg::with_name("value")
.help("The value to set") .help("The value to set")
.required(true) .required(true)
.index(2) .index(2)
@@ -34,15 +34,20 @@ pub fn create() -> Command {
) )
} }
pub fn handler(args: &ArgMatches) { pub fn handler(args: &ArgMatches<'_>) {
match args.subcommand() { match args.subcommand() {
Some(("set", set_matches)) => { ("set", Some(set_matches)) => {
commands::config::config_set(ConfigSetArgs { commands::config::config_set(ConfigSetArgs {
name: set_matches.get_one::<String>("name").unwrap().to_string(), name: set_matches.values_of("name"),
value: set_matches.get_one::<String>("value").unwrap().to_string(), value: set_matches.values_of("value"),
}); });
} }
_ => println!("Invalid or missing subcommand for 'config'"), _ => println!("Invalid or missing subcommand for 'config'"),
} }
// AddArgs {
// files: args.values_of("files"),
// force: args.is_present("force"),
// all: args.is_present("all"),
// });
} }

View File

@@ -1,24 +1,24 @@
use clap::{Arg, Command, ArgMatches}; use clap::{App, Arg, SubCommand, ArgMatches};
use crate::commands; use crate::commands;
use crate::commands::credential::CredentialArgs; use crate::commands::credential::CredentialArgs;
pub fn create() -> Command { pub fn create() -> App<'static, 'static> {
Command::new("credential") SubCommand::with_name("credential")
.about("Manage set of credentials") .about("Manage set of credentials")
.subcommand( .subcommand(
Command::new("add") SubCommand::with_name("add")
.arg( .arg(
Arg::new("username") Arg::with_name("username")
.required(true) .required(true)
.num_args(1) .takes_value(true)
.value_name("NAME") .value_name("NAME")
.help("The username used to connect to nextcloud"), .help("The username used to connect to nextcloud"),
) )
.arg( .arg(
Arg::new("password") Arg::with_name("password")
.required(false) .required(false)
.num_args(1) .takes_value(true)
.value_name("PASSWORD") .value_name("PASSWORD")
.help("The passowd used to connect to nextcloud (optional)"), .help("The passowd used to connect to nextcloud (optional)"),
) )
@@ -26,12 +26,12 @@ pub fn create() -> Command {
) )
} }
pub fn handler(args: &ArgMatches) { pub fn handler(args: &ArgMatches<'_>) {
match args.subcommand() { match args.subcommand() {
Some(("add", add_matches)) => { ("add", Some(add_matches)) => {
commands::credential::credential_add(CredentialArgs { commands::credential::credential_add(CredentialArgs {
username: add_matches.get_one::<String>("username").unwrap().to_string(), username: add_matches.values_of("username"),
password: add_matches.get_one::<String>("password").cloned(), password: add_matches.values_of("password"),
}); });
} }
_ => println!("Invalid or missing subcommand for 'credential'"), _ => println!("Invalid or missing subcommand for 'credential'"),

View File

@@ -1,23 +1,23 @@
use clap::{Arg, Command, ArgMatches}; use clap::{App, Arg, SubCommand, ArgMatches};
use crate::global; use crate::global;
use crate::commands; use crate::commands;
pub fn create() -> Command { pub fn create() -> App<'static, 'static> {
Command::new("init") SubCommand::with_name("init")
.arg( .arg(
Arg::new("directory") Arg::with_name("directory")
.required(false) .required(false)
.num_args(1) .takes_value(true)
.value_name("DIRECTORY") .value_name("DIRECTORY")
) )
.about("Create an empty Nextsync repository") .about("Create an empty Nextsync repository")
// Create an empty nextsync repository or reinitialize an existing one // Create an empty nextsync repository or reinitialize an existing one
} }
pub fn handler(args: &ArgMatches) { pub fn handler(args: &ArgMatches<'_>) {
if let Some(val) = args.get_one::<String>("directory") { if let Some(val) = args.values_of("directory") {
global::global::set_dir_path(val.to_string()); global::global::set_dir_path(String::from(val.clone().next().unwrap()));
} }
commands::init::init(); commands::init::init();
} }

View File

@@ -1,23 +1,23 @@
use clap::{Arg, Command, ArgMatches}; use clap::{App, Arg, SubCommand, ArgMatches};
use crate::global; use crate::global;
use crate::commands; use crate::commands;
pub fn create() -> Command { pub fn create() -> App<'static, 'static> {
Command::new("pull") SubCommand::with_name("pull")
.arg( .arg(
Arg::new("path") Arg::with_name("path")
.required(false) .required(false)
.num_args(1) .takes_value(true)
.value_name("PATH") .value_name("PATH")
.help("The path to pull."), .help("The path to pull."),
) )
.about("Fetch and integrate changes from the nextcloud server.") .about("Fetch and integrate changes from the nextcloud server.")
} }
pub fn handler(args: &ArgMatches) { pub fn handler(args: &ArgMatches<'_>) {
if let Some(val) = args.get_one::<String>("path") { if let Some(val) = args.values_of("path") {
global::global::set_dir_path(val.to_string()); global::global::set_dir_path(String::from(val.clone().next().unwrap()));
} }
commands::pull::pull(); commands::pull::pull();
} }

View File

@@ -1,6 +1,6 @@
use clap::Command; use clap::{App, SubCommand};
pub fn create() -> Command { pub fn create() -> App<'static, 'static> {
Command::new("push") SubCommand::with_name("push")
.about("Push changes on nextcloud") .about("Push changes on nextcloud")
} }

View File

@@ -1,45 +1,37 @@
use clap::{Arg, Command, ArgMatches}; use clap::{App, Arg, SubCommand, ArgMatches};
use crate::commands; use crate::commands;
use crate::commands::remote::RemoteArgs; use crate::commands::remote::RemoteArgs;
pub fn create() -> Command { pub fn create() -> App<'static, 'static> {
Command::new("remote") SubCommand::with_name("remote")
.about("Manage set of tracked repositories") .about("Manage set of tracked repositories")
.subcommand( .subcommand(
Command::new("add") SubCommand::with_name("add")
.arg( .arg(
Arg::new("name") Arg::with_name("name")
.required(true) .required(true)
.index(1) .index(1)
.help("The name of the remote"), .help("The name of the remote"),
) )
.arg( .arg(
Arg::new("url") Arg::with_name("url")
.required(true) .required(true)
.index(2) .index(2)
.help("The url of the remote"), .help("The url of the remote"),
) )
.about("Add a new remote to this repository") .about("Add a new remote to this repository")
) )
.arg(
Arg::new("verbose")
.short('v')
.long("verbose")
.help("Be a little more verbose and show remote url after name.")
)
} }
pub fn handler(args: &ArgMatches) { pub fn handler(args: &ArgMatches<'_>) {
match args.subcommand() { match args.subcommand() {
Some(("add", add_matches)) => { ("add", Some(add_matches)) => {
commands::remote::remote_add(RemoteArgs { commands::remote::remote_add(RemoteArgs {
name: add_matches.get_one::<String>("name").unwrap().to_string(), name: add_matches.values_of("name"),
url: add_matches.get_one::<String>("url").unwrap().to_string(), url: add_matches.values_of("url"),
}); });
} }
_ => { _ => println!("Invalid or missing subcommand for 'remote'"),
commands::remote::remote_list(args.contains_id("verbose"));
}
} }
} }

View File

@@ -1,14 +1,14 @@
use clap::{Arg, Command, ArgMatches}; use clap::{App, Arg, SubCommand, ArgMatches};
use crate::global; use crate::global;
use crate::commands; use crate::commands;
pub fn create() -> Command { pub fn create() -> App<'static, 'static> {
Command::new("remote-diff") SubCommand::with_name("remote-diff")
.arg( .arg(
Arg::new("path") Arg::with_name("path")
.required(false) .required(false)
.num_args(1) .takes_value(true)
.value_name("PATH") .value_name("PATH")
.help("The path to pull."), .help("The path to pull."),
) )
@@ -16,9 +16,9 @@ pub fn create() -> Command {
} }
pub fn handler(args: &ArgMatches) { pub fn handler(args: &ArgMatches<'_>) {
if let Some(val) = args.get_one::<String>("path") { if let Some(val) = args.values_of("path") {
global::global::set_dir_path(val.to_string()); global::global::set_dir_path(String::from(val.clone().next().unwrap()));
} }
commands::remote_diff::remote_diff(); commands::remote_diff::remote_diff();
} }

View File

@@ -1,6 +1,6 @@
use clap::Command; use clap::{App, SubCommand};
pub fn create() -> Command { pub fn create() -> App<'static, 'static> {
Command::new("reset") SubCommand::with_name("reset")
.about("Clear the index") .about("Clear the index")
} }

View File

@@ -1,30 +1,30 @@
use clap::{Arg, Command, ArgMatches}; use clap::{App, Arg, SubCommand, ArgMatches};
use crate::global; use crate::global;
use crate::commands; use crate::commands;
use crate::commands::status::StatusArgs; use crate::commands::status::StatusArgs;
pub fn create() -> Command { pub fn create() -> App<'static, 'static> {
Command::new("status") SubCommand::with_name("status")
.arg( .arg(
Arg::new("directory") Arg::with_name("directory")
.num_args(1) .required(false)
.takes_value(true)
.value_name("DIRECTORY") .value_name("DIRECTORY")
) )
.arg( .arg(
Arg::new("nostyle") Arg::with_name("nostyle")
.long("nostyle") .long("nostyle")
.help("Status with minium information and style"), .help("Status with minium information and style"),
) )
.about("Show the working tree status") .about("Show the working tree status")
} }
pub fn handler(args: &ArgMatches) { pub fn handler(args: &ArgMatches<'_>) {
if let Some(val) = args.get_one::<String>("directory") { if let Some(val) = args.values_of("directory") {
global::global::set_dir_path(val.to_string()); global::global::set_dir_path(String::from(val.clone().next().unwrap()));
} }
commands::status::status(StatusArgs { commands::status::status(StatusArgs {
nostyle: args.contains_id("nostyle"), nostyle: args.is_present("nostyle"),
}); });
} }

View File

@@ -1,4 +1,4 @@
use std::path::{PathBuf, Path}; use std::path::PathBuf;
pub trait IntoPathBuf { pub trait IntoPathBuf {
fn into(self) -> PathBuf; fn into(self) -> PathBuf;
@@ -10,12 +10,6 @@ impl IntoPathBuf for PathBuf {
} }
} }
impl IntoPathBuf for &Path {
fn into(self) -> PathBuf {
PathBuf::from(self)
}
}
impl IntoPathBuf for String { impl IntoPathBuf for String {
fn into(self) -> PathBuf { fn into(self) -> PathBuf {
PathBuf::from(self) PathBuf::from(self)

View File

@@ -114,13 +114,6 @@ pub fn nextsync() -> PathBuf {
path path
} }
pub fn config() -> PathBuf {
let mut path = repo_root();
path.push(".nextsync");
path.push("config");
path
}
pub fn objects() -> PathBuf { pub fn objects() -> PathBuf {
let mut path = repo_root(); let mut path = repo_root();
path.push(".nextsync"); path.push(".nextsync");

View File

@@ -20,7 +20,6 @@ pub fn enumerate_remote(
let mut deleted: Vec<PathBuf> = vec![]; let mut deleted: Vec<PathBuf> = vec![];
let mut files: Vec<ObjProps> = vec![]; let mut files: Vec<ObjProps> = vec![];
let mut objs_hashmap: HashMap<String, Vec<String>> = HashMap::new(); let mut objs_hashmap: HashMap<String, Vec<String>> = HashMap::new();
objs_hashmap.insert( objs_hashmap.insert(
options.relative_s.clone().unwrap_or(String::new()), options.relative_s.clone().unwrap_or(String::new()),
Vec::new()); Vec::new());
@@ -54,11 +53,7 @@ pub fn enumerate_remote(
}; };
// separate folders and files in response // separate folders and files in response
let d = options.depth.clone() let d = options.depth.clone().unwrap_or("0".to_owned()).parse::<u16>().unwrap();
.unwrap_or("0".to_owned())
.parse::<u16>()
.unwrap();
// first element is not used as it is the fetched folder // first element is not used as it is the fetched folder
if let Some(should_skip_fct) = should_skip.clone() { if let Some(should_skip_fct) = should_skip.clone() {
iter_with_skip_fct( iter_with_skip_fct(
@@ -87,7 +82,6 @@ pub fn enumerate_remote(
&mut all_folders); &mut all_folders);
} }
} }
// go through all folders not checked for deletion before // go through all folders not checked for deletion before
// as they were empty // as they were empty
if let Some(_) = should_skip.clone() { if let Some(_) = should_skip.clone() {
@@ -96,13 +90,18 @@ pub fn enumerate_remote(
objs_hashmap.remove(&key); objs_hashmap.remove(&key);
} }
} }
dbg!(deleted);
dbg!(objs_hashmap);
(all_folders, files) (all_folders, files)
} }
fn calc_depth(obj: &ObjProps) -> u16 { fn calc_depth(obj: &ObjProps) -> u16 {
let path = obj.relative_s.clone().unwrap_or(String::new()); calc_depth_string(obj.relative_s.clone().unwrap_or(String::new()))
path.split("/").count() as u16 }
fn calc_depth_string(s: String) -> u16 {
s.split("/").count() as u16
} }
fn iter_with_skip_fct( fn iter_with_skip_fct(
@@ -122,6 +121,7 @@ fn iter_with_skip_fct(
let current_depth = calc_depth(object); let current_depth = calc_depth(object);
if object.is_dir() { if object.is_dir() {
// add folder to parent folder only if exists // add folder to parent folder only if exists
let mut r_path = PathBuf::from(object.relative_s.clone().unwrap()); let mut r_path = PathBuf::from(object.relative_s.clone().unwrap());
r_path.pop(); r_path.pop();
@@ -208,7 +208,7 @@ fn get_non_new_local_element(iter: &mut dyn Iterator<Item = &PathBuf>) -> Option
!Object::new(el.unwrap().clone().to_str().unwrap()).exists() !Object::new(el.unwrap().clone().to_str().unwrap()).exists()
} else { } else {
// ignore newly created file (not sync) // ignore newly created file (not sync)
Blob::from_path(el.unwrap().clone()).status(&mut None) == State::New Blob::new(el.unwrap().clone()).status(&mut None) == State::New
} }
} { } {
el = iter.next(); el = iter.next();

View File

@@ -1,97 +0,0 @@
use std::str;
mod utils;
use utils::{utils::*, client::ClientTest};
fn line_should_contains(lines: &Vec<String>, nb: usize, str: &str) {
if lines[nb].find(str).is_none()
{
eprintln!("'{}' not found in '{}'", str, lines[nb]);
dbg!(lines);
}
assert!(lines[nb].find(str).is_some());
}
fn lines_should_not_contains(lines: Vec<String>, str: &str) {
for line in lines {
if line.find("Changes not staged for push").is_some() {
return;
}
if line.find(str).is_some() {
eprintln!("'{}' found in '{}'", str, line);
}
assert!(line.find(str).is_none());
}
}
fn collect_status_lines(client: &mut ClientTest) -> Vec<String> {
let out = client.run_cmd("status");
str::from_utf8(&out.stdout)
.unwrap()
.split("\n")
.map(|s| s.to_owned())
.collect()
}
#[cfg(test)]
mod add_tests {
use super::*;
#[test]
fn simple_add() {
let id = get_random_test_id();
let mut client = ClientTest::new(id).init();
let _ = client.add_file("file1", "foo");
client.run_cmd_ok("add file1");
let lines = collect_status_lines(&mut client);
// test
line_should_contains(&lines, 2, "file1");
client.clean();
}
#[test]
fn add_config_file() {
let id = get_random_test_id();
let mut client = ClientTest::new(id).init();
let _ = client.add_file("file1", "foo");
client.run_cmd_ok("add .nextsync -f");
let lines = collect_status_lines(&mut client);
// test
lines_should_not_contains(lines, ".nextsync");
client.clean();
}
#[test]
fn add_dir_implicit() {
let id = get_random_test_id();
let mut client = ClientTest::new(id).init();
let _ = client.add_dir("dir");
let _ = client.add_file("dir/file1", "foo");
// adding the file should add the dir
client.run_cmd_ok("add dir/file1");
let lines = collect_status_lines(&mut client);
// tests
line_should_contains(&lines, 2, "dir");
line_should_contains(&lines, 3, "dir/file1");
client.clean();
}
}

45
tests/add/directory.sh Executable file
View File

@@ -0,0 +1,45 @@
#!/bin/sh
source ./utils.sh
nb_tests=0
TEST_SUITE_NAME="add/directory/"
add_test_no_env() {
touch $2
$exe add $3
status_cmp "$1" "$4"
}
add_test() {
nb_tests=$((nb_tests + 1))
setup_env
$exe init
add_test_no_env "$1" "$2" "$3" "$4"
}
add_dir() {
nb_tests=$((nb_tests + 1))
setup_env
$exe init
mkdir dir
$exe add "dir"
res=$($exe status --nostyle)
status_cmp "dir" "new: dir"
}
add_subdir() {
nb_tests=$((nb_tests + 1))
setup_env
$exe init
mkdir foo foo/bar
$exe add "foo"
res=$($exe status --nostyle)
status_cmp "dir" "new: foo/bar\nnew: foo"
}
add_dir
add_subdir
echo $nb_tests
exit 0

114
tests/add/file.sh Executable file
View File

@@ -0,0 +1,114 @@
#!/bin/sh
source ./utils.sh
nb_tests=0
TEST_SUITE_NAME="add/file/"
add_test_no_env() {
touch $2
$exe add $3
status_cmp "$1" "$4"
}
add_test() {
nb_tests=$((nb_tests + 1))
setup_env
$exe init
add_test_no_env "$1" "$2" "$3" "$4"
}
add_basics() {
add_test "basic" "toto" "toto" "new: toto"
}
add_space() {
nb_tests=$((nb_tests + 1))
setup_env
$exe init
touch 'to to'
$exe add 'to to'
res=$($exe status --nostyle)
status_cmp "space" "new: to to"
}
add_multiple() {
add_test "multiple" "titi riri" "titi riri" "new: titi\nnew: riri"
}
add_regex() {
add_test "regex" "titi riri" "./*" "new: riri\nnew: titi"
}
add_file_subdir() {
nb_tests=$((nb_tests + 1))
setup_env
$exe init
mkdir dir
touch dir/toto
$exe add "./dir/toto"
res=$($exe status --nostyle)
status_cmp "file_subdir" "new: dir/toto"
}
add_whole_subdir() {
nb_tests=$((nb_tests + 1))
setup_env
$exe init
mkdir dir
touch dir/toto
touch dir/roro
$exe add "dir"
res=$($exe status --nostyle)
status_cmp "whole_subdir" "new: dir/roro\nnew: dir/toto\nnew: dir"
}
add_subdir_regex() {
nb_tests=$((nb_tests + 1))
setup_env
$exe init
mkdir dir
touch dir/toto dir/roro
$exe add "./dir/*"
res=$($exe status --nostyle)
status_cmp "subdir_regex" "new: dir/roro\nnew: dir/toto"
}
add_duplication() {
add_test "duplication" "toto" "toto toto" "new: toto"
}
add_duplication_subdir() {
nb_tests=$((nb_tests + 1))
setup_env
$exe init
mkdir dir
add_test_no_env "duplication_subdir" "dir/toto" "dir/toto dir/toto" "new: dir/toto"
}
add_all() {
nb_tests=$((nb_tests + 1))
setup_env
$exe init
mkdir dir
touch dir/toto dir/roro lolo
$exe add -A
res=$($exe status --nostyle)
status_cmp "all" "new: .nextsyncignore\nnew: dir/roro\nnew: dir/toto\nnew: dir\nnew: lolo"
}
#test add file without changes
add_basics
add_space
add_multiple
add_regex
add_file_subdir
add_whole_subdir
add_subdir_regex
add_duplication
add_duplication_subdir
add_all
echo $nb_tests
exit 0

45
tests/main.sh Executable file
View File

@@ -0,0 +1,45 @@
#!/bin/sh
source ./utils.sh
# Getting all tests
TESTS=$(find -mindepth 2 -name "*.sh")
if [ $# -ne 0 ]; then
TESTS=$(find -mindepth 2 -path "*$1*")
fi
# Executing tests
nb_tests=0
nb_success=0
for test in $TESTS; do
#nb_tests=$((nb_tests + 1))
# run file
tmp_stderr=$(mktf)
nb_tests_tmp=$($test 2>"$tmp_stderr")
exit_code=$?
capture_stderr=$(<"$tmp_stderr")
[ "$capture_stderr" != "" ] && echo -e "$capture_stderr"
rm $tmp_stderr
# add nb_tests from executed test_suite to global nb_test
[ "$nb_tests_tmp" != "" ] &&
[ $nb_tests_tmp -gt 0 ] &&
nb_tests=$((nb_tests + nb_tests_tmp))
# deal with the result of the test
if [ $exit_code -eq 0 ]; then
nb_success=$((nb_success + nb_tests_tmp))
echo "$test ran successfully"
elif [ $exit_code -eq 4 ]; then
# not executable (nextsync) found, not need to try other tests
exit 1
else
nb_success=$((nb_success + nb_tests_tmp - 1))
echo "$test failed with exit code $exit_code"
fi
done;
rm -rf /tmp/*_nextsync
echo -e "\nRan $nb_tests tests ($((nb_tests - nb_success)) Failed)"

View File

@@ -1,44 +0,0 @@
mod utils;
use utils::{utils::*, server::ServerTest, client::ClientTest};
#[cfg(test)]
mod pull_tests {
use super::*;
#[test]
fn simple_pull() {
let id = get_random_test_id();
let mut server = ServerTest::new(id.clone());
server.init();
let mut client = ClientTest::new(id).init();
let _ = server.add_file("file1", "foo");
client.run_cmd_ok("pull");
// tests
assert!(client.has_file("file1", "foo"));
client.clean();
server.clean();
}
#[test]
fn simple_pull_directory() {
let id = get_random_test_id();
let mut server = ServerTest::new(id.clone());
server.init();
let mut client = ClientTest::new(id).init();
let _ = server.add_dir("dir");
let _ = server.add_file("dir/file1", "foo");
client.run_cmd_ok("pull");
// tests
assert!(client.has_file("dir/file1", "foo"));
client.clean();
server.clean();
}
}

View File

@@ -1,164 +0,0 @@
mod utils;
use utils::{utils::*, status_utils::*, server::ServerTest, client::ClientTest};
fn init_test() -> (ClientTest, ServerTest) {
let id = get_random_test_id();
let mut server = ServerTest::new(id.clone());
server.init();
let client = ClientTest::new(id).init();
(client, server)
}
#[cfg(test)]
mod push_tests {
use super::*;
#[test]
fn simple_push() {
let (mut client, mut server) = init_test();
let _ = client.add_file("file1", "foo");
client.run_cmd_ok("add file1");
client.run_cmd_ok("push");
// tests
assert!(server.has_file("file1", "foo"));
let (staged, not_staged) = client.get_status();
lines_should_not_contains(staged, "file1");
lines_should_not_contains(not_staged, "file1");
client.clean();
server.clean();
}
#[test]
fn push_update() {
let (mut client, mut server) = init_test();
// init content of file1
let _ = client.add_file("file1", "foo");
client.run_cmd_ok("add file1");
client.run_cmd_ok("push");
// tests
assert!(server.has_file("file1", "foo"));
let (staged, not_staged) = client.get_status();
lines_should_not_contains(staged, "file1");
lines_should_not_contains(not_staged, "file1");
// change content of file1
let _ = client.add_file("file1", "bar");
client.run_cmd_ok("add file1");
client.run_cmd_ok("push");
// tests
assert!(server.has_file("file1", "bar"));
let (staged, not_staged) = client.get_status();
lines_should_not_contains(staged, "file1");
lines_should_not_contains(not_staged, "file1");
client.clean();
server.clean();
}
#[test]
fn push_dir_explicit() {
let (mut client, mut server) = init_test();
let _ = client.add_dir("dir");
let _ = client.add_file("dir/file2", "bar");
// push dir and file2
client.run_cmd_ok("add dir");
client.run_cmd_ok("push");
// tests
assert!(server.has_file("dir/file2", "bar"));
let (staged, not_staged) = client.get_status();
lines_should_not_contains(staged.clone(), "file2");
lines_should_not_contains(staged, "foo");
lines_should_not_contains(not_staged.clone(), "file2");
lines_should_not_contains(not_staged, "foo");
client.clean();
server.clean();
}
#[test]
fn push_dir_implicit() {
let (mut client, mut server) = init_test();
let _ = client.add_dir("dir");
let _ = client.add_file("dir/file2", "bar");
// push dir and file2
client.run_cmd_ok("add dir/file2");
client.run_cmd_ok("push");
// tests
assert!(server.has_file("dir/file2", "bar"));
let (staged, not_staged) = client.get_status();
lines_should_not_contains(staged.clone(), "file2");
lines_should_not_contains(staged, "foo");
lines_should_not_contains(not_staged.clone(), "file2");
lines_should_not_contains(not_staged, "foo");
client.clean();
server.clean();
}
#[test]
fn push_all() {
let (mut client, mut server) = init_test();
let _ = client.add_file("file1", "foo");
let _ = client.add_dir("dir");
let _ = client.add_file("dir/file2", "bar");
// push dir and file2
client.run_cmd_ok("add *");
client.run_cmd_ok("push");
// tests
assert!(server.has_file("file1", "foo"));
assert!(server.has_file("dir/file2", "bar"));
let (staged, not_staged) = client.get_status();
assert!(staged.len() == 0);
assert!(not_staged.len() == 0);
client.clean();
server.clean();
}
#[test]
fn push_file_deletion() {
let (mut client, mut server) = init_test();
let _ = client.add_file("file1", "foo");
// push file1
client.run_cmd_ok("add file1");
client.run_cmd_ok("push");
// tests
assert!(server.has_file("file1", "foo"));
status_should_be_empty(&mut client);
// remove it
let _ = client.remove_file("file1");
client.run_cmd_ok("add file1");
dbg!(client.get_status());
client.run_cmd_ok("push");
// tests
assert!(server.has_not_file("file1"));
status_should_be_empty(&mut client);
client.clean();
server.clean();
}
}

216
tests/tests.rs Normal file
View File

@@ -0,0 +1,216 @@
use std::process::{Command, Output};
use std::os::unix::fs::PermissionsExt;
use rand::{distributions::Alphanumeric, Rng}; // 0.8
use std::fs::{self, File, Permissions};
use std::io::{Write, BufReader, BufRead};
use std::env;
use std::path::PathBuf;
struct ServerTest {
user: String,
volume: PathBuf,
test_id: String
}
impl ServerTest {
fn new(id: String) -> Self {
let mut volume = env::current_dir().unwrap();
volume = volume.join("tests/data/admin/files");
ServerTest {
user: String::from("admin"),
volume,
test_id: id
}
}
fn init(mut self) -> Self {
self.add_dir(self.test_id.clone());
self.volume = self.volume.join(self.test_id.clone());
self.sync_test()
}
fn clean(mut self) -> Self {
self.remove_dir(self.test_id.clone());
self.sync_root()
}
fn add_dir(&mut self, path: String) -> &mut ServerTest {
let mut full_path = self.volume.clone();
full_path.push(path);
match fs::create_dir(&full_path) {
Ok(_) => {
// Set permissions to 777 to allow nextcloud to access it (workaround avoiding to
// set group and owner to www-data)
if let Err(e) = fs::set_permissions(&full_path, Permissions::from_mode(0o777)) {
eprintln!("Error setting permissions: {}", e);
}
},
Err(e) => eprintln!("Error creating directory: {}", e),
}
self
}
fn remove_dir(&mut self, path: String) -> &mut ServerTest {
let mut full_path = self.volume.clone();
full_path.push(path);
let _ = fs::remove_dir_all(&full_path);
self
}
fn sync_root(self) -> Self {
self.sync("")
}
fn sync_test(self) -> Self {
let test_id = self.test_id.clone();
self.sync(&test_id)
}
fn sync(self, path: &str) -> Self {
// perform the occ files:scan command inside the nextcloud docker container
let nextcloud_docker = "master-nextcloud-1";
let mut args = String::from("exec -ti --user www-data");
args.push_str(nextcloud_docker);
args.push_str("/var/www/html/occ files:scan --path=/");
args.push_str(&self.user);
args.push_str("files/");
args.push_str(path);
let _output = Command::new("docker")
.args(args.split(" "))
.output()
.expect("Could not execute nextsync command");
self
}
fn has_file(&mut self, file: &str, content: &str) -> bool {
let full_path = self.volume.clone().join(file);
if !full_path.exists() {
eprintln!("File '{}' does't exists", file);
return false;
}
let file = File::open(full_path).unwrap();
for line in BufReader::new(file).lines(){
if let Ok(line) = line {
return line == content;
}
}
return false;
}
}
struct ClientTest {
user: String,
volume: String,
test_id: String,
exe_path: PathBuf,
}
impl ClientTest {
fn new(id: String) -> Self {
// create a directory in /tmp with the given id
let mut vol = String::from("/tmp/");
vol.push_str(&id);
let _ = fs::create_dir(vol.clone());
// get nextsync path
let mut exe_path = env::current_dir().unwrap();
exe_path = exe_path.join("target/debug/nextsync");
let _ = env::set_current_dir(vol.clone());
// build the client
ClientTest {
user: String::from("admin"),
volume: vol,
test_id: id,
exe_path
}
}
fn init(mut self) -> Self {
self.run_cmd_ok("init");
// set remote url
let url = String::from(format!("{}@nextcloud.local/{}", self.user, self.test_id));
self.run_cmd_ok(&format!("remote add origin {}", url));
// set force_unsecure as debug server has not certificate
self.run_cmd_ok("config set force_insecure true");
// set token for request
self.run_cmd_ok(&format!("credential add {} {}", self.user, self.user));
self
}
fn clean(self) -> Self {
let _ = fs::remove_dir_all(&self.volume);
self
}
fn run_cmd_ok(&mut self, args: &str) -> Output {
let output = self.run_cmd(args);
if !output.status.success() {
println!("Failed to execute: '{}'", args);
println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
}
assert!(output.status.success());
output
}
fn run_cmd(&mut self, args: &str) -> Output {
let output = Command::new(self.exe_path.to_str().unwrap())
.args(args.split(" "))
.output()
.expect("Could not execute nextsync command");
return output;
}
fn add_file(&mut self, name: &str, content: &str) -> std::io::Result<()> {
let mut path = self.volume.clone();
path.push_str("/");
path.push_str(name);
let mut file = File::create(path)?;
file.write_all(content.as_bytes())?;
Ok(())
}
}
fn get_random_test_id() -> String {
let mut id: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(7)
.map(char::from)
.collect();
id.push_str("_nextsync");
id.to_owned()
}
#[test]
fn test1() {
let id = get_random_test_id();
dbg!(id.clone());
let mut server = ServerTest::new(id.clone()).init();
let mut client = ClientTest::new(id).init();
let _ = client.add_file("file1", "foo");
client.run_cmd_ok("add file1");
client.run_cmd_ok("push");
// tests
assert!(server.has_file("file1", "foo"));
client.clean();
server.clean();
}

View File

@@ -1,14 +0,0 @@
#[path = "utils/server.rs"]
pub mod server;
#[path = "utils/client.rs"]
pub mod client;
#[path = "utils/utils.rs"]
pub mod utils;
#[path = "utils/status_utils.rs"]
pub mod status_utils;
#[path = "utils/files_utils.rs"]
pub mod files_utils;

38
tests/utils.sh Executable file
View File

@@ -0,0 +1,38 @@
#!/bin/sh
mktd()
{
echo $(mktemp -d --suffix=_nextsync)
}
mktf()
{
echo $(mktemp --suffix=_nextsync)
}
get_exe() {
exe=$(pwd)
exe+="/../target/debug/nextsync"
if [ ! -f $exe ]; then
echo "No executable found, try to compile first" >&2
exit 4
fi
}
setup_env() {
[ ! -v exe ] && get_exe
path=$(mktd)
cd $path
}
# test_name expected_output
status_cmp() {
res=$($exe status --nostyle)
diff <(echo -e "$2" ) <(echo -e "$res") 2> /dev/null > /dev/null
if [ $? -ne 0 ]; then
echo -e "$TEST_SUITE_NAME$1: Output differ:" >&2
diff -u <(echo -e "$2" ) <(echo -e "$res") | grep "^[-\+\ ][^-\+]" >&2
echo -e "\nMore in $path" >&2
echo $nb_tests
exit 1
fi
}

View File

@@ -1,155 +0,0 @@
use std::str;
use std::process::{Command, Output};
use std::fs::{self, File};
use std::io::Write;
use std::env;
use std::path::PathBuf;
use super::files_utils::has_files;
#[cfg(test)]
pub struct ClientTest {
user: String, // the nextcloud user
volume: String, // temp dir for the test
pub test_id: String, // name of the test (e.g nextsync_rand)
exe_path: PathBuf, // absolute path of nextsync executable
}
#[cfg(test)]
impl ClientTest {
pub fn new(id: String) -> Self {
// create a directory in /tmp with the given id
let mut vol = String::from("/tmp/");
vol.push_str(&id);
let _ = fs::create_dir(vol.clone());
// get nextsync path
let mut exe_path = env::current_dir().unwrap();
exe_path = exe_path.join("target/debug/nextsync");
// build the client
ClientTest {
user: String::from("admin"),
volume: vol,
test_id: id,
exe_path
}
}
pub fn init(mut self) -> Self {
self.run_cmd_ok("init");
// set remote url
let url = String::from(format!("{}@nextcloud.local/{}", self.user, self.test_id));
self.run_cmd_ok(&format!("remote add origin {}", url));
// set force_unsecure as debug server has not certificate
self.run_cmd_ok("config set force_insecure true");
// set token for request
self.run_cmd_ok(&format!("credential add {} {}", self.user, self.user));
self
}
pub fn clean(self) -> Self {
let _ = fs::remove_dir_all(&self.volume);
self
}
pub fn run_cmd_ok(&mut self, args: &str) -> Output {
let output = self.run_cmd(args);
if !output.status.success() {
println!("id: {}", self.test_id.clone());
println!("Failed to execute: '{}'", args);
println!("stderr: {}", String::from_utf8_lossy(&output.stderr));
println!("stdout: {}", String::from_utf8_lossy(&output.stdout));
}
assert!(output.status.success());
output
}
pub fn run_cmd(&mut self, args: &str) -> Output {
let output = Command::new(self.exe_path.to_str().unwrap())
.current_dir(self.volume.clone())
.args(args.split(" "))
.output()
.expect("Could not execute nextsync command");
return output;
}
pub fn add_dir(&mut self, name: &str) -> std::io::Result<()> {
let mut path = self.volume.clone();
path.push_str("/");
path.push_str(name);
let _ = fs::create_dir_all(path)?;
Ok(())
}
pub fn add_file(&mut self, name: &str, content: &str) -> std::io::Result<()> {
let mut path = self.volume.clone();
path.push_str("/");
path.push_str(name);
let mut file = File::create(path)?;
file.write_all(content.as_bytes())?;
Ok(())
}
pub fn remove_file(&mut self, name: &str) -> std::io::Result<()> {
let mut path = self.volume.clone();
path.push_str("/");
path.push_str(name);
fs::remove_file(name)?;
Ok(())
}
pub fn has_file(&mut self, file: &str, content: &str) -> bool {
let full_path = PathBuf::from(self.volume.clone()).join(file);
has_files(full_path, file, content, self.test_id.clone())
}
/// get the files given by the status command in two vector (staged and not staged)
pub fn get_status(&mut self) -> (Vec<String>, Vec<String>) {
let out = self.run_cmd("status");
let lines: Vec<String> = str::from_utf8(&out.stdout)
.unwrap()
.split("\n")
.map(|s| s.to_owned())
.collect();
let mut staged = vec![];
let mut not_staged = vec![];
let mut in_staged = true;
let mut counter = 0;
for line in lines {
if line.find("not staged").is_some() {
in_staged = false;
counter = 1;
continue;
}
// skip two first line as there are not files
if counter < 2 {
counter += 1;
continue;
}
if line == String::from("") {
continue;
}
if in_staged {
staged.push(line);
} else {
not_staged.push(line);
}
}
return (staged, not_staged);
}
}

View File

@@ -1,38 +0,0 @@
use std::io::{BufReader, BufRead};
use std::fs::File;
use std::path::PathBuf;
#[cfg(test)]
pub fn has_files(full_path: PathBuf, file: &str, content: &str, test_id: String) -> bool
{
if !full_path.exists() {
println!("id: {}", test_id.clone());
eprintln!("File '{}' doesn't exists", file);
return false;
}
let f = File::open(full_path).unwrap();
for line in BufReader::new(f).lines(){
if let Ok(line) = line {
if line != content {
println!("id: {}", test_id);
eprintln!("File '{}' is not equal, {} != {}", file, line, content);
return false;
}
return line == content;
}
}
return true;
}
#[cfg(test)]
pub fn has_not_file(full_path: PathBuf, file: &str, test_id: String) -> bool
{
if full_path.exists() {
println!("id: {}", test_id.clone());
eprintln!("File '{}' exists but it shouldn't", file);
return false;
}
return true;
}

View File

@@ -1,117 +0,0 @@
use std::process::Command;
use std::os::unix::fs::PermissionsExt;
use std::fs::{self, File, Permissions};
use std::io::Write;
use std::env;
use std::path::PathBuf;
use super::files_utils::{self, has_files};
#[cfg(test)]
pub struct ServerTest {
user: String,
volume: PathBuf,
pub test_id: String
}
#[cfg(test)]
impl ServerTest {
pub fn new(id: String) -> Self {
let mut volume = env::current_dir().unwrap();
volume = volume.join("tests/data/admin/files");
ServerTest {
user: String::from("admin"),
volume,
test_id: id
}
}
pub fn init(&mut self) -> &mut ServerTest{
self.add_dir(&self.test_id.clone());
self.volume = self.volume.join(self.test_id.clone());
self.sync_root();
self
}
pub fn clean(&mut self) -> &mut ServerTest{
self.remove_dir(self.test_id.clone());
self.sync_root();
self
}
pub fn add_dir(&mut self, path: &str) -> &mut ServerTest {
let mut full_path = self.volume.clone();
full_path.push(path);
match fs::create_dir(&full_path) {
Ok(_) => {
// Set permissions to 777 to allow nextcloud to access it (workaround avoiding to
// set group and owner to www-data)
if let Err(e) = fs::set_permissions(&full_path, Permissions::from_mode(0o777)) {
eprintln!("Error setting permissions: {}", e);
}
},
Err(e) => eprintln!("Error creating directory: {}", e),
}
// do not sync test directory when creating it
if !path.ends_with("_nextsync")
{
self.sync_test();
}
self
}
pub fn add_file(&mut self, name: &str, content: &str) -> std::io::Result<()> {
let mut full_path = self.volume.clone();
full_path.push(name);
let mut file = File::create(full_path)?;
file.write_all(content.as_bytes())?;
self.sync_test();
Ok(())
}
pub fn remove_dir(&mut self, path: String) -> &mut ServerTest {
let mut full_path = self.volume.clone();
full_path.push(path);
let _ = fs::remove_dir_all(&full_path);
self.sync_test();
self
}
fn sync_root(&self) -> &Self {
self.sync("")
}
fn sync_test(&self) -> &Self {
let test_id = self.test_id.clone();
self.sync(&test_id)
}
fn sync(&self, path: &str) -> &Self {
// perform the occ files:scan command inside the nextcloud docker container
let nextcloud_docker = "master-nextcloud-1";
let args = format!("exec -t --user www-data {} /var/www/html/occ files:scan --path=/{}/files/{}", nextcloud_docker, &self.user, path);
let _output = Command::new("docker")
.args(args.split(" "))
.output()
.expect("Could not execute docker exec command");
self
}
pub fn has_file(&mut self, file: &str, content: &str) -> bool {
let full_path = self.volume.clone().join(file);
has_files(full_path, file, content, self.test_id.clone())
}
pub fn has_not_file(&mut self, file: &str) -> bool {
let full_path = self.volume.clone().join(file);
files_utils::has_not_file(full_path, file, self.test_id.clone())
}
}

View File

@@ -1,27 +0,0 @@
use super::client::ClientTest;
#[cfg(test)]
pub fn lines_should_not_contains(lines: Vec<String>, str: &str) {
for line in lines {
if line.find(str).is_some() {
eprintln!("'{}' found in '{}'", str, line);
}
assert!(line.find(str).is_none());
}
}
#[cfg(test)]
pub fn status_should_be_empty(client: &mut ClientTest) {
let (staged, not_staged) = client.get_status();
if staged.len() != 0 {
eprintln!("id: {}", client.test_id.clone());
eprintln!("Staged should be empty but has '{}'", staged.len());
assert!(staged.len() == 0);
}
if staged.len() != 0 {
eprintln!("id: {}", client.test_id.clone());
eprintln!("Not Staged should be empty but has '{}'", not_staged.len());
assert!(not_staged.len() == 0);
}
}

View File

@@ -1,12 +0,0 @@
use rand::{distributions::Alphanumeric, Rng};
#[cfg(test)]
pub fn get_random_test_id() -> String {
let mut id: String = rand::thread_rng()
.sample_iter(&Alphanumeric)
.take(7)
.map(char::from)
.collect();
id.push_str("_nextsync");
id.to_owned()
}