debian-analyzer-0.158.8/.cargo_vcs_info.json0000644000000001460000000000100142650ustar { "git": { "sha1": "31e754c0aadde97d2e353969eaa726f28e0dab6a" }, "path_in_vcs": "analyzer" }debian-analyzer-0.158.8/Cargo.lock0000644000001542630000000000100122520ustar # This file is automatically @generated by Cargo. # It is not intended for manual editing. version = 3 [[package]] name = "addr2line" version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" dependencies = [ "gimli", ] [[package]] name = "adler" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" [[package]] name = "aho-corasick" version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" dependencies = [ "memchr", ] [[package]] name = "android-tzdata" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" [[package]] name = "android_system_properties" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" dependencies = [ "libc", ] [[package]] name = "anstream" version = "0.6.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64e15c1ab1f89faffbf04a634d5e1962e9074f2741eef6d97f3c4e322426d526" dependencies = [ "anstyle", "anstyle-parse", "anstyle-query", "anstyle-wincon", "colorchoice", "is_terminal_polyfill", "utf8parse", ] [[package]] name = "anstyle" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1bec1de6f59aedf83baf9ff929c98f2ad654b97c9510f4e70cf6f661d49fd5b1" [[package]] name = "anstyle-parse" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb47de1e80c2b463c735db5b217a0ddc39d612e7ac9e2e96a5aed1f57616c1cb" dependencies = [ "utf8parse", ] [[package]] name = "anstyle-query" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d36fc52c7f6c869915e99412912f22093507da8d9e942ceaf66fe4b7c14422a" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "anstyle-wincon" version = "3.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5bf74e1b6e971609db8ca7a9ce79fd5768ab6ae46441c572e46cf596f59e57f8" dependencies = [ "anstyle", "windows-sys 0.52.0", ] [[package]] name = "atomic-waker" version = "1.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" [[package]] name = "autocfg" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] name = "backtrace" version = "0.3.73" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" dependencies = [ "addr2line", "cc", "cfg-if", "libc", "miniz_oxide", "object", "rustc-demangle", ] [[package]] name = "base64" version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" [[package]] name = "breezyshim" version = "0.1.160" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "159f64ea49553b73a2c5e6d6d53431c133553730508690baa2c31dce5588b8b3" dependencies = [ "chrono", "ctor", "dirty-tracker", "lazy-regex", "lazy_static", "log", "patchkit", "pyo3", "pyo3-filelike", "serde", "tempfile", "url", ] [[package]] name = "bumpalo" version = "3.16.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" [[package]] name = "bytes" version = "1.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8318a53db07bb3f8dca91a600466bdb3f2eaadeedfdbcf02e1accbad9271ba50" [[package]] name = "cc" version = "1.1.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "26a5c3fd7bfa1ce3897a3a3501d362b2d87b7f2583ebcb4a949ec25911025cbc" [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] name = "chrono" version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ "android-tzdata", "iana-time-zone", "js-sys", "num-traits", "serde", "wasm-bindgen", "windows-targets 0.52.6", ] [[package]] name = "clap" version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fbb260a053428790f3de475e304ff84cdbc4face759ea7a3e64c1edd938a7fc" dependencies = [ "clap_builder", "clap_derive", ] [[package]] name = "clap_builder" version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "64b17d7ea74e9f833c7dbf2cbe4fb12ff26783eda4782a8975b72f895c9b4d99" dependencies = [ "anstream", "anstyle", "clap_lex", "strsim", ] [[package]] name = "clap_derive" version = "4.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "501d359d5f3dcaf6ecdeee48833ae73ec6e42723a1e52419c79abf9507eec0a0" dependencies = [ "heck", "proc-macro2", "quote", "syn 2.0.72", ] [[package]] name = "clap_lex" version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1462739cb27611015575c0c11df5df7601141071f07518d56fcc1be504cbec97" [[package]] name = "colorchoice" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3fd119d74b830634cea2a0f58bbd0d54540518a14397557951e79340abc28c0" [[package]] name = "configparser" version = "3.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e57e3272f0190c3f1584272d613719ba5fc7df7f4942fe542e63d949cf3a649b" [[package]] name = "core-foundation" version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "core-foundation-sys" version = "0.8.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f" [[package]] name = "countme" version = "3.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7704b5fdd17b18ae31c4c1da5a2e0305a2bf17b5249300a9ee9ed7b72114c636" [[package]] name = "crossbeam-channel" version = "0.5.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33480d6946193aa8033910124896ca395333cae7e2d1113d1fef6c3272217df2" dependencies = [ "crossbeam-utils", ] [[package]] name = "crossbeam-utils" version = "0.8.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80" [[package]] name = "csv" version = "1.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe" dependencies = [ "csv-core", "itoa", "ryu", "serde", ] [[package]] name = "csv-core" version = "0.1.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70" dependencies = [ "memchr", ] [[package]] name = "ctor" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f" dependencies = [ "quote", "syn 2.0.72", ] [[package]] name = "deb822-lossless" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11973bdcad319fa3957d75f85c1f85b26b4a5c9f4cf1db350e2dbf58be392e80" dependencies = [ "regex", "rowan", "serde", ] [[package]] name = "debian-analyzer" version = "0.158.8" dependencies = [ "breezyshim", "chrono", "clap", "configparser", "debian-changelog", "debian-control", "debian-copyright", "debversion", "dep3", "distro-info", "env_logger", "lazy-regex", "lazy_static", "log", "makefile-lossless", "maplit", "patchkit", "pyo3", "reqwest", "serde", "serde_json", "tempfile", "url", ] [[package]] name = "debian-changelog" version = "0.1.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9608e140c8637c6e33acc349ba20872ec6184e516957506a51d1a48146ea3e34" dependencies = [ "chrono", "debversion", "lazy-regex", "log", "rowan", "textwrap", "whoami", ] [[package]] name = "debian-control" version = "0.1.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce8a0b4ae16c79a9320638edbf142b395c967b19773c0c5c4f263d4f523836bc" dependencies = [ "deb822-lossless", "debversion", "regex", "rowan", "url", ] [[package]] name = "debian-copyright" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "932841dfa0330c99ba781a7e57643e308dd95db04b96486d8171c827f1713304" dependencies = [ "deb822-lossless", "debversion", "regex", ] [[package]] name = "debversion" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b6c96844ac16a110ee3ea355b7e511ffe0221242caebb2cc5e1bbfd285744cf" dependencies = [ "lazy-regex", "pyo3", "serde", ] [[package]] name = "dep3" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a755dbddee2d3a2af806ad7a305bda8630891ac4b2edbe354b0243c2ef867ec3" dependencies = [ "chrono", "deb822-lossless", ] [[package]] name = "dirty-tracker" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "57f673af5cabab0d10b822fae4b348c2f5fdc56d90474e26f5dcde0f94fce488" dependencies = [ "notify", "tempfile", ] [[package]] name = "distro-info" version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ef12237f2ced990e453ec0b69230752e73be0a357817448c50a62f8bbbe0ca71" dependencies = [ "chrono", "csv", "failure", ] [[package]] name = "encoding_rs" version = "0.8.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b45de904aa0b010bce2ab45264d0631681847fa7b6f2eaa7dab7619943bc4f59" dependencies = [ "cfg-if", ] [[package]] name = "env_filter" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4f2c92ceda6ceec50f43169f9ee8424fe2db276791afde7b2cd8bc084cb376ab" dependencies = [ "log", "regex", ] [[package]] name = "env_logger" version = "0.11.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13fa619b91fb2381732789fc5de83b45675e882f66623b7d8cb4f643017018d" dependencies = [ "anstream", "anstyle", "env_filter", "humantime", "log", ] [[package]] name = "equivalent" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "failure" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d32e9bd16cc02eae7db7ef620b392808b89f6a5e16bb3497d159c6b92a0f4f86" dependencies = [ "backtrace", "failure_derive", ] [[package]] name = "failure_derive" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "aa4da3c766cd7a0db8242e326e9e4e081edd567072893ed320008189715366a4" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", "synstructure", ] [[package]] name = "fastrand" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fc0510504f03c51ada170672ac806f1f105a88aa97a5281117e1ddc3368e51a" [[package]] name = "filetime" version = "0.2.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" dependencies = [ "cfg-if", "libc", "redox_syscall", "windows-sys 0.52.0", ] [[package]] name = "fnv" version = "1.0.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" [[package]] name = "foreign-types" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" dependencies = [ "foreign-types-shared", ] [[package]] name = "foreign-types-shared" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" [[package]] name = "form_urlencoded" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e13624c2627564efccf4934284bdd98cbaa14e79b0b5a141218e507b3a823456" dependencies = [ "percent-encoding", ] [[package]] name = "fsevent-sys" version = "4.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76ee7a02da4d231650c7cea31349b889be2f45ddb3ef3032d2ec8185f6313fd2" dependencies = [ "libc", ] [[package]] name = "futures-channel" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eac8f7d7865dcb88bd4373ab671c8cf4508703796caa2b1985a9ca867b3fcb78" dependencies = [ "futures-core", "futures-sink", ] [[package]] name = "futures-core" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dfc6580bb841c5a68e9ef15c77ccc837b40a7504914d52e47b8b0e9bbda25a1d" [[package]] name = "futures-io" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44623e20b9681a318efdd71c299b6b222ed6f231972bfe2f224ebad6311f0c1" [[package]] name = "futures-sink" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fb8e00e87438d937621c1c6269e53f536c14d3fbd6a042bb24879e57d474fb5" [[package]] name = "futures-task" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38d84fa142264698cdce1a9f9172cf383a0c82de1bddcf3092901442c4097004" [[package]] name = "futures-util" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3d6401deb83407ab3da39eba7e33987a73c3df0c82b4bb5813ee871c19c41d48" dependencies = [ "futures-core", "futures-io", "futures-sink", "futures-task", "memchr", "pin-project-lite", "pin-utils", "slab", ] [[package]] name = "getrandom" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", "libc", "wasi", ] [[package]] name = "gimli" version = "0.29.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" [[package]] name = "h2" version = "0.4.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fa82e28a107a8cc405f0839610bdc9b15f1e25ec7d696aa5cf173edbcb1486ab" dependencies = [ "atomic-waker", "bytes", "fnv", "futures-core", "futures-sink", "http", "indexmap", "slab", "tokio", "tokio-util", "tracing", ] [[package]] name = "hashbrown" version = "0.14.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" [[package]] name = "heck" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" [[package]] name = "hermit-abi" version = "0.3.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" [[package]] name = "http" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21b9ddb458710bc376481b842f5da65cdf31522de232c1ca8146abce2a358258" dependencies = [ "bytes", "fnv", "itoa", ] [[package]] name = "http-body" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1efedce1fb8e6913f23e0c92de8e62cd5b772a67e7b3946df930a62566c93184" dependencies = [ "bytes", "http", ] [[package]] name = "http-body-util" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "793429d76616a256bcb62c2a2ec2bed781c8307e797e2598c50010f2bee2544f" dependencies = [ "bytes", "futures-util", "http", "http-body", "pin-project-lite", ] [[package]] name = "httparse" version = "1.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" [[package]] name = "humantime" version = "2.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a3a5bfb195931eeb336b2a7b4d761daec841b97f947d34394601737a7bba5e4" [[package]] name = "hyper" version = "1.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50dfd22e0e76d0f662d429a5f80fcaf3855009297eab6a0a9f8543834744ba05" dependencies = [ "bytes", "futures-channel", "futures-util", "h2", "http", "http-body", "httparse", "itoa", "pin-project-lite", "smallvec", "tokio", "want", ] [[package]] name = "hyper-rustls" version = "0.27.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ee4be2c948921a1a5320b629c4193916ed787a7f7f293fd3f7f5a6c9de74155" dependencies = [ "futures-util", "http", "hyper", "hyper-util", "rustls", "rustls-pki-types", "tokio", "tokio-rustls", "tower-service", ] [[package]] name = "hyper-tls" version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70206fc6890eaca9fde8a0bf71caa2ddfc9fe045ac9e5c70df101a7dbde866e0" dependencies = [ "bytes", "http-body-util", "hyper", "hyper-util", "native-tls", "tokio", "tokio-native-tls", "tower-service", ] [[package]] name = "hyper-util" version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3ab92f4f49ee4fb4f997c784b7a2e0fa70050211e0b6a287f898c3c9785ca956" dependencies = [ "bytes", "futures-channel", "futures-util", "http", "http-body", "hyper", "pin-project-lite", "socket2", "tokio", "tower", "tower-service", "tracing", ] [[package]] name = "iana-time-zone" version = "0.1.60" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" dependencies = [ "android_system_properties", "core-foundation-sys", "iana-time-zone-haiku", "js-sys", "wasm-bindgen", "windows-core", ] [[package]] name = "iana-time-zone-haiku" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" dependencies = [ "cc", ] [[package]] name = "idna" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" dependencies = [ "unicode-bidi", "unicode-normalization", ] [[package]] name = "indexmap" version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3fc2e30ba82dd1b3911c8de1ffc143c74a914a14e99514d7637e3099df5ea0" dependencies = [ "equivalent", "hashbrown", ] [[package]] name = "indoc" version = "2.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b248f5224d1d606005e02c97f5aa4e88eeb230488bcc03bc9ca4d7991399f2b5" [[package]] name = "inotify" version = "0.9.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8069d3ec154eb856955c1c0fbffefbf5f3c40a104ec912d4797314c1801abff" dependencies = [ "bitflags 1.3.2", "inotify-sys", "libc", ] [[package]] name = "inotify-sys" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e05c02b5e89bff3b946cedeca278abc628fe811e604f027c45a8aa3cf793d0eb" dependencies = [ "libc", ] [[package]] name = "ipnet" version = "2.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f518f335dce6725a761382244631d86cf0ccb2863413590b31338feb467f9c3" [[package]] name = "is_terminal_polyfill" version = "1.70.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf" [[package]] name = "itoa" version = "1.0.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" dependencies = [ "wasm-bindgen", ] [[package]] name = "kqueue" version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7447f1ca1b7b563588a205fe93dea8df60fd981423a768bc1c0ded35ed147d0c" dependencies = [ "kqueue-sys", "libc", ] [[package]] name = "kqueue-sys" version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed9625ffda8729b85e45cf04090035ac368927b8cebc34898e7c120f52e4838b" dependencies = [ "bitflags 1.3.2", "libc", ] [[package]] name = "lazy-regex" version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "576c8060ecfdf2e56995cf3274b4f2d71fa5e4fa3607c1c0b63c10180ee58741" dependencies = [ "lazy-regex-proc_macros", "once_cell", "regex", ] [[package]] name = "lazy-regex-proc_macros" version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9efb9e65d4503df81c615dc33ff07042a9408ac7f26b45abee25566f7fbfd12c" dependencies = [ "proc-macro2", "quote", "regex", "syn 2.0.72", ] [[package]] name = "lazy_static" version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" [[package]] name = "libc" version = "0.2.155" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "97b3888a4aecf77e811145cadf6eef5901f4782c53886191b2f693f24761847c" [[package]] name = "linux-raw-sys" version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" [[package]] name = "log" version = "0.4.22" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" [[package]] name = "makefile-lossless" version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "98528c9312dcce9d03baaf1912a93f852c2deb18fa5673797e4126987a1f5a76" dependencies = [ "log", "rowan", ] [[package]] name = "maplit" version = "1.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3e2e65a1a2e43cfcb47a895c4c8b10d1f4a61097f9f254f183aee60cad9c651d" [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "memoffset" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "488016bfae457b036d996092f6cb448677611ce4449e970ceaf42695203f218a" dependencies = [ "autocfg", ] [[package]] name = "mime" version = "0.3.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6877bb514081ee2a7ff5ef9de3281f14a4dd4bceac4c09388074a6b5df8a139a" [[package]] name = "miniz_oxide" version = "0.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" dependencies = [ "adler", ] [[package]] name = "mio" version = "0.8.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" dependencies = [ "libc", "log", "wasi", "windows-sys 0.48.0", ] [[package]] name = "mio" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4569e456d394deccd22ce1c1913e6ea0e54519f577285001215d33557431afe4" dependencies = [ "hermit-abi", "libc", "wasi", "windows-sys 0.52.0", ] [[package]] name = "native-tls" version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8614eb2c83d59d1c8cc974dd3f920198647674a0a035e1af1fa58707e317466" dependencies = [ "libc", "log", "openssl", "openssl-probe", "openssl-sys", "schannel", "security-framework", "security-framework-sys", "tempfile", ] [[package]] name = "notify" version = "6.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6205bd8bb1e454ad2e27422015fb5e4f2bcc7e08fa8f27058670d208324a4d2d" dependencies = [ "bitflags 2.6.0", "crossbeam-channel", "filetime", "fsevent-sys", "inotify", "kqueue", "libc", "log", "mio 0.8.11", "walkdir", "windows-sys 0.48.0", ] [[package]] name = "num-traits" version = "0.2.19" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" dependencies = [ "autocfg", ] [[package]] name = "object" version = "0.36.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e" dependencies = [ "memchr", ] [[package]] name = "once_cell" version = "1.19.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" [[package]] name = "openssl" version = "0.10.66" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9529f4786b70a3e8c61e11179af17ab6188ad8d0ded78c5529441ed39d4bd9c1" dependencies = [ "bitflags 2.6.0", "cfg-if", "foreign-types", "libc", "once_cell", "openssl-macros", "openssl-sys", ] [[package]] name = "openssl-macros" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a948666b637a0f465e8564c73e89d4dde00d72d4d473cc972f390fc3dcee7d9c" dependencies = [ "proc-macro2", "quote", "syn 2.0.72", ] [[package]] name = "openssl-probe" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ff011a302c396a5197692431fc1948019154afc178baf7d8e37367442a4601cf" [[package]] name = "openssl-sys" version = "0.9.103" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7f9e8deee91df40a943c71b917e5874b951d32a802526c85721ce3b776c929d6" dependencies = [ "cc", "libc", "pkg-config", "vcpkg", ] [[package]] name = "patchkit" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1628449e4d9f7e8501e9389daf6242adfd8284c2364a3f2a87e16176756b6768" dependencies = [ "chrono", "lazy-regex", "lazy_static", "once_cell", "regex", ] [[package]] name = "percent-encoding" version = "2.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" [[package]] name = "pin-project" version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6bf43b791c5b9e34c3d182969b4abb522f9343702850a2e57f460d00d09b4b3" dependencies = [ "pin-project-internal", ] [[package]] name = "pin-project-internal" version = "1.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2f38a4412a78282e09a2cf38d195ea5420d15ba0602cb375210efbc877243965" dependencies = [ "proc-macro2", "quote", "syn 2.0.72", ] [[package]] name = "pin-project-lite" version = "0.2.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bda66fc9667c18cb2758a2ac84d1167245054bcf85d5d1aaa6923f45801bdd02" [[package]] name = "pin-utils" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" [[package]] name = "pkg-config" version = "0.3.30" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" [[package]] name = "portable-atomic" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da544ee218f0d287a911e9c99a39a8c9bc8fcad3cb8db5959940044ecfc67265" [[package]] name = "proc-macro2" version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "pyo3" version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "831e8e819a138c36e212f3af3fd9eeffed6bf1510a805af35b0edee5ffa59433" dependencies = [ "cfg-if", "chrono", "indoc", "libc", "memoffset", "once_cell", "portable-atomic", "pyo3-build-config", "pyo3-ffi", "pyo3-macros", "serde", "unindent", ] [[package]] name = "pyo3-build-config" version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e8730e591b14492a8945cdff32f089250b05f5accecf74aeddf9e8272ce1fa8" dependencies = [ "once_cell", "target-lexicon", ] [[package]] name = "pyo3-ffi" version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e97e919d2df92eb88ca80a037969f44e5e70356559654962cbb3316d00300c6" dependencies = [ "libc", "pyo3-build-config", ] [[package]] name = "pyo3-filelike" version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e4d4ba4774757be317f112fb7b09f9d2c157a77f07d229a08144f275223f06e8" dependencies = [ "pyo3", ] [[package]] name = "pyo3-macros" version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eb57983022ad41f9e683a599f2fd13c3664d7063a3ac5714cae4b7bee7d3f206" dependencies = [ "proc-macro2", "pyo3-macros-backend", "quote", "syn 2.0.72", ] [[package]] name = "pyo3-macros-backend" version = "0.22.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec480c0c51ddec81019531705acac51bcdbeae563557c982aa8263bb96880372" dependencies = [ "heck", "proc-macro2", "pyo3-build-config", "quote", "syn 2.0.72", ] [[package]] name = "quote" version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] [[package]] name = "redox_syscall" version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa" dependencies = [ "bitflags 1.3.2", ] [[package]] name = "regex" version = "1.10.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4219d74c6b67a3654a9fbebc4b419e22126d13d2f3c4a07ee0cb61ff79a79619" dependencies = [ "aho-corasick", "memchr", "regex-automata", "regex-syntax", ] [[package]] name = "regex-automata" version = "0.4.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "38caf58cc5ef2fed281f89292ef23f6365465ed9a41b7a7754eb4e26496c92df" dependencies = [ "aho-corasick", "memchr", "regex-syntax", ] [[package]] name = "regex-syntax" version = "0.8.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a66a03ae7c801facd77a29370b4faec201768915ac14a721ba36f20bc9c209b" [[package]] name = "reqwest" version = "0.12.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7d6d2a27d57148378eb5e111173f4276ad26340ecc5c49a4a2152167a2d6a37" dependencies = [ "base64", "bytes", "encoding_rs", "futures-channel", "futures-core", "futures-util", "h2", "http", "http-body", "http-body-util", "hyper", "hyper-rustls", "hyper-tls", "hyper-util", "ipnet", "js-sys", "log", "mime", "native-tls", "once_cell", "percent-encoding", "pin-project-lite", "rustls-pemfile", "serde", "serde_json", "serde_urlencoded", "sync_wrapper", "system-configuration", "tokio", "tokio-native-tls", "tower-service", "url", "wasm-bindgen", "wasm-bindgen-futures", "web-sys", "winreg", ] [[package]] name = "ring" version = "0.17.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c17fa4cb658e3583423e915b9f3acc01cceaee1860e33d59ebae66adc3a2dc0d" dependencies = [ "cc", "cfg-if", "getrandom", "libc", "spin", "untrusted", "windows-sys 0.52.0", ] [[package]] name = "rowan" version = "0.15.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32a58fa8a7ccff2aec4f39cc45bf5f985cec7125ab271cf681c279fd00192b49" dependencies = [ "countme", "hashbrown", "memoffset", "rustc-hash", "text-size", ] [[package]] name = "rustc-demangle" version = "0.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" [[package]] name = "rustc-hash" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" [[package]] name = "rustix" version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ "bitflags 2.6.0", "errno", "libc", "linux-raw-sys", "windows-sys 0.52.0", ] [[package]] name = "rustls" version = "0.23.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c58f8c84392efc0a126acce10fa59ff7b3d2ac06ab451a33f2741989b806b044" dependencies = [ "once_cell", "rustls-pki-types", "rustls-webpki", "subtle", "zeroize", ] [[package]] name = "rustls-pemfile" version = "2.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" dependencies = [ "base64", "rustls-pki-types", ] [[package]] name = "rustls-pki-types" version = "1.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "976295e77ce332211c0d24d92c0e83e50f5c5f046d11082cea19f3df13a3562d" [[package]] name = "rustls-webpki" version = "0.102.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8e6b52d4fda176fd835fdc55a835d4a89b8499cad995885a21149d5ad62f852e" dependencies = [ "ring", "rustls-pki-types", "untrusted", ] [[package]] name = "ryu" version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" [[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" dependencies = [ "winapi-util", ] [[package]] name = "schannel" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbc91545643bcf3a0bbb6569265615222618bdf33ce4ffbbd13c4bbd4c093534" dependencies = [ "windows-sys 0.52.0", ] [[package]] name = "security-framework" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "897b2245f0b511c87893af39b033e5ca9cce68824c4d7e7630b5a1d339658d02" dependencies = [ "bitflags 2.6.0", "core-foundation", "core-foundation-sys", "libc", "security-framework-sys", ] [[package]] name = "security-framework-sys" version = "2.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "75da29fe9b9b08fe9d6b22b5b4bcbc75d8db3aa31e639aa56bb62e9d46bfceaf" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "serde" version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", "syn 2.0.72", ] [[package]] name = "serde_json" version = "1.0.122" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "784b6203951c57ff748476b126ccb5e8e2959a5c19e5c617ab1956be3dbc68da" dependencies = [ "itoa", "memchr", "ryu", "serde", ] [[package]] name = "serde_urlencoded" version = "0.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d3491c14715ca2294c4d6a88f15e84739788c1d030eed8c110436aafdaa2f3fd" dependencies = [ "form_urlencoded", "itoa", "ryu", "serde", ] [[package]] name = "slab" version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f92a496fb766b417c996b9c5e57daf2f7ad3b0bebe1ccfca4856390e3d3bb67" dependencies = [ "autocfg", ] [[package]] name = "smallvec" version = "1.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" [[package]] name = "smawk" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "socket2" version = "0.5.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ce305eb0b4296696835b71df73eb912e0f1ffd2556a501fcede6e0c50349191c" dependencies = [ "libc", "windows-sys 0.52.0", ] [[package]] name = "spin" version = "0.9.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67" [[package]] name = "strsim" version = "0.11.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "subtle" version = "2.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "13c2bddecc57b384dee18652358fb23172facb8a2c51ccc10d74c157bdea3292" [[package]] name = "syn" version = "1.0.109" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "syn" version = "2.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" dependencies = [ "proc-macro2", "quote", "unicode-ident", ] [[package]] name = "sync_wrapper" version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a7065abeca94b6a8a577f9bd45aa0867a2238b74e8eb67cf10d492bc39351394" [[package]] name = "synstructure" version = "0.12.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f36bdaa60a83aca3921b5259d5400cbf5e90fc51931376a9bd4a0eb79aa7210f" dependencies = [ "proc-macro2", "quote", "syn 1.0.109", "unicode-xid", ] [[package]] name = "system-configuration" version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba3a3adc5c275d719af8cb4272ea1c4a6d668a777f37e115f6d11ddbc1c8e0e7" dependencies = [ "bitflags 1.3.2", "core-foundation", "system-configuration-sys", ] [[package]] name = "system-configuration-sys" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75fb188eb626b924683e3b95e3a48e63551fcfb51949de2f06a9d91dbee93c9" dependencies = [ "core-foundation-sys", "libc", ] [[package]] name = "target-lexicon" version = "0.12.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "61c41af27dd6d1e27b1b16b489db798443478cef1f06a660c96db617ba5de3b1" [[package]] name = "tempfile" version = "3.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8fcd239983515c23a32fb82099f97d0b11b8c72f654ed659363a95c3dad7a53" dependencies = [ "cfg-if", "fastrand", "once_cell", "rustix", "windows-sys 0.52.0", ] [[package]] name = "text-size" version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f18aa187839b2bdb1ad2fa35ead8c4c2976b64e4363c386d45ac0f7ee85c9233" [[package]] name = "textwrap" version = "0.16.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23d434d3f8967a09480fb04132ebe0a3e088c173e6d0ee7897abbdf4eab0f8b9" dependencies = [ "smawk", "unicode-linebreak", "unicode-width", ] [[package]] name = "tinyvec" version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "445e881f4f6d382d5f27c034e25eb92edd7c784ceab92a0937db7f2e9471b938" 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 = "tokio" version = "1.39.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" dependencies = [ "backtrace", "bytes", "libc", "mio 1.0.1", "pin-project-lite", "socket2", "windows-sys 0.52.0", ] [[package]] name = "tokio-native-tls" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bbae76ab933c85776efabc971569dd6119c580d8f5d448769dec1764bf796ef2" dependencies = [ "native-tls", "tokio", ] [[package]] name = "tokio-rustls" version = "0.26.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c7bc40d0e5a97695bb96e27995cd3a08538541b0a846f65bba7a359f36700d4" dependencies = [ "rustls", "rustls-pki-types", "tokio", ] [[package]] name = "tokio-util" version = "0.7.11" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9cf6b47b3771c49ac75ad09a6162f53ad4b8088b76ac60e8ec1455b31a189fe1" dependencies = [ "bytes", "futures-core", "futures-sink", "pin-project-lite", "tokio", ] [[package]] name = "tower" version = "0.4.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8fa9be0de6cf49e536ce1851f987bd21a43b771b09473c3549a6c853db37c1c" dependencies = [ "futures-core", "futures-util", "pin-project", "pin-project-lite", "tokio", "tower-layer", "tower-service", ] [[package]] name = "tower-layer" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c20c8dbed6283a09604c3e69b4b7eeb54e298b8a600d4d5ecb5ad39de609f1d0" [[package]] name = "tower-service" version = "0.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6bc1c9ce2b5135ac7f93c72918fc37feb872bdc6a5533a8b85eb4b86bfdae52" [[package]] name = "tracing" version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" dependencies = [ "pin-project-lite", "tracing-core", ] [[package]] name = "tracing-core" version = "0.1.32" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" dependencies = [ "once_cell", ] [[package]] name = "try-lock" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "unicode-bidi" version = "0.3.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "08f95100a766bf4f8f28f90d77e0a5461bbdb219042e7679bebe79004fed8d75" [[package]] name = "unicode-ident" version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" [[package]] name = "unicode-linebreak" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "3b09c83c3c29d37506a3e260c08c03743a6bb66a9cd432c6934ab501a190571f" [[package]] name = "unicode-normalization" version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a56d1686db2308d901306f92a263857ef59ea39678a5458e7cb17f01415101f5" dependencies = [ "tinyvec", ] [[package]] name = "unicode-width" version = "0.1.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0336d538f7abc86d282a4189614dfaa90810dfc2c6f6427eaf88e16311dd225d" [[package]] name = "unicode-xid" version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" [[package]] name = "unindent" version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c7de7d73e1754487cb58364ee906a499937a0dfabd86bcb980fa99ec8c8fa2ce" [[package]] name = "untrusted" version = "0.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8ecb6da28b8a351d773b68d5825ac39017e680750f980f3a1a85cd8dd28a47c1" [[package]] name = "url" version = "2.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "22784dbdf76fdde8af1aeda5622b546b422b6fc585325248a2bf9f5e41e94d6c" dependencies = [ "form_urlencoded", "idna", "percent-encoding", "serde", ] [[package]] name = "utf8parse" version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" [[package]] name = "vcpkg" version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" [[package]] name = "walkdir" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" dependencies = [ "same-file", "winapi-util", ] [[package]] name = "want" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bfa7760aed19e106de2c7c0b581b509f2f25d3dacaf737cb82ac61bc6d760b0e" dependencies = [ "try-lock", ] [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] name = "wasite" version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b" [[package]] name = "wasm-bindgen" version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" dependencies = [ "cfg-if", "wasm-bindgen-macro", ] [[package]] name = "wasm-bindgen-backend" version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" dependencies = [ "bumpalo", "log", "once_cell", "proc-macro2", "quote", "syn 2.0.72", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-futures" version = "0.4.42" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "76bc14366121efc8dbb487ab05bcc9d346b3b5ec0eaa76e46594cabbe51762c0" dependencies = [ "cfg-if", "js-sys", "wasm-bindgen", "web-sys", ] [[package]] name = "wasm-bindgen-macro" version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" dependencies = [ "quote", "wasm-bindgen-macro-support", ] [[package]] name = "wasm-bindgen-macro-support" version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" dependencies = [ "proc-macro2", "quote", "syn 2.0.72", "wasm-bindgen-backend", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" version = "0.2.92" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" [[package]] name = "web-sys" version = "0.3.69" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77afa9a11836342370f4817622a2f0f418b134426d91a82dfb48f532d2ec13ef" dependencies = [ "js-sys", "wasm-bindgen", ] [[package]] name = "whoami" version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a44ab49fad634e88f55bf8f9bb3abd2f27d7204172a112c7c9987e01c1c94ea9" dependencies = [ "redox_syscall", "wasite", ] [[package]] name = "winapi-util" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "cf221c93e13a30d793f7645a0e7762c55d169dbb0a49671918a2319d289b10bb" dependencies = [ "windows-sys 0.59.0", ] [[package]] name = "windows-core" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.48.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" dependencies = [ "windows-targets 0.48.5", ] [[package]] name = "windows-sys" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-sys" version = "0.59.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" dependencies = [ "windows-targets 0.52.6", ] [[package]] name = "windows-targets" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" dependencies = [ "windows_aarch64_gnullvm 0.48.5", "windows_aarch64_msvc 0.48.5", "windows_i686_gnu 0.48.5", "windows_i686_msvc 0.48.5", "windows_x86_64_gnu 0.48.5", "windows_x86_64_gnullvm 0.48.5", "windows_x86_64_msvc 0.48.5", ] [[package]] name = "windows-targets" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" dependencies = [ "windows_aarch64_gnullvm 0.52.6", "windows_aarch64_msvc 0.52.6", "windows_i686_gnu 0.52.6", "windows_i686_gnullvm", "windows_i686_msvc 0.52.6", "windows_x86_64_gnu 0.52.6", "windows_x86_64_gnullvm 0.52.6", "windows_x86_64_msvc 0.52.6", ] [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" [[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.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" [[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.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" [[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.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" [[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.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" [[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.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" [[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.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" [[package]] name = "windows_x86_64_msvc" version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" [[package]] name = "winreg" version = "0.52.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a277a57398d4bfa075df44f501a17cfdf8542d224f0d36095a2adc7aee4ef0a5" dependencies = [ "cfg-if", "windows-sys 0.48.0", ] [[package]] name = "zeroize" version = "1.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ced3678a2879b30306d323f4542626697a464a97c0a07c9aebf7ebca65cd4dde" debian-analyzer-0.158.8/Cargo.toml0000644000000050530000000000100122650ustar # 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" name = "debian-analyzer" version = "0.158.8" authors = ["Jelmer Vernooij "] build = false autobins = false autoexamples = false autotests = false autobenches = false description = "Debian analyzer" homepage = "https://salsa.debian.org/jelmer/lintian-brush" documentation = "https://docs.rs/debian-analyzer" readme = false license = "GPL-2.0+" repository = "https://salsa.debian.org/jelmer/lintian-brush.git" [lib] name = "debian_analyzer" path = "src/lib.rs" [[bin]] name = "deb-vcs-publish" path = "src/bin/deb-vcs-publish.rs" required-features = ["cli"] [[bin]] name = "detect-changelog-behaviour" path = "src/bin/detect-changelog-behaviour.rs" required-features = ["cli"] [dependencies.breezyshim] version = ">=0.1.160" features = ["dirty-tracker"] [dependencies.chrono] version = ">=0.4" features = ["serde"] [dependencies.clap] version = "4,<5" features = [ "derive", "env", "suggestions", "cargo", "string", ] optional = true [dependencies.configparser] version = "3" [dependencies.debian-changelog] version = ">=0.1" [dependencies.debian-control] version = ">=0.1" [dependencies.debian-copyright] version = "0.1.2" [dependencies.debversion] version = ">=0.3.0" features = [ "serde", "python-debian", ] [dependencies.dep3] version = "0.1.0" [dependencies.distro-info] version = ">=0.4.0" [dependencies.env_logger] version = ">=0.10" optional = true [dependencies.lazy-regex] version = ">=2" [dependencies.lazy_static] version = "1.4.0" [dependencies.log] version = "0.4" [dependencies.makefile-lossless] version = "0.1.0" [dependencies.maplit] version = "1.0.2" [dependencies.patchkit] version = "0.1.0" [dependencies.pyo3] version = ">=0.22" features = [ "serde", "chrono", ] [dependencies.reqwest] version = ">=0.11" features = [ "blocking", "json", ] [dependencies.serde] version = "1" features = ["derive"] [dependencies.serde_json] version = "1" [dependencies.tempfile] version = "3" [dependencies.url] version = "2" [features] cli = [ "dep:clap", "dep:env_logger", ] default = ["python"] python = [] debian-analyzer-0.158.8/Cargo.toml.orig000064400000000000000000000030561046102023000157470ustar 00000000000000[package] name = "debian-analyzer" version = { workspace = true } authors = [ "Jelmer Vernooij "] edition = "2021" license = "GPL-2.0+" description = "Debian analyzer" repository = "https://salsa.debian.org/jelmer/lintian-brush.git" homepage = "https://salsa.debian.org/jelmer/lintian-brush" documentation = "https://docs.rs/debian-analyzer" [dependencies] pyo3 = { workspace = true, features = ["serde", "chrono"] } debversion = { workspace = true, features = ["serde", "python-debian"] } log = { workspace = true } lazy-regex.workspace = true serde = { workspace = true, features = ["derive"] } breezyshim.workspace = true configparser.workspace = true clap = { workspace = true, features = ["derive", "env", "suggestions", "cargo", "string"], optional = true } env_logger = { workspace = true, optional = true } serde_json.workspace = true chrono = { workspace = true, features = ["serde"] } distro-info = { version = ">=0.4.0" } url = { workspace = true } tempfile.workspace = true maplit = "1.0.2" lazy_static = "1.4.0" debian-changelog = { workspace = true } debian-control = { workspace = true } debian-copyright = "0.1.2" makefile-lossless = "0.1.0" patchkit = "0.1.0" dep3 = "0.1.0" reqwest = { workspace = true, features = ["blocking", "json"] } [features] default = ["python"] python = [] cli = ["dep:clap", "dep:env_logger"] [lib] [[bin]] name = "detect-changelog-behaviour" path = "src/bin/detect-changelog-behaviour.rs" required-features = ["cli"] [[bin]] name = "deb-vcs-publish" path = "src/bin/deb-vcs-publish.rs" required-features = ["cli"] debian-analyzer-0.158.8/src/bin/deb-vcs-publish.rs000064400000000000000000000103151046102023000177500ustar 00000000000000use breezyshim::controldir::open; use breezyshim::error::Error; use breezyshim::tree::WorkingTree; use clap::Parser; use debian_analyzer::publish::{create_vcs_url, update_official_vcs}; use debian_changelog::get_maintainer; use debian_analyzer::get_committer; use std::io::Write as _; #[derive(clap::Args, Clone, Debug)] #[group()] struct OutputArgs {} #[derive(Parser, Debug)] #[command(author, version)] struct Args { /// Enable debug output #[arg(long, default_value_t = false)] debug: bool, /// Print user identity that would be used when committing #[arg(long, default_value_t = false)] identity: bool, /// directory to run in #[arg(short, long, default_value = std::env::current_dir().unwrap().into_os_string(), value_name = "DIR")] directory: std::path::PathBuf, /// Do not create the repository #[arg(default_value_t = false)] no_create: bool, #[arg(default_value_t = false)] force: bool, /// Push branch #[arg(default_value_t = false)] push: bool, url: Option, } fn main() -> Result<(), Box> { let args = Args::parse(); env_logger::builder() .format(|buf, record| writeln!(buf, "{}", record.args())) .filter( None, if args.debug { log::LevelFilter::Debug } else { log::LevelFilter::Info }, ) .init(); breezyshim::init(); let (wt, subpath) = match WorkingTree::open_containing(&args.directory) { Ok((wt, subpath)) => (wt, subpath.display().to_string()), Err(Error::NotBranchError(_msg, _)) => { log::error!("No version control directory found (e.g. a .git directory)."); std::process::exit(1); } Err(Error::DependencyNotPresent(name, _reason)) => { log::error!( "Unable to open tree at {}: missing package {}", args.directory.display(), name ); std::process::exit(1); } Err(e) => { log::error!("Unable to open tree at {}: {}", args.directory.display(), e); std::process::exit(1); } }; if args.identity { println!("Committer identity: {}", get_committer(&wt)); let (maintainer, email) = get_maintainer().unwrap_or(("".to_string(), "".to_string())); println!("Changelog identity: {} <{}>", maintainer, email); std::process::exit(0); } let parsed_vcs = match update_official_vcs( &wt, std::path::Path::new(subpath.as_str()), args.url.as_ref(), None, None, Some(args.force), ) { Ok(o) => o, Err(e) => { log::error!("Unable to update official VCS: {}", e); std::process::exit(1); } }; let repo_url: url::Url = parsed_vcs.repo_url.parse().unwrap(); if !args.no_create { match create_vcs_url(&repo_url, parsed_vcs.branch.as_deref()) { Ok(()) => {} Err(Error::UnsupportedForge(_)) => { log::error!("Unable to find a way to create {}", repo_url); } Err(Error::ForgeProjectExists(..)) | Err(Error::AlreadyControlDir(..)) => { log::error!("Unable to create {}: already exists", repo_url); std::process::exit(1); } Err(Error::ForgeLoginRequired) => { log::error!("Unable to create {}: login required", repo_url); std::process::exit(1); } Err(e) => { log::error!("Unable to create {}: {}", repo_url, e); std::process::exit(1); } } } let controldir = open(&repo_url, None).unwrap(); let branch = match controldir.open_branch(parsed_vcs.branch.as_deref()) { Ok(branch) => branch, Err(Error::NotBranchError(_, _)) => controldir .create_branch(parsed_vcs.branch.as_deref()) .unwrap(), Err(e) => { log::error!("Unable to open or create branch: {}", e); std::process::exit(1); } }; wt.branch() .push(branch.as_ref(), false, None, None) .unwrap(); Ok(()) } debian-analyzer-0.158.8/src/bin/detect-changelog-behaviour.rs000064400000000000000000000024711046102023000221440ustar 00000000000000use breezyshim::tree::WorkingTree; use clap::Parser; use std::io::Write as _; #[derive(Parser)] #[command(author, version)] struct Args { /// Be verbose #[clap(long)] verbose: bool, /// The directory to check #[clap(default_value = ".")] directory: std::path::PathBuf, } fn main() { let args = Args::parse(); env_logger::builder() .format(|buf, record| writeln!(buf, "{}", record.args())) .filter( None, if args.verbose { log::LevelFilter::Debug } else { log::LevelFilter::Info }, ) .init(); breezyshim::init(); let (wt, subpath) = WorkingTree::open_containing(&args.directory).unwrap(); let debian_path = if debian_analyzer::control_files_in_root(&wt, subpath.as_path()) { subpath } else { subpath.join("debian") }; let changelog_behaviour = debian_analyzer::detect_gbp_dch::guess_update_changelog(&wt, debian_path.as_path(), None); if let Some(changelog_behaviour) = changelog_behaviour { log::info!("{}", changelog_behaviour.explanation); println!("{}", changelog_behaviour.update_changelog); } else { log::info!("Unable to determine changelog updating behaviour"); std::process::exit(1) } } debian-analyzer-0.158.8/src/changelog.rs000064400000000000000000000227071046102023000161500ustar 00000000000000use breezyshim::error::Error; use breezyshim::tree::{Tree, TreeChange, WorkingTree}; use debian_changelog::ChangeLog; /// Check whether the only change in a tree is to the last changelog entry. /// /// # Arguments /// * `tree`: Tree to analyze /// * `changelog_path`: Path to the changelog file /// * `changes`: Changes in the tree pub fn only_changes_last_changelog_block<'a>( tree: &WorkingTree, basis_tree: &dyn Tree, changelog_path: &std::path::Path, changes: impl Iterator, ) -> Result { let read_lock = tree.lock_read(); let basis_lock = basis_tree.lock_read(); let mut changes_seen = false; for change in changes { if let Some(path) = change.path.1.as_ref() { if path == std::path::Path::new("") { continue; } if path == changelog_path { changes_seen = true; continue; } if !tree.has_versioned_directories() && changelog_path.starts_with(path) { // Directory leading up to changelog continue; } } // If the change is not in the changelog, it's not just a changelog change return Ok(false); } if !changes_seen { // Doesn't change the changelog at all return Ok(false); } let mut new_cl = match tree.get_file(changelog_path) { Ok(f) => ChangeLog::read(f)?, Err(Error::NoSuchFile(_)) => { return Ok(false); } Err(e) => { panic!("Error reading changelog: {}", e); } }; let mut old_cl = match basis_tree.get_file(changelog_path) { Ok(f) => ChangeLog::read(f)?, Err(Error::NoSuchFile(_)) => { return Ok(true); } Err(e) => { panic!("Error reading changelog: {}", e); } }; let first_entry = if let Some(e) = new_cl.pop_first() { e } else { // No entries return Ok(false); }; if first_entry.distributions().as_deref() != Some(&["UNRELEASED".into()]) { // Not unreleased return Ok(false); } old_cl.pop_first(); std::mem::drop(read_lock); std::mem::drop(basis_lock); Ok(new_cl.to_string() == old_cl.to_string()) } pub fn find_last_distribution(cl: &ChangeLog) -> Option { for block in cl.entries() { if block.is_unreleased() != Some(true) { if let Some(distributions) = block.distributions() { if distributions.len() == 1 { return Some(distributions[0].clone()); } } } } None } pub const DEBIAN_POCKETS: &[&str] = &["", "-security", "-proposed-updates", "-backports"]; pub const UBUNTU_POCKETS: &[&str] = &["", "-proposed", "-updates", "-security", "-backports"]; /// Given a tree, find the previous upload to the distribution. /// /// When e.g. Ubuntu merges from Debian they want to build with /// -vPREV_VERSION. Here's where we find that previous version. /// /// We look at the last changelog entry and find the upload target. /// We then search backwards until we find the same target. That's /// the previous version that we return. /// /// We require there to be a previous version, otherwise we throw /// an error. /// /// It's not a simple string comparison to find the same target in /// a previous version, as we should consider old series in e.g. /// Ubuntu. pub fn find_previous_upload(changelog: &ChangeLog) -> Option { let current_target = find_last_distribution(changelog)?; // multiple debian pockets with all debian releases let all_debian = crate::release_info::debian_releases() .iter() .flat_map(|r| DEBIAN_POCKETS.iter().map(move |t| format!("{}{}", r, t))) .collect::>(); let all_ubuntu = crate::release_info::ubuntu_releases() .iter() .flat_map(|r| UBUNTU_POCKETS.iter().map(move |t| format!("{}{}", r, t))) .collect::>(); let match_targets = if all_debian.contains(¤t_target) { vec![current_target] } else if all_ubuntu.contains(¤t_target) { let mut match_targets = crate::release_info::ubuntu_releases(); if current_target.contains('-') { let distro = current_target.split('-').next().unwrap(); match_targets.extend(DEBIAN_POCKETS.iter().map(|r| format!("{}{}", r, distro))); } match_targets } else { // If we do not recognize the current target in order to apply special // rules to it, then just assume that only previous uploads to exactly // the same target count. vec![current_target] }; for block in changelog.entries().skip(1) { if match_targets.contains(&block.distributions().unwrap()[0]) { return block.version().clone(); } } None } #[derive(Debug)] pub enum FindChangelogError { MissingChangelog(Vec), AddChangelog(std::path::PathBuf), ChangelogParseError(String), BrzError(breezyshim::error::Error), } impl std::fmt::Display for FindChangelogError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { FindChangelogError::MissingChangelog(files) => { write!(f, "No changelog found in {:?}", files) } FindChangelogError::AddChangelog(file) => { write!(f, "Add a changelog at {:?}", file) } FindChangelogError::ChangelogParseError(e) => write!(f, "{}", e), FindChangelogError::BrzError(e) => write!(f, "{}", e), } } } impl std::error::Error for FindChangelogError {} impl From for FindChangelogError { fn from(e: breezyshim::error::Error) -> Self { FindChangelogError::BrzError(e) } } /// Find the changelog in the given tree. /// /// First looks for 'debian/changelog'. If "merge" is true will also /// look for 'changelog'. /// /// The returned changelog is created with 'allow_empty_author=True' /// as some people do this but still want to build. /// 'max_blocks' defaults to 1 to try and prevent old broken /// changelog entries from causing the command to fail. /// /// "top_level" is a subset of "merge" mode. It indicates that the /// '.bzr' dir is at the same level as 'changelog' etc., rather /// than being at the same level as 'debian/'. /// /// # Arguments /// * `tree`: Tree to look in /// * `subpath`: Path to the changelog file /// * `merge`: Whether this is a "merge" package /// /// # Returns /// * (changelog, top_level) where changelog is the Changelog, /// and top_level is a boolean indicating whether the file is /// located at 'changelog' (rather than 'debian/changelog') if /// merge was given, False otherwise. pub fn find_changelog( tree: &dyn Tree, subpath: &std::path::Path, merge: Option, ) -> Result<(ChangeLog, bool), FindChangelogError> { let mut top_level = false; let lock = tree.lock_read(); let mut changelog_file = subpath.join("debian/changelog"); if !tree.has_filename(&changelog_file) { let mut checked_files = vec![changelog_file.clone()]; let changelog_file = if merge.unwrap_or(false) { // Assume LarstiQ's layout (.bzr in debian/) let changelog_file = subpath.join("changelog"); top_level = true; if !tree.has_filename(&changelog_file) { checked_files.push(changelog_file); None } else { Some(changelog_file) } } else { None }; if changelog_file.is_none() { return Err(FindChangelogError::MissingChangelog(checked_files)); } } else if merge.unwrap_or(true) && tree.has_filename(&subpath.join("changelog")) { // If it is a "top_level" package and debian is a symlink to // "." then it will have found debian/changelog. Try and detect // this. let debian_file = subpath.join("debian"); if tree.is_versioned(&debian_file) && tree.kind(&debian_file)? == breezyshim::tree::Kind::Symlink && tree.get_symlink_target(&debian_file)?.as_path() == std::path::Path::new(".") { changelog_file = "changelog".into(); top_level = true; } } log::debug!( "Using '{}' to get package information", changelog_file.display() ); if !tree.is_versioned(&changelog_file) { return Err(FindChangelogError::AddChangelog(changelog_file)); } let contents = tree.get_file_text(&changelog_file)?; std::mem::drop(lock); let changelog = ChangeLog::read_relaxed(contents.as_slice()).unwrap(); Ok((changelog, top_level)) } #[cfg(test)] mod tests { #[test] fn test_find_previous_upload() { let cl = r#"test (1.0-1) unstable; urgency=medium * Initial release. -- Test User Fri, 01 Jan 2021 00:00:00 +0000 "# .parse() .unwrap(); assert_eq!(super::find_previous_upload(&cl), None); let cl = r#"test (1.0-1) unstable; urgency=medium * More change. -- Test User Fri, 01 Jan 2021 00:00:00 +0000 test (1.0-0) unstable; urgency=medium * Initial release. -- Test User Fri, 01 Jan 2021 00:00:00 +0000 "# .parse() .unwrap(); assert_eq!( super::find_previous_upload(&cl), Some("1.0-0".parse().unwrap()) ); } } debian-analyzer-0.158.8/src/config.rs000064400000000000000000000061051046102023000154600ustar 00000000000000//! Lintian-brush configuration file. use crate::Certainty; use breezyshim::tree::WorkingTree; use configparser::ini::Ini; use log::warn; const SUPPORTED_KEYS: &[&str] = &[ "compat-release", "minimum-certainty", "allow-reformatting", "update-changelog", ]; pub const PACKAGE_CONFIG_FILENAME: &str = "debian/lintian-brush.conf"; pub struct Config { obj: Ini, } impl Config { pub fn from_workingtree( tree: &WorkingTree, subpath: &std::path::Path, ) -> std::io::Result { let path = tree .abspath(&subpath.join(PACKAGE_CONFIG_FILENAME)) .unwrap(); Self::load_from_path(&path) } pub fn load_from_path(path: &std::path::Path) -> Result { let mut ini = Ini::new(); let data = std::fs::read_to_string(path)?; ini.read(data) .map_err(|e| std::io::Error::new(std::io::ErrorKind::Other, e))?; for (section, contents) in ini.get_map_ref() { if section != "default" { warn!( "unknown section {} in {}, ignoring.", section, path.display() ); continue; } for key in contents.keys() { if !SUPPORTED_KEYS.contains(&key.as_str()) { warn!( "unknown key {} in section {} in {}, ignoring.", key, section, path.display() ); continue; } } } Ok(Config { obj: ini }) } pub fn compat_release(&self) -> Option { if let Some(value) = self.obj.get("default", "compat-release") { let codename = crate::release_info::resolve_release_codename(value.as_str(), None); if codename.is_none() { warn!("unknown compat release {}, ignoring.", value); } codename } else { None } } pub fn allow_reformatting(&self) -> Option { match self.obj.getbool("default", "allow-reformatting") { Ok(value) => value, Err(e) => { warn!("invalid allow-reformatting value {}, ignoring.", e); None } } } pub fn minimum_certainty(&self) -> Option { self.obj .get("default", "minimum-certainty") .and_then(|value| { value .parse::() .map_err(|e| { warn!("invalid minimum-certainty value {}, ignoring.", value); e }) .ok() }) } pub fn update_changelog(&self) -> Option { match self.obj.getbool("default", "update-changelog") { Ok(value) => value, Err(e) => { warn!("invalid update-changelog value {}, ignoring.", e); None } } } } debian-analyzer-0.158.8/src/debmutateshim.rs000064400000000000000000000337541046102023000170600ustar 00000000000000use debversion::Version; use pyo3::exceptions::{PyKeyError, PyRuntimeError, PyValueError}; use pyo3::prelude::*; use pyo3::types::{PyDict, PyTuple}; use std::path::Path; pub struct Deb822Paragraph(pub(crate) PyObject); pub trait ControlLikeEditor: ToPyObject { fn source(&self) -> Option; fn binaries(&self) -> Vec; } impl ToPyObject for Deb822Paragraph { fn to_object(&self, py: Python) -> PyObject { self.0.to_object(py) } } impl Deb822Paragraph { pub fn set(&self, key: &str, value: &str) { Python::with_gil(|py| { self.0 .call_method1(py, "__setitem__", (key, value)) .unwrap(); }) } pub fn remove(&self, key: &str) { Python::with_gil(|py| { self.0.call_method1(py, "remove", (key,)).unwrap(); }) } pub fn get(&self, key: &str) -> Option { Python::with_gil(|py| { let result = self.0.call_method1(py, "get", (key,)).unwrap(); if result.is_none(py) { None } else { Some(result.extract(py).unwrap()) } }) } } pub struct ControlEditor(PyObject); impl ToPyObject for ControlEditor { fn to_object(&self, py: Python) -> PyObject { self.0.to_object(py) } } impl ControlEditor { pub fn new() -> Self { Python::with_gil(|py| { let editor = py .import_bound("debmutate.control") .unwrap() .getattr("ControlEditor") .unwrap() .call0() .unwrap(); let o = editor.to_object(py); o.call_method0(py, "__enter__").unwrap(); ControlEditor(o) }) } pub fn open(path: Option<&Path>, allow_reformatting: Option) -> Self { Python::with_gil(|py| { let path = path.map_or_else(|| "debian/control", |p| p.to_str().unwrap()); let kwargs = PyDict::new_bound(py); if let Some(allow_reformatting) = allow_reformatting { kwargs .set_item("allow_reformatting", allow_reformatting) .unwrap(); } let control = py .import_bound("debmutate.control") .unwrap() .call_method("ControlEditor", (path,), Some(&kwargs)) .unwrap(); let o = control.to_object(py); o.call_method0(py, "__enter__").unwrap(); ControlEditor(o) }) } pub fn create(path: Option<&Path>) -> Self { Python::with_gil(|py| { let path = path.map_or_else(|| "debian/control", |p| p.to_str().unwrap()); let control = py .import_bound("debmutate.control") .unwrap() .getattr("ControlEditor") .unwrap() .call_method1("create", (path,)) .unwrap(); let o = control.to_object(py); o.call_method0(py, "__enter__").unwrap(); ControlEditor(o) }) } } impl Default for ControlEditor { fn default() -> Self { Self::new() } } impl ControlLikeEditor for ControlEditor { fn source(&self) -> Option { Python::with_gil(|py| { let result = self.0.getattr(py, "source").unwrap(); if result.is_none(py) { None } else { Some(Deb822Paragraph(result)) } }) } fn binaries(&self) -> Vec { Python::with_gil(|py| { let elements = self.0.getattr(py, "binaries").unwrap(); let mut binaries = vec![]; for elem in elements.bind(py).iter().unwrap() { let elem = elem.unwrap(); binaries.push(Deb822Paragraph(elem.to_object(py))); } binaries }) } } impl Drop for ControlEditor { fn drop(&mut self) { Python::with_gil(|py| { self.0 .call_method1(py, "__exit__", (py.None(), py.None(), py.None())) .unwrap(); }) } } #[derive(Debug, PartialEq, Eq)] pub struct ArchRestriction { pub enabled: bool, pub arch: String, } impl FromPyObject<'_> for ArchRestriction { fn extract_bound(ob: &Bound) -> PyResult { let enabled = ob.getattr("enabled")?.extract()?; let arch = ob.getattr("arch")?.extract()?; Ok(ArchRestriction { enabled, arch }) } } impl ToPyObject for ArchRestriction { fn to_object(&self, py: Python) -> PyObject { let enabled = self.enabled.to_object(py); let arch = self.arch.to_object(py); let ar_cls = py .import_bound("debmutate.control") .unwrap() .getattr("PkgRelation") .unwrap() .getattr("ArchRestriction") .unwrap(); ar_cls.call1((enabled, arch)).unwrap().to_object(py) } } #[derive(Debug, PartialEq, Eq)] pub struct BuildRestriction { pub enabled: bool, pub profile: String, } impl FromPyObject<'_> for BuildRestriction { fn extract_bound(ob: &Bound) -> PyResult { let enabled = ob.getattr("enabled")?.extract()?; let profile = ob.getattr("profile")?.extract()?; Ok(BuildRestriction { enabled, profile }) } } impl ToPyObject for BuildRestriction { fn to_object(&self, py: Python) -> PyObject { let enabled = self.enabled.to_object(py); let profile = self.profile.to_object(py); let br_cls = py .import_bound("debmutate.control") .unwrap() .getattr("PkgRelation") .unwrap() .getattr("BuildRestriction") .unwrap(); br_cls.call1((enabled, profile)).unwrap().to_object(py) } } #[derive(Debug, PartialEq, Eq)] pub struct VersionConstraint { pub operator: String, pub version: Version, } impl VersionConstraint { pub fn check(&self, version: &Version) -> bool { match self.operator.as_str() { "=" => version == &self.version, "<<" => version < &self.version, "<=" => version <= &self.version, ">>" => version > &self.version, ">=" => version >= &self.version, _ => panic!("Unknown operator {}", self.operator), } } } impl FromPyObject<'_> for VersionConstraint { fn extract_bound(ob: &Bound) -> PyResult { // Extract operator and version from ob (a tuple) if ob.len()? != 2 { return Err(PyValueError::new_err( "VersionConstraint must be a tuple of length 2", )); } let operator = ob.get_item(0)?.extract()?; let version = ob.get_item(1)?; if let Ok(version) = version.extract() { Ok(VersionConstraint { operator, version }) } else { Ok(VersionConstraint { operator, version: version.extract::()?.parse().unwrap(), }) } } } impl ToPyObject for VersionConstraint { fn to_object(&self, py: Python) -> PyObject { let operator = self.operator.to_object(py); let version = self.version.to_object(py); PyTuple::new_bound(py, vec![operator, version]).to_object(py) } } #[derive(Debug, PartialEq, Eq)] pub struct ParsedRelation { pub name: String, pub archqual: Option, pub version: Option, pub arch: Option>, pub restrictions: Option>, } pub struct PkgRelation(Vec>); impl FromPyObject<'_> for ParsedRelation { fn extract_bound(ob: &Bound) -> PyResult { let name = ob.getattr("name")?.extract()?; let archqual = ob.getattr("archqual")?.extract()?; let version = ob.getattr("version")?.extract()?; let arch = ob.getattr("arch")?.extract()?; let restrictions = ob.getattr("restrictions")?.extract()?; Ok(ParsedRelation { name, archqual, version, arch, restrictions, }) } } impl ToPyObject for ParsedRelation { fn to_object(&self, py: Python) -> PyObject { let pr_cls = py .import_bound("debmutate._deb822") .unwrap() .getattr("PkgRelation") .unwrap(); let ret = PyDict::new_bound(py); ret.set_item("name", self.name.to_object(py)).unwrap(); ret.set_item("archqual", self.archqual.to_object(py)) .unwrap(); ret.set_item("version", self.version.to_object(py)).unwrap(); ret.set_item("arch", self.arch.to_object(py)).unwrap(); ret.set_item("restrictions", self.restrictions.to_object(py)) .unwrap(); pr_cls.call((), Some(&ret)).unwrap().to_object(py) } } pub fn format_relations(relations: &[(&str, &[ParsedRelation], &str)]) -> String { Python::with_gil(|py| { let relations = relations.to_object(py); println!("relations: {}", relations); let result = py .import_bound("debmutate.control") .unwrap() .call_method1("format_relations", (relations,)); result.unwrap().extract().unwrap() }) } pub fn parse_relations(relations: &str) -> Vec<(String, Vec, String)> { Python::with_gil(|py| { let result = py .import_bound("debmutate.control") .unwrap() .call_method1("parse_relations", (relations,)) .unwrap(); result.extract().unwrap() }) } pub fn source_package_vcs(control: &Deb822Paragraph) -> Option<(String, String)> { Python::with_gil(|py| { let control = control.to_object(py); match py .import_bound("debmutate.control") .unwrap() .call_method1("source_package_vcs", (control,)) { Ok(o) => o.extract::>().unwrap(), Err(e) if e.is_instance_of::(py) => None, Err(e) => panic!("unexpected error: {}", e), } }) } pub struct DebcargoControlShimEditor(PyObject); impl ToPyObject for DebcargoControlShimEditor { fn to_object(&self, py: Python) -> PyObject { self.0.to_object(py) } } impl DebcargoControlShimEditor { pub fn from_debian_dir(debian_dir: &Path) -> Self { Python::with_gil(|py| { let control = py .import_bound("debmutate.control") .unwrap() .getattr("DebcargoControlShimEditor") .unwrap() .call_method1("from_debian_dir", (debian_dir,)) .unwrap(); DebcargoControlShimEditor(control.into()) }) } } impl ControlLikeEditor for DebcargoControlShimEditor { fn source(&self) -> Option { Python::with_gil(|py| { let result = self.0.getattr(py, "source").unwrap(); if result.is_none(py) { None } else { Some(Deb822Paragraph(result)) } }) } fn binaries(&self) -> Vec { Python::with_gil(|py| { let elements = self.0.getattr(py, "binaries").unwrap(); let mut binaries = vec![]; for elem in elements.bind(py).iter().unwrap() { let elem = elem.unwrap(); binaries.push(Deb822Paragraph(elem.to_object(py))); } binaries }) } } pub fn edit_control( editor: &mut dyn ControlLikeEditor, cb: impl FnOnce(&mut dyn ControlLikeEditor) -> Result, ) -> Result { Python::with_gil(|py| { editor.to_object(py).call_method0(py, "__enter__").unwrap(); let result = cb(editor); let exc = if let Err(e) = &result { ( PyRuntimeError::new_err((e.to_string(),)).into_py(py), py.None(), py.None(), ) } else { (py.None(), py.None(), py.None()) }; editor .to_object(py) .call_method1(py, "__exit__", exc) .unwrap(); result }) } #[cfg(test)] mod tests { #[test] fn test_create() { use super::*; let td = tempfile::tempdir().unwrap(); let ce = ControlEditor::create(Some(td.path().join("control").as_path())); assert!(ce.source().is_some()); assert!(ce.binaries().is_empty()); } #[test] fn parse_relations() { assert_eq!( super::parse_relations("foo (>= 1.0) [amd64]"), vec![( "".to_string(), vec![super::ParsedRelation { name: "foo".to_string(), archqual: None, version: Some(super::VersionConstraint { operator: ">=".to_string(), version: "1.0".parse().unwrap() }), arch: Some(vec![super::ArchRestriction { enabled: true, arch: "amd64".to_string() }]), restrictions: None }], "".to_string() )] ); } #[test] fn format_relations() { assert_eq!( super::format_relations(&[( "", &[super::ParsedRelation { name: "foo".to_string(), archqual: None, version: Some(super::VersionConstraint { operator: ">=".to_string(), version: "1.0".parse().unwrap() }), arch: Some(vec![super::ArchRestriction { enabled: true, arch: "amd64".to_string() }]), restrictions: None }], "" )]), "foo (>= 1.0) [amd64]" ); } } debian-analyzer-0.158.8/src/detect_gbp_dch.rs000064400000000000000000000262731046102023000171410ustar 00000000000000use breezyshim::branch::Branch; use breezyshim::error::Error; use breezyshim::graph::{Error as GraphError, Graph}; use breezyshim::revisionid::RevisionId; use breezyshim::tree::{Tree, WorkingTree}; use debian_changelog::{ChangeLog, Entry as ChangeLogEntry}; use lazy_regex::regex; #[derive(Debug, serde::Serialize, serde::Deserialize, Clone, PartialEq, Eq)] pub struct ChangelogBehaviour { #[serde(rename = "update")] pub update_changelog: bool, pub explanation: String, } impl From for (bool, String) { fn from(b: ChangelogBehaviour) -> Self { (b.update_changelog, b.explanation) } } impl From<&ChangelogBehaviour> for (bool, String) { fn from(b: &ChangelogBehaviour) -> Self { (b.update_changelog, b.explanation.clone()) } } // Number of revisions to search back const DEFAULT_BACKLOG: usize = 50; // TODO(jelmer): Check that what's added in the changelog is actually based on // what was in the commit messages? pub fn gbp_conf_has_dch_section(tree: &dyn Tree, debian_path: &std::path::Path) -> bool { let gbp_conf_path = debian_path.join("gbp.conf"); let gbp_conf_text = match tree.get_file_text(gbp_conf_path.as_path()) { Ok(text) => text, Err(Error::NoSuchFile(_)) => return false, Err(e) => panic!("Unexpected error reading gbp.conf: {:?}", e), }; let mut parser = configparser::ini::Ini::new(); parser .read(String::from_utf8_lossy(gbp_conf_text.as_slice()).to_string()) .unwrap(); parser.sections().contains(&"dch".to_string()) } /// Guess whether the changelog should be updated. /// /// # Arguments /// * `tree` - Tree to edit /// * `debian_path` - Path to packaging in tree /// /// # Returns /// * `None` if it is not possible to guess /// * `True` if the changelog should be updated /// * `False` if the changelog should not be updated pub fn guess_update_changelog( tree: &WorkingTree, debian_path: &std::path::Path, mut cl: Option, ) -> Option { if debian_path != std::path::Path::new("debian") { return Some(ChangelogBehaviour{ update_changelog: true, explanation: "assuming changelog needs to be updated since gbp dch only supports a debian directory in the root of the repository".to_string(), }); } let changelog_path = debian_path.join("changelog"); if cl.is_none() { match tree.get_file(changelog_path.as_path()) { Ok(f) => { cl = Some(ChangeLog::read(f).unwrap()); } Err(Error::NoSuchFile(_)) => { log::debug!("No changelog found"); } Err(e) => { panic!("Unexpected error reading changelog: {:?}", e); } } } if let Some(ref cl) = cl { if debian_changelog::is_unreleased_inaugural(cl) { return Some(ChangelogBehaviour { update_changelog: false, explanation: "assuming changelog does not need to be updated since it is the inaugural unreleased entry".to_string() }); } if let Some(first_entry) = cl.entries().next() { for line in first_entry.change_lines() { if line.contains("generated at release time") { return Some(ChangelogBehaviour { update_changelog: false, explanation: "last changelog entry warns changelog is generated at release time" .to_string(), }); } } } } if let Some(ret) = guess_update_changelog_from_tree(tree, debian_path, cl) { Some(ret) } else { guess_update_changelog_from_branch(tree.branch().as_ref(), debian_path, None) } } pub fn guess_update_changelog_from_tree( tree: &dyn Tree, debian_path: &std::path::Path, cl: Option, ) -> Option { if gbp_conf_has_dch_section(tree, debian_path) { return Some(ChangelogBehaviour { update_changelog: false, explanation: "Assuming changelog does not need to be updated, since there is a [dch] section in gbp.conf.".to_string() }); } // TODO(jelmes): Do something more clever here, perhaps looking at history of the changelog file? if let Some(cl) = cl { if let Some(entry) = cl.entries().next() { if all_sha_prefixed(&entry) { return Some(ChangelogBehaviour { update_changelog: false, explanation: "Assuming changelog does not need to be updated, since all entries in last changelog entry are prefixed by git shas.".to_string() }); } } } None } pub fn greedy_revisions( graph: &Graph, revid: &RevisionId, length: usize, ) -> (Vec, bool) { let mut ret = vec![]; let mut it = graph.iter_lefthand_ancestry(revid, None); while ret.len() < length { ret.push(match it.next() { None => break, Some(Ok(rev)) => rev, Some(Err(GraphError::RevisionNotPresent(_))) => { if !ret.is_empty() { ret.pop(); } // Shallow history return (ret, true); } }); } (ret, false) } #[derive(Debug, Default)] struct ChangelogStats { mixed: usize, changelog_only: usize, other_only: usize, dch_references: usize, unreleased_references: usize, } fn changelog_stats( branch: &dyn Branch, history: usize, debian_path: &std::path::Path, ) -> ChangelogStats { let mut ret = ChangelogStats::default(); let branch_lock = branch.lock_read(); let graph = branch.repository().get_graph(); let (revids, _truncated) = greedy_revisions(&graph, &branch.last_revision(), history); let mut revs = vec![]; for (_revid, rev) in branch.repository().iter_revisions(revids) { if rev.is_none() { // Ghost continue; } let rev = rev.unwrap(); if rev.message.contains("Git-Dch: ") || rev.message.contains("Gbp-Dch: ") { ret.dch_references += 1; } revs.push(rev); } for (rev, delta) in revs.iter().zip( branch .repository() .get_revision_deltas(revs.as_slice(), None), ) { let mut filenames = vec![]; for a in delta.added { if let Some(p) = a.path.1 { filenames.push(p.clone()); } } for r in delta.removed { if let Some(p) = r.path.0 { filenames.push(p.clone()); } } for r in delta.renamed { if let Some(p) = r.path.0 { filenames.push(p.clone()); } if let Some(p) = r.path.1 { filenames.push(p.clone()); } } for m in delta.modified { if let Some(p) = m.path.0 { filenames.push(p.clone()); } } if !filenames.iter().any(|f| f.starts_with(debian_path)) { continue; } let cl_path = debian_path.join("changelog"); if filenames.contains(&cl_path) { let revtree = branch.repository().revision_tree(&rev.revision_id).unwrap(); match revtree.get_file_lines(cl_path.as_path()) { Err(Error::NoSuchFile(_p)) => {} Err(e) => { panic!("Error reading changelog: {}", e); } Ok(cl_lines) => { if String::from_utf8_lossy(cl_lines[0].as_slice()).contains("UNRELEASED") { ret.unreleased_references += 1; } } } if filenames.len() > 1 { ret.mixed += 1; } else { ret.changelog_only += 1; } } else { ret.other_only += 1; } } std::mem::drop(branch_lock); ret } /// Guess whether the changelog should be updated manually. /// /// # Arguments /// /// * `branch` - A branch object /// * `debian_path` - Path to the debian directory /// * `history` - Number of revisions back to analyze /// /// # Returns /// /// boolean indicating whether changelog should be updated pub fn guess_update_changelog_from_branch( branch: &dyn Branch, debian_path: &std::path::Path, history: Option, ) -> Option { let history = history.unwrap_or(DEFAULT_BACKLOG); // Two indications this branch may be doing changelog entries at // release time: // - "Git-Dch: " or "Gbp-Dch: " is used in the commit messages // - The vast majority of lines in changelog get added in // commits that only touch the changelog let stats = changelog_stats(branch, history, debian_path); log::debug!("Branch history analysis: changelog_only: {}, other_only: {}, mixed: {}, dch_references: {}, unreleased_references: {}", stats.changelog_only, stats.other_only, stats.mixed, stats.dch_references, stats.unreleased_references); if stats.dch_references > 0 { return Some(ChangelogBehaviour { update_changelog: false, explanation: "Assuming changelog does not need to be updated, since there are Gbp-Dch stanzas in commit messages".to_string() }); } if stats.changelog_only == 0 { return Some(ChangelogBehaviour { update_changelog: true, explanation: "Assuming changelog needs to be updated, since it is always changed together with other files in the tree.".to_string() }); } if stats.unreleased_references == 0 { return Some(ChangelogBehaviour { update_changelog: false, explanation: "Assuming changelog does not need to be updated, since it never uses UNRELEASED entries".to_string() }); } if stats.mixed == 0 && stats.changelog_only > 0 && stats.other_only > 0 { // changelog is *always* updated in a separate commit. return Some(ChangelogBehaviour { update_changelog: false, explanation: "Assuming changelog does not need to be updated, since changelog entries are always updated in separate commits.".to_string() }); } // Is this a reasonable threshold? if stats.changelog_only > stats.mixed && stats.other_only > stats.mixed { return Some(ChangelogBehaviour{ update_changelog: false, explanation: "Assuming changelog does not need to be updated, since changelog entries are usually updated in separate commits.".to_string() }); } None } /// This is generally done by gbp-dch(1). /// /// # Arguments /// /// * `cl` - Changelog entry #[deprecated(note = "Use debian_changelog::changes::all_sha_prefixed instead")] pub fn all_sha_prefixed(cb: &ChangeLogEntry) -> bool { let mut sha_prefixed = 0; for change in cb.change_lines() { if !change.starts_with("* ") { continue; } if regex!(r"\* \[[0-9a-f]{7}\] ").is_match(change.as_str()) { sha_prefixed += 1; } else { return false; } } sha_prefixed > 0 } debian-analyzer-0.158.8/src/lib.rs000064400000000000000000000272531046102023000147700ustar 00000000000000use breezyshim::branch::Branch; use breezyshim::dirty_tracker::DirtyTreeTracker; use breezyshim::error::Error; use breezyshim::tree::{MutableTree, Tree, TreeChange, WorkingTree}; use breezyshim::workspace::reset_tree_with_dirty_tracker; use debian_changelog::ChangeLog; use pyo3::prelude::*; #[cfg(feature = "python")] use std::str::FromStr; pub mod changelog; pub mod config; pub mod debmutateshim; pub mod detect_gbp_dch; pub mod patches; pub mod publish; pub mod release_info; pub mod salsa; pub mod svp; pub mod vcs; // TODO(jelmer): Import this from ognibuild pub const DEFAULT_BUILDER: &str = "sbuild --no-clean-source"; #[derive(Debug)] pub enum ApplyError { /// Error from the callback CallbackError(E), /// Error from the tree BrzError(Error), /// No changes made NoChanges(R), } impl From for ApplyError { fn from(e: Error) -> Self { ApplyError::BrzError(e) } } /// Apply a change in a clean tree. /// /// This will either run a callback in a tree, or if the callback fails, /// revert the tree to the original state. /// /// The original tree should be clean; you can run check_clean_tree() to /// verify this. /// /// # Arguments /// * `local_tree` - Local tree /// * `subpath` - Subpath to apply changes to /// * `basis_tree` - Basis tree to reset to /// * `dirty_tracker` - Dirty tracker /// * `applier` - Callback to apply changes /// /// # Returns /// * `Result<(R, Vec, Option>), E>` - Result of the callback, /// the changes made, and the files that were changed pub fn apply_or_revert( local_tree: &WorkingTree, subpath: &std::path::Path, basis_tree: &dyn Tree, dirty_tracker: Option<&mut DirtyTreeTracker>, applier: impl FnOnce(&std::path::Path) -> Result, ) -> Result<(R, Vec, Option>), ApplyError> { let r = match applier(local_tree.abspath(subpath).unwrap().as_path()) { Ok(r) => r, Err(e) => { reset_tree_with_dirty_tracker( local_tree, Some(basis_tree), Some(subpath), dirty_tracker, ) .unwrap(); return Err(ApplyError::CallbackError(e)); } }; let specific_files = if let Some(relpaths) = dirty_tracker.and_then(|x| x.relpaths()) { let mut relpaths: Vec<_> = relpaths.into_iter().collect(); relpaths.sort(); // Sort paths so that directories get added before the files they // contain (on VCSes where it matters) local_tree.add( relpaths .iter() .filter_map(|p| { if local_tree.has_filename(p) && local_tree.is_ignored(p).is_some() { Some(p.as_path()) } else { None } }) .collect::>() .as_slice(), )?; let specific_files = relpaths .into_iter() .filter(|p| local_tree.is_versioned(p)) .collect::>(); if specific_files.is_empty() { return Err(ApplyError::NoChanges(r)); } Some(specific_files) } else { local_tree.smart_add(&[local_tree.abspath(subpath).unwrap().as_path()])?; if subpath.as_os_str().is_empty() { None } else { Some(vec![subpath.to_path_buf()]) } }; if local_tree.supports_setting_file_ids() { breezyshim::rename_map::guess_renames(basis_tree, local_tree).unwrap(); } let specific_files_ref = specific_files .as_ref() .map(|fs| fs.iter().map(|p| p.as_path()).collect::>()); let changes = local_tree .iter_changes( basis_tree, specific_files_ref.as_deref(), Some(false), Some(true), )? .collect::, _>>()?; if local_tree.get_parent_ids()?.len() <= 1 && changes.is_empty() { return Err(ApplyError::NoChanges(r)); } Ok((r, changes, specific_files)) } pub enum ChangelogError { NotDebianPackage(std::path::PathBuf), Python(pyo3::PyErr), } impl std::fmt::Display for ChangelogError { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { match self { ChangelogError::NotDebianPackage(path) => { write!(f, "Not a Debian package: {}", path.display()) } ChangelogError::Python(e) => write!(f, "{}", e), } } } #[cfg(feature = "python")] impl From for ChangelogError { fn from(e: pyo3::PyErr) -> Self { use pyo3::import_exception; import_exception!(breezy.transport, NoSuchFile); pyo3::Python::with_gil(|py| { if e.is_instance_of::(py) { return ChangelogError::NotDebianPackage( e.into_value(py) .bind(py) .getattr("path") .unwrap() .extract() .unwrap(), ); } else { ChangelogError::Python(e) } }) } } pub fn add_changelog_entry( working_tree: &WorkingTree, changelog_path: &std::path::Path, entry: &[&str], ) -> Result<(), ChangelogError> { let f = match working_tree.get_file(changelog_path) { Ok(f) => f, Err(Error::NoSuchFile(_)) => { return Err(ChangelogError::NotDebianPackage( working_tree.abspath(changelog_path).unwrap(), )) } Err(e) => panic!("Unexpected error: {}", e), }; let mut cl = ChangeLog::read(f).unwrap(); cl.auto_add_change( entry, debian_changelog::get_maintainer().unwrap(), None, None, ); working_tree .put_file_bytes_non_atomic(changelog_path, cl.to_string().as_bytes()) .unwrap(); Ok(()) } #[derive( Clone, Copy, PartialEq, Eq, Debug, Default, PartialOrd, Ord, serde::Serialize, serde::Deserialize, )] pub enum Certainty { #[serde(rename = "possible")] Possible, #[serde(rename = "likely")] Likely, #[serde(rename = "confident")] Confident, #[default] #[serde(rename = "certain")] Certain, } impl std::str::FromStr for Certainty { type Err = String; fn from_str(value: &str) -> Result { match value { "certain" => Ok(Certainty::Certain), "confident" => Ok(Certainty::Confident), "likely" => Ok(Certainty::Likely), "possible" => Ok(Certainty::Possible), _ => Err(format!("Invalid certainty: {}", value)), } } } impl std::fmt::Display for Certainty { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { match self { Certainty::Certain => write!(f, "certain"), Certainty::Confident => write!(f, "confident"), Certainty::Likely => write!(f, "likely"), Certainty::Possible => write!(f, "possible"), } } } #[cfg(feature = "python")] impl pyo3::FromPyObject<'_> for Certainty { fn extract_bound(ob: &pyo3::Bound) -> pyo3::PyResult { let s = ob.extract::()?; Certainty::from_str(&s).map_err(pyo3::exceptions::PyValueError::new_err) } } #[cfg(feature = "python")] impl pyo3::ToPyObject for Certainty { fn to_object(&self, py: pyo3::Python) -> pyo3::PyObject { self.to_string().to_object(py) } } /// Check if the actual certainty is sufficient. /// /// # Arguments /// /// * `actual_certainty` - Actual certainty with which changes were made /// * `minimum_certainty` - Minimum certainty to keep changes /// /// # Returns /// /// * `bool` - Whether the actual certainty is sufficient pub fn certainty_sufficient( actual_certainty: Certainty, minimum_certainty: Option, ) -> bool { if let Some(minimum_certainty) = minimum_certainty { actual_certainty >= minimum_certainty } else { true } } /// Return the minimum certainty from a list of certainties. pub fn min_certainty(certainties: &[Certainty]) -> Option { certainties.iter().min().cloned() } /// Get the committer string for a tree pub fn get_committer(working_tree: &WorkingTree) -> String { pyo3::prepare_freethreaded_python(); pyo3::Python::with_gil(|py| { let m = py.import_bound("lintian_brush")?; let get_committer = m.getattr("get_committer")?; get_committer.call1((&working_tree.0,))?.extract() }) .unwrap() } /// Check whether there are any control files present in a tree. /// /// # Arguments /// /// * `tree`: tree to check /// * `subpath`: subpath to check /// /// # Returns /// /// whether control file is present pub fn control_file_present(tree: &dyn Tree, subpath: &std::path::Path) -> bool { for name in [ "debian/control", "debian/control.in", "control", "control.in", "debian/debcargo.toml", ] { let name = subpath.join(name); if tree.has_filename(name.as_path()) { return true; } } false } pub fn is_debcargo_package(tree: &dyn Tree, subpath: &std::path::Path) -> bool { tree.has_filename(subpath.join("debian/debcargo.toml").as_path()) } pub fn control_files_in_root(tree: &dyn Tree, subpath: &std::path::Path) -> bool { let debian_path = subpath.join("debian"); if tree.has_filename(debian_path.as_path()) { return false; } let control_path = subpath.join("control"); if tree.has_filename(control_path.as_path()) { return true; } tree.has_filename(subpath.join("control.in").as_path()) } pub fn branch_vcs_type(branch: &dyn Branch) -> String { pyo3::prepare_freethreaded_python(); pyo3::Python::with_gil(|py| { let repo = branch.to_object(py).getattr(py, "repository").unwrap(); if repo.bind(py).hasattr("_git").unwrap() { Ok::("git".to_string()) } else { Ok::("bzr".to_string()) } }) .unwrap() } pub fn parseaddr(input: &str) -> Option<(Option, Option)> { if let Some((_whole, name, addr)) = lazy_regex::regex_captures!(r"(?:(?P[^<]*)\s*<)?(?P[^<>]*)>?", input) { let name = match name.trim() { "" => None, x => Some(x.to_string()), }; let addr = match addr.trim() { "" => None, x => Some(x.to_string()), }; return Some((name, addr)); } else if let Some((_whole, addr)) = lazy_regex::regex_captures!(r"(?P[^<>]*)", input) { let addr = Some(addr.trim().to_string()); return Some((None, addr)); } else if input.is_empty() { return None; } else if !input.contains('<') { return Some((None, Some(input.to_string()))); } None } pub fn gbp_dch(path: &std::path::Path) -> Result<(), std::io::Error> { let mut cmd = std::process::Command::new("gbp"); cmd.arg("dch").arg("--ignore-branch"); cmd.current_dir(path); let status = cmd.status()?; if !status.success() { return Err(std::io::Error::new( std::io::ErrorKind::Other, format!("gbp dch failed: {}", status), )); } Ok(()) } #[cfg(test)] mod tests { use super::*; #[test] fn test_parseaddr() { assert_eq!( parseaddr("foo ").unwrap(), (Some("foo".to_string()), Some("bar@example.com".to_string())) ); assert_eq!(parseaddr("foo").unwrap(), (None, Some("foo".to_string()))); } } debian-analyzer-0.158.8/src/patches.rs000064400000000000000000000674721046102023000156600ustar 00000000000000use breezyshim::branch::Branch; use breezyshim::delta::filter_excluded; use breezyshim::error::Error as BrzError; use breezyshim::patches::AppliedPatches; use breezyshim::tree::{MutableTree, Tree, WorkingTree}; use breezyshim::workspace::reset_tree_with_dirty_tracker; use breezyshim::RevisionId; use debian_changelog::ChangeLog; use patchkit::patch::UnifiedPatch; use std::io::Write; use std::path::{Path, PathBuf}; // TODO(jelmer): Use debmutate version pub const DEFAULT_DEBIAN_PATCHES_DIR: &str = "debian/patches"; /// Find the name of the patches directory. /// /// This will always return a path, even if the patches directory does not yet exist. /// /// # Arguments /// /// * `tree` - Tree to check /// * `subpath` - Subpath to check /// /// # Returns /// /// Path to patches directory, or what it should be pub fn tree_patches_directory(tree: &dyn Tree, subpath: &Path) -> PathBuf { find_patches_directory(tree, subpath).unwrap_or(DEFAULT_DEBIAN_PATCHES_DIR.into()) } #[cfg(test)] mod tree_patches_directory_tests { use breezyshim::tree::MutableTree; #[test] fn test_simple() { let td = tempfile::tempdir().unwrap(); let local_tree = breezyshim::controldir::create_standalone_workingtree( td.path(), &breezyshim::controldir::ControlDirFormat::default(), ) .unwrap(); assert_eq!( super::tree_patches_directory(&local_tree, std::path::Path::new("")), std::path::Path::new("debian/patches") ); } #[test] fn test_default() { let td = tempfile::tempdir().unwrap(); let local_tree = breezyshim::controldir::create_standalone_workingtree( td.path(), &breezyshim::controldir::ControlDirFormat::default(), ) .unwrap(); local_tree.mkdir(std::path::Path::new("debian")).unwrap(); local_tree .mkdir(std::path::Path::new("debian/patches")) .unwrap(); assert_eq!( super::tree_patches_directory(&local_tree, std::path::Path::new("")), std::path::Path::new("debian/patches") ); } #[test] fn test_custom() { let td = tempfile::tempdir().unwrap(); let local_tree = breezyshim::controldir::create_standalone_workingtree( td.path(), &breezyshim::controldir::ControlDirFormat::default(), ) .unwrap(); local_tree.mkdir(std::path::Path::new("debian")).unwrap(); local_tree .mkdir(std::path::Path::new("debian/patches")) .unwrap(); local_tree .put_file_bytes_non_atomic( std::path::Path::new("debian/rules"), br#" QUILT_PATCH_DIR := debian/patches-applied all: blah: bloe foo "#, ) .unwrap(); assert_eq!( super::tree_patches_directory(&local_tree, std::path::Path::new("")), std::path::Path::new("debian/patches-applied") ); } } /// Find the name of the patches directory in a debian/rules file pub fn rules_find_patches_directory(mf: &makefile_lossless::Makefile) -> Option { let v = mf .variable_definitions() .find(|v| v.name().as_deref() == Some("QUILT_PATCH_DIR"))?; v.raw_value().map(PathBuf::from) } #[test] fn test_rules_find_patches_directory() { let mf = makefile_lossless::Makefile::read_relaxed( &br#"QUILT_PATCH_DIR := debian/patches-applied "#[..], ) .unwrap(); assert_eq!( rules_find_patches_directory(&mf), Some(PathBuf::from("debian/patches-applied")) ); } pub fn find_patches_directory(tree: &dyn Tree, subpath: &Path) -> Option { let rules_path = subpath.join("debian/rules"); let rules_file = match tree.get_file(&rules_path) { Ok(f) => Some(f), Err(BrzError::NoSuchFile(_)) => None, Err(e) => { log::warn!("Failed to read {}: {}", rules_path.display(), e); None } }; if let Some(rules_file) = rules_file { let mf_patch_dir = match makefile_lossless::Makefile::read_relaxed(rules_file) { Ok(mf) => rules_find_patches_directory(&mf).or_else(|| { log::debug!("No QUILT_PATCH_DIR in {}", rules_path.display()); None }), Err(e) => { log::warn!("Failed to parse {}: {}", rules_path.display(), e); None } }; if let Some(mf_patch_dir) = mf_patch_dir { return Some(mf_patch_dir); } } if tree.has_filename(Path::new(DEFAULT_DEBIAN_PATCHES_DIR)) { return Some(DEFAULT_DEBIAN_PATCHES_DIR.into()); } None } /// Find the base revision to apply patches to. /// /// * `tree` - Tree to find the patch base for pub fn find_patch_base(tree: &WorkingTree) -> Option { let f = match tree.get_file(std::path::Path::new("debian/changelog")) { Ok(f) => f, Err(BrzError::NoSuchFile(_)) => return None, Err(e) => { log::warn!("Failed to read debian/changelog: {}", e); return None; } }; let cl = match ChangeLog::read(f) { Ok(cl) => cl, Err(e) => { log::warn!("Failed to parse debian/changelog: {}", e); return None; } }; let entry = cl.entries().next()?; let package = entry.package().unwrap(); let upstream_version = entry.version().unwrap().upstream_version; let possible_tags = vec![ format!("upstream-{}", upstream_version), format!("upstream/{}", upstream_version), format!("{}", upstream_version), format!("v{}", upstream_version), format!("{}-{}", package, upstream_version), ]; let tags = tree.branch().tags().unwrap().get_tag_dict().unwrap(); for possible_tag in possible_tags { if let Some(revid) = tags.get(&possible_tag) { return Some(revid.clone()); } } // TODO(jelmer): Do something clever, like look for the last merge? None } #[cfg(test)] mod find_patch_base_tests { use breezyshim::tree::{MutableTree, WorkingTree}; use breezyshim::RevisionId; fn setup() -> (tempfile::TempDir, WorkingTree, RevisionId) { let td = tempfile::tempdir().unwrap(); let tree = breezyshim::controldir::create_standalone_workingtree( td.path(), &breezyshim::controldir::ControlDirFormat::default(), ) .unwrap(); let upstream_revid = tree.commit("upstream", None, None, None).unwrap(); tree.mkdir(std::path::Path::new("debian")).unwrap(); std::fs::write( td.path().join("debian/changelog"), r#"blah (0.38) unstable; urgency=medium * Fix something -- Jelmer Vernooij Sat, 19 Oct 2019 15:21:53 +0000 "#, ) .unwrap(); tree.add(&[std::path::Path::new("debian/changelog")]) .unwrap(); (td, tree, upstream_revid) } #[test] fn test_none() { let (td, tree, _upstream_revid) = setup(); assert_eq!(None, super::find_patch_base(&tree)); std::mem::drop(td); } #[test] fn test_upstream_dash() { let (td, tree, upstream_revid) = setup(); tree.branch() .tags() .unwrap() .set_tag("upstream-0.38", &upstream_revid) .unwrap(); let tags = tree.branch().tags().unwrap().get_tag_dict().unwrap(); assert_eq!(Some(&upstream_revid), tags.get("upstream-0.38")); assert_eq!(Some(upstream_revid), super::find_patch_base(&tree)); std::mem::drop(td); } } /// Find the branch that is used to track patches. /// /// * `tree` - Tree for which to find patches branch /// /// Returns: /// A `Branch` instance pub fn find_patches_branch(tree: &WorkingTree) -> Option> { let local_branch_name = tree.branch().name()?; let branch_name = format!("patch-queue/{}", local_branch_name); match tree .branch() .controldir() .open_branch(Some(branch_name.as_str())) { Ok(b) => return Some(b), Err(BrzError::NotBranchError(..)) => {} Err(e) => { log::warn!("Failed to open branch {}: {}", branch_name, e); } } let branch_name = if local_branch_name == "master" { "patched".to_string() } else { format!("patched-{}", local_branch_name) }; match tree .branch() .controldir() .open_branch(Some(branch_name.as_str())) { Ok(b) => return Some(b), Err(BrzError::NotBranchError(..)) => {} Err(e) => { log::warn!("Failed to open branch {}: {}", branch_name, e); } } None } #[cfg(test)] mod find_patches_branch_tests { use breezyshim::tree::WorkingTree; fn make_named_branch_and_tree(name: &str) -> (tempfile::TempDir, WorkingTree) { let td = tempfile::tempdir().unwrap(); let dir = breezyshim::controldir::create( &url::Url::from_directory_path(td.path()).unwrap(), &breezyshim::controldir::ControlDirFormat::default(), None, ) .unwrap(); dir.create_repository(None).unwrap(); let branch = dir.create_branch(Some(name)).unwrap(); dir.set_branch_reference(branch.as_ref(), None).unwrap(); let wt = dir.create_workingtree().unwrap(); (td, wt) } #[test] fn test_none() { let td = tempfile::tempdir().unwrap(); let local_tree = breezyshim::controldir::create_standalone_workingtree( td.path(), &breezyshim::controldir::ControlDirFormat::default(), ) .unwrap(); assert!(super::find_patches_branch(&local_tree).is_none()); } #[test] fn test_patch_queue() { let (td, master) = make_named_branch_and_tree("master"); master .branch() .controldir() .create_branch(Some("patch-queue/master")) .unwrap(); assert_eq!( "patch-queue/master", super::find_patches_branch(&master) .unwrap() .name() .unwrap() .as_str() ); std::mem::drop(td); } #[test] fn test_patched_master() { let (td, master) = make_named_branch_and_tree("master"); master .branch() .controldir() .create_branch(Some("patched")) .unwrap(); assert_eq!( "patched", super::find_patches_branch(&master).unwrap().name().unwrap() ); std::mem::drop(td); } #[test] fn test_patched_other() { let (td, other) = make_named_branch_and_tree("other"); other .branch() .controldir() .create_branch(Some("patched-other")) .unwrap(); assert_eq!( "patched-other", super::find_patches_branch(&other).unwrap().name().unwrap() ); std::mem::drop(td); } } /// Add a new patch. /// /// # Arguments /// * `tree` - Tree to edit /// * `patches_directory` - Name of patches directory /// * `name` - Patch name without suffix /// * `contents` - Diff /// * `header` - RFC822 to read /// /// Returns: /// Name of the patch that was written (including suffix) pub fn add_patch( tree: &WorkingTree, patches_directory: &Path, name: &str, contents: &[u8], header: Option, ) -> Result<(Vec, String), String> { if !tree.has_filename(patches_directory) { let parent = patches_directory.parent().unwrap(); if !tree.has_filename(parent) { tree.mkdir(parent) .expect("Failed to create parent directory"); } tree.mkdir(patches_directory).unwrap(); } let series_path = patches_directory.join("series"); let mut series = match tree.get_file(&series_path) { Ok(f) => patchkit::quilt::Series::read(f).unwrap(), Err(BrzError::NoSuchFile(_)) => patchkit::quilt::Series::new(), Err(e) => { return Err(format!("Failed to read {}: {}", series_path.display(), e)); } }; let patch_suffix = patchkit::quilt::find_common_patch_suffix(series.patches()).unwrap_or(".patch"); let patchname = format!("{}{}", name, patch_suffix); let path = patches_directory.join(patchname.as_str()); if tree.has_filename(path.as_path()) { return Err(format!("Patch {} already exists", patchname)); } let mut patch_contents = Vec::new(); if let Some(header) = header { header.write(&mut patch_contents).unwrap(); } patch_contents.write_all(b"---\n").unwrap(); patch_contents.write_all(contents).unwrap(); tree.put_file_bytes_non_atomic(&path, patch_contents.as_slice()) .map_err(|e| format!("Failed to write patch: {}", e))?; // TODO(jelmer): Write to patches branch if applicable series.append(patchname.as_str(), None); let mut series_bytes = Vec::new(); series .write(&mut series_bytes) .map_err(|e| format!("Failed to write series: {}", e))?; tree.put_file_bytes_non_atomic(&series_path, series_bytes.as_slice()) .map_err(|e| format!("Failed to write series: {}", e))?; tree.add(&[series_path.as_path(), path.as_path()]) .map_err(|e| format!("Failed to add patch: {}", e))?; let specific_files = vec![series_path, path]; Ok((specific_files, patchname)) } /// Move upstream changes to patch. /// /// # Arguments /// /// * `local_tree` - Local tree /// * `basis_tree` - Basis tree /// * `subpath` - Subpath /// * `patch_name` - Suggested patch name /// * `description` - Description pub fn move_upstream_changes_to_patch( local_tree: &WorkingTree, basis_tree: &dyn Tree, subpath: &std::path::Path, patch_name: &str, description: &str, dirty_tracker: Option<&mut breezyshim::dirty_tracker::DirtyTreeTracker>, timestamp: Option, ) -> Result<(Vec, String), String> { let timestamp = if let Some(timestamp) = timestamp { timestamp } else { chrono::Utc::now().naive_utc().date() }; let mut diff = Vec::new(); breezyshim::diff::show_diff_trees(basis_tree, local_tree, &mut diff, None, None) .map_err(|e| format!("Failed to generate diff: {}", e))?; reset_tree_with_dirty_tracker(local_tree, Some(basis_tree), Some(subpath), dirty_tracker) .map_err(|e| format!("Failed to reset tree: {}", e))?; // See https://dep-team.pages.debian.net/deps/dep3/ for fields. let mut dep3_header = dep3::PatchHeader::new(); dep3_header.set_description(description); dep3_header.set_origin(None, dep3::Origin::Other("other".to_string())); dep3_header.set_last_update(timestamp); let patches_directory = subpath.join(tree_patches_directory(local_tree, subpath)); let (specific_files, patchname) = add_patch( local_tree, &patches_directory, patch_name, diff.as_slice(), Some(dep3_header), )?; Ok((specific_files, patchname)) } #[cfg(test)] mod move_upstream_changes_to_patch_tests { use breezyshim::controldir::ControlDirFormat; use breezyshim::tree::MutableTree; #[test] fn test_simple() { breezyshim::init(); let td = tempfile::tempdir().unwrap(); let local_tree = breezyshim::controldir::create_standalone_workingtree( td.path(), &ControlDirFormat::default(), ) .unwrap(); std::fs::write(td.path().join("foo"), b"foo\n").unwrap(); local_tree.mkdir(std::path::Path::new("debian")).unwrap(); local_tree.add(&[std::path::Path::new("foo")]).unwrap(); super::move_upstream_changes_to_patch( &local_tree, &local_tree.basis_tree().unwrap(), std::path::Path::new(""), "patch", "This is a description", None, Some(chrono::NaiveDate::from_ymd(2020, 1, 1)), ) .unwrap(); let path = td.path(); assert!(!path.join("foo").exists()); assert!(path.join("debian/patches").exists()); assert!(path.join("debian/patches/series").exists()); assert!(path.join("debian/patches/patch.patch").exists()); let series = std::fs::read_to_string(path.join("debian/patches/series")).unwrap(); assert_eq!(series, "patch.patch\n"); let patch = std::fs::read_to_string(path.join("debian/patches/patch.patch")).unwrap(); assert!( patch.starts_with( r#"Description: This is a description Origin: other Last-Update: 2020-01-01 --- "# ), "{:?}", patch ); assert!( patch.ends_with( r#"@@ -0,0 +1,1 @@ +foo "# ), "{:?}", patch ); } } pub fn read_quilt_patches<'a>( tree: &'a dyn Tree, directory: &'a std::path::Path, ) -> impl Iterator + 'a { let series_path = directory.join("series"); let series = match tree.get_file(series_path.as_path()) { Ok(series) => patchkit::quilt::Series::read(series).unwrap(), Err(BrzError::NoSuchFile(..)) => patchkit::quilt::Series::new(), Err(e) => panic!("error reading series: {:?}", e), }; let mut ret = vec![]; for patch in series.patches() { let p = directory.join(patch); let lines = tree.get_file_lines(p.as_path()).unwrap(); // TODO(jelmer): Pass on options? ret.push(patchkit::patch::UnifiedPatch::parse_patches(lines.into_iter()).unwrap()); } ret.into_iter().flatten() } #[cfg(test)] mod read_quilt_patches_tests { use breezyshim::controldir::ControlDirFormat; use breezyshim::tree::MutableTree; #[test] fn test_read_patches() { let patch = "\ --- a/a +++ b/a @@ -1,5 +1,5 @@ line 1 line 2 -line 3 +new line 3 line 4 line 5 "; breezyshim::init(); let td = tempfile::tempdir().unwrap(); let tree = breezyshim::controldir::create_standalone_workingtree( td.path(), &ControlDirFormat::default(), ) .unwrap(); tree.mkdir(std::path::Path::new("debian")).unwrap(); tree.mkdir(std::path::Path::new("debian/patches")).unwrap(); std::fs::write(td.path().join("debian/patches/series"), "foo\n").unwrap(); std::fs::write(td.path().join("debian/patches/foo"), patch).unwrap(); tree.add( [ "debian", "debian/patches", "debian/patches/series", "debian/patches/foo", ] .into_iter() .map(std::path::Path::new) .collect::>() .as_slice(), ) .unwrap(); tree.commit("add patch", None, None, None).unwrap(); let patches = super::read_quilt_patches(&tree, std::path::Path::new("debian/patches")) .collect::>(); assert_eq!(1, patches.len()); assert_eq!(patch, std::str::from_utf8(&patches[0].as_bytes()).unwrap()); } #[test] fn test_no_series_file() { breezyshim::init(); let td = tempfile::tempdir().unwrap(); let tree = breezyshim::controldir::create_standalone_workingtree( td.path(), &ControlDirFormat::default(), ) .unwrap(); let patches = super::read_quilt_patches(&tree, std::path::Path::new("debian/patches")) .collect::>(); assert_eq!(0, patches.len()); } #[test] fn test_comments() { let td = tempfile::tempdir().unwrap(); let tree = breezyshim::controldir::create_standalone_workingtree( td.path(), &ControlDirFormat::default(), ) .unwrap(); tree.mkdir(std::path::Path::new("debian")).unwrap(); tree.mkdir(std::path::Path::new("debian/patches")).unwrap(); tree.put_file_bytes_non_atomic( std::path::Path::new("debian/patches/series"), b"# This file intentionally left blank.\n", ) .unwrap(); tree.add(&[std::path::Path::new("debian/patches/series")]) .unwrap(); tree.commit("add series", None, None, None).unwrap(); let patches = super::read_quilt_patches(&tree, std::path::Path::new("debian/patches")) .collect::>(); assert_eq!(0, patches.len()); } } pub fn upstream_with_applied_patches( tree: WorkingTree, patches: Vec, ) -> breezyshim::Result> { if let Some(patches_branch) = find_patches_branch(&tree) { Ok(Box::new(patches_branch.basis_tree()?) as Box) } else { let upstream_revision = find_patch_base(&tree).unwrap(); // PatchApplicationBaseNotFound(tree) let upstream_tree = tree .branch() .repository() .revision_tree(&upstream_revision)?; if patches.is_empty() { Ok(Box::new(tree) as Box) } else { Ok(Box::new(AppliedPatches::new(&upstream_tree, patches, None)?) as Box) } } } #[cfg(test)] mod upstream_with_applied_patches_tests { use breezyshim::tree::{MutableTree, WorkingTree}; use breezyshim::RevisionId; fn setup() -> (tempfile::TempDir, WorkingTree, RevisionId) { let td = tempfile::tempdir().unwrap(); let tree = breezyshim::controldir::create_standalone_workingtree( td.path(), &breezyshim::controldir::ControlDirFormat::default(), ) .unwrap(); std::fs::write(td.path().join("afile"), b"some line\n").unwrap(); tree.add(&[std::path::Path::new("afile")]).unwrap(); let upstream_revid = tree.commit("upstream", None, None, None).unwrap(); tree.mkdir(std::path::Path::new("debian")).unwrap(); std::fs::write( td.path().join("debian/changelog"), r#"blah (0.38) unstable; urgency=medium * Fix something -- Jelmer Vernooij Sat, 19 Oct 2019 15:21:53 +0000 "#, ) .unwrap(); tree.add(&[std::path::Path::new("debian/changelog")]) .unwrap(); tree.mkdir(std::path::Path::new("debian/patches")).unwrap(); std::fs::write(td.path().join("debian/patches/series"), "1.patch\n").unwrap(); tree.add(&[std::path::Path::new("debian/patches/series")]) .unwrap(); std::fs::write( td.path().join("debian/patches/1.patch"), r#"--- a/afile +++ b/afile @@ -1 +1 @@ -some line +another line --- /dev/null +++ b/newfile @@ -0,0 +1 @@ +new line "#, ) .unwrap(); tree.add(&[std::path::Path::new("debian/patches/1.patch")]) .unwrap(); std::fs::write(td.path().join("unchangedfile"), b"unchanged\n").unwrap(); tree.add(&[std::path::Path::new("unchangedfile")]).unwrap(); (td, tree, upstream_revid) } #[test] fn test_upstream_branch() { let (td, tree, upstream_revid) = setup(); tree.branch() .tags() .unwrap() .set_tag("upstream/0.38", &upstream_revid) .unwrap(); let tags = tree.branch().tags().unwrap().get_tag_dict().unwrap(); assert_eq!(Some(&upstream_revid), tags.get("upstream/0.38")); let patches = super::read_quilt_patches(&tree, std::path::Path::new("debian/patches")) .collect::>(); let t = super::upstream_with_applied_patches(tree, patches).unwrap(); assert_eq!( b"another line\n".to_vec(), t.get_file_text(std::path::Path::new("afile")).unwrap() ); assert_eq!( b"new line\n".to_vec(), t.get_file_text(std::path::Path::new("newfile")).unwrap() ); // TODO(jelmer): PreviewTree appears to be broken // self.assertEqual(b'unchanged\n', // t.get_file_text('unchangedfile')) std::mem::drop(td); } } /// Check if a Debian tree has changes vs upstream tree. pub fn tree_non_patches_changes( tree: WorkingTree, patches_directory: Option<&std::path::Path>, ) -> breezyshim::Result> { let patches = if let Some(patches_directory) = patches_directory.as_ref() { read_quilt_patches(&tree, patches_directory).collect::>() } else { vec![] }; let patches_tree = if patches.is_empty() { Box::new(tree.clone()) } else { Box::new(AppliedPatches::new(&tree, patches.clone(), None)?) as Box }; let upstream_patches_tree = upstream_with_applied_patches(tree, patches)?; let changes = patches_tree .iter_changes(upstream_patches_tree.as_ref(), None, None, None)? .map(|c| c.unwrap()); let paths = &[std::path::Path::new("debian")][..]; Ok(filter_excluded(changes, paths) .filter_map(|change| { if change.path.1.as_deref() == Some(std::path::Path::new("")) { None } else { Some(change) } }) .collect()) } #[cfg(test)] mod tree_non_patches_changes_tests { use breezyshim::tree::{MutableTree, WorkingTree}; use breezyshim::RevisionId; fn setup() -> (tempfile::TempDir, WorkingTree, RevisionId) { breezyshim::init(); let td = tempfile::tempdir().unwrap(); let local_tree = breezyshim::controldir::create_standalone_workingtree( td.path(), &breezyshim::controldir::ControlDirFormat::default(), ) .unwrap(); std::fs::write(td.path().join("afile"), b"some line\n").unwrap(); local_tree.add(&[std::path::Path::new("afile")]).unwrap(); let upstream_revid = local_tree.commit("upstream", None, None, None).unwrap(); local_tree.mkdir(std::path::Path::new("debian")).unwrap(); std::fs::write( td.path().join("debian/changelog"), r#"blah (0.38) unstable; urgency=medium * Fix something -- Jelmer Vernooij Sat, 19 Oct 2019 15:21:53 +0000 "#, ) .unwrap(); local_tree .mkdir(std::path::Path::new("debian/patches")) .unwrap(); std::fs::write(td.path().join("debian/patches/series"), "1.patch\n").unwrap(); std::fs::write( td.path().join("debian/patches/1.patch"), r#"--- a/afile +++ b/afile @@ -1 +1 @@ -some line +another line "#, ) .unwrap(); local_tree .add(&[ std::path::Path::new("debian/changelog"), std::path::Path::new("debian/patches"), std::path::Path::new("debian/patches/series"), std::path::Path::new("debian/patches/1.patch"), ]) .unwrap(); (td, local_tree, upstream_revid) } #[test] fn test_no_delta() { let (td, tree, upstream_revid) = setup(); tree.branch() .tags() .unwrap() .set_tag("upstream/0.38", &upstream_revid) .unwrap(); assert_eq!( Vec::::new(), super::tree_non_patches_changes(tree, Some(std::path::Path::new("debian/patches"))) .unwrap() ); std::mem::drop(td); } #[test] fn test_delta() { let (td, tree, upstream_revid) = setup(); tree.branch() .tags() .unwrap() .set_tag("upstream/0.38", &upstream_revid) .unwrap(); let tags = tree.branch().tags().unwrap().get_tag_dict().unwrap(); assert_eq!(Some(&upstream_revid), tags.get("upstream/0.38")); std::fs::write(tree.basedir().join("anotherfile"), b"blah\n").unwrap(); tree.add(&[std::path::Path::new("anotherfile")]).unwrap(); assert_eq!( 1, super::tree_non_patches_changes(tree, Some(std::path::Path::new("debian/patches"))) .unwrap() .len() ); std::mem::drop(td); } } debian-analyzer-0.158.8/src/publish.rs000064400000000000000000000135451046102023000156670ustar 00000000000000use crate::debmutateshim::{ edit_control, source_package_vcs, ControlEditor, ControlLikeEditor, Deb822Paragraph, DebcargoControlShimEditor, }; use crate::salsa::guess_repository_url; use crate::vcs::determine_browser_url; use crate::{branch_vcs_type, get_committer, parseaddr}; use breezyshim::error::Error as BrzError; use breezyshim::forge::create_project; use breezyshim::tree::{Tree, WorkingTree}; use breezyshim::workspace::check_clean_tree; use debian_control::vcs::ParsedVcs; use std::path::Path; use url::Url; pub fn update_control_for_vcs_url(source: &mut Deb822Paragraph, vcs_type: &str, vcs_url: &str) { source.set(format!("Vcs-{}", vcs_type).as_str(), vcs_url); if let Some(url) = determine_browser_url("git", vcs_url, None) { source.set("Vcs-Browser", url.as_str()); } else { source.remove("Vcs-Browser"); } } pub fn create_vcs_url(repo_url: &Url, summary: Option<&str>) -> Result<(), BrzError> { match create_project(repo_url.as_str(), summary) { Ok(()) => { log::info!("Created {}", repo_url); Ok(()) } Err(BrzError::ForgeProjectExists(..)) | Err(BrzError::AlreadyControlDir(..)) => { log::debug!("{} already exists", repo_url); Ok(()) } Err(e) => Err(e), } } #[derive(Debug, Clone)] pub enum Error { NoVcsLocation, FileNotFound(std::path::PathBuf), ConflictingVcsAlreadySpecified(String, String, String), } impl std::fmt::Display for Error { fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { use Error::*; match self { NoVcsLocation => write!(f, "No Vcs-* location specified"), FileNotFound(path) => write!(f, "File not found: {}", path.display()), ConflictingVcsAlreadySpecified(_vcs_type, existing_url, new_url) => write!( f, "Conflicting Vcs-* location already specified: {} vs {}", existing_url, new_url ), } } } pub fn update_official_vcs( wt: &WorkingTree, subpath: &Path, repo_url: Option<&Url>, branch: Option<&str>, committer: Option<&str>, force: Option, ) -> Result { let force = force.unwrap_or(false); // TODO(jelmer): Allow creation of the repository as well check_clean_tree(wt, &wt.basis_tree().unwrap(), subpath).unwrap(); let debian_path = subpath.join("debian"); let subpath = match subpath.to_string_lossy().as_ref() { "" | "." => None, _ => Some(subpath.to_path_buf()), }; let debcargo_path = debian_path.join("debcargo.toml"); let control_path = debian_path.join("control"); let mut editor: Box = if wt.has_filename(debcargo_path.as_path()) { Box::new(DebcargoControlShimEditor::from_debian_dir( wt.abspath(debian_path.as_path()).unwrap().as_path(), )) } else if wt.has_filename(control_path.as_path()) { let control_path = wt.abspath(control_path.as_path()).unwrap(); Box::new(ControlEditor::open(Some(control_path.as_path()), None)) as Box } else { return Err(Error::FileNotFound(control_path)); }; let parsed_vcs: ParsedVcs = edit_control(editor.as_mut(), |e| { let mut source = e.source().unwrap(); if let Some((vcs_type, existing_url)) = source_package_vcs(&source) { let existing: ParsedVcs = existing_url.parse().unwrap(); let actual = ParsedVcs { repo_url: repo_url.unwrap().to_string(), branch: branch.map(|s| s.to_string()), subpath: subpath.map(|p| p.to_string_lossy().to_string()), }; if let Some(repo_url) = repo_url { // TODO: Avoid .to_string() once ParsedVcs implements PartialEq if existing.to_string() != actual.to_string() && !force { return Err(Error::ConflictingVcsAlreadySpecified( vcs_type, existing_url, actual.to_string(), )); } } log::debug!("Using existing URL {}", existing_url); return Ok(existing); } let maintainer_email = parseaddr(source.get("Maintainer").unwrap().as_str()) .unwrap() .1 .unwrap(); let source_name = source.get("Source").unwrap(); let mut repo_url = repo_url.map(|u| u.to_owned()); if repo_url.is_none() { repo_url = guess_repository_url(source_name.as_str(), maintainer_email.as_str()); } let repo_url = match repo_url { Some(url) => url, None => { return Err(Error::NoVcsLocation); } }; log::info!("Using repository URL: {}", repo_url); // TODO(jelmer): Detect vcs type in a better way let branch = wt.branch(); let vcs_type = branch_vcs_type(branch.as_ref()); let branch = match vcs_type.as_str() { "git" => Some("debian/main"), "bzr" => None, _ => { panic!("Unknown VCS type"); } }; let vcs_url = ParsedVcs { repo_url: repo_url.to_string(), branch: branch.map(|s| s.to_string()), subpath: subpath.map(|p| p.to_string_lossy().to_string()), }; update_control_for_vcs_url(&mut source, vcs_type.as_str(), &vcs_url.to_string()); Ok(vcs_url) })?; let committer = committer.map_or_else(|| get_committer(wt), |s| s.to_string()); match wt.commit( "Set Vcs headers.", Some(false), Some(committer.as_str()), None, ) { Ok(_) | Err(BrzError::PointlessCommit) => {} Err(e) => { panic!("Failed to commit: {:?}", e); } } Ok(parsed_vcs) } debian-analyzer-0.158.8/src/release_info.rs000064400000000000000000000154011046102023000166450ustar 00000000000000use chrono::{NaiveDate, Utc}; use distro_info::DistroInfo; pub const DEBIAN_POCKETS: &[&str] = &["", "-security", "-proposed-updates", "-backports"]; pub const UBUNTU_POCKETS: &[&str] = &["", "-proposed", "-updates", "-security", "-backports"]; pub fn debian_releases() -> Vec { let debian = distro_info::DebianDistroInfo::new().unwrap(); debian .all_at(Utc::now().naive_utc().date()) .iter() .map(|r| r.series().to_string()) .collect() } pub fn ubuntu_releases() -> Vec { let ubuntu = distro_info::UbuntuDistroInfo::new().unwrap(); ubuntu .all_at(Utc::now().naive_utc().date()) .iter() .map(|r| r.series().to_string()) .collect() } #[derive(Debug, PartialEq)] pub enum Vendor { Debian, Ubuntu, Kali, } /// Infer the distribution from a suite. /// /// When passed the name of a suite (anything in the distributions field of /// a changelog) it will infer the distribution from that (i.e. Debian or /// Ubuntu). /// /// # Arguments /// * `suite`: the string containing the suite pub fn suite_to_distribution(suite: &str) -> Option { let all_debian = debian_releases() .iter() .flat_map(|r| DEBIAN_POCKETS.iter().map(move |t| r.to_string() + t)) .collect::>(); let all_ubuntu = ubuntu_releases() .iter() .flat_map(|r| UBUNTU_POCKETS.iter().map(move |t| r.to_string() + t)) .collect::>(); if all_debian.contains(&suite.to_string()) { return Some(Vendor::Debian); } if all_ubuntu.contains(&suite.to_string()) { return Some(Vendor::Ubuntu); } if suite == "kali" || suite.starts_with("kali-") { return Some(Vendor::Kali); } None } pub fn resolve_release_codename(name: &str, date: Option) -> Option { let date = date.unwrap_or(Utc::now().naive_utc().date()); let (distro, mut name) = if let Some((distro, name)) = name.split_once('/') { (Some(distro), name) } else { (None, name) }; let active = |x: &Option| x.map(|x| x > date).unwrap_or(false); if distro.is_none() || distro == Some("debian") { let debian = distro_info::DebianDistroInfo::new().unwrap(); if name == "lts" { let lts = debian .all_at(date) .into_iter() .filter(|r| active(r.eol_lts())) .min_by_key(|r| r.created()); return lts.map(|r| r.series().to_string()); } if name == "elts" { let elts = debian .all_at(date) .into_iter() .filter(|r| active(r.eol_elts())) .min_by_key(|r| r.created()); return elts.map(|r| r.series().to_string()); } let mut all_released = debian .all_at(date) .into_iter() .filter(|r| r.release().is_some()) .collect::>(); all_released.sort_by_key(|r| r.created()); all_released.reverse(); if name == "stable" { return Some(all_released[0].series().to_string()); } if name == "oldstable" { return Some(all_released[1].series().to_string()); } if name == "oldoldstable" { return Some(all_released[2].series().to_string()); } if name == "unstable" { name = "sid"; } if name == "testing" { let mut all_unreleased = debian .all_at(date) .into_iter() .filter(|r| r.release().is_none()) .collect::>(); all_unreleased.sort_by_key(|r| r.created()); return Some(all_unreleased.last().unwrap().series().to_string()); } let all = debian.all_at(date); if let Some(series) = all .iter() .find(|r| r.codename() == name || r.series() == name) { return Some(series.series().to_string()); } } if distro.is_none() || distro == Some("ubuntu") { let ubuntu = distro_info::UbuntuDistroInfo::new().unwrap(); if name == "esm" { return ubuntu .all_at(date) .into_iter() .filter(|r| active(r.eol_esm())) .min_by_key(|r| r.created()) .map(|r| r.series().to_string()); } if name == "lts" { return ubuntu .all_at(date) .into_iter() .filter(|r| r.is_lts() && r.supported_at(date)) .min_by_key(|r| r.created()) .map(|r| r.series().to_string()); } let all = ubuntu.all_at(date); if let Some(series) = all .iter() .find(|r| r.codename() == name || r.series() == name) { return Some(series.series().to_string()); } } None } #[cfg(test)] mod tests { use super::resolve_release_codename; #[test] fn test_debian() { assert_eq!("sid", resolve_release_codename("debian/sid", None).unwrap()); assert_eq!("sid", resolve_release_codename("sid", None).unwrap()); assert_eq!("sid", resolve_release_codename("unstable", None).unwrap()); assert_eq!( "experimental", resolve_release_codename("experimental", None).unwrap() ); } #[test] fn test_ubuntu() { assert_eq!( "trusty", resolve_release_codename("ubuntu/trusty", None).unwrap() ); assert_eq!("trusty", resolve_release_codename("trusty", None).unwrap()); assert!(resolve_release_codename("ubuntu/lts", None).is_some()); } #[test] fn test_resolve_debian() { assert_eq!("sid", resolve_release_codename("sid", None).unwrap()); assert_eq!("buster", resolve_release_codename("buster", None).unwrap()); assert_eq!("sid", resolve_release_codename("unstable", None).unwrap()); assert_eq!( "sid", resolve_release_codename("debian/unstable", None).unwrap() ); assert!(resolve_release_codename("oldstable", None).is_some()); assert!(resolve_release_codename("oldoldstable", None).is_some()); } #[test] fn test_resolve_unknown() { assert!(resolve_release_codename("blah", None).is_none()); } #[test] fn test_resolve_ubuntu() { assert_eq!("trusty", resolve_release_codename("trusty", None).unwrap()); assert_eq!( "trusty", resolve_release_codename("ubuntu/trusty", None).unwrap() ); assert!(resolve_release_codename("ubuntu/lts", None).is_some()) } #[test] fn test_resolve_ubuntu_esm() { assert!(resolve_release_codename("ubuntu/esm", None).is_some()) } } debian-analyzer-0.158.8/src/salsa.rs000064400000000000000000000061631046102023000153220ustar 00000000000000use std::collections::HashMap; use url::Url; lazy_static::lazy_static! { static ref MAINTAINER_EMAIL_MAP: HashMap<&'static str, &'static str> = maplit::hashmap! { "pkg-javascript-devel@lists.alioth.debian.org" => "js-team", "python-modules-team@lists.alioth.debian.org" => "python-team/modules", "python-apps-team@lists.alioth.debian.org" => "python-team/applications", "debian-science-maintainers@lists.alioth.debian.org" => "science-team", "pkg-perl-maintainers@lists.alioth.debian.org" => "perl-team/modules/packages", "pkg-java-maintainers@lists.alioth.debian.org" => "java-team", "pkg-ruby-extras-maintainers@lists.alioth.debian.org" => "ruby-team", "pkg-clamav-devel@lists.alioth.debian.org" => "clamav-team", "pkg-go-maintainers@lists.alioth.debian.org" => "go-team/packages", "pkg-games-devel@lists.alioth.debian.org" => "games-team", "pkg-telepathy-maintainers@lists.alioth.debian.org" => "telepathy-team", "debian-fonts@lists.debian.org" => "fonts-team", "pkg-gnustep-maintainers@lists.alioth.debian.org" => "gnustep-team", "pkg-gnome-maintainers@lists.alioth.debian.org" => "gnome-team", "pkg-multimedia-maintainers@lists.alioth.debian.org" => "multimedia-team", "debian-ocaml-maint@lists.debian.org" => "ocaml-team", "pkg-php-pear@lists.alioth.debian.org" => "php-team/pear", "pkg-mpd-maintainers@lists.alioth.debian.org" => "mpd-team", "pkg-cli-apps-team@lists.alioth.debian.org" => "dotnet-team", "pkg-mono-group@lists.alioth.debian.org" => "dotnet-team", "team+python@tracker.debian.org" => "python-team/packages", }; } /// Guess the repository URL for a package hosted on Salsa. /// /// # Arguments: /// * `package`: Package name /// * `maintainer_email`: The maintainer's email address (e.g. team list address) /// /// # Returns: /// A guessed repository URL pub fn guess_repository_url(package: &str, maintainer_email: &str) -> Option { let team_name = if maintainer_email.ends_with("@debian.org") { maintainer_email.split('@').next().unwrap() } else if let Some(team_name) = MAINTAINER_EMAIL_MAP.get(maintainer_email) { team_name } else { return None; }; format!("https://salsa.debian.org/{}/{}.git", team_name, package) .parse() .ok() } #[cfg(test)] mod guess_repository_url_tests { use super::*; #[test] fn test_unknown() { assert_eq!( None, guess_repository_url("blah", "unknown-team@lists.alioth.debian.org") ); } #[test] fn test_individual() { assert_eq!( Some( "https://salsa.debian.org/jelmer/lintian-brush.git" .parse() .unwrap() ), guess_repository_url("lintian-brush", "jelmer@debian.org") ); } #[test] fn test_team() { assert_eq!( Some( "https://salsa.debian.org/js-team/node-blah.git" .parse() .unwrap() ), guess_repository_url("node-blah", "pkg-javascript-devel@lists.alioth.debian.org") ); } } debian-analyzer-0.158.8/src/svp.rs000064400000000000000000000073011046102023000150220ustar 00000000000000use std::collections::HashMap; #[derive(Debug, serde::Serialize)] struct Failure { result_code: String, versions: HashMap, description: String, transient: Option, } #[derive(Debug, serde::Serialize)] struct ChangelogBehaviour { update: bool, explanation: String, } #[derive(Debug, serde::Serialize)] struct DebianContext { changelog: Option, } #[derive(Debug, serde::Serialize)] struct Success { versions: HashMap, value: Option, context: Option, debian: Option, } pub fn report_success( versions: HashMap, value: Option, context: Option, ) { if std::env::var("SVP_API").ok().as_deref() == Some("1") { let f = std::fs::File::create(std::env::var("SVP_RESULT").unwrap()).unwrap(); serde_json::to_writer( f, &Success { versions, value, context, debian: None, }, ) .unwrap(); } } pub fn report_success_debian( versions: HashMap, value: Option, context: Option, changelog: Option<(bool, String)>, ) { if std::env::var("SVP_API").ok().as_deref() == Some("1") { let f = std::fs::File::create(std::env::var("SVP_RESULT").unwrap()).unwrap(); serde_json::to_writer( f, &Success { versions, value, context, debian: Some(DebianContext { changelog: changelog.map(|cl| ChangelogBehaviour { update: cl.0, explanation: cl.1, }), }), }, ) .unwrap(); } } pub fn report_nothing_to_do(versions: HashMap, description: Option<&str>) -> ! { let description = description.unwrap_or("Nothing to do"); if std::env::var("SVP_API").ok().as_deref() == Some("1") { let f = std::fs::File::create(std::env::var("SVP_RESULT").unwrap()).unwrap(); serde_json::to_writer( f, &Failure { result_code: "nothing-to-do".to_string(), versions, description: description.to_string(), transient: None, }, ) .unwrap(); } log::error!("{}", description); std::process::exit(0); } pub fn report_fatal( versions: HashMap, code: &str, description: &str, hint: Option<&str>, transient: Option, ) -> ! { if std::env::var("SVP_API").ok().as_deref() == Some("1") { let f = std::fs::File::create(std::env::var("SVP_RESULT").unwrap()).unwrap(); serde_json::to_writer( f, &Failure { result_code: code.to_string(), versions, description: description.to_string(), transient, }, ) .unwrap(); } log::error!("{}", description); if let Some(hint) = hint { log::info!("{}", hint); } std::process::exit(1); } pub fn load_resume() -> Option { if std::env::var("SVP_API").ok().as_deref() == Some("1") { if let Ok(resume_path) = std::env::var("SVP_RESUME") { let f = std::fs::File::open(resume_path).unwrap(); let resume: serde_json::Value = serde_json::from_reader(f).unwrap(); Some(resume) } else { None } } else { None } } pub fn enabled() -> bool { std::env::var("SVP_API").ok().as_deref() == Some("1") } debian-analyzer-0.158.8/src/vcs.rs000064400000000000000000000470321046102023000150120ustar 00000000000000use debian_control::vcs::ParsedVcs; use log::debug; use url::Url; pub const KNOWN_GITLAB_SITES: &[&str] = &["salsa.debian.org", "invent.kde.org", "0xacab.org"]; pub fn is_gitlab_site(hostname: &str, net_access: Option) -> bool { if KNOWN_GITLAB_SITES.contains(&hostname) { return true; } if hostname.starts_with("gitlab.") { return true; } if net_access.unwrap_or(false) { probe_gitlab_host(hostname) } else { false } } pub fn probe_gitlab_host(hostname: &str) -> bool { use reqwest::header::HeaderMap; let url = format!("https://{}/api/v4/version", hostname); let mut headers = HeaderMap::new(); headers.insert(reqwest::header::ACCEPT, "application/json".parse().unwrap()); let client = reqwest::blocking::Client::builder() .default_headers(headers) .build() .unwrap(); let http_url: reqwest::Url = Into::::into(url.clone()).parse().unwrap(); let request = client.get(http_url).build().unwrap(); let response = client.execute(request).unwrap(); match response.status().as_u16() { 401 => { if let Ok(data) = response.json::() { if let Some(message) = data["message"].as_str() { if message == "401 Unauthorized" { true } else { debug!("failed to parse JSON response: {:?}", data); false } } else { debug!("failed to parse JSON response: {:?}", data); false } } else { debug!("failed to parse JSON response"); false } } 200 => true, _ => { debug!("unexpected HTTP status code: {:?}", response.status()); false } } } pub fn determine_gitlab_browser_url(url: &str) -> Url { let parsed_vcs: ParsedVcs = url.trim_end_matches('/').parse().unwrap(); // TODO(jelmer): Add support for branches let parsed_url = Url::parse(&parsed_vcs.repo_url).unwrap(); let path = parsed_url .path() .trim_end_matches('/') .trim_end_matches(".git"); let branch = if let Some(branch) = parsed_vcs.branch { Some(branch) } else if parsed_vcs.subpath.is_some() { Some("HEAD".to_string()) } else { None }; let mut path = if let Some(branch) = branch { format!("{}/-/tree/{}", path, branch) } else { path.to_string() }; if let Some(subpath) = parsed_vcs.subpath { path.push_str(&format!("/{}", subpath)); } let url = format!( "https://{}/{}", parsed_url.host_str().unwrap(), path.trim_start_matches('/') ); Url::parse(&url).unwrap() } pub fn determine_browser_url( vcs_type: &str, vcs_url: &str, net_access: Option, ) -> Option { let parsed_vcs: ParsedVcs = vcs_url.parse().unwrap(); let parsed_url: Url = parsed_vcs.repo_url.parse().unwrap(); match parsed_url.host_str().unwrap() { host if is_gitlab_site(host, net_access) => Some(determine_gitlab_browser_url(vcs_url)), "github.com" => { let path = parsed_url.path().trim_end_matches(".git"); let branch = if let Some(branch) = parsed_vcs.branch { Some(branch) } else if parsed_vcs.subpath.is_some() { Some("HEAD".to_string()) } else { None }; let mut path = if let Some(branch) = branch { format!("{}/tree/{}", path, branch) } else { path.to_string() }; if let Some(subpath) = parsed_vcs.subpath { path.push_str(&format!("/{}", subpath)); } let url = format!( "https://{}/{}", parsed_url.host_str().unwrap(), path.trim_start_matches('/') ); Some(Url::parse(&url).unwrap()) } host if (host == "code.launchpad.net" || host == "launchpad.net") && parsed_vcs.branch.is_none() && parsed_vcs.subpath.is_none() => { let url = format!( "https://code.launchpad.net/{}", parsed_url.path().trim_start_matches('/') ); Some(Url::parse(&url).unwrap()) } "git.savannah.gnu.org" | "git.sv.gnu.org" => { let mut path_elements = parsed_url.path_segments().unwrap().collect::>(); if parsed_url.scheme() == "https" && path_elements.first() == Some(&"git") { path_elements.remove(0); } // Why cgit and not gitweb? path_elements.insert(0, "cgit"); Some( Url::parse(&format!( "https://{}/{}", parsed_url.host_str().unwrap(), path_elements.join("/") )) .unwrap(), ) } "git.code.sf.net" | "git.code.sourceforge.net" => { let path_elements = parsed_url.path_segments().unwrap().collect::>(); if path_elements.first() != Some(&"p") { return None; } let project = path_elements[1]; let repository = path_elements[2]; let mut path_elements = vec!["p", project, repository]; let branch = if let Some(branch) = parsed_vcs.branch { Some(branch) } else if parsed_vcs.subpath.is_some() { Some("HEAD".to_string()) } else { None }; if let Some(branch) = branch.as_deref() { path_elements.extend(["ci", branch, "tree"]); } if let Some(subpath) = parsed_vcs.subpath.as_ref() { path_elements.push(subpath); } let url = format!("https://sourceforge.net/{}", path_elements.join("/")); Some(Url::parse(&url).unwrap()) } _ => None, } } pub fn canonicalize_vcs_browser_url(url: &str) -> String { let url = url.replace( "https://svn.debian.org/wsvn/", "https://anonscm.debian.org/viewvc/", ); let url = url.replace( "http://svn.debian.org/wsvn/", "https://anonscm.debian.org/viewvc/", ); let url = url.replace( "https://git.debian.org/?p=", "https://anonscm.debian.org/git/", ); let url = url.replace( "http://git.debian.org/?p=", "https://anonscm.debian.org/git/", ); let url = url.replace( "https://bzr.debian.org/loggerhead/", "https://anonscm.debian.org/loggerhead/", ); let url = url.replace( "http://bzr.debian.org/loggerhead/", "https://anonscm.debian.org/loggerhead/", ); lazy_regex::regex_replace!( r"^https?://salsa.debian.org/([^/]+/[^/]+)\.git/?$", &url, |_, x| "https://salsa.debian.org/".to_string() + x ) .into_owned() } #[derive(Debug, PartialEq, Eq, Clone)] pub enum PackageVcs { Git { url: Url, branch: Option, subpath: Option, }, Svn(Url), Bzr(Url), Hg { url: Url, branch: Option, subpath: Option, }, Mtn(Url), Cvs(String), Darcs(Url), Arch(Url), Svk(Url), } pub trait VcsSource { fn vcs_git(&self) -> Option; fn vcs_svn(&self) -> Option; fn vcs_bzr(&self) -> Option; fn vcs_hg(&self) -> Option; fn vcs_mtn(&self) -> Option; fn vcs_cvs(&self) -> Option; fn vcs_darcs(&self) -> Option; fn vcs_arch(&self) -> Option; fn vcs_svk(&self) -> Option; } impl VcsSource for debian_control::Source { fn vcs_git(&self) -> Option { self.vcs_git() } fn vcs_svn(&self) -> Option { self.vcs_svn() } fn vcs_bzr(&self) -> Option { self.vcs_bzr() } fn vcs_hg(&self) -> Option { self.vcs_hg() } fn vcs_mtn(&self) -> Option { self.vcs_mtn() } fn vcs_cvs(&self) -> Option { self.vcs_cvs() } fn vcs_darcs(&self) -> Option { self.vcs_darcs() } fn vcs_arch(&self) -> Option { self.vcs_arch() } fn vcs_svk(&self) -> Option { self.vcs_svk() } } impl VcsSource for debian_control::apt::Source { fn vcs_git(&self) -> Option { self.vcs_git() } fn vcs_svn(&self) -> Option { self.vcs_svn() } fn vcs_bzr(&self) -> Option { self.vcs_bzr() } fn vcs_hg(&self) -> Option { self.vcs_hg() } fn vcs_mtn(&self) -> Option { self.vcs_mtn() } fn vcs_cvs(&self) -> Option { self.vcs_cvs() } fn vcs_darcs(&self) -> Option { self.vcs_darcs() } fn vcs_arch(&self) -> Option { self.vcs_arch() } fn vcs_svk(&self) -> Option { self.vcs_svk() } } pub fn vcs_field(source_package: &impl VcsSource) -> Option<(String, String)> { if let Some(value) = source_package.vcs_git() { return Some(("Git".to_string(), value)); } if let Some(value) = source_package.vcs_svn() { return Some(("Svn".to_string(), value)); } if let Some(value) = source_package.vcs_bzr() { return Some(("Bzr".to_string(), value)); } if let Some(value) = source_package.vcs_hg() { return Some(("Hg".to_string(), value)); } if let Some(value) = source_package.vcs_mtn() { return Some(("Mtn".to_string(), value)); } if let Some(value) = source_package.vcs_cvs() { return Some(("Cvs".to_string(), value)); } if let Some(value) = source_package.vcs_darcs() { return Some(("Darcs".to_string(), value)); } if let Some(value) = source_package.vcs_arch() { return Some(("Arch".to_string(), value)); } if let Some(value) = source_package.vcs_svk() { return Some(("Svk".to_string(), value)); } None } pub fn source_package_vcs(source_package: &impl VcsSource) -> Option { if let Some(value) = source_package.vcs_git() { let parsed_vcs: ParsedVcs = value.parse().unwrap(); let url = parsed_vcs.repo_url.parse().unwrap(); return Some(PackageVcs::Git { url, branch: parsed_vcs.branch, subpath: parsed_vcs.subpath.map(std::path::PathBuf::from), }); } if let Some(value) = source_package.vcs_svn() { let url = value.parse().unwrap(); return Some(PackageVcs::Svn(url)); } if let Some(value) = source_package.vcs_bzr() { let url = value.parse().unwrap(); return Some(PackageVcs::Bzr(url)); } if let Some(value) = source_package.vcs_hg() { let parsed_vcs: ParsedVcs = value.parse().unwrap(); let url = parsed_vcs.repo_url.parse().unwrap(); return Some(PackageVcs::Hg { url, branch: parsed_vcs.branch, subpath: parsed_vcs.subpath.map(std::path::PathBuf::from), }); } if let Some(value) = source_package.vcs_mtn() { let url = value.parse().unwrap(); return Some(PackageVcs::Mtn(url)); } if let Some(value) = source_package.vcs_cvs() { return Some(PackageVcs::Cvs(value.clone())); } if let Some(value) = source_package.vcs_darcs() { let url = value.parse().unwrap(); return Some(PackageVcs::Darcs(url)); } if let Some(value) = source_package.vcs_arch() { let url = value.parse().unwrap(); return Some(PackageVcs::Arch(url)); } if let Some(value) = source_package.vcs_svk() { let url = value.parse().unwrap(); return Some(PackageVcs::Svk(url)); } None } #[cfg(test)] mod tests { #[test] fn test_source_package_vcs() { use super::PackageVcs; use debian_control::Control; let control: Control = r#"Source: foo Vcs-Git: https://salsa.debian.org/foo/bar.git "# .parse() .unwrap(); assert_eq!( super::source_package_vcs(&control.source().unwrap()), Some(PackageVcs::Git { url: "https://salsa.debian.org/foo/bar.git".parse().unwrap(), branch: None, subpath: None }) ); let control: Control = r#"Source: foo Vcs-Svn: https://svn.debian.org/svn/foo/bar "# .parse() .unwrap(); assert_eq!( super::source_package_vcs(&control.source().unwrap()), Some(PackageVcs::Svn( "https://svn.debian.org/svn/foo/bar".parse().unwrap() )) ); } #[test] fn test_determine_gitlab_browser_url() { use super::determine_gitlab_browser_url; assert_eq!( determine_gitlab_browser_url("https://salsa.debian.org/foo/bar"), "https://salsa.debian.org/foo/bar".parse().unwrap() ); assert_eq!( determine_gitlab_browser_url("https://salsa.debian.org/foo/bar.git"), "https://salsa.debian.org/foo/bar".parse().unwrap() ); assert_eq!( determine_gitlab_browser_url("https://salsa.debian.org/foo/bar/"), "https://salsa.debian.org/foo/bar".parse().unwrap() ); assert_eq!( determine_gitlab_browser_url("https://salsa.debian.org/foo/bar/.git"), "https://salsa.debian.org/foo/bar/".parse().unwrap() ); assert_eq!( determine_gitlab_browser_url("https://salsa.debian.org/foo/bar.git -b baz"), "https://salsa.debian.org/foo/bar/-/tree/baz" .parse() .unwrap() ); assert_eq!( determine_gitlab_browser_url( "https://salsa.debian.org/foo/bar.git/ -b baz [otherpath]" ), "https://salsa.debian.org/foo/bar/-/tree/baz/otherpath" .parse() .unwrap() ); } #[test] fn test_determine_browser_url() { use super::determine_browser_url; use url::Url; assert_eq!( determine_browser_url("git", "https://salsa.debian.org/foo/bar", Some(false)), Some(Url::parse("https://salsa.debian.org/foo/bar").unwrap()) ); assert_eq!( determine_browser_url("git", "https://salsa.debian.org/foo/bar.git", Some(false)), Some(Url::parse("https://salsa.debian.org/foo/bar").unwrap()) ); assert_eq!( determine_browser_url("git", "https://salsa.debian.org/foo/bar/", Some(false)), Some(Url::parse("https://salsa.debian.org/foo/bar").unwrap()) ); assert_eq!( determine_browser_url("git", "https://salsa.debian.org/foo/bar/.git", Some(false)), Some(Url::parse("https://salsa.debian.org/foo/bar/").unwrap()) ); assert_eq!( determine_browser_url("git", "https://salsa.debian.org/foo/bar.git/", Some(false)), Some(Url::parse("https://salsa.debian.org/foo/bar").unwrap()) ); assert_eq!( determine_browser_url( "git", "https://salsa.debian.org/foo/bar.git/.git", Some(false) ), Some(Url::parse("https://salsa.debian.org/foo/bar.git/").unwrap()) ); assert_eq!( determine_browser_url( "git", "https://salsa.debian.org/foo/bar.git.git", Some(false) ), Some(Url::parse("https://salsa.debian.org/foo/bar").unwrap()) ); assert_eq!( determine_browser_url( "git", "https://salsa.debian.org/foo/bar.git.git/", Some(false) ), Some(Url::parse("https://salsa.debian.org/foo/bar").unwrap()) ); assert_eq!( Some(Url::parse("https://salsa.debian.org/jelmer/dulwich").unwrap()), determine_browser_url( "git", "https://salsa.debian.org/jelmer/dulwich.git", Some(false) ) ); assert_eq!( Some(Url::parse("https://github.com/jelmer/dulwich").unwrap()), determine_browser_url("git", "https://github.com/jelmer/dulwich.git", Some(false)) ); assert_eq!( Some(Url::parse("https://github.com/jelmer/dulwich/tree/master").unwrap()), determine_browser_url( "git", "https://github.com/jelmer/dulwich.git -b master", Some(false) ) ); assert_eq!( Some(Url::parse("https://github.com/jelmer/dulwich/tree/master").unwrap()), determine_browser_url( "git", "git://github.com/jelmer/dulwich -b master", Some(false) ), ); assert_eq!( Some(Url::parse("https://github.com/jelmer/dulwich/tree/master/blah").unwrap()), determine_browser_url( "git", "git://github.com/jelmer/dulwich -b master [blah]", Some(false) ), ); assert_eq!( Some(Url::parse("https://github.com/jelmer/dulwich/tree/HEAD/blah").unwrap()), determine_browser_url("git", "git://github.com/jelmer/dulwich [blah]", Some(false)), ); assert_eq!( Some(Url::parse("https://git.sv.gnu.org/cgit/rcs.git").unwrap()), determine_browser_url("git", "https://git.sv.gnu.org/git/rcs.git", Some(false)), ); assert_eq!( Some(Url::parse("https://git.savannah.gnu.org/cgit/rcs.git").unwrap()), determine_browser_url("git", "git://git.savannah.gnu.org/rcs.git", Some(false)), ); assert_eq!( Some(Url::parse("https://sourceforge.net/p/shorewall/debian").unwrap()), determine_browser_url( "git", "git://git.code.sf.net/p/shorewall/debian", Some(false) ), ); assert_eq!( Some(Url::parse("https://sourceforge.net/p/shorewall/debian/ci/foo/tree").unwrap()), determine_browser_url( "git", "git://git.code.sf.net/p/shorewall/debian -b foo", Some(false) ), ); assert_eq!( Some(Url::parse("https://sourceforge.net/p/shorewall/debian/ci/HEAD/tree/sp").unwrap()), determine_browser_url( "git", "git://git.code.sf.net/p/shorewall/debian [sp]", Some(false) ), ); assert_eq!( Some(Url::parse("https://sourceforge.net/p/shorewall/debian/ci/foo/tree/sp").unwrap()), determine_browser_url( "git", "git://git.code.sf.net/p/shorewall/debian -b foo [sp]", Some(false) ), ); } #[test] fn test_vcs_field() { use debian_control::Control; let control: Control = r#"Source: foo Vcs-Git: https://salsa.debian.org/foo/bar.git "# .parse() .unwrap(); assert_eq!( super::vcs_field(&control.source().unwrap()), Some(( "Git".to_string(), "https://salsa.debian.org/foo/bar.git".to_string() )) ); } }