diff --git a/rust/Cargo.lock b/rust/Cargo.lock --- a/rust/Cargo.lock +++ b/rust/Cargo.lock @@ -1,3 +1,8 @@ +[[package]] +name = "adler32" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "aho-corasick" version = "0.5.3" @@ -7,25 +12,150 @@ ] [[package]] +name = "aho-corasick" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "atty" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "build_const" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "byteorder" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "cc" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "clap" +version = "2.31.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "cpython" version = "0.1.0" source = "git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52#c90d65cf84abfffce7ef54476bbfed56017a2f52" dependencies = [ - "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", - "num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)", "python27-sys 0.1.2 (git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52)", ] [[package]] +name = "crc" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "build_const 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "flate2" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "miniz_oxide_c_api 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "hex" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "hgcli" version = "0.1.0" dependencies = [ + "clap 2.31.1 (registry+https://github.com/rust-lang/crates.io-index)", "cpython 0.1.0 (git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52)", - "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", + "hgstorage 0.1.0", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "python27-sys 0.1.2 (git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52)", +] + +[[package]] +name = "hgext" +version = "0.1.0" +dependencies = [ + "cpython 0.1.0 (git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "python27-sys 0.1.2 (git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52)", ] [[package]] +name = "hgstorage" +version = "0.1.0" +dependencies = [ + "byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "flate2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "hex 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "lru-cache 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", + "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", + "regex 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)", + "sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)", + "tempdir 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", + "threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)", + "walkdir 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "kernel32-sys" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -35,33 +165,110 @@ ] [[package]] +name = "lazy_static" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "libc" -version = "0.2.35" +version = "0.2.36" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "linked-hash-map" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "lru-cache" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "linked-hash-map 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "memchr" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ - "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "memchr" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "miniz_oxide" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "adler32 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "miniz_oxide_c_api" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cc 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", + "crc 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "miniz_oxide 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] name = "num-traits" -version = "0.1.41" +version = "0.1.42" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "num_cpus" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "python27-sys" version = "0.1.2" source = "git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52#c90d65cf84abfffce7ef54476bbfed56017a2f52" dependencies = [ - "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", "regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] +name = "rand" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "redox_syscall" +version = "0.1.37" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "redox_termios" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "regex" version = "0.1.80" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -74,17 +281,88 @@ ] [[package]] +name = "regex" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)", + "memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)", + "regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", + "utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "regex-syntax" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "regex-syntax" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "remove_dir_all" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "same-file" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "sha1" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "strsim" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "tempdir" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)", + "remove_dir_all 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "termion" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "textwrap" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "thread-id" version = "2.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" dependencies = [ "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", - "libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] @@ -96,32 +374,149 @@ ] [[package]] +name = "thread_local" +version = "0.3.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "threadpool" +version = "1.7.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "unicode-width" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unreachable" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "utf8-ranges" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "utf8-ranges" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "vec_map" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "walkdir" +version = "2.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "winapi" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "winapi" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "winapi-build" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [metadata] +"checksum adler32 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6cbd0b9af8587c72beadc9f72d35b9fbb070982c9e6203e46e93f10df25f8f45" "checksum aho-corasick 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ca972c2ea5f742bfce5687b9aef75506a764f61d37f8f649047846a9686ddb66" +"checksum aho-corasick 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "d6531d44de723825aa81398a6415283229725a00fa30713812ab9323faa82fc4" +"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +"checksum atty 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "8352656fd42c30a0c3c89d26dea01e3b77c0ab2af18230835c15e2e13cd51859" +"checksum bitflags 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b3c30d3802dfb7281680d6285f2ccdaa8c2d8fee41f93805dba5c4cf50dc23cf" +"checksum build_const 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e90dc84f5e62d2ebe7676b83c22d33b6db8bd27340fb6ffbff0a364efa0cb9c9" +"checksum byteorder 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "652805b7e73fada9d85e9a6682a4abd490cb52d96aeecc12e33a0de34dfd0d23" +"checksum cc 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)" = "9be26b24e988625409b19736d130f0c7d224f01d06454b5f81d8d23d6c1a618f" +"checksum clap 2.31.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5dc18f6f4005132120d9711636b32c46a233fad94df6217fa1d81c5e97a9f200" "checksum cpython 0.1.0 (git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52)" = "" +"checksum crc 1.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bd5d02c0aac6bd68393ed69e00bbc2457f3e89075c6349db7189618dc4ddc1d7" +"checksum flate2 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "9fac2277e84e5e858483756647a9d0aa8d9a2b7cba517fd84325a0aaa69a0909" +"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" +"checksum hex 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "459d3cf58137bb02ad4adeef5036377ff59f066dbb82517b7192e3a5462a2abc" "checksum kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7507624b29483431c0ba2d82aece8ca6cdba9382bff4ddd0f7490560c056098d" -"checksum libc 0.2.35 (registry+https://github.com/rust-lang/crates.io-index)" = "96264e9b293e95d25bfcbbf8a88ffd1aedc85b754eba8b7d78012f638ba220eb" +"checksum lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c8f31047daa365f19be14b47c29df4f7c3b581832407daabe6ae77397619237d" +"checksum libc 0.2.36 (registry+https://github.com/rust-lang/crates.io-index)" = "1e5d97d6708edaa407429faa671b942dc0f2727222fb6b6539bf1db936e4b121" +"checksum linked-hash-map 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "7860ec297f7008ff7a1e3382d7f7e1dcd69efc94751a2284bafc3d013c2aa939" +"checksum lru-cache 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "4d06ff7ff06f729ce5f4e227876cb88d10bc59cd4ae1e09fbb2bde15c850dc21" "checksum memchr 0.1.11 (registry+https://github.com/rust-lang/crates.io-index)" = "d8b629fb514376c675b98c1421e80b151d3817ac42d7c667717d282761418d20" -"checksum num-traits 0.1.41 (registry+https://github.com/rust-lang/crates.io-index)" = "cacfcab5eb48250ee7d0c7896b51a2c5eec99c1feea5f32025635f5ae4b00070" +"checksum memchr 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "796fba70e76612589ed2ce7f45282f5af869e0fdd7cc6199fa1aa1f1d591ba9d" +"checksum miniz_oxide 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "aaa2d3ad070f428fffbd7d3ca2ea20bb0d8cffe9024405c44e1840bc1418b398" +"checksum miniz_oxide_c_api 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "92d98fdbd6145645828069b37ea92ca3de225e000d80702da25c20d3584b38a5" +"checksum num-traits 0.1.42 (registry+https://github.com/rust-lang/crates.io-index)" = "9936036cc70fe4a8b2d338ab665900323290efb03983c86cbe235ae800ad8017" +"checksum num_cpus 1.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c51a3322e4bca9d212ad9a158a02abc6934d005490c054a2778df73a70aa0a30" "checksum python27-sys 0.1.2 (git+https://github.com/indygreg/rust-cpython.git?rev=c90d65cf84abfffce7ef54476bbfed56017a2f52)" = "" +"checksum rand 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "eba5f8cb59cc50ed56be8880a5c7b496bfd9bd26394e176bc67884094145c2c5" +"checksum redox_syscall 0.1.37 (registry+https://github.com/rust-lang/crates.io-index)" = "0d92eecebad22b767915e4d529f89f28ee96dbbf5a4810d2b844373f136417fd" +"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" "checksum regex 0.1.80 (registry+https://github.com/rust-lang/crates.io-index)" = "4fd4ace6a8cf7860714a2c2280d6c1f7e6a413486c13298bbc86fd3da019402f" +"checksum regex 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)" = "5be5347bde0c48cfd8c3fdc0766cdfe9d8a755ef84d620d6794c778c91de8b2b" "checksum regex-syntax 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "f9ec002c35e86791825ed294b50008eea9ddfc8def4420124fbc6b08db834957" +"checksum regex-syntax 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "8e931c58b93d86f080c734bfd2bce7dd0079ae2331235818133c8be7f422e20e" +"checksum remove_dir_all 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b5d2f806b0fcdabd98acd380dc8daef485e22bcb7cddc811d1337967f2528cf5" +"checksum same-file 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "cfb6eded0b06a0b512c8ddbcf04089138c9b4362c2f696f3c3d76039d68f3637" +"checksum sha1 0.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "2579985fda508104f7587689507983eadd6a6e84dd35d6d115361f530916fa0d" +"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" +"checksum tempdir 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "f73eebdb68c14bcb24aef74ea96079830e7fa7b31a6106e42ea7ee887c1e134e" +"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" +"checksum textwrap 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "c0b59b6b4b44d867f1370ef1bd91bfb262bf07bf0ae65c202ea2fbc16153b693" "checksum thread-id 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "a9539db560102d1cef46b8b78ce737ff0bb64e7e18d35b2a5688f7d097d0ff03" "checksum thread_local 0.2.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8576dbbfcaef9641452d5cf0df9b0e7eeab7694956dd33bb61515fb8f18cfdd5" +"checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963" +"checksum threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e2f0c90a5f3459330ac8bc0d2f879c693bb7a2f59689c1083fc4ef83834da865" +"checksum unicode-width 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "bf3a113775714a22dcb774d8ea3655c53a32debae63a063acc00a91cc586245f" +"checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" "checksum utf8-ranges 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "a1ca13c08c41c9c3e04224ed9ff80461d97e121589ff27c753a16cb10830ae0f" +"checksum utf8-ranges 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "662fab6525a98beff2921d7f61a39e7d59e0b425ebc7d0d9e66d316e55124122" +"checksum vec_map 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "887b5b631c2ad01628bbbaa7dd4c869f80d3186688f8d0b6f58774fbe324988c" +"checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" +"checksum walkdir 2.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "63636bd0eb3d00ccb8b9036381b526efac53caf112b7783b730ab3f8e44da369" "checksum winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)" = "167dc9d6949a9b857f3451275e911c3f44255842c1f7a76f33c55103a909087a" +"checksum winapi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "04e3bd221fcbe8a271359c04f21a76db7d0c6028862d1bb5512d85e1e2eb5bb3" "checksum winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "2d315eee3b34aca4797b2da6b13ed88266e6d612562a0c46390af8299fc699bc" +"checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" +"checksum winapi-x86_64-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" diff --git a/rust/Cargo.toml b/rust/Cargo.toml --- a/rust/Cargo.toml +++ b/rust/Cargo.toml @@ -1,2 +1,8 @@ [workspace] -members = ["hgcli"] +members = ["hgcli", "hgbase85", "hgstorage"] + +[profile.release] +debug = true + +[profile.debug] +debug = true diff --git a/rust/hgbase85/Cargo.toml b/rust/hgbase85/Cargo.toml new file mode 100644 --- /dev/null +++ b/rust/hgbase85/Cargo.toml @@ -0,0 +1,34 @@ +[package] +name = "hgext" +version = "0.1.0" +authors = ["Sheng Mao "] +license = "GPL-2.0" + +[lib] +name = "hgbase85" +crate-type = ["cdylib", "rlib"] + +[features] +# localdev: detect Python in PATH and use files from source checkout. +default = ["localdev"] +localdev = [] + +[dependencies] +libc = "0.2.34" + +# We currently use a custom build of cpython and python27-sys with the +# following changes: +# * GILGuard call of prepare_freethreaded_python() is removed. +# TODO switch to official release when our changes are incorporated. +[dependencies.cpython] +version = "0.1" +default-features = false +features = ["python27-sys"] +git = "https://github.com/indygreg/rust-cpython.git" +rev = "c90d65cf84abfffce7ef54476bbfed56017a2f52" + +[dependencies.python27-sys] +version = "0.1.2" +git = "https://github.com/indygreg/rust-cpython.git" +rev = "c90d65cf84abfffce7ef54476bbfed56017a2f52" + diff --git a/rust/hgbase85/src/base85.rs b/rust/hgbase85/src/base85.rs new file mode 100644 --- /dev/null +++ b/rust/hgbase85/src/base85.rs @@ -0,0 +1,383 @@ +use std; +use std::{mem, sync}; +use std::io::{Cursor, Write}; + +use cpython::{exc, PyBytes, PyErr, PyObject, PyResult, Py_ssize_t, Python, PythonObject}; +use cpython::_detail::ffi; + +use super::cpython_ext; + +const B85CHARS: &[u8; 85] = + b"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~"; +static mut B85DEC: [u8; 256] = [0; 256]; +static B85DEC_START: sync::Once = sync::ONCE_INIT; + +fn b85prep() { + B85DEC_START.call_once(|| { + for i in 0..mem::size_of_val(B85CHARS) { + unsafe { + B85DEC[B85CHARS[i] as usize] = (i + 1) as u8; + } + } + }); +} + +pub fn b85encode(py: Python, text: &str, pad: i32) -> PyResult { + let pad = pad != 0; + let text = text.as_bytes(); + let tlen: Py_ssize_t = text.len() as Py_ssize_t; + let olen: Py_ssize_t = if pad { + ((tlen + 3) / 4 * 5) - 3 + } else { + let mut olen: Py_ssize_t = tlen % 4; + if olen > 0 { + olen += 1; + } + olen += tlen / 4 * 5; + olen + }; + + let out: PyBytes = cpython_ext::pybytes_new_without_copying(py, olen + 3); + + let mut dst = Cursor::new(unsafe { + let buffer = ffi::PyBytes_AsString(out.as_object().as_ptr()) as *mut u8; + let length = ffi::PyBytes_Size(out.as_object().as_ptr()) as usize; + std::slice::from_raw_parts_mut(buffer, length) + }); + + let mut src_buf = text.iter().cloned().peekable(); + + while src_buf.peek().is_some() { + let mut accum: u32 = 0; + + for i in &[24, 16, 8, 0] { + if let Some(val) = src_buf.next() { + accum |= (val as u32) << i; + } else { + break; + } + } + + let mut tmp_buf = [0; 5]; + + for i in &[4, 3, 2, 1, 0] { + let val = (accum % 85) as usize; + accum /= 85; + + tmp_buf[*i] = B85CHARS[val]; + } + + match dst.write(&tmp_buf) { + Ok(_) => continue, + Err(_) => break, + } + } + + if !pad { + unsafe { + ffi::_PyString_Resize( + &mut out.as_object().as_ptr() as *mut *mut ffi::PyObject, + olen, + ); + } + } + + return Ok(out.into_object()); +} + +pub fn b85decode(py: Python, text: &str) -> PyResult { + // error[E0133]: use of mutable static requires unsafe function or block + let b85dec = unsafe { B85DEC }; + + let text = text.as_bytes(); + + let out_buf_len: usize = { + let len = { text.len() }; + let i = len % 5; + + len / 5 * 4 + { + if i > 0 { + i - 1 + } else { + 0 + } + } + }; + + let out: PyBytes = cpython_ext::pybytes_new_without_copying(py, out_buf_len as Py_ssize_t); + + let dst = unsafe { + let buffer = ffi::PyBytes_AsString(out.as_object().as_ptr()) as *mut u8; + let length = ffi::PyBytes_Size(out.as_object().as_ptr()) as usize; + std::slice::from_raw_parts_mut(buffer, length) + }; + + let mut dst_cursor = 0; + + let mut src = text.iter().cloned().peekable(); + + while src.peek().is_some() { + let mut acc: u32 = 0; + + for _ in 0..4 { + if let Some(val) = src.next() { + let c = (b85dec[val as usize] as i32) + .checked_sub(1) + .ok_or_else(|| { + PyErr::new::( + py, + format!("bad base85 character {}", val), + ) + })?; + + acc = acc * 85 + (c as u32); + } else { + break; + } + } + + if let Some(val) = src.next() { + let c = (b85dec[val as usize] as i32) + .checked_sub(1) + .ok_or_else(|| { + PyErr::new::(py, format!("bad base85 character {}", val)) + })?; + + acc = acc.checked_mul(85).ok_or_else(|| { + PyErr::new::(py, format!("bad base85 character {}", val)) + })?; + + acc = acc.checked_add(c as u32).ok_or_else(|| { + PyErr::new::(py, format!("bad base85 character {}", val)) + })?; + } + + let available_buf_len = out_buf_len - dst_cursor; + + let cap = if available_buf_len < 4 { + available_buf_len + } else { + 4 + }; + + for _ in 0..(4 - cap) { + acc *= 85; + } + + if (cap > 0) && (cap < 4) { + acc += 0xffffff >> (cap - 1) * 8; + } + + for j in 0..cap { + acc = (acc << 8) | (acc >> 24); + dst[j + dst_cursor] = acc as u8; + } + + dst_cursor += cap; + } + + return Ok(out.into_object()); +} + +py_module_initializer!(base85, initbase85, PyInit_base85, |py, m| { + b85prep(); + m.add(py, "__doc__", "base85 module")?; + m.add(py, "b85encode", py_fn!(py, b85encode(text: &str, pad: i32)))?; + m.add(py, "b85decode", py_fn!(py, b85decode(text: &str)))?; + Ok(()) +}); + +#[cfg(test)] +mod test { + use cpython::Python; + + #[test] + fn test_encoder_abc_pad() -> () { + ::set_py_env(); + + let gil = Python::acquire_gil(); + let py = gil.python(); + ::init_all_hg_ext(py); + + let res: String = super::b85encode(py, "abc", 1).unwrap().extract(py).unwrap(); + assert_eq!(res, "VPazd"); + + let base85 = py.import("base85").unwrap(); + let res: String = base85 + .call(py, "b85encode", ("abc", 1), None) + .unwrap() + .extract(py) + .unwrap(); + assert_eq!(res, "VPazd"); + } + + #[test] + fn test_encoder_chinese_pad() -> () { + ::set_py_env(); + + let gil = Python::acquire_gil(); + let py = gil.python(); + ::init_all_hg_ext(py); + + let res: String = super::b85encode(py, "这是一个测试的例子", 1) + .unwrap() + .extract(py) + .unwrap(); + assert_eq!(res, "=)alfn6KoxfaJKU=CzCHua)PTgyg=9<*kqa"); + + let base85 = py.import("base85").unwrap(); + let res: String = base85 + .call(py, "b85encode", ("这是一个测试的例子", 1), None) + .unwrap() + .extract(py) + .unwrap(); + assert_eq!(res, "=)alfn6KoxfaJKU=CzCHua)PTgyg=9<*kqa"); + } + + #[test] + fn test_encoder_abc_no_pad() -> () { + ::set_py_env(); + + let gil = Python::acquire_gil(); + let py = gil.python(); + ::init_all_hg_ext(py); + + let res: String = super::b85encode(py, "abc", 0).unwrap().extract(py).unwrap(); + assert_eq!(res, "VPaz"); + + let base85 = py.import("base85").unwrap(); + let res: String = base85 + .call(py, "b85encode", ("abc", 0), None) + .unwrap() + .extract(py) + .unwrap(); + assert_eq!(res, "VPaz"); + } + + #[test] + fn test_encoder_chinese_no_pad() -> () { + ::set_py_env(); + + let gil = Python::acquire_gil(); + let py = gil.python(); + ::init_all_hg_ext(py); + + let res: String = super::b85encode(py, "这是一个测试的例子", 0) + .unwrap() + .extract(py) + .unwrap(); + assert_eq!(res, "=)alfn6KoxfaJKU=CzCHua)PTgyg=9<*kq"); + + let base85 = py.import("base85").unwrap(); + let res: String = base85 + .call(py, "b85encode", ("这是一个测试的例子", 0), None) + .unwrap() + .extract(py) + .unwrap(); + assert_eq!(res, "=)alfn6KoxfaJKU=CzCHua)PTgyg=9<*kq"); + } + + #[test] + fn test_decoder_abc_no_pad() -> () { + ::set_py_env(); + + let gil = Python::acquire_gil(); + let py = gil.python(); + ::init_all_hg_ext(py); + + let res: String = super::b85decode(py, "VPaz").unwrap().extract(py).unwrap(); + assert_eq!(res, "abc"); + + let base85 = py.import("base85").unwrap(); + let res: String = base85 + .call(py, "b85decode", ("VPaz",), None) + .unwrap() + .extract(py) + .unwrap(); + assert_eq!(res, "abc"); + } + + #[test] + fn test_decoder_abc_pad() -> () { + ::set_py_env(); + + let gil = Python::acquire_gil(); + let py = gil.python(); + ::init_all_hg_ext(py); + + let mut res: String = super::b85decode(py, "VPazd").unwrap().extract(py).unwrap(); + let len = { res.len() }; + res.truncate(len - 1); + assert_eq!(res, "abc"); + + let base85 = py.import("base85").unwrap(); + let mut res: String = base85 + .call(py, "b85decode", ("VPazd",), None) + .unwrap() + .extract(py) + .unwrap(); + let len = { res.len() }; + res.truncate(len - 1); + assert_eq!(res, "abc"); + } + + #[test] + fn test_decoder_chinese_pad() -> () { + ::set_py_env(); + + let gil = Python::acquire_gil(); + let py = gil.python(); + ::init_all_hg_ext(py); + + let mut res: String = super::b85decode(py, "=)alfn6KoxfaJKU=CzCHua)PTgyg=9<*kqa") + .unwrap() + .extract(py) + .unwrap(); + let len = { res.len() }; + res.truncate(len - 1); + assert_eq!(res, "这是一个测试的例子"); + + let base85 = py.import("base85").unwrap(); + let mut res: String = base85 + .call( + py, + "b85decode", + ("=)alfn6KoxfaJKU=CzCHua)PTgyg=9<*kqa",), + None, + ) + .unwrap() + .extract(py) + .unwrap(); + let len = { res.len() }; + res.truncate(len - 1); + assert_eq!(res, "这是一个测试的例子"); + } + + #[test] + fn test_decoder_chinese_no_pad() -> () { + ::set_py_env(); + + let gil = Python::acquire_gil(); + let py = gil.python(); + ::init_all_hg_ext(py); + + let res: String = super::b85decode(py, "=)alfn6KoxfaJKU=CzCHua)PTgyg=9<*kq") + .unwrap() + .extract(py) + .unwrap(); + assert_eq!(res, "这是一个测试的例子"); + + let base85 = py.import("base85").unwrap(); + let res: String = base85 + .call( + py, + "b85decode", + ("=)alfn6KoxfaJKU=CzCHua)PTgyg=9<*kq",), + None, + ) + .unwrap() + .extract(py) + .unwrap(); + assert_eq!(res, "这是一个测试的例子"); + } +} diff --git a/rust/hgbase85/src/cpython_ext.rs b/rust/hgbase85/src/cpython_ext.rs new file mode 100644 --- /dev/null +++ b/rust/hgbase85/src/cpython_ext.rs @@ -0,0 +1,26 @@ +use cpython::{PyBytes, PyObject, Py_ssize_t, Python, PythonObjectWithCheckedDowncast}; + +use python27_sys as ffi; + +use std; + +#[inline] +pub unsafe fn cast_from_owned_ptr_or_panic(py: Python, p: *mut ffi::PyObject) -> T +where + T: PythonObjectWithCheckedDowncast, +{ + if p.is_null() { + panic!("NULL pointer detected.") + } else { + PyObject::from_owned_ptr(py, p).cast_into(py).unwrap() + } +} + +pub fn pybytes_new_without_copying(py: Python, len: Py_ssize_t) -> PyBytes { + unsafe { + if len <= 0 { + panic!("the request bytes length should be > 0.") + } + cast_from_owned_ptr_or_panic(py, ffi::PyBytes_FromStringAndSize(std::ptr::null(), len)) + } +} diff --git a/rust/hgbase85/src/lib.rs b/rust/hgbase85/src/lib.rs new file mode 100644 --- /dev/null +++ b/rust/hgbase85/src/lib.rs @@ -0,0 +1,104 @@ +#[macro_use] +extern crate cpython; +extern crate libc; +extern crate python27_sys; + +use python27_sys as ffi; + +pub mod base85; +pub mod cpython_ext; + +use std::{env, sync}; +use std::path::PathBuf; +use std::ffi::{CString, OsStr}; + +#[cfg(target_family = "unix")] +use std::os::unix::ffi::OsStrExt; + +static HG_EXT_REG: sync::Once = sync::ONCE_INIT; + +#[no_mangle] +pub fn init_all_hg_ext(_py: cpython::Python) { + HG_EXT_REG.call_once(|| unsafe { + base85::initbase85(); + }); +} + +#[derive(Debug)] +pub struct Environment { + _exe: PathBuf, + python_exe: PathBuf, + python_home: PathBuf, + mercurial_modules: PathBuf, +} + +// On UNIX, platform string is just bytes and should not contain NUL. +#[cfg(target_family = "unix")] +fn cstring_from_os>(s: T) -> CString { + CString::new(s.as_ref().as_bytes()).unwrap() +} + +#[cfg(target_family = "windows")] +fn cstring_from_os>(s: T) -> CString { + CString::new(s.as_ref().to_str().unwrap()).unwrap() +} + +fn set_python_home(env: &Environment) { + let raw = cstring_from_os(&env.python_home).into_raw(); + unsafe { + ffi::Py_SetPythonHome(raw); + } +} + +static PYTHON_ENV_START: sync::Once = sync::ONCE_INIT; + +/// the second half initialization code are copied from rust-cpython +/// fn pythonrun::prepare_freethreaded_python() +/// because this function is called mainly by `cargo test` +/// and the multi-thread nature requires to properly +/// set up threads and GIL. In the corresponding version, +/// prepare_freethreaded_python() is turned off, so the cargo +/// test features must be properly called. +pub fn set_py_env() { + PYTHON_ENV_START.call_once(|| { + let env = { + let exe = env::current_exe().unwrap(); + + let mercurial_modules = std::env::var("HGROOT").expect( + "must set mercurial's root folder (one layer above mercurial folder itself", + ); + + let python_exe = std::env::var("HGRUST_PYTHONEXE") + .expect("set PYTHONEXE to the full path of the python.exe file"); + + let python_home = std::env::var("HGRUST_PYTHONHOME").expect( + "if you don't want to use system one, set PYTHONHOME according to python doc", + ); + + Environment { + _exe: exe.clone(), + python_exe: PathBuf::from(python_exe), + python_home: PathBuf::from(python_home), + mercurial_modules: PathBuf::from(mercurial_modules), + } + }; + + set_python_home(&env); + + let program_name = cstring_from_os(&env.python_exe).as_ptr(); + unsafe { + ffi::Py_SetProgramName(program_name as *mut i8); + } + + unsafe { + if ffi::Py_IsInitialized() != 0 { + assert!(ffi::PyEval_ThreadsInitialized() != 0); + } else { + assert!(ffi::PyEval_ThreadsInitialized() == 0); + ffi::Py_InitializeEx(0); + ffi::PyEval_InitThreads(); + let _thread_state = ffi::PyEval_SaveThread(); + } + } + }); +} diff --git a/rust/hgcli/Cargo.toml b/rust/hgcli/Cargo.toml --- a/rust/hgcli/Cargo.toml +++ b/rust/hgcli/Cargo.toml @@ -17,6 +17,8 @@ [dependencies] libc = "0.2.34" +hgstorage = { path = "../hgstorage" } +clap = "~2.31.1" # We currently use a custom build of cpython and python27-sys with the # following changes: diff --git a/rust/hgcli/build.rs b/rust/hgcli/build.rs --- a/rust/hgcli/build.rs +++ b/rust/hgcli/build.rs @@ -18,10 +18,10 @@ fn get_python_config() -> PythonConfig { // The python27-sys crate exports a Cargo variable defining the full // path to the interpreter being used. - let python = env::var("DEP_PYTHON27_PYTHON_INTERPRETER").expect( - "Missing DEP_PYTHON27_PYTHON_INTERPRETER; bad python27-sys crate?", - ); + let python = env::var("DEP_PYTHON27_PYTHON_INTERPRETER") + .expect("Missing DEP_PYTHON27_PYTHON_INTERPRETER; bad python27-sys crate?"); + println!("{}", python); if !Path::new(&python).exists() { panic!( "Python interpreter {} does not exist; this should never happen", @@ -33,8 +33,8 @@ let separator = "SEPARATOR STRING"; let script = "import sysconfig; \ -c = sysconfig.get_config_vars(); \ -print('SEPARATOR STRING'.join('%s=%s' % i for i in c.items()))"; + c = sysconfig.get_config_vars(); \ + print('SEPARATOR STRING'.join('%s=%s' % i for i in c.items()))"; let mut command = Command::new(&python); command.arg("-c").arg(script); @@ -110,6 +110,8 @@ } } + println!("have shared {}", have_shared(&config)); + // We need a Python shared library. if !have_shared(&config) { panic!("Detected Python lacks a shared library, which is required"); diff --git a/rust/hgcli/src/main.rs b/rust/hgcli/src/main.rs --- a/rust/hgcli/src/main.rs +++ b/rust/hgcli/src/main.rs @@ -5,10 +5,13 @@ // This software may be used and distributed according to the terms of the // GNU General Public License version 2 or any later version. -extern crate libc; +extern crate clap; extern crate cpython; +extern crate libc; extern crate python27_sys; +extern crate hgstorage; + use cpython::{NoArgs, ObjectProtocol, PyModule, PyResult, Python}; use libc::{c_char, c_int}; @@ -18,6 +21,9 @@ #[cfg(target_family = "unix")] use std::os::unix::ffi::{OsStrExt, OsStringExt}; +use hgstorage::local_repo; +use hgstorage::repository::Repository; + #[derive(Debug)] struct Environment { _exe: PathBuf, @@ -224,10 +230,44 @@ } fn main() { - let exit_code = match run() { - Err(err) => err, - Ok(()) => 0, - }; + let matches = clap::App::new("hg rust oxidation") + .arg( + clap::Arg::with_name("repository") + .short("c") + .long("repository") + .value_name("dash_r"), + ) + .subcommand(clap::SubCommand::with_name("r-status")) + .get_matches_safe() + .unwrap_or_else(|e| { + match run() { + Err(err) => { + println!("running python-version hg with error code {}", err); + println!("If you want to run rust-version hg, please see the error."); + e.exit(); + } + Ok(()) => std::process::exit(0), + }; + }); - std::process::exit(exit_code); + if let Some(_r_matches) = matches.subcommand_matches("r-status") { + let dash_r = match matches.value_of("dash_r") { + Some(dash_r) => Some(PathBuf::from(dash_r)), + None => None, + }; + let repo = local_repo::LocalRepo::new(dash_r); + let res = repo.status(); + for f in res.modified.iter() { + println!("M {}", f.to_str().unwrap()); + } + for f in res.added.iter() { + println!("A {}", f.to_str().unwrap()); + } + for f in res.removed.iter() { + println!("R {}", f.to_str().unwrap()); + } + for f in res.unknown.iter() { + println!("? {}", f.to_str().unwrap()); + } + } } diff --git a/rust/hgstorage/Cargo.toml b/rust/hgstorage/Cargo.toml new file mode 100644 --- /dev/null +++ b/rust/hgstorage/Cargo.toml @@ -0,0 +1,29 @@ +[package] +name = "hgstorage" +version = "0.1.0" +authors = ["Sheng Mao "] +license = "GPL-2.0" + +[lib] +name = "hgstorage" +crate-type = ["cdylib", "rlib"] + +[features] +# localdev: detect Python in PATH and use files from source checkout. +default = ["localdev"] +localdev = [] + +[dependencies] +libc = "0.2.34" +byteorder = "1.0" +walkdir = "2" +tempdir = "0.3.6" +regex = "0.2.6" +threadpool = "1.7.1" +num_cpus = "1.0" +lazy_static = "1.0.0" +lru-cache = "0.1.1" +flate2 = { version = "1.0", features = ["rust_backend"], default-features = false } +sha1 = "0.6.0" +hex = "0.3.1" + diff --git a/rust/hgstorage/src/changelog.rs b/rust/hgstorage/src/changelog.rs new file mode 100644 --- /dev/null +++ b/rust/hgstorage/src/changelog.rs @@ -0,0 +1,39 @@ +use std::sync::{Arc, RwLock}; + +use hex; + +use revlog::{NodeId, Revlog}; + +#[derive(Clone)] +pub struct CommitInfo { + pub manifest_id: NodeId, + pub msg: Vec, +} + +#[derive(Clone)] +pub struct ChangeLog { + pub inner: Arc>, +} + +impl ChangeLog { + pub fn get_commit_info(&self, id: &NodeId) -> CommitInfo { + let rev = self.inner.read().unwrap().node_id_to_rev(id).unwrap(); + + let mut content = self.inner.read().unwrap().revision(&rev).unwrap(); + + assert_eq!(content[NodeId::hex_len()], b'\n', "node id ends with \\n"); + + let manifest_id = { + let hex_id = &content[..NodeId::hex_len()]; + NodeId::new_from_bytes(&hex::decode(hex_id).unwrap()) + }; + + // keep the rest of the message for other functionalities + content.drain(..NodeId::hex_len()); + + CommitInfo { + manifest_id, + msg: content, + } + } +} diff --git a/rust/hgstorage/src/config.rs b/rust/hgstorage/src/config.rs new file mode 100644 --- /dev/null +++ b/rust/hgstorage/src/config.rs @@ -0,0 +1,101 @@ +use std::default::Default; +use std::collections::{HashMap, HashSet}; +use std::io::{BufRead, BufReader}; +use std::fs::File; +use std::path::Path; + +pub enum RevlogFormat { + V1, +} + +impl Default for RevlogFormat { + fn default() -> Self { + RevlogFormat::V1 + } +} + +pub enum Compressor { + Zlib, + Zstd, + Gzip, + None, +} + +impl Default for Compressor { + fn default() -> Self { + Compressor::Zlib + } +} + +pub enum DeltaPolicy { + ParentDelta, + GeneralDelta, +} + +impl Default for DeltaPolicy { + fn default() -> Self { + DeltaPolicy::GeneralDelta + } +} + +/// the configuration is read from .hg/requires file +#[derive(Default)] +pub struct Configuration { + /// store all requires key words + pub requires: HashSet, + /// callback functions, if any keyword is found, then do some settings + pub reg_conf: HashMap, + /// default revlog format, currently only support revlog v1 + pub revlog_format: RevlogFormat, + /// where to get delta base, please refer to wiki page + pub delta: DeltaPolicy, +} + +pub type RegFn = fn(&mut Configuration) -> (); + +impl Configuration { + pub fn new(path: &Path) -> Self { + let mut s: Configuration = Default::default(); + + s.register_all(); + + if path.exists() { + let f = File::open(path).unwrap(); + + let buffer = BufReader::new(&f); + + for line in buffer.lines() { + let key = line.unwrap(); + + if s.reg_conf.contains_key(&key) { + s.reg_conf[&key](&mut s); + } + + s.requires.insert(key); + } + } + + s + } + + pub fn register_conf(&mut self, key: String, func: RegFn) { + self.reg_conf.insert(key, func); + } + + fn register_all(&mut self) { + self.register_conf(String::from("revlogv1"), |conf| { + conf.revlog_format = RevlogFormat::V1; + }); + self.register_conf(String::from("generaldelta"), |conf| { + conf.delta = DeltaPolicy::GeneralDelta; + }); + } + + pub fn get_revlog_format(&self) -> RevlogFormat { + if self.requires.contains("revlogv1") { + RevlogFormat::V1 + } else { + unimplemented!("right now only support revlog v1"); + } + } +} diff --git a/rust/hgstorage/src/dirstate.rs b/rust/hgstorage/src/dirstate.rs new file mode 100644 --- /dev/null +++ b/rust/hgstorage/src/dirstate.rs @@ -0,0 +1,271 @@ +use std::str; +use std::path::{Path, PathBuf}; +use std::io; +use std::io::Read; +use std::fs::File; +use std::collections::HashMap; +#[cfg(target_family = "unix")] +use std::os::unix::fs::FileTypeExt; +use std::collections::HashSet; +use std::time::{SystemTime, UNIX_EPOCH}; +use std::error::Error; + +use byteorder::{BigEndian, ReadBytesExt}; +use walkdir::{DirEntry, WalkDir}; + +use matcher; +use revlog::*; + +#[derive(Debug, Default)] +pub struct DirStateEntry { + status: u8, + mode: u32, + /// size of file + size: u32, + mtime: u32, + /// length of file name + length: u32, +} + +pub fn read_dirstate_entry( + rdr: &mut R, +) -> io::Result { + let status = rdr.read_u8()?; + let mode = rdr.read_u32::()?; + let size = rdr.read_u32::()?; + let mtime = rdr.read_u32::()?; + let length = rdr.read_u32::()?; + + Ok(DirStateEntry { + status, + mode, + size, + mtime, + length, + }) +} + +pub struct DirState { + pub p1: NodeId, + pub p2: NodeId, + pub path: PathBuf, + /// corresponding to dmap in python version + pub dir_state_map: HashMap, + + pub mtime: SystemTime, +} + +#[derive(Debug)] +pub struct CurrentState { + /// per status-call + pub added: HashSet, + /// a.k.a forget + pub removed: HashSet, + /// a.k.a missing + pub deleted: HashSet, + pub modified: HashSet, + /// the worker thread handling ignored will first add all sub files of the ignored dir/file + /// to the ignored set, later, when checking the remaining paths, if the path is in ignored set, + /// then remove them from ignored set + pub ignored: HashSet, + pub unknown: HashSet, + pub clean: HashSet, + pub lookup: HashSet, +} + +impl CurrentState { + pub fn new() -> Self { + Self { + added: HashSet::new(), + removed: HashSet::new(), + deleted: HashSet::new(), + modified: HashSet::new(), + ignored: HashSet::new(), + unknown: HashSet::new(), + clean: HashSet::new(), + lookup: HashSet::new(), + } + } +} + +fn build_relatvie_path_w_trailing_slash_for_dir_path(root: &Path, path: &Path) -> PathBuf { + let mut path_buf = path.strip_prefix(root).unwrap().to_owned(); + path_buf.push(""); + + path_buf +} + +impl DirState { + pub fn new(p: PathBuf) -> Option { + let meta_data = p.metadata(); + + if let Ok(meta_data) = meta_data { + let mtime = meta_data.modified().expect("default mtime must exist"); + + let mut ret = Self { + p1: NULL_ID.clone(), + p2: NULL_ID.clone(), + path: p, + dir_state_map: HashMap::new(), + + mtime, + }; + + ret.parse_dirstate(); + + return Some(ret); + } else { + return None; + } + } + + fn parse_dirstate(&mut self) { + let mut dfile = File::open(&self.path).expect("Cannot open dirstate file"); + + dfile.read_exact(&mut self.p1.node).unwrap(); + dfile.read_exact(&mut self.p2.node).unwrap(); + + loop { + let entry: DirStateEntry = match read_dirstate_entry(&mut dfile) { + Ok(v) => v, + Err(_) => break, + }; + + let mut fname = vec![0u8; entry.length as usize]; + dfile.read_exact(fname.as_mut()).unwrap(); + + self.dir_state_map + .entry(PathBuf::from(str::from_utf8(fname.as_ref()).unwrap())) + .or_insert(entry); + } + } + + #[cfg(target_family = "unix")] + fn is_bad(entry: &DirEntry) -> bool { + entry.file_type().is_block_device() || entry.file_type().is_fifo() + || entry.file_type().is_char_device() || entry.file_type().is_symlink() + || entry.file_type().is_socket() + } + + #[cfg(not(target_family = "unix"))] + fn is_bad(_entry: &DirEntry) -> bool { + false + } + + pub fn walk_dir( + &mut self, + root: &Path, + matcher: &matcher::Matcher, + ) -> io::Result { + let mut files_not_in_walkdir = { + let mut files_not_in_walkdir = HashSet::new(); + files_not_in_walkdir.extend(self.dir_state_map.keys().map(|s| s.as_path())); + files_not_in_walkdir + }; + + let mut res = CurrentState::new(); + + let walker = WalkDir::new(root).into_iter(); + + for entry in walker.filter_entry(|ent| { + if ent.file_type().is_dir() { + let p = build_relatvie_path_w_trailing_slash_for_dir_path(root, ent.path()); + !matcher.check_path_ignored(p.to_str().unwrap()) + } else { + true + } + }) { + match entry { + Ok(entry) => { + let abs_path = entry.path(); + let rel_path = abs_path.strip_prefix(root).unwrap(); + + if DirState::is_bad(&entry) || entry.file_type().is_dir() { + continue; + } + + if self.dir_state_map.contains_key(rel_path) { + let dir_entry = &self.dir_state_map[rel_path]; + files_not_in_walkdir.remove(rel_path); + DirState::check_status(&mut res, abs_path, rel_path, dir_entry); + } else if !matcher.check_path_ignored(rel_path.to_str().unwrap()) { + res.unknown.insert(rel_path.to_path_buf()); + } + } + Err(err) => { + let path = err.path().unwrap_or(Path::new("")); + eprintln!("failed to access entry {}", path.display()); + + if let Some(inner) = err.io_error() { + match inner.kind() { + io::ErrorKind::InvalidData => { + eprintln!("entry contains invalid data: {}", inner) + } + io::ErrorKind::PermissionDenied => { + eprintln!("Missing permission to read entry: {}", inner) + } + _ => eprintln!("Unexpected error occurred: {}", inner), + } + + if self.dir_state_map + .contains_key(path.strip_prefix(root).unwrap()) + { + return Err(io::Error::new(inner.kind(), inner.description())); + } + } + } + } + } + + for path in files_not_in_walkdir.drain() { + if res.ignored.contains(path) { + res.ignored.remove(path); + } + + let relpath = path; + let abspath = root.join(relpath); + + let dir_entry = &self.dir_state_map[relpath]; + + DirState::check_status(&mut res, &abspath, &relpath, dir_entry); + } + + return Ok(res); + } + + fn check_status( + res: &mut CurrentState, + abspath: &Path, + relpath: &Path, + dir_entry: &DirStateEntry, + ) { + let pb = relpath.to_path_buf(); + + // the order here is very important + // if it is 'r' then it can be 'forget' or 'remove', + // so the file existence doesn't matter. + // other status all rely on file existence. + if dir_entry.status == b'r' { + res.removed.insert(pb); + } else if !abspath.exists() { + res.deleted.insert(pb); + } else if dir_entry.status == b'a' { + res.added.insert(pb); + } else { + let mtd = abspath.metadata().unwrap(); + + if mtd.len() != dir_entry.size as u64 { + res.modified.insert(pb); + } else if mtd.modified() + .unwrap() + .duration_since(UNIX_EPOCH) + .unwrap() + .as_secs() != dir_entry.mtime as u64 + { + res.lookup.insert(pb); + } else { + res.clean.insert(pb); + } + } + } +} diff --git a/rust/hgstorage/src/lib.rs b/rust/hgstorage/src/lib.rs new file mode 100644 --- /dev/null +++ b/rust/hgstorage/src/lib.rs @@ -0,0 +1,64 @@ +extern crate byteorder; +extern crate flate2; +extern crate hex; +#[macro_use] +extern crate lazy_static; +extern crate lru_cache; +extern crate num_cpus; +extern crate regex; +extern crate sha1; +extern crate tempdir; +extern crate threadpool; +extern crate walkdir; + +use std::process::Command; +use std::path::Path; + +pub mod mpatch; +pub mod revlog; +pub mod revlog_v1; +pub mod changelog; +pub mod manifest; +pub mod path_encoding; +pub mod matcher; +pub mod dirstate; +pub mod repository; +pub mod local_repo; +pub mod config; +pub mod working_context; + +/// assume cffi repo is in the same level of hg repo +pub fn prepare_testing_repo(temp_dir: &Path) { + if !Path::new("../../../cffi/").exists() { + let hg_msg = Command::new("hg") + .args(&[ + "clone", + "https://ivzhh@bitbucket.org/ivzhh/cffi", + "../../../cffi/", + "-u", + "e8f05076085cd24d01ba1f5d6163fdee16e90103", + ]) + .output() + .unwrap(); + println!("stdout: {}", String::from_utf8_lossy(&hg_msg.stdout)); + println!("stderr: {}", String::from_utf8_lossy(&hg_msg.stderr)); + } + + let dst = temp_dir.join("cffi"); + let hg_msg = Command::new("hg") + .args(&[ + "clone", + "../../../cffi/", + "-u", + "e8f05076085cd24d01ba1f5d6163fdee16e90103", + dst.to_str().unwrap(), + ]) + .output() + .unwrap(); + if !hg_msg.stdout.is_empty() { + println!("stdout: {}", String::from_utf8_lossy(&hg_msg.stdout)); + } + if !hg_msg.stderr.is_empty() { + println!("stderr: {}", String::from_utf8_lossy(&hg_msg.stderr)); + } +} diff --git a/rust/hgstorage/src/local_repo.rs b/rust/hgstorage/src/local_repo.rs new file mode 100644 --- /dev/null +++ b/rust/hgstorage/src/local_repo.rs @@ -0,0 +1,164 @@ +use std; +use std::path::{Path, PathBuf}; +use std::sync::{Arc, RwLock}; + +use lru_cache::LruCache; + +use repository::Repository; +use config; +use revlog::Revlog; +use revlog_v1 as rv1; +use matcher::Matcher; +use dirstate::CurrentState; +use path_encoding::encode_path; +use manifest::FlatManifest; +use working_context::WorkCtx; +use changelog::ChangeLog; + +const LRU_SIZE: usize = 100; + +type RevlogPtr = Arc>; +type RevlogLRU = Arc>>; + +pub struct LocalRepo { + pub repo_root: Arc, + pub dot_hg_path: Arc, + pub pwd: Arc, + pub store_data: Arc, + pub matcher: Matcher, + pub config: Arc, + pub revlog_factory: Arc, + pub revlog_db: RevlogLRU, + pub manifest: Arc, + pub changelog: Arc, + pub work_ctx: Arc, +} + +impl LocalRepo { + pub fn new(dash_r: Option) -> Self { + let pwd = Arc::new(std::env::current_dir().unwrap()); + + let repo_root = Arc::new(match dash_r { + Some(p) => { + let dot_hg_path = p.join(".hg"); + assert!( + dot_hg_path.exists(), + ".hg folder not found for the path given by -R argument: {:?}", + p + ); + p + } + None => { + let mut root = pwd.as_path(); + while !root.join(".hg").exists() { + root = root.parent().expect(".hg folder not found"); + } + root.to_owned() + } + }); + + let dot_hg_path = Arc::new(repo_root.join(".hg")); + let requires = dot_hg_path.join("requires"); + let config = Arc::new(config::Configuration::new(&requires)); + let store = dot_hg_path.join("store"); + let store_data = Arc::new(store.join("data")); + //let fn_changelog = store.join("00changelog.i"); + let fn_manifest = store.join("00manifest.i"); + let fn_changelog = store.join("00changelog.i"); + + let revlog_factory = match config.revlog_format { + config::RevlogFormat::V1 => Arc::new(rv1::RevlogIO::get_factory()), + }; + + let manifest = Arc::new(FlatManifest { + inner: revlog_factory.create(fn_manifest.as_path()), + }); + + let changelog = Arc::new(ChangeLog { + inner: revlog_factory.create(fn_changelog.as_path()), + }); + + let matcher = { + let default_hgignore = repo_root.join(".hgignore"); + let mut matcher = Matcher::new(&[default_hgignore]); + matcher + }; + + let revlog_db = Arc::new(RwLock::new(LruCache::new(LRU_SIZE))); + + let work_ctx = Arc::new(WorkCtx::new( + dot_hg_path.clone(), + manifest.clone(), + changelog.clone(), + )); + + return Self { + repo_root, + dot_hg_path, + pwd, + store_data, + matcher, + config, + revlog_factory, + revlog_db, + manifest, + changelog, + work_ctx, + }; + } + + pub fn get_filelog(&self, path: &Path) -> Arc> { + let relpath = encode_path(path); + let abspath = self.store_data.join(&relpath); + + assert!(abspath.exists(), "path not exists: {:?}", abspath); + + let mut gd = self.revlog_db.write().unwrap(); + + if !gd.contains_key(path) { + let rl = self.revlog_factory.create(abspath.as_path()); + gd.insert(path.to_path_buf(), rl); + } + + return gd.get_mut(path).unwrap().clone(); + } +} + +impl Repository for LocalRepo { + fn status(&self) -> CurrentState { + self.work_ctx.status(&self) + } +} + +#[cfg(test)] +mod test { + use std::env; + use tempdir::TempDir; + use local_repo::LocalRepo; + use repository::Repository; + use dirstate::DirState; + + #[test] + fn test_create_dirstate() -> () { + println!( + "current dir {}", + env::current_dir().unwrap().to_str().unwrap() + ); + let tmp_dir = TempDir::new("cffi").unwrap(); + + println!("tmp_dir {:?}", tmp_dir.path()); + + ::prepare_testing_repo(tmp_dir.path()); + + let root = tmp_dir.path().join("cffi"); + + let dfile = tmp_dir.path().join("cffi/.hg/dirstate"); + let ds = DirState::new(dfile).unwrap(); + + println!("p1: {}", ds.p1); + + let repo = LocalRepo::new(Some(root)); + + println!("status: {:?}", repo.status()); + } +} diff --git a/rust/hgstorage/src/manifest.rs b/rust/hgstorage/src/manifest.rs new file mode 100644 --- /dev/null +++ b/rust/hgstorage/src/manifest.rs @@ -0,0 +1,121 @@ +use std::sync::{Arc, RwLock}; +use std::collections::HashMap as Map; +use std::path::PathBuf; +use std::str; + +use hex; + +use revlog::{NodeId, Revlog}; + +#[derive(Debug)] +pub struct ManifestEntry { + pub id: NodeId, + pub flag: String, +} + +type FileRevMap = Map; + +#[derive(Clone)] +pub struct FlatManifest { + pub inner: Arc>, +} + +impl FlatManifest { + pub fn build_file_rev_mapping(&self, rev: &i32) -> FileRevMap { + let mut res = FileRevMap::new(); + + let content = self.inner.read().unwrap().revision(rev).unwrap(); + + let mut line_start = 0; + let mut prev_i: usize = 0; + + for i in 0..(content.len()) { + if content[i] == b'\0' { + // field break + prev_i = i; + } else if content[i] == b'\n' { + // line break + let file_name = str::from_utf8(&content[line_start..prev_i]) + .unwrap() + .to_string(); + + line_start = i + 1; + + let entry = if i - prev_i - 1 == NodeId::hex_len() { + let id = + NodeId::new_from_bytes(&hex::decode(&content[(prev_i + 1)..i]).unwrap()); + let flag = "".to_string(); + ManifestEntry { id, flag } + } else { + let id = NodeId::new_from_bytes(&hex::decode( + &content[(prev_i + 1)..(prev_i + 41)], // the length of node id in hex format is 40 bytes long + ).unwrap()); + let flag = str::from_utf8(&content[(prev_i + 41)..i]) // an extra flag may exist after node id + .unwrap() + .to_string(); + ManifestEntry { id, flag } + }; + + res.insert(PathBuf::from(file_name.as_str()), entry); + } + } + + return res; + } +} + +#[cfg(test)] +mod test { + use std::env; + use tempdir::TempDir; + use super::*; + use manifest::FlatManifest; + use std::sync::{Arc, RwLock}; + use revlog_v1::*; + + #[test] + fn test_hgstorage_manifest() -> () { + println!( + "current dir {}", + env::current_dir().unwrap().to_str().unwrap() + ); + let tmp_dir = TempDir::new("cffi").unwrap(); + + println!("tmp_dir {:?}", tmp_dir.path()); + + ::prepare_testing_repo(tmp_dir.path()); + let mfest = tmp_dir.path().join("cffi/.hg/store/00manifest.i"); + println!("mfest {:?}", mfest); + + assert!(mfest.exists()); + + let rvlg = RevlogIO::new(&mfest); + + assert_eq!(rvlg.tip(), 2957); + + let tip = rvlg.tip() as i32; + let node = rvlg.node_id(&tip); + println!("node rev{}: {}", tip, node); + + let p1r = rvlg.p1(&tip); + let p1id = rvlg.p1_nodeid(&tip); + println!("p1 rev{}: {}", p1r, p1id); + + let p2r = rvlg.p2(&tip); + let p2id = rvlg.p2_nodeid(&tip); + println!("p2 rev{}: {}", p2r, p2id); + + let content = rvlg.revision(&tip).unwrap(); + + let hash = rvlg.check_hash(&content, &p1id, &p2id); + println!("{}", str::from_utf8(&content).unwrap()); + + assert_eq!(hash, rvlg.rev(&tip).unwrap().borrow().node_id()); + + let manifest = FlatManifest { + inner: Arc::new(RwLock::new(rvlg)), + }; + + manifest.build_file_rev_mapping(&2957); + } +} diff --git a/rust/hgstorage/src/matcher.rs b/rust/hgstorage/src/matcher.rs new file mode 100644 --- /dev/null +++ b/rust/hgstorage/src/matcher.rs @@ -0,0 +1,246 @@ +use std::fs; +use std::path::PathBuf; +use std::vec::Vec; +use std::io::BufReader; +use std::io::BufRead; + +use regex::Regex; +use regex::{escape, RegexSet}; + +pub fn glob_to_re(glob: &str) -> String { + let mut res = String::with_capacity(glob.len()); + + let pat: Vec = glob.chars().collect(); + let mut i = 0; + let n = pat.len(); + + let mut group = 0; + + while i < n { + let c = pat[i]; + i += 1; + + match c { + '*' => { + if i < n && pat[i] == '*' { + i += 1; + if i < n && pat[i] == '/' { + i += 1; + res.push_str("(?:.*/)?"); + } else { + res.push_str(".*"); + } + } else { + res.push_str("[^/]*"); + } + } + '?' => { + res.push('.'); + } + '[' => { + let mut j = i; + if j < n && (pat[j] == '!' || pat[j] == ']') { + j += 1; + } + while j < n && pat[j] != ']' { + j += 1; + } + if j >= n { + res.push_str("\\["); + } else { + let mut stuff = String::new(); + + if pat[i] == '!' { + stuff.push('^'); + i += 1; + } else if pat[i] == '^' { + stuff.push('\\'); + stuff.push('^'); + i += 1; + } + + for cc in pat[i..j].iter().cloned() { + stuff.push(cc); + if cc == '\\' { + stuff.push('\\'); + } + } + i = j + 1; + + res.push('['); + res.push_str(stuff.as_str()); + res.push(']'); + } + } + '{' => { + group += 1; + res.push_str("(?:"); + } + '}' if group != 0 => { + res.push(')'); + group -= 1; + } + ',' if group != 0 => { + res.push('|'); + } + '\\' => { + if i < n { + res.push_str(escape(pat[i].to_string().as_str()).as_str()); + i += 1; + } else { + res.push_str(escape(pat[i].to_string().as_str()).as_str()); + } + } + _ => { + res.push_str(escape(c.to_string().as_str()).as_str()); + } + } + } + + let res = if cfg!(target_family = "unix") { + res + } else { + res.replace("/", "\\\\") + }; + + res +} + +fn relglob(glob: &str) -> String { + let mut res = String::with_capacity(glob.len()); + //res.push_str("(?:|.*/)"); + res.push_str(glob_to_re(glob).as_str()); + //res.push_str("(?:/|$)"); + + res +} + +#[derive(Debug)] +pub struct Matcher { + pub inner: Option, +} + +impl Matcher { + pub fn new(hgignores: &[PathBuf]) -> Self { + let mut inner = Vec::::new(); + + for fname in hgignores { + if !fname.exists() { + continue; + } + + let file_buffer = BufReader::new(fs::File::open(fname).unwrap()); + + #[derive(PartialEq)] + enum State { + Glob, + Re, + } + + inner.push(relglob(".hg/*")); + + let mut cur_state = State::Re; + for line in file_buffer.lines() { + let line = match line { + Err(_) => break, + Ok(line) => line, + }; + + let line = line.trim(); + + if line.is_empty() { + continue; + } + + if line.starts_with("syntax:") { + let mut iter = line.split_whitespace(); + assert_eq!("syntax:", iter.next().unwrap()); + let pat = iter.next().unwrap(); + + cur_state = if pat == "glob" { + State::Glob + } else if pat == "regexp" { + State::Re + } else { + panic!("unsupported pattern {} in file {:?}", pat, fname); + } + } else { + match cur_state { + /*State::None => panic!( + "syntax error in {:?}, please use 'syntax: [glob|regexp]'", + fname + ),*/ + State::Glob => { + inner.push(relglob(line)); + } + State::Re => { + let check_support = Regex::new(line); + match check_support { + Ok(_) => inner.push(line.to_owned()), + Err(_) => {} + } + } + } + } + } + } + + return match RegexSet::new(inner) { + Ok(inner) => Self { inner: Some(inner) }, + Err(e) => panic!("error in building ignore {:?}", e), + }; + } + + /// rp: relative path, relative to the root of repository + pub fn check_path_ignored(&self, rel_path: &str) -> bool { + if let Some(ref m) = self.inner { + m.is_match(rel_path) + } else { + false + } + } +} + +#[cfg(test)] +mod test { + use std::env; + use std::path::Path; + use tempdir::TempDir; + use super::*; + use regex::escape; + + #[test] + fn test_hgstorage_ignore() -> () { + println!( + "current dir {}", + env::current_dir().unwrap().to_str().unwrap() + ); + let tmp_dir = TempDir::new("cffi").unwrap(); + + println!("tmp_dir {:?}", tmp_dir.path()); + + ::prepare_testing_repo(tmp_dir.path()); + + let ignore_file = tmp_dir.path().join("cffi/.hgignore"); + let mut m = Matcher::new(&[ignore_file]); + + assert!(!m.check_path_ignored("a.py")); + assert!(m.check_path_ignored("testing/__pycache__")); + assert!(m.check_path_ignored("test/dfsdf/a.egg-info")); + assert!(!m.check_path_ignored("a.egg-info.tmp")); + } + + #[test] + fn test_hgstorage_globre() -> () { + //assert_eq!(escape(r"/"), r"\/"); + assert_eq!(glob_to_re(r"?"), r"."); + assert_eq!(glob_to_re(r"*"), r"[^/]*"); + assert_eq!(glob_to_re(r"**"), r".*"); + assert_eq!(glob_to_re(r"**/a"), r"(?:.*/)?a"); + //assert_eq!(glob_to_re(r"a/**/b"), r"a\/(?:.*/)?b"); + assert_eq!(glob_to_re(r"a/**/b"), r"a/(?:.*/)?b"); + assert_eq!(glob_to_re(r"[a*?!^][^b][!c]"), r"[a*?!^][\^b][^c]"); + assert_eq!(glob_to_re(r"{a,b}"), r"(?:a|b)"); + assert_eq!(glob_to_re(r".\*\?"), r"\.\*\?"); + } +} diff --git a/rust/hgstorage/src/mpatch.rs b/rust/hgstorage/src/mpatch.rs new file mode 100644 --- /dev/null +++ b/rust/hgstorage/src/mpatch.rs @@ -0,0 +1,153 @@ +use std::io::prelude::*; +use std::io::{Cursor, Seek}; +use std::io::SeekFrom::Start; +use std::vec::Vec; +use std::mem::swap; +use std::iter::Extend; + +use byteorder::{BigEndian, ReadBytesExt}; + +#[derive(Debug, Default)] +pub struct PatchHeader { + /// the pos (offset of byte) where previous chunk ends + prev_chunk_ends: u32, + /// the pos where next chunk starts + next_chunk_starts: u32, + /// size of the current diff patch + diff_size: u32, +} + +#[derive(Clone, Debug)] +struct Patch { + len: u32, + offset: u32, +} + +impl Patch { + pub fn new(len: u32, ofs: u32) -> Self { + Patch { + len: len, + offset: ofs, + } + } +} + +fn pull_patch_into_queue(dst: &mut Vec, src: &mut Vec, l: u32) { + let mut l = l; + + while l > 0 { + let f = src.pop().unwrap(); + if f.len > l { + src.push(Patch::new(f.len - l, f.offset + l)); + dst.push(Patch::new(l, f.offset)); + return; + } + l -= f.len; + dst.push(f); + } +} + +fn move_patch_to_new_pos(m: &mut Cursor>, dest: u32, src: u32, count: u32) { + m.seek(Start(src as u64)).unwrap(); + let mut buf: Vec = Vec::new(); + buf.resize(count as usize, 0); + + m.read_exact(&mut buf[..]).unwrap(); + m.seek(Start(dest as u64)).unwrap(); + m.write(&buf[..]).unwrap(); +} + +fn collect_fragments_and_compact(m: &mut Cursor>, buf: u32, list: &Vec) -> Patch { + let start = buf; + let mut buf = buf; + + for &Patch { len: l, offset: p } in list.iter().rev() { + move_patch_to_new_pos(m, buf, p, l); + buf += l; + } + return Patch::new(buf - start, start); +} + +#[derive(Debug)] +pub struct PatchChain { + pub base: Vec, + pub bins: Vec>, +} + +pub fn patches(ptc: &PatchChain) -> Vec { + let &PatchChain { + base: ref a, + ref bins, + } = ptc; + + if bins.len() == 0 { + return a.iter().cloned().collect(); + } + + let plens: Vec = bins.iter().map(|it| it.len() as u32).collect(); + let pl: u32 = plens.iter().sum(); + let bl: u32 = a.len() as u32 + pl; + let tl: u32 = bl + bl + pl; + + if tl == 0 { + return a.iter().cloned().collect(); + } + + let (mut b1, mut b2) = (0_u32, bl); + + let mut m_buf = Vec::::new(); + m_buf.resize(tl as usize, 0); + + let mut m = Cursor::new(m_buf); + + m.write(&a[..]).unwrap(); + + let mut frags = vec![Patch::new(a.len() as u32, b1 as u32)]; + + let mut pos: u32 = b2 + bl; + m.seek(Start(pos as u64)).unwrap(); + + for p in bins.iter() { + m.write(p).unwrap(); + } + + for plen in plens.iter() { + if frags.len() > 128 { + swap(&mut b1, &mut b2); + frags = vec![collect_fragments_and_compact(&mut m, b1, &mut frags)]; + } + + let mut new: Vec = Vec::new(); + let end = pos + plen; + let mut last = 0; + + while pos < end { + m.seek(Start(pos as u64)).unwrap(); + + let p1 = m.read_u32::().unwrap(); + let p2 = m.read_u32::().unwrap(); + let l = m.read_u32::().unwrap(); + + pull_patch_into_queue(&mut new, &mut frags, p1 - last); + assert!(!frags.is_empty()); + pull_patch_into_queue(&mut vec![], &mut frags, p2 - p1); + + new.push(Patch::new(l, pos + 12)); + pos += l + 12; + last = p2; + } + + frags.extend(new.iter().rev().cloned()); + } + + let t = collect_fragments_and_compact(&mut m, b2, &mut frags); + + m.seek(Start(t.offset as u64)).unwrap(); + + let mut res: Vec = Vec::new(); + res.resize(t.len as usize, 0); + + m.read_exact(&mut res[..]).unwrap(); + + return res; +} diff --git a/rust/hgstorage/src/path_encoding.rs b/rust/hgstorage/src/path_encoding.rs new file mode 100644 --- /dev/null +++ b/rust/hgstorage/src/path_encoding.rs @@ -0,0 +1,206 @@ +use std::path::{Path, PathBuf}; +use std::str; + +const HEX_DIGIT: [u8; 16] = *b"0123456789abcdef"; + +fn hex_encode(c: usize) -> (char, char) { + (char::from(HEX_DIGIT[c >> 4]), char::from(HEX_DIGIT[c & 15])) +} + +/// according to hg-python implementation, escape() will escape u8 as "~\x\x" format +fn escape(buf: &mut String, c: u8) { + buf.push('~'); + let res = hex_encode(c as usize); + buf.push(res.0); + buf.push(res.1); +} + +/// hg stores files not folders, for each file, it is stored as file_name.ext.i for index +/// and optional file_name.ext.d for data. If there is a file "aaa" and folder "aaa.i", +/// then there is a conflict in hg storage. Thus this function appends extra ".hg" extension +/// to these dirs. +fn encode_dir(p: String) -> String { + let mut p = p; + + if p.ends_with(".i") || p.ends_with(".d") { + p.push_str(".hg"); + } else if p.ends_with(".hg") { + p.push_str(".hg"); + } + + return p; +} + +pub fn encode_special_letters(path: &str) -> String { + let mut out = String::with_capacity(path.len()); + + for c in path.bytes() { + match c { + b'A'...b'Z' => { + out.push('_'); + out.push(char::from(c.to_ascii_lowercase())); + } + b'\\' | b':' | b'*' | b'?' | b'"' | b'<' | b'>' | b'|' => { + escape(&mut out, c); + } + // The rest of the printable range. + b'_' => { + out.push_str("__"); + } + b' '...b'~' => { + out.push(char::from(c)); + } + _ => { + escape(&mut out, c); + } + } + } + + out +} + +pub fn aux_encode(input_path: &str) -> String { + let mut path = String::with_capacity(input_path.len()); + + let bytes = input_path.as_bytes(); + + if bytes[0] == b'.' || bytes[0] == b' ' { + escape(&mut path, bytes[0].clone()); + let mut iter = input_path.chars(); + iter.next(); + path.push_str(iter.as_str()); + } else { + let dotpos = { + let mut i = 0; + loop { + if i < bytes.len() && bytes[i] == b'.' { + break i as i32; + } else if i >= bytes.len() { + break -1 as i32; + } + + i += 1; + } + }; + + let l = if dotpos == -1 { + bytes.len() + } else { + dotpos as usize + }; + + let mut is_aux = false; + let mut cursor: usize; + + if l == 3 { + let key = &bytes[..3]; + if key == b"aux" || key == b"con" || key == b"prn" || key == b"nul" { + bytes[..2] + .iter() + .for_each(|b: &u8| path.push(char::from(*b))); + escape(&mut path, bytes[2]); + is_aux = true; + } + } else if l == 4 { + let key = &bytes[..3]; + if (key == b"com" || key == b"lpt") && b'1' <= bytes[3] && bytes[3] <= b'9' { + bytes[..2] + .iter() + .for_each(|b: &u8| path.push(char::from(*b))); + escape(&mut path, bytes[2]); + path.push(char::from(bytes[3])); + is_aux = true; + } + } + + if !is_aux { + path.push_str(str::from_utf8(&bytes[..l]).unwrap()); + } + + cursor = l; + + if cursor < bytes.len() - 1 { + path.push_str(str::from_utf8(&bytes[cursor..(bytes.len() - 1)]).unwrap()); + cursor = bytes.len() - 1; + } + + if cursor == bytes.len() - 1 { + if bytes[cursor] == b'.' || bytes[cursor] == b' ' { + escape(&mut path, bytes[cursor]); + } else { + path.push(char::from(bytes[cursor])); + } + } + } + + return path; +} + +/// According to discussions in [D2057](https://phab.mercurial-scm.org/D2057#44269), +/// hg uses raw byte strings internally. Raw byte string here means either utf8 (always used in +/// *nix) or MBCS (windows with *A() functions). +/// The hg store path encoding assumes byte strings underline, e.g. file name "测试.txt" is +/// encoded as "~e6~b5~8b~e8~af~95.txt.i" underline, and the encoding is byte by byte from utf8 bytes +/// Thus the following codes should directly focus on Vec internally, and when IO invoked, +/// on windows platform, [crate local-encoding](https://crates.io/crates/local-encoding) can help call +/// windows to convert MBCS to Unicode. +pub fn encode_path(p: &Path) -> PathBuf { + let mut res = PathBuf::new(); + + let mut leaves = p.iter().map(|s| s.to_str().unwrap().to_string()).peekable(); + + let mut component = leaves + .next() + .expect("the path should have at least one component"); + + while !leaves.peek().is_none() { + let leaf = component; + let leaf = encode_dir(leaf); + let leaf = encode_special_letters(leaf.as_str()); + let leaf = aux_encode(leaf.as_str()); + + res.push(leaf); + + component = leaves + .next() + .expect("previous peak guarantees the next exists"); + } + + let leaf = component; + let leaf = encode_special_letters(leaf.as_str()); + let mut leaf = aux_encode(leaf.as_str()); + leaf.push_str(".i"); + res.push(leaf); + + return res; +} + +#[cfg(test)] +mod test { + use std::path::Path; + use super::*; + + #[test] + fn test_hgstorage_path_encoding() -> () { + assert_eq!( + "~2efoo/au~78.txt/txt.aux/co~6e/pr~6e/nu~6c/foo~2e.i", + encode_path(&Path::new(".foo/aux.txt/txt.aux/con/prn/nul/foo.")) + .to_str() + .unwrap() + ); + + assert_eq!( + "foo.i.hg/bar.d.hg/bla.hg.hg/hi~3aworld~3f/_h_e_l_l_o.i", + encode_path(&Path::new("foo.i/bar.d/bla.hg/hi:world?/HELLO")) + .to_str() + .unwrap() + ); + + assert_eq!( + "~2ecom1com2/lp~749.lpt4.lpt1/conprn/com0/lpt0/foo~2e.i", + encode_path(&Path::new(".com1com2/lpt9.lpt4.lpt1/conprn/com0/lpt0/foo.")) + .to_str() + .unwrap() + ); + } +} diff --git a/rust/hgstorage/src/repository.rs b/rust/hgstorage/src/repository.rs new file mode 100644 --- /dev/null +++ b/rust/hgstorage/src/repository.rs @@ -0,0 +1,5 @@ +use dirstate::CurrentState; + +pub trait Repository { + fn status(&self) -> CurrentState; +} diff --git a/rust/hgstorage/src/revlog.rs b/rust/hgstorage/src/revlog.rs new file mode 100644 --- /dev/null +++ b/rust/hgstorage/src/revlog.rs @@ -0,0 +1,82 @@ +use std::path::Path; +use std::cell::RefCell; +use std::fmt; +use std::sync::{Arc, RwLock}; + +#[derive(Debug, Default, Clone, PartialEq, Eq, Hash)] +pub struct NodeId { + pub node: [u8; 20], +} + +lazy_static! { + pub static ref NULL_ID: NodeId = { NodeId::new([0_u8; 20]) }; +} + +impl NodeId { + pub fn new(content: [u8; 20]) -> Self { + assert_eq!(content.len(), 20); + return Self { node: content }; + } + + pub fn new_from_bytes(bytes: &[u8]) -> Self { + assert_eq!(bytes.len(), 20); + + let mut content = [0_u8; 20]; + + content.copy_from_slice(bytes); + + return Self { node: content }; + } + + pub fn null_id() -> Self { + NULL_ID.clone() + } + + pub fn is_valid(&self) -> bool { + self.node.len() <= 20 + } + + pub fn hex_len() -> usize { + 40 + } + pub fn bin_len() -> usize { + 20 + } +} + +impl fmt::Display for NodeId { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + for &byte in self.node.iter() { + write!(f, "{:02X}", byte).expect("Unable to write"); + } + return Ok(()); + } +} + +#[derive(Debug)] +pub enum NodeLookupError { + KeyNotFound, + AmbiguousKeys, +} + +pub trait RevEntry { + fn p1(&self) -> u32; + fn p2(&self) -> u32; + fn node_id(&self) -> NodeId; +} + +pub trait Revlog: Send + Sync { + fn node_id_to_rev(&self, id: &NodeId) -> Result; + fn rev(&self, id: &i32) -> Result<&RefCell, NodeLookupError>; + fn delta_chain(&self, id: &i32) -> Result, NodeLookupError>; + fn revision(&self, id: &i32) -> Result, NodeLookupError>; + fn tip(&self) -> usize; + fn check_hash(&self, text: &[u8], p1: &NodeId, p2: &NodeId) -> NodeId; + fn p1(&self, rev: &i32) -> i32; + fn p1_nodeid(&self, rev: &i32) -> NodeId; + fn p2(&self, rev: &i32) -> i32; + fn p2_nodeid(&self, rev: &i32) -> NodeId; + fn node_id(&self, rev: &i32) -> NodeId; + + fn create(&self, index_file: &Path) -> Arc>; +} diff --git a/rust/hgstorage/src/revlog_v1.rs b/rust/hgstorage/src/revlog_v1.rs new file mode 100644 --- /dev/null +++ b/rust/hgstorage/src/revlog_v1.rs @@ -0,0 +1,435 @@ +use std::path::{Path, PathBuf}; +use std::io; +use std::io::{BufReader, Read, Seek, SeekFrom}; +use std::fs; +use std::cell::RefCell; +use std::sync::{Arc, RwLock}; +use std::collections::HashMap as Map; + +use byteorder::{BigEndian, ReadBytesExt}; +use flate2::read::ZlibDecoder; +use sha1::Sha1 as Sha; + +use revlog::*; +use revlog::NodeLookupError; +use revlog::NodeLookupError::*; +use mpatch::{patches, PatchChain}; + +pub const FLAG_INLINE_DATA: u32 = (1 << 16) as u32; +pub const FLAG_GENERALDELTA: u32 = (1 << 17) as u32; + +#[derive(Debug, Default)] +pub struct Entry { + offset_w_flags: u64, + len_compressed: u32, + len_uncompressesd: u32, + base_rev: u32, + link_rev: u32, + parent1_rev: u32, + parent2_rev: u32, + nodeid: [u8; 20], + padding: [u8; 12], +} + +pub fn read_entry(rdr: &mut R) -> io::Result { + let offset_w_flags = rdr.read_u64::()?; + let len_compressed = rdr.read_u32::()?; + let len_uncompressesd = rdr.read_u32::()?; + let base_rev = rdr.read_u32::()?; + let link_rev = rdr.read_u32::()?; + let parent1_rev = rdr.read_u32::()?; + let parent2_rev = rdr.read_u32::()?; + let mut nodeid = [0_u8; 20]; + rdr.read_exact(&mut nodeid)?; + let mut padding = [0_u8; 12]; + rdr.read_exact(&mut padding)?; + + Ok(Entry { + offset_w_flags, + len_compressed, + len_uncompressesd, + base_rev, + link_rev, + parent1_rev, + parent2_rev, + nodeid, + padding, + }) +} + +impl Entry { + pub fn packed_size() -> u32 { + 8 + 4 * 6 + 20 + 12 + } + + fn offset(&self) -> u64 { + self.offset_w_flags >> 16 + } +} + +pub enum DFileFlag { + Inline, + Separated(PathBuf), +} + +use self::DFileFlag::*; + +pub enum CachedEntry { + Offset(u32), + Cached(Box), +} + +use self::CachedEntry::*; + +impl RevEntry for CachedEntry { + fn p1(&self) -> u32 { + return match self { + &Offset(_) => panic!("this rev should have been cached."), + &Cached(ref ent) => ent.parent1_rev, + }; + } + + fn p2(&self) -> u32 { + return match self { + &Offset(_) => panic!("this rev should have been cached."), + &Cached(ref ent) => ent.parent2_rev, + }; + } + + fn node_id(&self) -> NodeId { + return match self { + &Offset(_) => panic!("this rev should have been cached."), + &Cached(ref ent) => NodeId::new(ent.nodeid.clone()), + }; + } +} + +pub struct RevlogIO { + index_file: PathBuf, + dflag: DFileFlag, + other_flags: u32, + node2rev: Map, + revs: Vec>, +} + +unsafe impl Send for RevlogIO {} +unsafe impl Sync for RevlogIO {} + +/// currently asssume RevlogNG v1 format, with parent-delta, without inline +impl RevlogIO { + pub fn get_factory() -> Self { + Self { + index_file: PathBuf::new(), + dflag: DFileFlag::Inline, + other_flags: 0, + node2rev: Map::new(), + revs: Vec::>::new(), + } + } + + pub fn new(index_file: &Path) -> Self { + println!("create revlog for {:?}", index_file); + if !index_file.exists() { + panic!("index file must exist: {:?}", index_file); + } + + let mut f = BufReader::new(fs::File::open(index_file).unwrap()); + let flag = f.read_u32::().unwrap(); + f.seek(SeekFrom::Start(0)).unwrap(); + + let dflag = if (flag & FLAG_INLINE_DATA) > 0 { + Inline + } else { + let data_file = index_file.with_extension("d"); + assert!(data_file.exists()); + Separated(data_file) + }; + + let other_flags = flag; + + let max_cap = (index_file.metadata().unwrap().len() as u32) / Entry::packed_size(); + + let mut node2rev = Map::new(); + let mut revs = Vec::with_capacity(max_cap as usize); + + //let mut ofs: u32 = 0; + + loop { + match read_entry(&mut f) { + Ok(hd) => { + let tip = revs.len() as i32; + + let id = NodeId::new(hd.nodeid.clone()); + + node2rev.insert(id, tip); + revs.push(RefCell::new(Cached(Box::new(hd)))); + + /*ofs += match dflag { + Inline => { + f.seek(SeekFrom::Current(hd.len_compressed as i64)).unwrap(); + hd.len_compressed + Entry::packed_size() + } + Separated(_) => Entry::packed_size(), + };*/ + } + Err(_) => break, + } + } + + return Self { + index_file: index_file.to_path_buf(), + dflag, + other_flags, + node2rev, + revs, + }; + } + + /// outer functions, which serve as interface, should check argument validity, + /// the internal calls use execute without question and thus panic on errors + fn make_sure_cached(&self, r: &usize) { + let r = *r; + + let ofs_opt = match *self.revs[r].borrow() { + Offset(ofs) => Some(ofs), + _ => None, + }; + + if let Some(ofs) = ofs_opt { + let mut f = fs::File::open(&self.index_file).unwrap(); + f.seek(SeekFrom::Start(ofs as u64)).unwrap(); + + let ent: Entry = read_entry(&mut f).unwrap(); + self.revs[r].replace(Cached(Box::new(ent))); + } else { + } + } + + fn prepare_id(&self, r: &i32) -> Result { + let len = self.revs.len() as i32; + + if *r < -len || *r >= len { + return Err(KeyNotFound); + } + + let r = if *r < 0 { + (len + *r) as usize + } else { + *r as usize + }; + + self.make_sure_cached(&r); + + return Ok(r); + } + + fn is_general_delta(&self) -> bool { + (self.other_flags & FLAG_GENERALDELTA) > 0 + } + + fn get_content(&self, f: &mut io::BufReader, r: &usize) -> Option> { + let r = *r; + + self.make_sure_cached(&r); + + if let Cached(ref hd) = *self.revs[r].borrow() { + let req_len = hd.len_compressed as usize; + + if req_len == 0 { + return None; + } + + let mut buf: Vec = Vec::new(); + buf.resize(req_len, 0); + + let ofs: u64 = if r == 0 { + 0_u64 + Entry::packed_size() as u64 + } else { + match self.dflag { + Inline => hd.offset() + (r * (Entry::packed_size() as usize)) as u64, + Separated(_) => hd.offset(), + } + }; + + f.seek(SeekFrom::Start(ofs)).unwrap(); + + f.read(&mut buf[..]).unwrap(); + + let flag_byte = buf[0]; + + match flag_byte { + b'x' => { + let mut dec = ZlibDecoder::new(&buf[..]); + + let mut out_buf: Vec = Vec::new(); + //out_buf.resize(hd.len_compressed as usize, 0); + dec.read_to_end(&mut out_buf).unwrap(); + + return Some(out_buf); + } + b'\0' => { + return Some(buf); + } + b'u' => { + return Some(buf[1..].to_vec()); + } + _ => { + panic!( + "decoding pattern not supported yet. fail to decode {}, first byte {}", + r, flag_byte + ); + } + } + } else { + panic!("read delta content failed."); + } + } + + fn get_all_bins(&self, rs: &[usize]) -> PatchChain { + assert_ne!(rs.len(), 0); + + let mut fhandle = BufReader::new(match self.dflag { + Inline => fs::File::open(self.index_file.as_path()).unwrap(), + Separated(ref dfile) => fs::File::open(dfile).unwrap(), + }); + + let mut it = rs.iter().rev(); + + let base_r = it.next().unwrap(); + let base = self.get_content(&mut fhandle, base_r).unwrap(); + + let mut bins: Vec> = Vec::with_capacity(rs.len() - 1); + + while let Some(ref chld_r) = it.next() { + if let Some(bin) = self.get_content(&mut fhandle, chld_r) { + bins.push(bin); + } else { + } + } + + return PatchChain { base, bins }; + } +} + +impl Revlog for RevlogIO { + fn node_id_to_rev(&self, id: &NodeId) -> Result { + let rev = { + if let Some(st) = self.node2rev.get(id) { + Ok(st) + } else { + Err(KeyNotFound) + } + }; + + match rev { + Ok(r) => Ok(*r), + Err(err) => Err(err), + } + } + + fn rev(&self, r: &i32) -> Result<&RefCell, NodeLookupError> { + let r = self.prepare_id(r).unwrap(); + + Ok(&self.revs[r]) + } + + fn delta_chain(&self, r: &i32) -> Result, NodeLookupError> { + let mut r = self.prepare_id(r).unwrap(); + + let mut res = vec![r]; + + loop { + if let Cached(ref rev) = *self.revs[r].borrow() { + if (r == (rev.base_rev as usize)) || (r == 0) { + break; + } + + r = if self.is_general_delta() { + rev.base_rev as usize + } else { + r - 1 + }; + res.push(r); + + self.make_sure_cached(&r); + } else { + panic!("the rev must has been cached."); + } + } + + return Ok(res); + } + + fn revision(&self, id: &i32) -> Result, NodeLookupError> { + if let Ok(chn) = self.delta_chain(id) { + let ptc = self.get_all_bins(&chn); + let res = patches(&ptc); + return Ok(res); + } else { + return Err(KeyNotFound); + } + } + + fn tip(&self) -> usize { + return self.revs.len() - 1; + } + + fn check_hash(&self, text: &[u8], p1: &NodeId, p2: &NodeId) -> NodeId { + let mut s = Sha::new(); + + if p2.node == NULL_ID.node { + s.update(&NULL_ID.node); + s.update(&p1.node); + } else { + let (a, b) = if p1.node < p2.node { + (p1, p2) + } else { + (p2, p1) + }; + s.update(&a.node); + s.update(&b.node); + } + s.update(text); + return NodeId::new(s.digest().bytes()); + } + + fn p1(&self, rev: &i32) -> i32 { + return self.rev(rev).unwrap().borrow().p1() as i32; + } + + fn p1_nodeid(&self, rev: &i32) -> NodeId { + let prev = self.rev(rev).unwrap().borrow().p1() as i32; + + if prev == -1 { + return NULL_ID.clone(); + } else { + return self.rev(&prev).unwrap().borrow().node_id().clone(); + } + } + + fn p2(&self, rev: &i32) -> i32 { + return self.rev(rev).unwrap().borrow().p2() as i32; + } + + fn p2_nodeid(&self, rev: &i32) -> NodeId { + let prev = self.rev(rev).unwrap().borrow().p2() as i32; + + if prev == -1 { + return NULL_ID.clone(); + } else { + return self.rev(&prev).unwrap().borrow().node_id().clone(); + } + } + + fn node_id(&self, rev: &i32) -> NodeId { + if *rev == -1 { + return NULL_ID.clone(); + } else { + return self.rev(rev).unwrap().borrow().node_id().clone(); + } + } + + fn create(&self, index_file: &Path) -> Arc> { + return Arc::new(RwLock::new(RevlogIO::new(index_file))); + } +} diff --git a/rust/hgstorage/src/working_context.rs b/rust/hgstorage/src/working_context.rs new file mode 100644 --- /dev/null +++ b/rust/hgstorage/src/working_context.rs @@ -0,0 +1,114 @@ +use std::path::PathBuf; +use std::io::prelude::*; +use std::fs; +use std::collections::HashMap; +use std::collections::HashSet as Set; +use std::sync::{Arc, Mutex, RwLock}; + +use threadpool::ThreadPool; +use num_cpus; + +use dirstate::{CurrentState, DirState}; +use local_repo::LocalRepo; +use manifest::{FlatManifest, ManifestEntry}; +use changelog::ChangeLog; + +pub struct WorkCtx { + pub dirstate: Arc>, + pub file_revs: HashMap, +} + +impl WorkCtx { + pub fn new( + dot_hg_path: Arc, + manifest: Arc, + changelog: Arc, + ) -> Self { + let dirstate = match DirState::new(dot_hg_path.join("dirstate")) { + Some(dir_state) => dir_state, + None => { + unimplemented!("creating dirstate is not supported yet."); + } + }; + + let manifest_id = changelog.get_commit_info(&dirstate.p1); + + let rev = manifest + .inner + .read() + .unwrap() + .node_id_to_rev(&manifest_id.manifest_id) + .unwrap(); + + let file_revs = manifest.build_file_rev_mapping(&rev); + + let dirstate = Arc::new(RwLock::new(dirstate)); + + Self { + dirstate, + file_revs, + } + } + + pub fn status(&self, repo: &LocalRepo) -> CurrentState { + let mut state = self.dirstate + .write() + .unwrap() + .walk_dir(repo.repo_root.as_path(), &repo.matcher) + .unwrap(); + + if !state.lookup.is_empty() { + let ncpus = num_cpus::get(); + + let nworkers = if state.lookup.len() < ncpus { + state.lookup.len() + } else { + ncpus + }; + + let pool = ThreadPool::new(nworkers); + + let clean = Arc::new(Mutex::new(Set::new())); + let modified = Arc::new(Mutex::new(Set::new())); + + for f in state.lookup.drain() { + let rl = repo.get_filelog(f.as_path()); + let fl = Arc::new(repo.repo_root.join(f.as_path())); + + let (id, p1, p2) = { + let id = &self.file_revs[f.as_path()].id; + let gd = rl.read().unwrap(); + let rev = gd.node_id_to_rev(id).unwrap(); + + let p1 = gd.p1_nodeid(&rev); + let p2 = gd.p2_nodeid(&rev); + (id.clone(), p1, p2) + }; + + let clean = clean.clone(); + let modified = modified.clone(); + + pool.execute(move || { + let mut wfile = fs::File::open(fl.as_path()).unwrap(); + let mut content = Vec::::new(); + wfile.read_to_end(&mut content).unwrap(); + if rl.read().unwrap().check_hash(&content, &p1, &p2) == id { + clean.lock().unwrap().insert(f); + } else { + modified.lock().unwrap().insert(f); + } + }); + } + + pool.join(); + assert_eq!(pool.panic_count(), 0); + + let mut gd = modified.lock().unwrap(); + state.modified.extend(gd.drain()); + let mut gd = clean.lock().unwrap(); + state.clean.extend(gd.drain()); + } + + return state; + } +}