gix-traverse-0.47.0/.cargo_vcs_info.json0000644000000001520000000000100135420ustar { "git": { "sha1": "5a919c48393020d47c7034946108577dd213b80a" }, "path_in_vcs": "gix-traverse" }gix-traverse-0.47.0/Cargo.lock0000644000000433600000000000100115250ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "autocfg" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" [[package]] name = "bitflags" version = "2.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967" [[package]] name = "block-buffer" version = "0.10.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" dependencies = [ "generic-array", ] [[package]] name = "bstr" version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "234113d19d0d7d613b40e86fb654acf958910802bcceab913a4f9e7cda03b1a4" dependencies = [ "memchr", "regex-automata", "serde", ] [[package]] name = "byteorder" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" [[package]] name = "cfg-if" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268" [[package]] name = "cpufeatures" version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" dependencies = [ "libc", ] [[package]] name = "crypto-common" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3" dependencies = [ "generic-array", "typenum", ] [[package]] name = "digest" version = "0.10.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" dependencies = [ "block-buffer", "crypto-common", ] [[package]] name = "faster-hex" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7223ae2d2f179b803433d9c830478527e92b8117eab39460edae7f1614d9fb73" dependencies = [ "heapless", "serde", ] [[package]] name = "fastrand" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" [[package]] name = "generic-array" version = "0.14.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", ] [[package]] name = "gix-actor" version = "0.35.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "58ebbb8f41071c7cf318a0b1db667c34e1df49db7bf387d282a4e61a3b97882c" dependencies = [ "bstr", "gix-date", "gix-utils", "itoa", "thiserror", "winnow", ] [[package]] name = "gix-chunk" version = "0.4.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b1f1d8764958699dc764e3f727cef280ff4d1bd92c107bbf8acd85b30c1bd6f" dependencies = [ "thiserror", ] [[package]] name = "gix-commitgraph" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6bb23121e952f43a5b07e3e80890336cb847297467a410475036242732980d06" dependencies = [ "bstr", "gix-chunk", "gix-hash", "memmap2", "thiserror", ] [[package]] name = "gix-date" version = "0.10.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d7235bdf4d9d54a6901928e3a37f91c16f419e6957f520ed929c3d292b84226e" dependencies = [ "bstr", "itoa", "jiff", "smallvec", "thiserror", ] [[package]] name = "gix-features" version = "0.43.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a92748623c201568785ee69a561f4eec06f745b4fac67dab1d44ca9891a57ee" dependencies = [ "gix-trace", "libc", "prodash", ] [[package]] name = "gix-hash" version = "0.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "251fad79796a731a2a7664d9ea95ee29a9e99474de2769e152238d4fdb69d50e" dependencies = [ "faster-hex", "gix-features", "sha1-checked", "thiserror", ] [[package]] name = "gix-hashtable" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c35300b54896153e55d53f4180460931ccd69b7e8d2f6b9d6401122cdedc4f07" dependencies = [ "gix-hash", "hashbrown", "parking_lot", ] [[package]] name = "gix-object" version = "0.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49664e3e212bc34f7060f5738ce7022247e4afd959b68a4f666b1fd29c00b23c" dependencies = [ "bstr", "gix-actor", "gix-date", "gix-features", "gix-hash", "gix-hashtable", "gix-path", "gix-utils", "gix-validate", "itoa", "smallvec", "thiserror", "winnow", ] [[package]] name = "gix-path" version = "0.10.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c6279d323d925ad4790602105ae27df4b915e7a7d81e4cdba2603121c03ad111" dependencies = [ "bstr", "gix-trace", "gix-validate", "home", "once_cell", "thiserror", ] [[package]] name = "gix-revwalk" version = "0.21.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06e74f91709729e099af6721bd0fa7d62f243f2005085152301ca5cdd86ec02c" dependencies = [ "gix-commitgraph", "gix-date", "gix-hash", "gix-hashtable", "gix-object", "smallvec", "thiserror", ] [[package]] name = "gix-trace" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2ccaf54b0b1743a695b482ca0ab9d7603744d8d10b2e5d1a332fef337bee658" [[package]] name = "gix-traverse" version = "0.47.0" dependencies = [ "bitflags", "gix-commitgraph", "gix-date", "gix-hash", "gix-hashtable", "gix-object", "gix-revwalk", "smallvec", "thiserror", ] [[package]] name = "gix-utils" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5351af2b172caf41a3728eb4455326d84e0d70fe26fc4de74ab0bd37df4191c5" dependencies = [ "fastrand", "unicode-normalization", ] [[package]] name = "gix-validate" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77b9e00cacde5b51388d28ed746c493b18a6add1f19b5e01d686b3b9ece66d4d" dependencies = [ "bstr", "thiserror", ] [[package]] name = "hash32" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" dependencies = [ "byteorder", ] [[package]] name = "hashbrown" version = "0.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5971ac85611da7067dbfcabef3c70ebb5606018acd9e2a3903a0da507521e0d5" [[package]] name = "heapless" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" dependencies = [ "hash32", "stable_deref_trait", ] [[package]] name = "home" version = "0.5.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "itoa" version = "1.0.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c" [[package]] name = "jiff" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "be1f93b8b1eb69c77f24bbb0afdf66f54b632ee39af40ca21c4365a1d7347e49" dependencies = [ "jiff-static", "jiff-tzdb-platform", "log", "portable-atomic", "portable-atomic-util", "serde", "windows-sys 0.52.0", ] [[package]] name = "jiff-static" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "03343451ff899767262ec32146f6d559dd759fdadf42ff0e227c7c48f72594b4" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "jiff-tzdb" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c1283705eb0a21404d2bfd6eef2a7593d240bc42a0bdb39db0ad6fa2ec026524" [[package]] name = "jiff-tzdb-platform" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "875a5a69ac2bab1a891711cf5eccbec1ce0341ea805560dcd90b7a2e925132e8" dependencies = [ "jiff-tzdb", ] [[package]] name = "libc" version = "0.2.174" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776" [[package]] name = "lock_api" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "96936507f153605bddfcda068dd804796c84324ed2510809e5b2a624c81da765" dependencies = [ "autocfg", "scopeguard", ] [[package]] name = "log" version = "0.4.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "memchr" version = "2.7.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0" [[package]] name = "memmap2" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fd3f7eed9d3848f8b98834af67102b720745c4ec028fcd0aa0239277e7de374f" dependencies = [ "libc", ] [[package]] name = "once_cell" version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "parking_lot" version = "0.12.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70d58bf43669b5795d1576d0641cfb6fbb2057bf629506267a92807158584a13" dependencies = [ "lock_api", "parking_lot_core", ] [[package]] name = "parking_lot_core" version = "0.9.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc838d2a56b5b1a6c25f55575dfc605fabb63bb2365f6c2353ef9159aa69e4a5" dependencies = [ "cfg-if", "libc", "redox_syscall", "smallvec", "windows-targets", ] [[package]] name = "portable-atomic" version = "1.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f84267b20a16ea918e43c6a88433c2d54fa145c92a811b5b047ccbe153674483" [[package]] name = "portable-atomic-util" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507" dependencies = [ "portable-atomic", ] [[package]] name = "proc-macro2" version = "1.0.95" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "02b3e5e68a3a1a02aad3ec490a98007cbc13c37cbe84a3cd7b8e406d76e7f778" dependencies = [ "unicode-ident", ] [[package]] name = "prodash" version = "30.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a6efc566849d3d9d737c5cb06cc50e48950ebe3d3f9d70631490fff3a07b139" dependencies = [ "parking_lot", ] [[package]] name = "quote" version = "1.0.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1885c039570dc00dcb4ff087a89e185fd56bae234ddc7f056a945bf36467248d" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d04b7d0ee6b4a0207a0a7adb104d23ecb0b47d6beae7152d0fa34b692b29fd6" dependencies = [ "bitflags", ] [[package]] name = "regex-automata" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" [[package]] name = "scopeguard" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" [[package]] name = "serde" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5f0e2c6ed6606019b4e29e69dbaba95b11854410e5347d525002456dbbb786b6" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.219" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b0276cf7f2c73365f7157c8123c21cd9a50fbbd844757af28ca1f5925fc2a00" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "sha1" version = "0.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3bf829a2d51ab4a5ddf1352d8470c140cadc8301b2ae1789db023f01cedd6ba" dependencies = [ "cfg-if", "cpufeatures", "digest", ] [[package]] name = "sha1-checked" version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89f599ac0c323ebb1c6082821a54962b839832b03984598375bff3975b804423" dependencies = [ "digest", "sha1", ] [[package]] name = "smallvec" version = "1.15.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "67b1b7a3b5fe4f1376887184045fcf45c69e92af734b7aaddc05fb777b6fbd03" [[package]] name = "stable_deref_trait" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" [[package]] name = "syn" version = "2.0.104" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b6f705963418cdb9927482fa304bc562ece2fdd4f616084c50b7023b435a40" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "thiserror" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567b8a2dae586314f7be2a752ec7474332959c6460e02bde30d702a66d488708" dependencies = [ "thiserror-impl", ] [[package]] name = "thiserror-impl" version = "2.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", "syn", ] [[package]] name = "tinyvec" version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09b3661f17e86524eccd4371ab0429194e0d7c008abb45f7a7495b1719463c71" dependencies = [ "tinyvec_macros", ] [[package]] name = "tinyvec_macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "typenum" version = "1.18.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f" [[package]] name = "unicode-ident" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512" [[package]] name = "unicode-normalization" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956" dependencies = [ "tinyvec", ] [[package]] name = "version_check" version = "0.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm", "windows_aarch64_msvc", "windows_i686_gnu", "windows_i686_gnullvm", "windows_i686_msvc", "windows_x86_64_gnu", "windows_x86_64_gnullvm", "windows_x86_64_msvc", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" [[package]] name = "windows_aarch64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" [[package]] name = "windows_i686_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" [[package]] name = "windows_i686_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" [[package]] name = "windows_i686_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" [[package]] name = "windows_x86_64_gnu" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" [[package]] name = "windows_x86_64_gnullvm" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winnow" version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "74c7b26e3480b707944fc872477815d29a8e429d2f93a1ce000f5fa84a15cbcd" dependencies = [ "memchr", ] gix-traverse-0.47.0/Cargo.toml0000644000000067730000000000100115570ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" rust-version = "1.70" name = "gix-traverse" version = "0.47.0" authors = ["Sebastian Thiel "] build = false include = [ "src/**/*", "LICENSE-*", ] autolib = false autobins = false autoexamples = false autotests = false autobenches = false description = "A crate of the gitoxide project" readme = false license = "MIT OR Apache-2.0" repository = "https://github.com/GitoxideLabs/gitoxide" [lib] name = "gix_traverse" path = "src/lib.rs" doctest = false [dependencies.bitflags] version = "2" [dependencies.gix-commitgraph] version = "^0.29.0" [dependencies.gix-date] version = "^0.10.3" [dependencies.gix-hash] version = "^0.19.0" [dependencies.gix-hashtable] version = "^0.9.0" [dependencies.gix-object] version = "^0.50.0" [dependencies.gix-revwalk] version = "^0.21.0" [dependencies.smallvec] version = "1.15.1" [dependencies.thiserror] version = "2.0.0" [lints.clippy] bool_to_int_with_if = "allow" borrow_as_ptr = "allow" cast_lossless = "allow" cast_possible_truncation = "allow" cast_possible_wrap = "allow" cast_precision_loss = "allow" cast_sign_loss = "allow" checked_conversions = "allow" copy_iterator = "allow" default_trait_access = "allow" doc_markdown = "allow" empty_docs = "allow" enum_glob_use = "allow" explicit_deref_methods = "allow" explicit_into_iter_loop = "allow" explicit_iter_loop = "allow" filter_map_next = "allow" fn_params_excessive_bools = "allow" from_iter_instead_of_collect = "allow" if_not_else = "allow" ignored_unit_patterns = "allow" implicit_clone = "allow" inconsistent_struct_constructor = "allow" inefficient_to_string = "allow" inline_always = "allow" items_after_statements = "allow" iter_not_returning_iterator = "allow" iter_without_into_iter = "allow" large_enum_variant = "allow" large_stack_arrays = "allow" manual_assert = "allow" manual_is_variant_and = "allow" manual_let_else = "allow" manual_string_new = "allow" many_single_char_names = "allow" match_bool = "allow" match_same_arms = "allow" match_wild_err_arm = "allow" match_wildcard_for_single_variants = "allow" missing_errors_doc = "allow" missing_panics_doc = "allow" module_name_repetitions = "allow" must_use_candidate = "allow" mut_mut = "allow" naive_bytecount = "allow" needless_continue = "allow" needless_for_each = "allow" needless_pass_by_value = "allow" needless_raw_string_hashes = "allow" no_effect_underscore_binding = "allow" option_option = "allow" range_plus_one = "allow" redundant_else = "allow" result_large_err = "allow" return_self_not_must_use = "allow" should_panic_without_expect = "allow" similar_names = "allow" single_match_else = "allow" stable_sort_primitive = "allow" struct_excessive_bools = "allow" struct_field_names = "allow" too_long_first_doc_paragraph = "allow" too_many_lines = "allow" transmute_ptr_to_ptr = "allow" trivially_copy_pass_by_ref = "allow" unnecessary_join = "allow" unnecessary_wraps = "allow" unreadable_literal = "allow" unused_self = "allow" used_underscore_binding = "allow" wildcard_imports = "allow" [lints.clippy.pedantic] level = "warn" priority = -1 [lints.rust] gix-traverse-0.47.0/Cargo.toml.orig000064400000000000000000000014761046102023000152330ustar 00000000000000lints.workspace = true [package] name = "gix-traverse" version = "0.47.0" repository = "https://github.com/GitoxideLabs/gitoxide" license = "MIT OR Apache-2.0" description = "A crate of the gitoxide project" authors = ["Sebastian Thiel "] edition = "2021" include = ["src/**/*", "LICENSE-*"] rust-version = "1.70" autotests = false [lib] doctest = false [dependencies] gix-hash = { version = "^0.19.0", path = "../gix-hash" } gix-object = { version = "^0.50.0", path = "../gix-object" } gix-date = { version = "^0.10.3", path = "../gix-date" } gix-hashtable = { version = "^0.9.0", path = "../gix-hashtable" } gix-revwalk = { version = "^0.21.0", path = "../gix-revwalk" } gix-commitgraph = { version = "^0.29.0", path = "../gix-commitgraph" } smallvec = "1.15.1" thiserror = "2.0.0" bitflags = "2" gix-traverse-0.47.0/LICENSE-APACHE000064400000000000000000000236761046102023000142760ustar 00000000000000 Apache License Version 2.0, January 2004 http://www.apache.org/licenses/ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 1. Definitions. "License" shall mean the terms and conditions for use, reproduction, and distribution as defined by Sections 1 through 9 of this document. "Licensor" shall mean the copyright owner or entity authorized by the copyright owner that is granting the License. "Legal Entity" shall mean the union of the acting entity and all other entities that control, are controlled by, or are under common control with that entity. For the purposes of this definition, "control" means (i) the power, direct or indirect, to cause the direction or management of such entity, whether by contract or otherwise, or (ii) ownership of fifty percent (50%) or more of the outstanding shares, or (iii) beneficial ownership of such entity. "You" (or "Your") shall mean an individual or Legal Entity exercising permissions granted by this License. "Source" form shall mean the preferred form for making modifications, including but not limited to software source code, documentation source, and configuration files. "Object" form shall mean any form resulting from mechanical transformation or translation of a Source form, including but not limited to compiled object code, generated documentation, and conversions to other media types. "Work" shall mean the work of authorship, whether in Source or Object form, made available under the License, as indicated by a copyright notice that is included in or attached to the work (an example is provided in the Appendix below). "Derivative Works" shall mean any work, whether in Source or Object form, that is based on (or derived from) the Work and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. For the purposes of this License, Derivative Works shall not include works that remain separable from, or merely link (or bind by name) to the interfaces of, the Work and Derivative Works thereof. "Contribution" shall mean any work of authorship, including the original version of the Work and any modifications or additions to that Work or Derivative Works thereof, that is intentionally submitted to Licensor for inclusion in the Work by the copyright owner or by an individual or Legal Entity authorized to submit on behalf of the copyright owner. For the purposes of this definition, "submitted" means any form of electronic, verbal, or written communication sent to the Licensor or its representatives, including but not limited to communication on electronic mailing lists, source code control systems, and issue tracking systems that are managed by, or on behalf of, the Licensor for the purpose of discussing and improving the Work, but excluding communication that is conspicuously marked or otherwise designated in writing by the copyright owner as "Not a Contribution." "Contributor" shall mean Licensor and any individual or Legal Entity on behalf of whom a Contribution has been received by Licensor and subsequently incorporated within the Work. 2. Grant of Copyright License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, sublicense, and distribute the Work and such Derivative Works in Source or Object form. 3. Grant of Patent License. Subject to the terms and conditions of this License, each Contributor hereby grants to You a perpetual, worldwide, non-exclusive, no-charge, royalty-free, irrevocable (except as stated in this section) patent license to make, have made, use, offer to sell, sell, import, and otherwise transfer the Work, where such license applies only to those patent claims licensable by such Contributor that are necessarily infringed by their Contribution(s) alone or by combination of their Contribution(s) with the Work to which such Contribution(s) was submitted. If You institute patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Work or a Contribution incorporated within the Work constitutes direct or contributory patent infringement, then any patent licenses granted to You under this License for that Work shall terminate as of the date such litigation is filed. 4. Redistribution. You may reproduce and distribute copies of the Work or Derivative Works thereof in any medium, with or without modifications, and in Source or Object form, provided that You meet the following conditions: (a) You must give any other recipients of the Work or Derivative Works a copy of this License; and (b) You must cause any modified files to carry prominent notices stating that You changed the files; and (c) You must retain, in the Source form of any Derivative Works that You distribute, all copyright, patent, trademark, and attribution notices from the Source form of the Work, excluding those notices that do not pertain to any part of the Derivative Works; and (d) If the Work includes a "NOTICE" text file as part of its distribution, then any Derivative Works that You distribute must include a readable copy of the attribution notices contained within such NOTICE file, excluding those notices that do not pertain to any part of the Derivative Works, in at least one of the following places: within a NOTICE text file distributed as part of the Derivative Works; within the Source form or documentation, if provided along with the Derivative Works; or, within a display generated by the Derivative Works, if and wherever such third-party notices normally appear. The contents of the NOTICE file are for informational purposes only and do not modify the License. You may add Your own attribution notices within Derivative Works that You distribute, alongside or as an addendum to the NOTICE text from the Work, provided that such additional attribution notices cannot be construed as modifying the License. You may add Your own copyright statement to Your modifications and may provide additional or different license terms and conditions for use, reproduction, or distribution of Your modifications, or for any such Derivative Works as a whole, provided Your use, reproduction, and distribution of the Work otherwise complies with the conditions stated in this License. 5. Submission of Contributions. Unless You explicitly state otherwise, any Contribution intentionally submitted for inclusion in the Work by You to the Licensor shall be under the terms and conditions of this License, without any additional terms or conditions. Notwithstanding the above, nothing herein shall supersede or modify the terms of any separate license agreement you may have executed with Licensor regarding such Contributions. 6. Trademarks. This License does not grant permission to use the trade names, trademarks, service marks, or product names of the Licensor, except as required for reasonable and customary use in describing the origin of the Work and reproducing the content of the NOTICE file. 7. Disclaimer of Warranty. Unless required by applicable law or agreed to in writing, Licensor provides the Work (and each Contributor provides its Contributions) on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied, including, without limitation, any warranties or conditions of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A PARTICULAR PURPOSE. You are solely responsible for determining the appropriateness of using or redistributing the Work and assume any risks associated with Your exercise of permissions under this License. 8. Limitation of Liability. In no event and under no legal theory, whether in tort (including negligence), contract, or otherwise, unless required by applicable law (such as deliberate and grossly negligent acts) or agreed to in writing, shall any Contributor be liable to You for damages, including any direct, indirect, special, incidental, or consequential damages of any character arising as a result of this License or out of the use or inability to use the Work (including but not limited to damages for loss of goodwill, work stoppage, computer failure or malfunction, or any and all other commercial damages or losses), even if such Contributor has been advised of the possibility of such damages. 9. Accepting Warranty or Additional Liability. While redistributing the Work or Derivative Works thereof, You may choose to offer, and charge a fee for, acceptance of support, warranty, indemnity, or other liability obligations and/or rights consistent with this License. However, in accepting such obligations, You may act only on Your own behalf and on Your sole responsibility, not on behalf of any other Contributor, and only if You agree to indemnify, defend, and hold each Contributor harmless for any liability incurred by, or claims asserted against, such Contributor by reason of your accepting any such warranty or additional liability. END OF TERMS AND CONDITIONS gix-traverse-0.47.0/LICENSE-MIT000064400000000000000000000017771046102023000140040ustar 00000000000000Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. gix-traverse-0.47.0/src/commit/mod.rs000064400000000000000000000104511046102023000155410ustar 00000000000000//! Provide multiple traversal implementations with different performance envelopes. //! //! Use [`Simple`] for fast walks that maintain minimal state, or [`Topo`] for a more elaborate traversal. use gix_hash::ObjectId; use gix_object::FindExt; use gix_revwalk::{graph::IdMap, PriorityQueue}; use smallvec::SmallVec; /// A fast iterator over the ancestors of one or more starting commits. pub struct Simple { objects: Find, cache: Option, predicate: Predicate, state: simple::State, parents: Parents, sorting: simple::Sorting, } /// Simple ancestors traversal, without the need to keep track of graph-state. pub mod simple; /// A commit walker that walks in topographical order, like `git rev-list /// --topo-order` or `--date-order` depending on the chosen [`topo::Sorting`]. /// /// Instantiate with [`topo::Builder`]. pub struct Topo { commit_graph: Option, find: Find, predicate: Predicate, indegrees: IdMap, states: IdMap, explore_queue: PriorityQueue, indegree_queue: PriorityQueue, topo_queue: topo::iter::Queue, parents: Parents, min_gen: u32, buf: Vec, } pub mod topo; /// Specify how to handle commit parents during traversal. #[derive(Default, Copy, Clone)] pub enum Parents { /// Traverse all parents, useful for traversing the entire ancestry. #[default] All, /// Only traverse along the first parent, which commonly ignores all branches. First, } /// The collection of parent ids we saw as part of the iteration. /// /// Note that this list is truncated if [`Parents::First`] was used. pub type ParentIds = SmallVec<[gix_hash::ObjectId; 1]>; /// Information about a commit that we obtained naturally as part of the iteration. #[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)] pub struct Info { /// The id of the commit. pub id: gix_hash::ObjectId, /// All parent ids we have encountered. Note that these will be at most one if [`Parents::First`] is enabled. pub parent_ids: ParentIds, /// The time at which the commit was created. It will only be `Some(_)` if the chosen traversal was /// taking dates into consideration. pub commit_time: Option, } /// Information about a commit that can be obtained either from a [`gix_object::CommitRefIter`] or /// a [`gix_commitgraph::file::Commit`]. #[derive(Clone, Copy)] pub enum Either<'buf, 'cache> { /// See [`gix_object::CommitRefIter`]. CommitRefIter(gix_object::CommitRefIter<'buf>), /// See [`gix_commitgraph::file::Commit`]. CachedCommit(gix_commitgraph::file::Commit<'cache>), } impl Either<'_, '_> { /// Get a commit’s `tree_id` by either getting it from a [`gix_commitgraph::Graph`], if /// present, or a [`gix_object::CommitRefIter`] otherwise. pub fn tree_id(self) -> Result { match self { Self::CommitRefIter(mut commit_ref_iter) => commit_ref_iter.tree_id(), Self::CachedCommit(commit) => Ok(commit.root_tree_id().into()), } } /// Get a committer timestamp by either getting it from a [`gix_commitgraph::Graph`], if /// present, or a [`gix_object::CommitRefIter`] otherwise. pub fn commit_time(self) -> Result { match self { Self::CommitRefIter(commit_ref_iter) => commit_ref_iter.committer().map(|c| c.seconds()), Self::CachedCommit(commit) => Ok(commit.committer_timestamp() as gix_date::SecondsSinceUnixEpoch), } } } /// Find information about a commit by either getting it from a [`gix_commitgraph::Graph`], if /// present, or a [`gix_object::CommitRefIter`] otherwise. pub fn find<'cache, 'buf, Find>( cache: Option<&'cache gix_commitgraph::Graph>, objects: Find, id: &gix_hash::oid, buf: &'buf mut Vec, ) -> Result, gix_object::find::existing_iter::Error> where Find: gix_object::Find, { match cache.and_then(|cache| cache.commit_by_id(id).map(Either::CachedCommit)) { Some(c) => Ok(c), None => objects.find_commit_iter(id, buf).map(Either::CommitRefIter), } } gix-traverse-0.47.0/src/commit/simple.rs000064400000000000000000000723101046102023000162550ustar 00000000000000use std::{cmp::Reverse, collections::VecDeque}; use gix_date::SecondsSinceUnixEpoch; use gix_hash::ObjectId; use smallvec::SmallVec; #[derive(Default, Debug, Copy, Clone)] /// The order with which to prioritize the search. pub enum CommitTimeOrder { #[default] /// Sort commits by newest first. NewestFirst, /// Sort commits by oldest first. #[doc(alias = "Sort::REVERSE", alias = "git2")] OldestFirst, } /// Specify how to sort commits during a [simple](super::Simple) traversal. /// /// ### Sample History /// /// The following history will be referred to for explaining how the sort order works, with the number denoting the commit timestamp /// (*their X-alignment doesn't matter*). /// /// ```text /// ---1----2----4----7 <- second parent of 8 /// \ \ /// 3----5----6----8--- /// ``` #[derive(Default, Debug, Copy, Clone)] pub enum Sorting { /// Commits are sorted as they are mentioned in the commit graph. /// /// In the *sample history* the order would be `8, 6, 7, 5, 4, 3, 2, 1`. /// /// ### Note /// /// This is not to be confused with `git log/rev-list --topo-order`, which is notably different from /// as it avoids overlapping branches. #[default] BreadthFirst, /// Commits are sorted by their commit time in the order specified, either newest or oldest first. /// /// The sorting applies to all currently queued commit ids and thus is full. /// /// In the *sample history* the order would be `8, 7, 6, 5, 4, 3, 2, 1` for [`NewestFirst`](CommitTimeOrder::NewestFirst), /// or `1, 2, 3, 4, 5, 6, 7, 8` for [`OldestFirst`](CommitTimeOrder::OldestFirst). /// /// # Performance /// /// This mode benefits greatly from having an object_cache in `find()` /// to avoid having to lookup each commit twice. ByCommitTime(CommitTimeOrder), /// This sorting is similar to [`ByCommitTime`](Sorting::ByCommitTime), but adds a cutoff to not return commits older than /// a given time, stopping the iteration once no younger commits is queued to be traversed. /// /// As the query is usually repeated with different cutoff dates, this search mode benefits greatly from an object cache. /// /// In the *sample history* and a cut-off date of 4, the returned list of commits would be `8, 7, 6, 4`. ByCommitTimeCutoff { /// The order in which to prioritize lookups. order: CommitTimeOrder, /// The number of seconds since unix epoch, the same value obtained by any `gix_date::Time` structure and the way git counts time. seconds: gix_date::SecondsSinceUnixEpoch, }, } /// The error is part of the item returned by the [Ancestors](super::Simple) iterator. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { #[error(transparent)] Find(#[from] gix_object::find::existing_iter::Error), #[error(transparent)] ObjectDecode(#[from] gix_object::decode::Error), } use Result as Either; type QueueKey = Either>; type CommitDateQueue = gix_revwalk::PriorityQueue, (ObjectId, CommitState)>; type Candidates = VecDeque; /// The state used and potentially shared by multiple graph traversals. #[derive(Clone)] pub(super) struct State { next: VecDeque<(ObjectId, CommitState)>, queue: CommitDateQueue, buf: Vec, seen: gix_revwalk::graph::IdMap, parents_buf: Vec, parent_ids: SmallVec<[(ObjectId, SecondsSinceUnixEpoch); 2]>, /// The list (FIFO) of thus far interesting commits. /// /// As they may turn hidden later, we have to keep them until the conditions are met to return them. /// If `None`, there is nothing to do with hidden commits. candidates: Option, } #[derive(Debug, Clone, Copy)] enum CommitState { /// The commit may be returned, it hasn't been hidden yet. Interesting, /// The commit should not be returned. Hidden, } impl CommitState { pub fn is_hidden(&self) -> bool { matches!(self, CommitState::Hidden) } pub fn is_interesting(&self) -> bool { matches!(self, CommitState::Interesting) } } /// mod init { use std::cmp::Reverse; use super::{ super::{simple::Sorting, Either, Info, ParentIds, Parents, Simple}, collect_parents, Candidates, CommitDateQueue, CommitState, CommitTimeOrder, Error, State, }; use gix_date::SecondsSinceUnixEpoch; use gix_hash::{oid, ObjectId}; use gix_hashtable::hash_map::Entry; use gix_object::{CommitRefIter, FindExt}; use std::collections::VecDeque; use Err as Oldest; use Ok as Newest; impl Default for State { fn default() -> Self { State { next: Default::default(), queue: gix_revwalk::PriorityQueue::new(), buf: vec![], seen: Default::default(), parents_buf: vec![], parent_ids: Default::default(), candidates: None, } } } impl State { fn clear(&mut self) { let Self { next, queue, buf, seen, parents_buf: _, parent_ids: _, candidates, } = self; next.clear(); queue.clear(); buf.clear(); seen.clear(); *candidates = None; } } fn to_queue_key(i: i64, order: CommitTimeOrder) -> super::QueueKey { match order { CommitTimeOrder::NewestFirst => Newest(i), CommitTimeOrder::OldestFirst => Oldest(Reverse(i)), } } /// Builder impl Simple where Find: gix_object::Find, { /// Set the `sorting` method. pub fn sorting(mut self, sorting: Sorting) -> Result { self.sorting = sorting; match self.sorting { Sorting::BreadthFirst => { self.queue_to_vecdeque(); } Sorting::ByCommitTime(order) | Sorting::ByCommitTimeCutoff { order, .. } => { let state = &mut self.state; for (commit_id, commit_state) in state.next.drain(..) { add_to_queue( commit_id, commit_state, order, sorting.cutoff_time(), &mut state.queue, &self.objects, &mut state.buf, )?; } } } Ok(self) } /// Change our commit parent handling mode to the given one. pub fn parents(mut self, mode: Parents) -> Self { self.parents = mode; if matches!(self.parents, Parents::First) { self.queue_to_vecdeque(); } self } /// Hide the given `tips`, along with all commits reachable by them so that they will not be returned /// by the traversal. /// /// Note that this will force the traversal into a non-intermediate mode and queue return candidates, /// to be released when it's clear that they truly are not hidden. /// /// Note that hidden objects are expected to exist. pub fn hide(mut self, tips: impl IntoIterator) -> Result { self.state.candidates = Some(VecDeque::new()); let state = &mut self.state; for id_to_ignore in tips { let previous = state.seen.insert(id_to_ignore, CommitState::Hidden); // If there was something, it will pick up whatever commit-state we have set last // upon iteration. Also, hidden states always override everything else. if previous.is_none() { // Assure we *start* traversing hidden variants of a commit first, give them a head-start. match self.sorting { Sorting::BreadthFirst => { state.next.push_front((id_to_ignore, CommitState::Hidden)); } Sorting::ByCommitTime(order) | Sorting::ByCommitTimeCutoff { order, .. } => { add_to_queue( id_to_ignore, CommitState::Hidden, order, self.sorting.cutoff_time(), &mut state.queue, &self.objects, &mut state.buf, )?; } } } } if !self .state .seen .values() .any(|state| matches!(state, CommitState::Hidden)) { self.state.candidates = None; } Ok(self) } /// Set the commitgraph as `cache` to greatly accelerate any traversal. /// /// The cache will be used if possible, but we will fall back without error to using the object /// database for commit lookup. If the cache is corrupt, we will fall back to the object database as well. pub fn commit_graph(mut self, cache: Option) -> Self { self.cache = cache; self } fn queue_to_vecdeque(&mut self) { let state = &mut self.state; state.next.extend( std::mem::replace(&mut state.queue, gix_revwalk::PriorityQueue::new()) .into_iter_unordered() .map(|(_time, id)| id), ); } } fn add_to_queue( commit_id: ObjectId, commit_state: CommitState, order: CommitTimeOrder, cutoff_time: Option, queue: &mut CommitDateQueue, objects: &impl gix_object::Find, buf: &mut Vec, ) -> Result<(), Error> { let commit_iter = objects.find_commit_iter(&commit_id, buf)?; let time = commit_iter.committer()?.seconds(); let key = to_queue_key(time, order); match (cutoff_time, order) { (Some(cutoff_time), _) if time >= cutoff_time => { queue.insert(key, (commit_id, commit_state)); } (Some(_), _) => {} (None, _) => { queue.insert(key, (commit_id, commit_state)); } } Ok(()) } /// Lifecycle impl Simple bool> where Find: gix_object::Find, { /// Create a new instance. /// /// * `find` - a way to lookup new object data during traversal by their `ObjectId`, writing their data into buffer and returning /// an iterator over commit tokens if the object is present and is a commit. Caching should be implemented within this function /// as needed. /// * `tips` /// * the starting points of the iteration, usually commits /// * each commit they lead to will only be returned once, including the tip that started it pub fn new(tips: impl IntoIterator>, find: Find) -> Self { Self::filtered(tips, find, |_| true) } } /// Lifecycle impl Simple where Find: gix_object::Find, Predicate: FnMut(&oid) -> bool, { /// Create a new instance with commit filtering enabled. /// /// * `find` - a way to lookup new object data during traversal by their `ObjectId`, writing their data into buffer and returning /// an iterator over commit tokens if the object is present and is a commit. Caching should be implemented within this function /// as needed. /// * `tips` /// * the starting points of the iteration, usually commits /// * each commit they lead to will only be returned once, including the tip that started it /// * `predicate` - indicate whether a given commit should be included in the result as well /// as whether its parent commits should be traversed. pub fn filtered( tips: impl IntoIterator>, find: Find, mut predicate: Predicate, ) -> Self { let tips = tips.into_iter(); let mut state = State::default(); { state.clear(); state.next.reserve(tips.size_hint().0); for tip in tips.map(Into::into) { let commit_state = CommitState::Interesting; let seen = state.seen.insert(tip, commit_state); // We know there can only be duplicate interesting ones. if seen.is_none() && predicate(&tip) { state.next.push_back((tip, commit_state)); } } } Self { objects: find, cache: None, predicate, state, parents: Default::default(), sorting: Default::default(), } } } /// Access impl Simple { /// Return an iterator for accessing data of the current commit, parsed lazily. pub fn commit_iter(&self) -> CommitRefIter<'_> { CommitRefIter::from_bytes(self.commit_data()) } /// Return the current commits' raw data, which can be parsed using [`gix_object::CommitRef::from_bytes()`]. pub fn commit_data(&self) -> &[u8] { &self.state.buf } } impl Iterator for Simple where Find: gix_object::Find, Predicate: FnMut(&oid) -> bool, { type Item = Result; fn next(&mut self) -> Option { if matches!(self.parents, Parents::First) { self.next_by_topology() } else { match self.sorting { Sorting::BreadthFirst => self.next_by_topology(), Sorting::ByCommitTime(order) => self.next_by_commit_date(order, None), Sorting::ByCommitTimeCutoff { seconds, order } => self.next_by_commit_date(order, seconds.into()), } } .or_else(|| { self.state .candidates .as_mut() .and_then(|candidates| candidates.pop_front().map(Ok)) }) } } impl Sorting { /// If not topo sort, provide the cutoff date if present. fn cutoff_time(&self) -> Option { match self { Sorting::ByCommitTimeCutoff { seconds, .. } => Some(*seconds), _ => None, } } } /// Utilities impl Simple where Find: gix_object::Find, Predicate: FnMut(&oid) -> bool, { fn next_by_commit_date( &mut self, order: CommitTimeOrder, cutoff: Option, ) -> Option> { let state = &mut self.state; let next = &mut state.queue; 'skip_hidden: loop { let (commit_time, (oid, _queued_commit_state)) = match next.pop()? { (Newest(t) | Oldest(Reverse(t)), o) => (t, o), }; let mut parents: ParentIds = Default::default(); // Always use the state that is actually stored, as we may change the type as we go. let commit_state = *state.seen.get(&oid).expect("every commit we traverse has state added"); if can_deplete_candidates_early( next.iter_unordered().map(|t| t.1), commit_state, state.candidates.as_ref(), ) { return None; } match super::super::find(self.cache.as_ref(), &self.objects, &oid, &mut state.buf) { Ok(Either::CachedCommit(commit)) => { if !collect_parents(&mut state.parent_ids, self.cache.as_ref(), commit.iter_parents()) { // drop corrupt caches and try again with ODB self.cache = None; return self.next_by_commit_date(order, cutoff); } for (id, parent_commit_time) in state.parent_ids.drain(..) { parents.push(id); insert_into_seen_and_queue( &mut state.seen, id, &mut state.candidates, commit_state, &mut self.predicate, next, order, cutoff, || parent_commit_time, ); } } Ok(Either::CommitRefIter(commit_iter)) => { for token in commit_iter { match token { Ok(gix_object::commit::ref_iter::Token::Tree { .. }) => continue, Ok(gix_object::commit::ref_iter::Token::Parent { id }) => { parents.push(id); insert_into_seen_and_queue( &mut state.seen, id, &mut state.candidates, commit_state, &mut self.predicate, next, order, cutoff, || { let parent = self.objects.find_commit_iter(id.as_ref(), &mut state.parents_buf).ok(); parent .and_then(|parent| { parent.committer().ok().map(|committer| committer.seconds()) }) .unwrap_or_default() }, ); } Ok(_unused_token) => break, Err(err) => return Some(Err(err.into())), } } } Err(err) => return Some(Err(err.into())), } match commit_state { CommitState::Interesting => { let info = Info { id: oid, parent_ids: parents, commit_time: Some(commit_time), }; match state.candidates.as_mut() { None => return Some(Ok(info)), Some(candidates) => { // assure candidates aren't prematurely returned - hidden commits may catch up with // them later. candidates.push_back(info); } } } CommitState::Hidden => continue 'skip_hidden, } } } } /// Returns `true` if we have only hidden cursors queued for traversal, assuming that we don't see interesting ones ever again. /// /// `unqueued_commit_state` is the state of the commit that is currently being processed. fn can_deplete_candidates_early( mut queued_states: impl Iterator, unqueued_commit_state: CommitState, candidates: Option<&Candidates>, ) -> bool { if candidates.is_none() { return false; } if unqueued_commit_state.is_interesting() { return false; } let mut is_empty = true; queued_states.all(|state| { is_empty = false; state.is_hidden() }) && !is_empty } /// Utilities impl Simple where Find: gix_object::Find, Predicate: FnMut(&oid) -> bool, { fn next_by_topology(&mut self) -> Option> { let state = &mut self.state; let next = &mut state.next; 'skip_hidden: loop { let (oid, _queued_commit_state) = next.pop_front()?; let mut parents: ParentIds = Default::default(); // Always use the state that is actually stored, as we may change the type as we go. let commit_state = *state.seen.get(&oid).expect("every commit we traverse has state added"); if can_deplete_candidates_early(next.iter().map(|t| t.1), commit_state, state.candidates.as_ref()) { return None; } match super::super::find(self.cache.as_ref(), &self.objects, &oid, &mut state.buf) { Ok(Either::CachedCommit(commit)) => { if !collect_parents(&mut state.parent_ids, self.cache.as_ref(), commit.iter_parents()) { // drop corrupt caches and try again with ODB self.cache = None; return self.next_by_topology(); } for (pid, _commit_time) in state.parent_ids.drain(..) { parents.push(pid); insert_into_seen_and_next( &mut state.seen, pid, &mut state.candidates, commit_state, &mut self.predicate, next, ); if commit_state.is_interesting() && matches!(self.parents, Parents::First) { break; } } } Ok(Either::CommitRefIter(commit_iter)) => { for token in commit_iter { match token { Ok(gix_object::commit::ref_iter::Token::Tree { .. }) => continue, Ok(gix_object::commit::ref_iter::Token::Parent { id: pid }) => { parents.push(pid); insert_into_seen_and_next( &mut state.seen, pid, &mut state.candidates, commit_state, &mut self.predicate, next, ); if commit_state.is_interesting() && matches!(self.parents, Parents::First) { break; } } Ok(_a_token_past_the_parents) => break, Err(err) => return Some(Err(err.into())), } } } Err(err) => return Some(Err(err.into())), } match commit_state { CommitState::Interesting => { let info = Info { id: oid, parent_ids: parents, commit_time: None, }; match state.candidates.as_mut() { None => return Some(Ok(info)), Some(candidates) => { // assure candidates aren't prematurely returned - hidden commits may catch up with // them later. candidates.push_back(info); } } } CommitState::Hidden => continue 'skip_hidden, } } } } #[inline] fn remove_candidate(candidates: Option<&mut Candidates>, remove: ObjectId) -> Option<()> { let candidates = candidates?; let pos = candidates .iter_mut() .enumerate() .find_map(|(idx, info)| (info.id == remove).then_some(idx))?; candidates.remove(pos); None } fn insert_into_seen_and_next( seen: &mut gix_revwalk::graph::IdMap, parent_id: ObjectId, candidates: &mut Option, commit_state: CommitState, predicate: &mut impl FnMut(&oid) -> bool, next: &mut VecDeque<(ObjectId, CommitState)>, ) { let enqueue = match seen.entry(parent_id) { Entry::Occupied(mut e) => { let enqueue = handle_seen(commit_state, *e.get(), parent_id, candidates); if commit_state.is_hidden() { e.insert(commit_state); } enqueue } Entry::Vacant(e) => { e.insert(commit_state); match commit_state { CommitState::Interesting => predicate(&parent_id), CommitState::Hidden => true, } } }; if enqueue { next.push_back((parent_id, commit_state)); } } #[allow(clippy::too_many_arguments)] fn insert_into_seen_and_queue( seen: &mut gix_revwalk::graph::IdMap, parent_id: ObjectId, candidates: &mut Option, commit_state: CommitState, predicate: &mut impl FnMut(&oid) -> bool, queue: &mut CommitDateQueue, order: CommitTimeOrder, cutoff: Option, get_parent_commit_time: impl FnOnce() -> gix_date::SecondsSinceUnixEpoch, ) { let enqueue = match seen.entry(parent_id) { Entry::Occupied(mut e) => { let enqueue = handle_seen(commit_state, *e.get(), parent_id, candidates); if commit_state.is_hidden() { e.insert(commit_state); } enqueue } Entry::Vacant(e) => { e.insert(commit_state); match commit_state { CommitState::Interesting => (predicate)(&parent_id), CommitState::Hidden => true, } } }; if enqueue { let parent_commit_time = get_parent_commit_time(); let key = to_queue_key(parent_commit_time, order); match cutoff { Some(cutoff_older_than) if parent_commit_time < cutoff_older_than => {} Some(_) | None => queue.insert(key, (parent_id, commit_state)), } } } #[inline] #[must_use] fn handle_seen( next_state: CommitState, current_state: CommitState, id: ObjectId, candidates: &mut Option, ) -> bool { match (current_state, next_state) { (CommitState::Hidden, CommitState::Hidden) => false, (CommitState::Interesting, CommitState::Interesting) => false, (CommitState::Hidden, CommitState::Interesting) => { // keep traversing to paint more hidden. After all, the commit_state overrides the current parent state true } (CommitState::Interesting, CommitState::Hidden) => { remove_candidate(candidates.as_mut(), id); true } } } } fn collect_parents( dest: &mut SmallVec<[(gix_hash::ObjectId, gix_date::SecondsSinceUnixEpoch); 2]>, cache: Option<&gix_commitgraph::Graph>, parents: gix_commitgraph::file::commit::Parents<'_>, ) -> bool { dest.clear(); let cache = cache.as_ref().expect("parents iter is available, backed by `cache`"); for parent_id in parents { match parent_id { Ok(pos) => dest.push({ let parent = cache.commit_at(pos); ( parent.id().to_owned(), parent.committer_timestamp() as gix_date::SecondsSinceUnixEpoch, // we can't handle errors here and trying seems overkill ) }), Err(_err) => return false, } } true } gix-traverse-0.47.0/src/commit/topo/init.rs000064400000000000000000000147701046102023000167160ustar 00000000000000use gix_hash::{oid, ObjectId}; use gix_revwalk::{graph::IdMap, PriorityQueue}; use crate::commit::{ find, topo::{iter::gen_and_commit_time, Error, Sorting, WalkFlags}, Info, Parents, Topo, }; /// Builder for [`Topo`]. pub struct Builder { commit_graph: Option, find: Find, predicate: Predicate, sorting: Sorting, parents: Parents, tips: Vec, ends: Vec, } impl Builder bool> where Find: gix_object::Find, { /// Create a new `Builder` for a [`Topo`] that reads commits from a repository with `find`. /// starting at the `tips` and ending at the `ends`. Like `git rev-list /// --topo-order ^ends tips`. pub fn from_iters( find: Find, tips: impl IntoIterator>, ends: Option>>, ) -> Self { Self::new(find).with_tips(tips).with_ends(ends.into_iter().flatten()) } /// Create a new `Builder` for a [`Topo`] that reads commits from a /// repository with `find`. pub fn new(find: Find) -> Self { Self { commit_graph: Default::default(), find, sorting: Default::default(), parents: Default::default(), tips: Default::default(), ends: Default::default(), predicate: |_| true, } } /// Set a `predicate` to filter out revisions from the walk. Can be used to /// implement e.g. filtering on paths or time. This does *not* exclude the /// parent(s) of a revision that is excluded. Specify a revision as an 'end' /// if you want that behavior. pub fn with_predicate(self, predicate: Predicate) -> Builder where Predicate: FnMut(&oid) -> bool, { Builder { commit_graph: self.commit_graph, find: self.find, sorting: self.sorting, parents: self.parents, tips: self.tips, ends: self.ends, predicate, } } } impl Builder where Find: gix_object::Find, Predicate: FnMut(&oid) -> bool, { /// Add commits to start reading from. /// /// The behavior is similar to specifying additional `ends` in `git rev-list --topo-order ^ends tips`. pub fn with_tips(mut self, tips: impl IntoIterator>) -> Self { self.tips.extend(tips.into_iter().map(Into::into)); self } /// Add commits ending the traversal. /// /// These commits themselves will not be read, i.e. the behavior is similar to specifying additional /// `ends` in `git rev-list --topo-order ^ends tips`. pub fn with_ends(mut self, ends: impl IntoIterator>) -> Self { self.ends.extend(ends.into_iter().map(Into::into)); self } /// Set the `sorting` to use for the topological walk. pub fn sorting(mut self, sorting: Sorting) -> Self { self.sorting = sorting; self } /// Specify how to handle commit `parents` during traversal. pub fn parents(mut self, parents: Parents) -> Self { self.parents = parents; self } /// Set or unset the `commit_graph` to use for the iteration. pub fn with_commit_graph(mut self, commit_graph: Option) -> Self { self.commit_graph = commit_graph; self } /// Build a new [`Topo`] instance. /// /// Note that merely building an instance is currently expensive. pub fn build(self) -> Result, Error> { let mut w = Topo { commit_graph: self.commit_graph, find: self.find, predicate: self.predicate, indegrees: IdMap::default(), states: IdMap::default(), explore_queue: PriorityQueue::new(), indegree_queue: PriorityQueue::new(), topo_queue: super::iter::Queue::new(self.sorting), parents: self.parents, min_gen: gix_commitgraph::GENERATION_NUMBER_INFINITY, buf: vec![], }; // Initial flags for the states of the tips and ends. All of them are // seen and added to the explore and indegree queues. The ends are by // definition (?) uninteresting and bottom. let tip_flags = WalkFlags::Seen | WalkFlags::Explored | WalkFlags::InDegree; let end_flags = tip_flags | WalkFlags::Uninteresting | WalkFlags::Bottom; for (id, flags) in self .tips .iter() .map(|id| (id, tip_flags)) .chain(self.ends.iter().map(|id| (id, end_flags))) { *w.indegrees.entry(*id).or_default() = 1; let commit = find(w.commit_graph.as_ref(), &w.find, id, &mut w.buf)?; let (gen, time) = gen_and_commit_time(commit)?; if gen < w.min_gen { w.min_gen = gen; } w.states.insert(*id, flags); w.explore_queue.insert((gen, time), *id); w.indegree_queue.insert((gen, time), *id); } // NOTE: Parents of the ends must also be marked uninteresting for some // reason. See handle_commit() for id in &self.ends { let parents = w.collect_all_parents(id)?; for (id, _) in parents { w.states .entry(id) .and_modify(|s| *s |= WalkFlags::Uninteresting) .or_insert(WalkFlags::Uninteresting | WalkFlags::Seen); } } w.compute_indegrees_to_depth(w.min_gen)?; // NOTE: in Git the ends are also added to the topo_queue in addition to // the tips, but then in simplify_commit() Git is told to ignore it. For // now the tests pass. for id in self.tips.iter() { let i = w.indegrees.get(id).ok_or(Error::MissingIndegreeUnexpected)?; if *i != 1 { continue; } let commit = find(w.commit_graph.as_ref(), &w.find, id, &mut w.buf)?; let (_, time) = gen_and_commit_time(commit)?; let parent_ids = w.collect_all_parents(id)?.into_iter().map(|e| e.0).collect(); w.topo_queue.push( time, Info { id: *id, parent_ids, commit_time: Some(time), }, ); } w.topo_queue.initial_sort(); Ok(w) } } gix-traverse-0.47.0/src/commit/topo/iter.rs000064400000000000000000000245701046102023000167150ustar 00000000000000use gix_hash::{oid, ObjectId}; use gix_revwalk::PriorityQueue; use smallvec::SmallVec; use crate::commit::{ find, topo::{Error, Sorting, WalkFlags}, Either, Info, Parents, Topo, }; pub(in crate::commit) type GenAndCommitTime = (u32, i64); // Git's priority queue works as a LIFO stack if no compare function is set, // which is the case for `--topo-order.` However, even in that case the initial // items of the queue are sorted according to the commit time before beginning // the walk. #[derive(Debug)] pub(in crate::commit) enum Queue { Date(PriorityQueue), Topo(Vec<(i64, Info)>), } impl Queue { pub(super) fn new(s: Sorting) -> Self { match s { Sorting::DateOrder => Self::Date(PriorityQueue::new()), Sorting::TopoOrder => Self::Topo(vec![]), } } pub(super) fn push(&mut self, commit_time: i64, info: Info) { match self { Self::Date(q) => q.insert(commit_time, info), Self::Topo(q) => q.push((commit_time, info)), } } fn pop(&mut self) -> Option { match self { Self::Date(q) => q.pop().map(|(_, info)| info), Self::Topo(q) => q.pop().map(|(_, info)| info), } } pub(super) fn initial_sort(&mut self) { if let Self::Topo(ref mut inner_vec) = self { inner_vec.sort_by(|a, b| a.0.cmp(&b.0)); } } } impl Topo where Find: gix_object::Find, { pub(super) fn compute_indegrees_to_depth(&mut self, gen_cutoff: u32) -> Result<(), Error> { while let Some(((gen, _), _)) = self.indegree_queue.peek() { if *gen >= gen_cutoff { self.indegree_walk_step()?; } else { break; } } Ok(()) } fn indegree_walk_step(&mut self) -> Result<(), Error> { if let Some(((gen, _), id)) = self.indegree_queue.pop() { self.explore_to_depth(gen)?; let parents = self.collect_parents(&id)?; for (id, gen_time) in parents { self.indegrees.entry(id).and_modify(|e| *e += 1).or_insert(2); let state = self.states.get_mut(&id).ok_or(Error::MissingStateUnexpected)?; if !state.contains(WalkFlags::InDegree) { *state |= WalkFlags::InDegree; self.indegree_queue.insert(gen_time, id); } } } Ok(()) } fn explore_to_depth(&mut self, gen_cutoff: u32) -> Result<(), Error> { while let Some(((gen, _), _)) = self.explore_queue.peek() { if *gen >= gen_cutoff { self.explore_walk_step()?; } else { break; } } Ok(()) } fn explore_walk_step(&mut self) -> Result<(), Error> { if let Some((_, id)) = self.explore_queue.pop() { let parents = self.collect_parents(&id)?; self.process_parents(&id, &parents)?; for (id, gen_time) in parents { let state = self.states.get_mut(&id).ok_or(Error::MissingStateUnexpected)?; if !state.contains(WalkFlags::Explored) { *state |= WalkFlags::Explored; self.explore_queue.insert(gen_time, id); } } } Ok(()) } fn expand_topo_walk(&mut self, id: &oid) -> Result<(), Error> { let parents = self.collect_parents(id)?; self.process_parents(id, &parents)?; for (pid, (parent_gen, parent_commit_time)) in parents { let parent_state = self.states.get(&pid).ok_or(Error::MissingStateUnexpected)?; if parent_state.contains(WalkFlags::Uninteresting) { continue; } if parent_gen < self.min_gen { self.min_gen = parent_gen; self.compute_indegrees_to_depth(self.min_gen)?; } let i = self.indegrees.get_mut(&pid).ok_or(Error::MissingIndegreeUnexpected)?; *i -= 1; if *i != 1 { continue; } let parent_ids = self.collect_all_parents(&pid)?.into_iter().map(|e| e.0).collect(); self.topo_queue.push( parent_commit_time, Info { id: pid, parent_ids, commit_time: Some(parent_commit_time), }, ); } Ok(()) } fn process_parents(&mut self, id: &oid, parents: &[(ObjectId, GenAndCommitTime)]) -> Result<(), Error> { let state = self.states.get_mut(id).ok_or(Error::MissingStateUnexpected)?; if state.contains(WalkFlags::Added) { return Ok(()); } *state |= WalkFlags::Added; // If the current commit is uninteresting we pass that on to ALL // parents, otherwise we set the Seen flag. let (pass, insert) = if state.contains(WalkFlags::Uninteresting) { let flags = WalkFlags::Uninteresting; for (id, _) in parents { let grand_parents = self.collect_all_parents(id)?; for (id, _) in &grand_parents { self.states .entry(*id) .and_modify(|s| *s |= WalkFlags::Uninteresting) .or_insert(WalkFlags::Uninteresting | WalkFlags::Seen); } } (flags, flags) } else { // NOTE: git sets SEEN like we do but keeps the SYMMETRIC_LEFT and // ANCENSTRY_PATH if they are set, but they have no purpose here. let flags = WalkFlags::empty(); (flags, WalkFlags::Seen) }; for (id, _) in parents { self.states.entry(*id).and_modify(|s| *s |= pass).or_insert(insert); } Ok(()) } fn collect_parents(&mut self, id: &oid) -> Result, Error> { collect_parents( &mut self.commit_graph, &self.find, id, matches!(self.parents, Parents::First), &mut self.buf, ) } // Same as collect_parents but disregards the first_parent flag pub(super) fn collect_all_parents( &mut self, id: &oid, ) -> Result, Error> { collect_parents(&mut self.commit_graph, &self.find, id, false, &mut self.buf) } fn pop_commit(&mut self) -> Option> { let commit = self.topo_queue.pop()?; let i = match self.indegrees.get_mut(&commit.id) { Some(i) => i, None => { return Some(Err(Error::MissingIndegreeUnexpected)); } }; *i = 0; if let Err(e) = self.expand_topo_walk(&commit.id) { return Some(Err(e)); } Some(Ok(commit)) } } impl Iterator for Topo where Find: gix_object::Find, Predicate: FnMut(&oid) -> bool, { type Item = Result; fn next(&mut self) -> Option { loop { match self.pop_commit()? { Ok(id) => { if (self.predicate)(&id.id) { return Some(Ok(id)); } } Err(e) => return Some(Err(e)), } } } } fn collect_parents( cache: &mut Option, f: Find, id: &oid, first_only: bool, buf: &mut Vec, ) -> Result, Error> where Find: gix_object::Find, { let mut parents = SmallVec::<[(ObjectId, GenAndCommitTime); 1]>::new(); match find(cache.as_ref(), &f, id, buf)? { Either::CommitRefIter(c) => { for token in c { use gix_object::commit::ref_iter::Token as T; match token { Ok(T::Tree { .. }) => continue, Ok(T::Parent { id }) => { parents.push((id, (0, 0))); // Dummy numbers to be filled in if first_only { break; } } Ok(_past_parents) => break, Err(err) => return Err(err.into()), } } // Need to check the cache again. That a commit is not in the cache // doesn't mean a parent is not. for (id, gen_time) in parents.iter_mut() { let commit = find(cache.as_ref(), &f, id, buf)?; *gen_time = gen_and_commit_time(commit)?; } } Either::CachedCommit(c) => { for pos in c.iter_parents() { let Ok(pos) = pos else { // drop corrupt cache and use ODB from now on. *cache = None; return collect_parents(cache, f, id, first_only, buf); }; let parent_commit = cache .as_ref() .expect("cache exists if CachedCommit was returned") .commit_at(pos); parents.push(( parent_commit.id().into(), (parent_commit.generation(), parent_commit.committer_timestamp() as i64), )); if first_only { break; } } } } Ok(parents) } pub(super) fn gen_and_commit_time(c: Either<'_, '_>) -> Result { match c { Either::CommitRefIter(c) => { let mut commit_time = 0; for token in c { use gix_object::commit::ref_iter::Token as T; match token { Ok(T::Tree { .. }) => continue, Ok(T::Parent { .. }) => continue, Ok(T::Author { .. }) => continue, Ok(T::Committer { signature }) => { commit_time = signature.seconds(); break; } Ok(_unused_token) => break, Err(err) => return Err(err.into()), } } Ok((gix_commitgraph::GENERATION_NUMBER_INFINITY, commit_time)) } Either::CachedCommit(c) => Ok((c.generation(), c.committer_timestamp() as i64)), } } gix-traverse-0.47.0/src/commit/topo/mod.rs000064400000000000000000000046071046102023000165300ustar 00000000000000//! Topological commit traversal, similar to `git log --topo-order`, which keeps track of graph state. use bitflags::bitflags; /// The errors that can occur during creation and iteration. #[derive(thiserror::Error, Debug)] #[allow(missing_docs)] pub enum Error { #[error("Indegree information is missing")] MissingIndegreeUnexpected, #[error("Internal state (bitflags) not found")] MissingStateUnexpected, #[error(transparent)] ObjectDecode(#[from] gix_object::decode::Error), #[error(transparent)] Find(#[from] gix_object::find::existing_iter::Error), } bitflags! { /// Set of flags to describe the state of a particular commit while iterating. // NOTE: The names correspond to the names of the flags in revision.h #[repr(transparent)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub(super) struct WalkFlags: u8 { /// Commit has been seen const Seen = 0b000001; /// Commit has been processed by the Explore walk const Explored = 0b000010; /// Commit has been processed by the Indegree walk const InDegree = 0b000100; /// Commit is deemed uninteresting for whatever reason const Uninteresting = 0b001000; /// Commit marks the end of a walk, like `foo` in `git rev-list foo..bar` const Bottom = 0b010000; /// Parents have been processed const Added = 0b100000; } } /// Sorting to use for the topological walk. /// /// ### Sample History /// /// The following history will be referred to for explaining how the sort order works, with the number denoting the commit timestamp /// (*their X-alignment doesn't matter*). /// /// ```text /// ---1----2----4----7 <- second parent of 8 /// \ \ /// 3----5----6----8--- /// ``` #[derive(Clone, Copy, Debug, Default)] pub enum Sorting { /// Show no parents before all of its children are shown, but otherwise show /// commits in the commit timestamp order. /// /// This is equivalent to `git rev-list --date-order`. #[default] DateOrder, /// Show no parents before all of its children are shown, and avoid /// showing commits on multiple lines of history intermixed. /// /// In the *sample history* the order would be `8, 6, 5, 3, 7, 4, 2, 1`. /// This is equivalent to `git rev-list --topo-order`. TopoOrder, } mod init; pub use init::Builder; pub(super) mod iter; gix-traverse-0.47.0/src/lib.rs000064400000000000000000000003121046102023000142330ustar 00000000000000//! Various ways to traverse commit graphs and trees with implementations as iterator #![deny(missing_docs, rust_2018_idioms)] #![forbid(unsafe_code)] pub mod commit; /// Tree traversal pub mod tree; gix-traverse-0.47.0/src/tree/breadthfirst.rs000064400000000000000000000075221046102023000171170ustar 00000000000000use std::collections::VecDeque; use gix_hash::ObjectId; /// The error is part of the item returned by the [`breadthfirst()`](crate::tree::breadthfirst()) and ///[`depthfirst()`](crate::tree::depthfirst()) functions. #[derive(Debug, thiserror::Error)] #[allow(missing_docs)] pub enum Error { #[error(transparent)] Find(#[from] gix_object::find::existing_iter::Error), #[error("The delegate cancelled the operation")] Cancelled, #[error(transparent)] ObjectDecode(#[from] gix_object::decode::Error), } /// The state used and potentially shared by multiple tree traversals. #[derive(Default, Clone)] pub struct State { next: VecDeque, buf: Vec, } impl State { fn clear(&mut self) { self.next.clear(); self.buf.clear(); } } pub(super) mod function { use std::borrow::BorrowMut; use gix_object::{FindExt, TreeRefIter}; use super::{Error, State}; use crate::tree::Visit; /// Start a breadth-first iteration over the `root` trees entries. /// /// Note that non-trees will be listed first, so the natural order of entries within a tree is lost. /// /// * `root` /// * the tree to iterate in a nested fashion. /// * `state` - all state used for the iteration. If multiple iterations are performed, allocations can be minimized by reusing /// this state. /// * `find` - a way to lookup new object data during traversal by their `ObjectId`, writing their data into buffer and returning /// an iterator over entries if the object is present and is a tree. Caching should be implemented within this function /// as needed. The return value is `Option` which degenerates all error information. Not finding a commit should also /// be considered an errors as all objects in the tree DAG should be present in the database. Hence [`Error::Find`] should /// be escalated into a more specific error if it's encountered by the caller. /// * `delegate` - A way to observe entries and control the iteration while allowing the optimizer to let you pay only for what you use. pub fn breadthfirst( root: TreeRefIter<'_>, mut state: StateMut, objects: Find, delegate: &mut V, ) -> Result<(), Error> where Find: gix_object::Find, StateMut: BorrowMut, V: Visit, { let state = state.borrow_mut(); state.clear(); let mut tree = root; loop { for entry in tree { let entry = entry?; if entry.mode.is_tree() { use crate::tree::visit::Action::*; delegate.push_path_component(entry.filename); let action = delegate.visit_tree(&entry); match action { Skip => {} Continue => { delegate.pop_path_component(); delegate.push_back_tracked_path_component(entry.filename); state.next.push_back(entry.oid.to_owned()); } Cancel => { return Err(Error::Cancelled); } } } else { delegate.push_path_component(entry.filename); if delegate.visit_nontree(&entry).cancelled() { return Err(Error::Cancelled); } } delegate.pop_path_component(); } match state.next.pop_front() { Some(oid) => { delegate.pop_front_tracked_path_and_set_current(); tree = objects.find_tree_iter(&oid, &mut state.buf)?; } None => break Ok(()), } } } } gix-traverse-0.47.0/src/tree/depthfirst.rs000064400000000000000000000100051046102023000166000ustar 00000000000000pub use super::breadthfirst::Error; /// The state used and potentially shared by multiple tree traversals, reusing memory. #[derive(Default, Clone)] pub struct State { freelist: Vec>, } impl State { /// Pop one empty buffer from the free-list. pub fn pop_buf(&mut self) -> Vec { match self.freelist.pop() { None => Vec::new(), Some(mut buf) => { buf.clear(); buf } } } /// Make `buf` available for re-use with [`Self::pop_buf()`]. pub fn push_buf(&mut self, buf: Vec) { self.freelist.push(buf); } } pub(super) mod function { use std::borrow::BorrowMut; use gix_hash::ObjectId; use gix_object::{FindExt, TreeRefIter}; use super::{Error, State}; use crate::tree::{visit::Action, Visit}; /// A depth-first traversal of the `root` tree, that preserves the natural order of a tree while immediately descending /// into sub-trees. /// /// `state` can be passed to re-use memory during multiple invocations. pub fn depthfirst( root: ObjectId, mut state: StateMut, objects: Find, delegate: &mut V, ) -> Result<(), Error> where Find: gix_object::Find, StateMut: BorrowMut, V: Visit, { enum Machine { GetTree(ObjectId), Iterate { tree_buf: Vec, byte_offset_to_next_entry: usize, }, } let state = state.borrow_mut(); let mut stack = vec![Machine::GetTree(root)]; 'outer: while let Some(item) = stack.pop() { match item { Machine::GetTree(id) => { let mut buf = state.pop_buf(); objects.find_tree_iter(&id, &mut buf)?; stack.push(Machine::Iterate { tree_buf: buf, byte_offset_to_next_entry: 0, }); } Machine::Iterate { tree_buf: buf, byte_offset_to_next_entry, } => { let mut iter = TreeRefIter::from_bytes(&buf[byte_offset_to_next_entry..]); delegate.pop_back_tracked_path_and_set_current(); while let Some(entry) = iter.next() { let entry = entry?; if entry.mode.is_tree() { delegate.push_path_component(entry.filename); let res = delegate.visit_tree(&entry); delegate.pop_path_component(); match res { Action::Continue => {} Action::Cancel => break 'outer, Action::Skip => continue, } delegate.push_back_tracked_path_component("".into()); delegate.push_back_tracked_path_component(entry.filename); let recurse_tree = Machine::GetTree(entry.oid.to_owned()); let continue_at_next_entry = Machine::Iterate { byte_offset_to_next_entry: iter.offset_to_next_entry(&buf), tree_buf: buf, }; stack.push(continue_at_next_entry); stack.push(recurse_tree); continue 'outer; } else { delegate.push_path_component(entry.filename); if let Action::Cancel = delegate.visit_nontree(&entry) { break 'outer; } delegate.pop_path_component(); } } state.push_buf(buf); } } } Ok(()) } } gix-traverse-0.47.0/src/tree/mod.rs000064400000000000000000000065531046102023000152200ustar 00000000000000use std::collections::VecDeque; use gix_object::bstr::{BStr, BString}; /// A trait to allow responding to a traversal designed to observe all entries in a tree, recursively while keeping track of /// paths if desired. pub trait Visit { /// Sets the full path in the back of the queue so future calls to push and pop components affect it instead. /// /// Note that the first call is made without an accompanying call to [`Self::push_back_tracked_path_component()`] /// /// This is used by the depth-first traversal of trees. fn pop_back_tracked_path_and_set_current(&mut self); /// Sets the full path in front of the queue so future calls to push and pop components affect it instead. /// /// This is used by the breadth-first traversal of trees. fn pop_front_tracked_path_and_set_current(&mut self); /// Append a `component` to the end of a path, which may be empty. /// /// If `component` is empty, store the current path. fn push_back_tracked_path_component(&mut self, component: &BStr); /// Append a `component` to the end of a path, which may be empty. fn push_path_component(&mut self, component: &BStr); /// Removes the last component from the path, which may leave it empty. fn pop_path_component(&mut self); /// Observe a tree entry that is a tree and return an instruction whether to continue or not. /// [`Action::Skip`][visit::Action::Skip] can be used to prevent traversing it, for example if it's known to the caller already. /// /// The implementation may use the current path to learn where in the tree the change is located. fn visit_tree(&mut self, entry: &gix_object::tree::EntryRef<'_>) -> visit::Action; /// Observe a tree entry that is NO tree and return an instruction whether to continue or not. /// [`Action::Skip`][visit::Action::Skip] has no effect here. /// /// The implementation may use the current path to learn where in the tree the change is located. fn visit_nontree(&mut self, entry: &gix_object::tree::EntryRef<'_>) -> visit::Action; } /// A [Visit] implementation to record every observed change and keep track of the changed paths. /// /// Recorders can also be instructed to track the filename only, or no location at all. #[derive(Clone, Debug)] pub struct Recorder { path_deque: VecDeque, path: BString, /// How to track the location. location: Option, /// The observed entries. pub records: Vec, } /// pub mod visit { /// What to do after an entry was [recorded][super::Visit::visit_tree()]. #[derive(Clone, Copy, PartialOrd, PartialEq, Ord, Eq, Hash)] pub enum Action { /// Continue the traversal of entries. Continue, /// Stop the traversal of entries, making this the last call to [`visit_(tree|nontree)(…)`][super::Visit::visit_nontree()]. Cancel, /// Don't dive into the entry, skipping children effectively. Only useful in [`visit_tree(…)`][super::Visit::visit_tree()]. Skip, } impl Action { /// Returns true if this action means to stop the traversal. pub fn cancelled(&self) -> bool { matches!(self, Action::Cancel) } } } /// pub mod recorder; /// pub mod breadthfirst; pub use breadthfirst::function::breadthfirst; /// pub mod depthfirst; pub use depthfirst::function::depthfirst; gix-traverse-0.47.0/src/tree/recorder.rs000064400000000000000000000102141046102023000162330ustar 00000000000000use gix_hash::ObjectId; use gix_object::{ bstr::{BStr, BString, ByteSlice, ByteVec}, tree, }; use crate::tree::{visit::Action, Recorder, Visit}; /// Describe how to track the location of an entry. #[derive(Debug, Clone, Copy, Eq, PartialEq)] pub enum Location { /// Track the entire path, relative to the repository. Path, /// Keep only the file-name as location, which may be enough for some calculations. /// /// This is less expensive than tracking the entire `Path`. FileName, } /// An owned entry as observed by a call to [`visit_(tree|nontree)(…)`][Visit::visit_tree()], enhanced with the full path to it. /// Otherwise similar to [`gix_object::tree::EntryRef`]. #[derive(Clone, Debug, PartialEq, Eq)] pub struct Entry { /// The kind of entry, similar to entries in a unix directory tree. pub mode: tree::EntryMode, /// The full path to the entry. A root entry would be `d`, and a file `a` within the directory would be `d/a`. /// /// This is independent of the platform and the path separators actually used there. pub filepath: BString, /// The id of the entry which can be used to locate it in an object database. pub oid: ObjectId, } impl Entry { fn new(entry: &tree::EntryRef<'_>, filepath: BString) -> Self { Entry { filepath, oid: entry.oid.to_owned(), mode: entry.mode, } } } impl Default for Recorder { fn default() -> Self { Recorder { path_deque: Default::default(), path: Default::default(), location: Location::Path.into(), records: vec![], } } } impl Recorder { fn pop_element(&mut self) { if let Some(pos) = self.path.rfind_byte(b'/') { self.path.resize(pos, 0); } else { self.path.clear(); } } fn push_element(&mut self, name: &BStr) { if name.is_empty() { return; } if !self.path.is_empty() { self.path.push(b'/'); } self.path.push_str(name); } } /// Builder impl Recorder { /// Obtain a copy of the currently tracked, full path of the entry. pub fn track_location(mut self, location: Option) -> Self { self.location = location; self } } /// Access impl Recorder { /// Obtain a copy of the currently tracked, full path of the entry. pub fn path_clone(&self) -> BString { self.path.clone() } /// Return the currently set path. pub fn path(&self) -> &BStr { self.path.as_ref() } } impl Visit for Recorder { fn pop_back_tracked_path_and_set_current(&mut self) { if let Some(Location::Path) = self.location { self.path = self.path_deque.pop_back().unwrap_or_default(); } } fn pop_front_tracked_path_and_set_current(&mut self) { if let Some(Location::Path) = self.location { self.path = self .path_deque .pop_front() .expect("every call is matched with push_tracked_path_component"); } } fn push_back_tracked_path_component(&mut self, component: &BStr) { if let Some(Location::Path) = self.location { self.push_element(component); self.path_deque.push_back(self.path.clone()); } } fn push_path_component(&mut self, component: &BStr) { match self.location { None => {} Some(Location::Path) => { self.push_element(component); } Some(Location::FileName) => { self.path.clear(); self.path.extend_from_slice(component); } } } fn pop_path_component(&mut self) { if let Some(Location::Path) = self.location { self.pop_element(); } } fn visit_tree(&mut self, entry: &tree::EntryRef<'_>) -> Action { self.records.push(Entry::new(entry, self.path_clone())); Action::Continue } fn visit_nontree(&mut self, entry: &tree::EntryRef<'_>) -> Action { self.records.push(Entry::new(entry, self.path_clone())); Action::Continue } }